New init / new config / options

This commit is contained in:
Aleksandr Kraiz
2022-05-17 22:06:30 +04:00
parent 8a1ad1d7a8
commit dd9a3051a3
12 changed files with 179 additions and 133 deletions

View File

@@ -18,38 +18,38 @@ npm i @orionprotocol/sdk
```ts
// Node.js
import "dotenv/config";
import { initOrionUnit } from "@orionprotocol/sdk";
import { OrionUnit } from "@orionprotocol/sdk";
import { Wallet } from "ethers";
const chain = process.env.CHAINID; // "56"
const chain = process.env.CHAIN; // "56" or "bsc"
const env = process.env.ENV; // production
const privateKey = process.env.PRIVATE_KEY; // 0x...
if (!chain) throw new Error("CHAINID is required");
if (!chain) throw new Error("CHAIN is required");
if (!env) throw new Error("ENV is required");
if (!privateKey) throw new Error("PRIVATE_KEY is required");
const wallet = new Wallet(privateKey);
// OrionUnit is chain-in-environment abstraction
const orionUnit = initOrionUnit(chain, env);
const orionUnit = new OrionUnit(chain, env);
```
```ts
// UI
import { initOrionUnit } from "@orionprotocol/sdk";
import { OrionUnit } from "@orionprotocol/sdk";
import detectEthereumProvider from "@metamask/detect-provider";
import { BaseProvider } from "@metamask/providers";
import { providers } from "ethers";
const chain = "97"; // bsc-testnet
const chain = "97"; // or "bsc" (in testing environment point to bsc-testnet)
const env = "testing";
const startApp = async (provider: BaseProvider) => {
const web3Provider = new providers.Web3Provider(provider);
await web3Provider.ready;
const signer = web3Provider.getSigner(); // ready to go
const orionUnit = initOrionUnit(chain, env); // ready to go
const orionUnit = new OrionUnit(chain, env); // ready to go
};
detectEthereumProvider().then((provider) => {
@@ -211,9 +211,10 @@ const { orderId } = await simpleFetch(orionUnit.orionAggregator.placeOrder)(
// Default ("verbose") fetch
const placeOrderFetchResult = await orionUnit.orionAggregator.placeOrder(
// Same params as above
);
const placeOrderFetchResult = await orionUnit.orionAggregator
.placeOrder
// Same params as above
();
if (placeOrderFetchResult.isErr()) {
// You can handle fetching errors here

View File

@@ -1,6 +1,6 @@
{
"name": "@orionprotocol/sdk",
"version": "0.5.20",
"version": "0.6.0",
"description": "Orion Protocol SDK",
"main": "./lib/esm/index.js",
"module": "./lib/esm/index.js",

View File

@@ -7,8 +7,25 @@ import { PriceFeed } from '../services/PriceFeed';
import { SupportedChainId } from '../types';
import Exchange from './Exchange';
import FarmingManager from './FarmingManager';
import { chains, envs } from '../config';
import { isValidChainId } from '../utils';
const orionAnalyticsHost = 'trade.orionprotocol.io';
const orionAnalyticsUrl = 'https://trade.orionprotocol.io';
type Options = {
api?: string;
services?: {
orionBlockchain?: {
api?: string;
},
orionAggregator?: {
api?: string;
},
priceFeed?: {
api?: string;
},
}
};
export default class OrionUnit {
public readonly env: string;
@@ -33,22 +50,76 @@ export default class OrionUnit {
public readonly apiUrl: string;
constructor(
chainId: SupportedChainId,
networkCode: string,
rpc: string,
chain: string,
env: string,
apiUrl: string,
options?: Options,
) {
if (!(env in envs)) {
throw new Error(`Env '${env}' not found. Available environments is: ${Object.keys(envs).join(', ')}`);
}
const envInfo = envs[env];
const envNetworks = envInfo?.networks;
let chainId: SupportedChainId;
if (isValidChainId(chain)) chainId = chain;
else {
const targetChains = Object
.keys(chains)
.filter(isValidChainId)
.filter((ch) => {
const chainInfo = chains[ch];
if (!chainInfo) return false;
return (chainInfo.chainId in envNetworks)
&& (chainInfo.code.toLowerCase() === chain.toLowerCase());
});
if (targetChains.length !== 1) {
throw new Error(
targetChains.length > 1
? 'Ambiguation detected. '
+ `Found ${targetChains.length} chain ids [${targetChains.join(', ')}] for chain name '${chain}' in env '${env}'. Expected 1.`
: `Chains not found for chain name '${chain}' in env '${env}'.`,
);
}
[chainId] = targetChains;
}
if (!(chainId in envNetworks)) {
throw new Error(`Chain '${chainId}' not found. `
+ `Available chains in selected environment (${env}) is: ${Object.keys(envNetworks).join(', ')}`);
}
const envNetworkInfo = envNetworks[chainId];
const chainInfo = chains[chainId];
if (!envNetworkInfo) throw new Error('Env network info is required');
if (!chainInfo) throw new Error('Chain info is required');
const apiUrl = envNetworkInfo.api;
this.chainId = chainId;
this.networkCode = networkCode;
this.provider = new ethers.providers.StaticJsonRpcProvider(rpc);
this.networkCode = chainInfo.code;
this.provider = new ethers.providers.StaticJsonRpcProvider(envNetworkInfo.rpc ?? chainInfo.rpc);
this.env = env;
this.apiUrl = apiUrl;
this.orionBlockchain = new OrionBlockchain(apiUrl);
this.orionAggregator = new OrionAggregator(apiUrl, chainId);
this.priceFeed = new PriceFeed(apiUrl);
this.orionAnalytics = new OrionAnalytics(orionAnalyticsHost);
this.orionBlockchain = new OrionBlockchain(
options?.services?.orionBlockchain?.api
?? options?.api
?? apiUrl,
);
this.orionAggregator = new OrionAggregator(
options?.services?.orionAggregator?.api
?? options?.api
?? apiUrl,
chainId,
);
this.priceFeed = new PriceFeed(
options?.services?.priceFeed?.api
?? options?.api
?? apiUrl,
);
this.orionAnalytics = new OrionAnalytics(orionAnalyticsUrl);
this.exchange = new Exchange(this);
this.farmingManager = new FarmingManager(this);
}

View File

@@ -21,7 +21,7 @@
"chainId": "97",
"explorer": "https://testnet.bscscan.com/",
"label": "Binance Smart Chain Testnet",
"shortName": "BSC-Testent",
"shortName": "BSC-Testnet",
"code": "bsc",
"rpc": "https://bsc-stage.node.orionprotocol.io/",
"baseCurrencyName": "BNB"

View File

@@ -2,73 +2,73 @@
"production": {
"networks": {
"1": {
"api": "trade.orionprotocol.io",
"api": "https://trade.orionprotocol.io",
"liquidityMigratorAddress": "0x23a1820a47BcD022E29f6058a5FD224242F50D1A"
},
"56": {
"api": "trade-exp.orionprotocol.io"
"api": "https://trade-exp.orionprotocol.io"
},
"250": {
"api": "trade-ftm.orionprotocol.io"
"api": "https://trade-ftm.orionprotocol.io"
}
}
},
"testing": {
"networks": {
"97": {
"api": "testing.orionprotocol.io/bsc-testnet",
"api": "https://testing.orionprotocol.io/bsc-testnet",
"liquidityMigratorAddress": "0x01b10dds12478C88A5E18e2707E729906bC25CfF6"
},
"3": {
"api": "testing.orionprotocol.io/eth-ropsten",
"api": "https://testing.orionprotocol.io/eth-ropsten",
"liquidityMigratorAddress": "0x36969a25622AE31bA9946e0c8151f0dc08b3A1c8"
},
"4002": {
"api": "testing.orionprotocol.io/ftm-testnet"
"api": "https://testing.orionprotocol.io/ftm-testnet"
}
}
},
"staging": {
"networks": {
"1": {
"api": "staging.orionprotocol.io"
"api": "https://staging.orionprotocol.io"
},
"56": {
"api": "staging-bsc.orionprotocol.io"
"api": "https://staging-bsc.orionprotocol.io"
},
"250": {
"api": "staging-ftm.orionprotocol.io"
"api": "https://staging-ftm.orionprotocol.io"
},
"137": {
"api": "staging-polygon.orionprotocol.io"
"api": "https://staging-polygon.orionprotocol.io"
}
}
},
"broken": {
"networks": {
"0": {
"api": "broken0.orionprotocol.io/everything-is-fine/this-url-was-created-for-tests-of-a-partially-broken-environment"
"api": "https://broken0.orionprotocol.io/everything-is-fine/this-url-was-created-for-tests-of-a-partially-broken-environment"
},
"97": {
"api": "broken1.orionprotocol.io/everything-is-fine/this-url-was-created-for-tests-of-a-partially-broken-environment"
"api": "https://broken1.orionprotocol.io/everything-is-fine/this-url-was-created-for-tests-of-a-partially-broken-environment"
}
}
},
"partially-broken": {
"networks": {
"97": {
"api": "testing.orionprotocol.io/bsc-testnet",
"api": "https://testing.orionprotocol.io/bsc-testnet",
"liquidityMigratorAddress": "0x01b10dd12478C88A5E18e2707E729906bC25CfF6"
},
"3": {
"api": "testing.orionprotocol.io/eth-ropsten",
"api": "https://testing.orionprotocol.io/eth-ropsten",
"liquidityMigratorAddress": "0x36969a25622AE31bA9946e0c8151f0dc08b3A1c8"
},
"4002": {
"api": "testing.orionprotocol.io/ftm-testnet"
"api": "https://testing.orionprotocol.io/ftm-testnet"
},
"0": {
"api": "broken.orionprotocol.io/everything-is-fine/this-url-was-created-for-tests-of-a-partially-broken-environment"
"api": "https://broken.orionprotocol.io/everything-is-fine/this-url-was-created-for-tests-of-a-partially-broken-environment"
}
}
}

View File

@@ -1,7 +1,7 @@
import initOrionUnit from './initOrionUnit';
import { SupportedChainId } from './types';
import { isValidChainId } from './utils';
import { envs } from './config';
import OrionUnit from './OrionUnit';
export default function getOrionUnitSiblings(siblingChain: SupportedChainId, env: string) {
if (!(env in envs)) throw new Error(`Env '${env}' not found. Available environments is: ${Object.keys(envs).join(', ')}`);
@@ -16,5 +16,5 @@ export default function getOrionUnitSiblings(siblingChain: SupportedChainId, env
.keys(envNetworks)
.filter(isValidChainId)
.filter((chainId) => chainId !== siblingChain);
return siblingsNetworks.map((chainId) => initOrionUnit(chainId, env));
return siblingsNetworks.map((chainId) => new OrionUnit(chainId, env));
}

View File

@@ -1,55 +1,8 @@
import OrionUnit from './OrionUnit';
import { isValidChainId } from './utils';
import { chains, envs } from './config';
import { SupportedChainId } from './types';
export default function initOrionUnit(chain: string, env: string) {
if (!(env in envs)) {
throw new Error(`Env '${env}' not found. Available environments is: ${Object.keys(envs).join(', ')}`);
}
const envInfo = envs[env];
const envNetworks = envInfo?.networks;
let chainId: SupportedChainId;
if (isValidChainId(chain)) chainId = chain;
else {
const targetChains = Object
.keys(chains)
.filter(isValidChainId)
.filter((ch) => {
const chainInfo = chains[ch];
if (!chainInfo) return false;
return (chainInfo.chainId in envNetworks)
&& (chainInfo.code.toLowerCase() === chain.toLowerCase());
});
if (targetChains.length !== 1) {
throw new Error(
targetChains.length > 1
? 'Ambiguation detected. '
+ `Found ${targetChains.length} chain ids [${targetChains.join(', ')}] for chain name '${chain}' in env '${env}'. Expected 1.`
: `Chains not found for chain name '${chain}' in env '${env}'.`,
);
}
[chainId] = targetChains;
}
if (!(chainId in envNetworks)) {
throw new Error(`Chain '${chainId}' not found. `
+ `Available chains in selected environment (${env}) is: ${Object.keys(envNetworks).join(', ')}`);
}
const envNetworkInfo = envNetworks[chainId];
const chainInfo = chains[chainId];
if (!envNetworkInfo) throw new Error('Env network info is required');
if (!chainInfo) throw new Error('Chain info is required');
// backward compatibility
export default function initOrionUnit(...params: ConstructorParameters<typeof OrionUnit>) {
return new OrionUnit(
chainId,
chainInfo.code,
envNetworkInfo.rpc ?? chainInfo.rpc,
env,
envNetworkInfo.api,
...params,
);
}

View File

@@ -39,10 +39,14 @@ class OrionAggregator {
this.getExchangeOrderbook = this.getExchangeOrderbook.bind(this);
}
get aggregatorWSUrl() { return `wss://${this.apiUrl}/v1`; }
get aggregatorWSUrl() {
const { host, pathname, protocol } = new URL(this.apiUrl);
const wsProtocol = protocol === 'https:' ? 'wss' : 'ws';
return `${wsProtocol}://${host + (pathname === '/' ? '' : pathname)}/v1`;
}
get aggregatorUrl() {
return `https://${this.apiUrl}/backend`;
return `${this.apiUrl}/backend`;
}
getPairsList = () => fetchWithValidation(

View File

@@ -11,7 +11,7 @@ export default class OrionAnalytics {
}
getOverview = () => fetchWithValidation(
`https://${this.apiUrl}/api/stats/overview`,
`${this.apiUrl}/api/stats/overview`,
overviewSchema,
);
}

View File

@@ -56,7 +56,7 @@ class OrionBlockchain {
constructor(apiUrl: string) {
this.apiUrl = apiUrl;
this.ws = new OrionBlockchainSocketIO(`https://${apiUrl}/`);
this.ws = new OrionBlockchainSocketIO(`${apiUrl}/`);
this.getAtomicSwapAssets = this.getAtomicSwapAssets.bind(this);
this.getAtomicSwapHistory = this.getAtomicSwapHistory.bind(this);
@@ -89,11 +89,11 @@ class OrionBlockchain {
}
get orionBlockchainWsUrl() {
return `https://${this.apiUrl}/`;
return `${this.apiUrl}/`;
}
private getSummaryRedeem = (brokerAddress: string, unshifted?: 1 | 0, sourceNetworkCode?: string) => {
const url = new URL(`https://${this.apiUrl}/api/atomic/summary-redeem/${brokerAddress}`);
const url = new URL(`${this.apiUrl}/api/atomic/summary-redeem/${brokerAddress}`);
if (unshifted) {
url.searchParams.append('unshifted', unshifted.toString());
}
@@ -107,12 +107,12 @@ class OrionBlockchain {
};
private getSummaryClaim = (brokerAddress: string) => fetchWithValidation(
`https://${this.apiUrl}/api/atomic/summary-claim/${brokerAddress}`,
`${this.apiUrl}/api/atomic/summary-claim/${brokerAddress}`,
atomicSummarySchema,
);
private getQueueLength = () => fetchWithValidation(
`https://${this.apiUrl}/api/queueLength`,
`${this.apiUrl}/api/queueLength`,
z.number().int(),
);
@@ -125,54 +125,54 @@ class OrionBlockchain {
}
getAuthToken = () => fetchWithValidation(
`https://${this.apiUrl}/api/auth/token`,
`${this.apiUrl}/api/auth/token`,
z.object({ token: z.string() }),
);
getCirculatingSupply = () => fetchWithValidation(
`https://${this.apiUrl}/api/circulating-supply`,
`${this.apiUrl}/api/circulating-supply`,
z.number(),
);
getInfo = () => fetchWithValidation(`https://${this.apiUrl}/api/info`, infoSchema);
getInfo = () => fetchWithValidation(`${this.apiUrl}/api/info`, infoSchema);
getPoolsConfig = () => fetchWithValidation(
`https://${this.apiUrl}/api/pools/config`,
`${this.apiUrl}/api/pools/config`,
poolsConfigSchema,
);
getPoolsInfo = () => fetchWithValidation(
`https://${this.apiUrl}/api/pools/info`,
`${this.apiUrl}/api/pools/info`,
poolsInfoSchema,
);
getHistory = (address: string) => fetchWithValidation(
`https://${this.apiUrl}/api/history/${address}`,
`${this.apiUrl}/api/history/${address}`,
historySchema,
);
getPrices = () => fetchWithValidation(
`https://${this.apiUrl}/api/prices`,
`${this.apiUrl}/api/prices`,
z.record(z.string()).transform(makePartial),
);
getTokensFee = () => fetchWithValidation(
`https://${this.apiUrl}/api/tokensFee`,
`${this.apiUrl}/api/tokensFee`,
z.record(z.string()).transform(makePartial),
);
getGasPriceWei = () => fetchWithValidation(
`https://${this.apiUrl}/api/gasPrice`,
`${this.apiUrl}/api/gasPrice`,
z.string(),
);
checkFreeRedeemAvailable = (walletAddress: string) => fetchWithValidation(
`https://${this.apiUrl}/api/atomic/has-free-redeem/${walletAddress}`,
`${this.apiUrl}/api/atomic/has-free-redeem/${walletAddress}`,
z.boolean(),
);
getRedeemOrderBySecretHash = (secretHash: string) => fetchWithValidation(
`https://${this.apiUrl}/api/atomic/redeem-order/${secretHash}`,
`${this.apiUrl}/api/atomic/redeem-order/${secretHash}`,
z.object({
secretHash: z.string(),
secret: z.string(),
@@ -181,7 +181,7 @@ class OrionBlockchain {
);
claimOrder = (secretHash: string, targetNetwork: string, redeemTxHash?: string) => fetchWithValidation(
`https://${this.apiUrl}/api/atomic/claim-order`,
`${this.apiUrl}/api/atomic/claim-order`,
z.string(),
{
method: 'POST',
@@ -201,7 +201,7 @@ class OrionBlockchain {
secret: string,
sourceNetwork: string,
) => fetchWithValidation(
`https://${this.apiUrl}/api/atomic/matcher-redeem`,
`${this.apiUrl}/api/atomic/matcher-redeem`,
z.string(),
{
method: 'POST',
@@ -223,7 +223,7 @@ class OrionBlockchain {
secret2: string,
sourceNetwork: string,
) => fetchWithValidation(
`https://${this.apiUrl}/api/atomic/matcher-redeem2atomics`,
`${this.apiUrl}/api/atomic/matcher-redeem2atomics`,
z.string(),
{
method: 'POST',
@@ -241,31 +241,31 @@ class OrionBlockchain {
);
checkRedeem = (secretHash: string) => fetchWithValidation(
`https://${this.apiUrl}/api/atomic/matcher-redeem/${secretHash}`,
`${this.apiUrl}/api/atomic/matcher-redeem/${secretHash}`,
z.enum(['OK', 'FAIL']).nullable(),
);
checkRedeem2Atomics = (firstSecretHash: string, secondSecretHash: string) => fetchWithValidation(
`https://${this.apiUrl}/api/atomic/matcher-redeem/${firstSecretHash}-${secondSecretHash}`,
`${this.apiUrl}/api/atomic/matcher-redeem/${firstSecretHash}-${secondSecretHash}`,
z.enum(['OK', 'FAIL']).nullable(),
);
getBlockNumber = () => fetchWithValidation(`https://${this.apiUrl}/api/blocknumber`, z.number().int());
getBlockNumber = () => fetchWithValidation(`${this.apiUrl}/api/blocknumber`, z.number().int());
getIDOInfo = () => fetchWithValidation(`https://${this.apiUrl}/api/solarflare`, IDOSchema);
getIDOInfo = () => fetchWithValidation(`${this.apiUrl}/api/solarflare`, IDOSchema);
checkAuth = (headers: IAdminAuthHeaders) => fetchWithValidation(`https://${this.apiUrl}/api/auth/check`, z.object({
checkAuth = (headers: IAdminAuthHeaders) => fetchWithValidation(`${this.apiUrl}/api/auth/check`, z.object({
auth: z.boolean(),
}), { headers });
getPoolsList = (headers: IAdminAuthHeaders) => fetchWithValidation(
`https://${this.apiUrl}/api/pools/list`,
`${this.apiUrl}/api/pools/list`,
adminPoolsListSchema,
{ headers },
);
editPool = (address: string, data: IEditPool, headers: IAdminAuthHeaders) => fetchWithValidation(
`https://${this.apiUrl}/api/pools/edit/${address}`,
`${this.apiUrl}/api/pools/edit/${address}`,
pairStatusSchema,
{
method: 'POST',
@@ -278,7 +278,7 @@ class OrionBlockchain {
);
addPool = (data: z.infer<typeof addPoolSchema>) => fetchWithValidation(
`https://${this.apiUrl}/api/pools/add`,
`${this.apiUrl}/api/pools/add`,
z.number(),
{
method: 'POST',
@@ -292,12 +292,12 @@ class OrionBlockchain {
);
checkPoolInformation = (poolAddress: string) => fetchWithValidation(
`https://${this.apiUrl}/api/pools/check/${poolAddress}`,
`${this.apiUrl}/api/pools/check/${poolAddress}`,
pairStatusSchema,
);
getAtomicSwapAssets = () => fetchWithValidation(
`https://${this.apiUrl}/api/atomic/swap-assets`,
`${this.apiUrl}/api/atomic/swap-assets`,
z.array(z.string()),
);
@@ -306,7 +306,7 @@ class OrionBlockchain {
* Receiver is user address in target Orion Blockchain instance
*/
getAtomicSwapHistory = (query: AtomicSwapHistorySourceQuery | AtomicSwapHistoryTargetQuery) => {
const url = new URL(`https://${this.apiUrl}/api/atomic/history/`);
const url = new URL(`${this.apiUrl}/api/atomic/history/`);
Object.entries(query)
.forEach(([key, value]) => url.searchParams.append(key, value.toString()));
@@ -315,7 +315,7 @@ class OrionBlockchain {
};
getSourceAtomicSwapHistory = (query: AtomicSwapHistorySourceQuery) => {
const url = new URL(`https://${this.apiUrl}/api/atomic/history/`);
const url = new URL(`${this.apiUrl}/api/atomic/history/`);
Object.entries(query)
.forEach(([key, value]) => url.searchParams.append(key, value.toString()));
@@ -326,7 +326,7 @@ class OrionBlockchain {
};
getTargetAtomicSwapHistory = (query: AtomicSwapHistoryTargetQuery) => {
const url = new URL(`https://${this.apiUrl}/api/atomic/history/`);
const url = new URL(`${this.apiUrl}/api/atomic/history/`);
Object.entries(query)
.forEach(([key, value]) => url.searchParams.append(key, value.toString()));
@@ -337,7 +337,7 @@ class OrionBlockchain {
};
checkIfHashUsed = (secretHashes: string[]) => fetchWithValidation(
`https://${this.apiUrl}/api/atomic/is-hash-used`,
`${this.apiUrl}/api/atomic/is-hash-used`,
z.record(z.boolean()).transform(makePartial),
{
headers: {

View File

@@ -17,7 +17,7 @@ class PriceFeed {
interval: '5m' | '30m' | '1h' | '1d',
exchange: string,
) => {
const url = new URL(`https://${this.apiUrl}/candles/candles`);
const url = new URL(`${this.apiUrl}/candles/candles`);
url.searchParams.append('symbol', symbol);
url.searchParams.append('timeStart', timeStart.toString());
url.searchParams.append('timeEnd', timeEnd.toString());
@@ -30,13 +30,27 @@ class PriceFeed {
);
};
get candlesUrl() { return `https://${this.apiUrl}/candles/candles`; }
get wsUrl() {
const url = new URL(this.apiUrl);
const wsProtocol = url.protocol === 'https:' ? 'wss' : 'ws';
return `${wsProtocol}://${url.host + url.pathname}`;
}
get allTickersWSUrl() { return `wss://${this.apiUrl}/ws2/allTickers`; }
get candlesUrl() {
return `${this.apiUrl}/candles/candles`;
}
get tickerWSUrl() { return `wss://${this.apiUrl}/ws2/ticker/`; }
get allTickersWSUrl() {
return `${this.wsUrl}/ws2/allTickers`;
}
get lastPriceWSUrl() { return `wss://${this.apiUrl}/ws2/lastPrice/`; }
get tickerWSUrl() {
return `${this.wsUrl}/ws2/ticker/`;
}
get lastPriceWSUrl() {
return `${this.wsUrl}/ws2/lastPrice/`;
}
}
export * as schemas from './schemas';

View File

@@ -10,6 +10,8 @@ const schema = z.tuple([
export default class PriceFeedTickerWS {
priceWebSocket: WebSocket;
private heartbeatInterval: ReturnType<typeof setInterval>;
constructor(
symbol: string,
url: string,
@@ -17,7 +19,7 @@ export default class PriceFeedTickerWS {
) {
this.priceWebSocket = new WebSocket(`${url}${symbol}`);
setInterval(() => {
this.heartbeatInterval = setInterval(() => {
this.priceWebSocket.send('heartbeat');
}, 15000);
@@ -32,6 +34,7 @@ export default class PriceFeedTickerWS {
}
kill() {
clearInterval(this.heartbeatInterval);
this.priceWebSocket.close();
}
}