diff --git a/config/setup.dev.json b/config/setup.dev.json index 0e53902..629ec07 100644 --- a/config/setup.dev.json +++ b/config/setup.dev.json @@ -22,7 +22,7 @@ "blockworks" ] }, - "sol:usd": { + "btc:usdt": { "decimals": 2, "minSubmissions": 1, "maxSubmissions": 13, @@ -33,7 +33,7 @@ "blockworks" ] }, - "srm:usd": { + "eth:usdt": { "decimals": 2, "minSubmissions": 1, "maxSubmissions": 13, @@ -43,6 +43,28 @@ "oracles": [ "blockworks" ] + }, + "sol:usd": { + "decimals": 4, + "minSubmissions": 1, + "maxSubmissions": 13, + "restartDelay": 0, + "rewardAmount": 10000, + "rewardTokenAccount": "3oLHHTaRqNsuTMjsTtkVy8bock6Bx8gCmDxku4TurVj1", + "oracles": [ + "blockworks" + ] + }, + "srm:usd": { + "decimals": 4, + "minSubmissions": 1, + "maxSubmissions": 13, + "restartDelay": 0, + "rewardAmount": 10000, + "rewardTokenAccount": "3oLHHTaRqNsuTMjsTtkVy8bock6Bx8gCmDxku4TurVj1", + "oracles": [ + "blockworks" + ] } }, "oracles": { diff --git a/deploy.dev.json b/deploy.dev.json index f57eabb..eabc023 100644 --- a/deploy.dev.json +++ b/deploy.dev.json @@ -1,6 +1,6 @@ { "aggregators": { - "btc:usd": { + "btc:usdt": { "pubkey": { "type": "PublicKey", "base58": "6Xvk6VC423bbhwnCfMyPfE4C1vytoqsVMUY1Lbqeh6pf" @@ -10,7 +10,7 @@ "base58": "GXdZLSvnreqyUd92tXFShpF8DPvpqDBRkukAtraZ3rCf" }, "config": { - "description": "btc:usd", + "description": "btc:usdt", "decimals": 2, "minSubmissions": 1, "maxSubmissions": 13, @@ -34,7 +34,7 @@ } } }, - "eth:usd": { + "eth:usdt": { "pubkey": { "type": "PublicKey", "base58": "4CoKvk3NUXYiHKGbQvihadw6TC8LTN1qjfadPcsaURbW" @@ -44,7 +44,7 @@ "base58": "GXdZLSvnreqyUd92tXFShpF8DPvpqDBRkukAtraZ3rCf" }, "config": { - "description": "eth:usd", + "description": "eth:usdt", "decimals": 2, "minSubmissions": 1, "maxSubmissions": 13, @@ -68,17 +68,17 @@ } } }, - "sol:usd": { + "btc:usd": { "pubkey": { "type": "PublicKey", - "base58": "Ej5FrNjhXaePK7cVMZtSooatzXMeunNsjxrnubefEyNC" + "base58": "FuEnReoxhqW8Li6EMLoaaUWbWAEjTfSRuBARo5GrGCqN" }, "owner": { "type": "PublicKey", "base58": "GXdZLSvnreqyUd92tXFShpF8DPvpqDBRkukAtraZ3rCf" }, "config": { - "description": "sol:usd", + "description": "btc:usd", "decimals": 2, "minSubmissions": 1, "maxSubmissions": 13, @@ -93,7 +93,75 @@ "blockworks": { "pubkey": { "type": "PublicKey", - "base58": "4fuTrajqBHGGM1ig4b57DD9JxcN5Q2MmoDVnMtnucjS7" + "base58": "AhD1vCnNBgECpoBrfsqP83KSZsd4TtrWvP3FgebUDoRu" + }, + "owner": { + "type": "PublicKey", + "base58": "GXdZLSvnreqyUd92tXFShpF8DPvpqDBRkukAtraZ3rCf" + } + } + } + }, + "eth:usd": { + "pubkey": { + "type": "PublicKey", + "base58": "GzfYWGM1oeVrha9zvM1awnTJEUAuinpnVRUyYQYELzqg" + }, + "owner": { + "type": "PublicKey", + "base58": "GXdZLSvnreqyUd92tXFShpF8DPvpqDBRkukAtraZ3rCf" + }, + "config": { + "description": "eth:usd", + "decimals": 2, + "minSubmissions": 1, + "maxSubmissions": 13, + "restartDelay": 0, + "rewardTokenAccount": { + "type": "PublicKey", + "base58": "3oLHHTaRqNsuTMjsTtkVy8bock6Bx8gCmDxku4TurVj1" + }, + "rewardAmount": 10000 + }, + "oracles": { + "blockworks": { + "pubkey": { + "type": "PublicKey", + "base58": "HhBmYDPRzJDCbtsTcaB2j6sbpcNDK41CUciev94eroUE" + }, + "owner": { + "type": "PublicKey", + "base58": "GXdZLSvnreqyUd92tXFShpF8DPvpqDBRkukAtraZ3rCf" + } + } + } + }, + "sol:usd": { + "pubkey": { + "type": "PublicKey", + "base58": "AshULbjkGvse8YW2ojjeqHdMbFGigLy2xxiGVhsLqX5T" + }, + "owner": { + "type": "PublicKey", + "base58": "GXdZLSvnreqyUd92tXFShpF8DPvpqDBRkukAtraZ3rCf" + }, + "config": { + "description": "sol:usd", + "decimals": 4, + "minSubmissions": 1, + "maxSubmissions": 13, + "restartDelay": 0, + "rewardTokenAccount": { + "type": "PublicKey", + "base58": "3oLHHTaRqNsuTMjsTtkVy8bock6Bx8gCmDxku4TurVj1" + }, + "rewardAmount": 10000 + }, + "oracles": { + "blockworks": { + "pubkey": { + "type": "PublicKey", + "base58": "2K38TaiEJEiPZY3TGKjuLMoHgZgXxX7YX2wRD2WvfUDM" }, "owner": { "type": "PublicKey", @@ -105,7 +173,7 @@ "srm:usd": { "pubkey": { "type": "PublicKey", - "base58": "GR9tYpi8CM8u8sdRaJZoP32KoWBphyoWV3xoNt4XwmRV" + "base58": "B3nWGxqNQzJeRfpYSXU8qJaTQxspZmqAt91FRAhfoFQL" }, "owner": { "type": "PublicKey", @@ -113,7 +181,7 @@ }, "config": { "description": "srm:usd", - "decimals": 2, + "decimals": 4, "minSubmissions": 1, "maxSubmissions": 13, "restartDelay": 0, @@ -127,7 +195,7 @@ "blockworks": { "pubkey": { "type": "PublicKey", - "base58": "8tn58rnF1QzPqQea4xegck556s45DUGBJvWLNdsLpT69" + "base58": "CsyV4JuPjdYRusw4sa7Ba8Ab8yR7vjyp4wAPuC2neejS" }, "owner": { "type": "PublicKey", @@ -141,4 +209,4 @@ "type": "PublicKey", "base58": "Agc6kwhaMw6rmAtfL7jfaFx1Su4QbjGBD5M42YwetqDe" } -} \ No newline at end of file +} diff --git a/src/PriceFeeder.ts b/src/PriceFeeder.ts index 567a86e..cbfdb28 100644 --- a/src/PriceFeeder.ts +++ b/src/PriceFeeder.ts @@ -16,22 +16,44 @@ import { Submitter, SubmitterConfig } from "./Submitter" import { log } from "./log" import { conn } from "./context" +// 0 => Coinbase, BTC-USD +// 1 => BitStamp, live_trades_btcusd +// 2 => FTX, BTC/USD +// 3 => Binance, BTCBUSD +// 4 => OKEx, BTC-USDC + const priceFeedMapping = { + 'btc:usdt': { + minValueChangeForNewRound: 5000, + useFeeds: [0,2,3,4], + pairNames: ['BTC-USDT', 'BTC/USDT', 'BTCUSDT', 'BTC-USDT'] + }, 'btc:usd': { minValueChangeForNewRound: 5000, - useFeeds: [0,1,2,3] + useFeeds: [0,1,2,3,4], + pairNames: ['BTC-USD', 'live_trades_btcusd', 'BTC/USD', 'BTCBUSD', 'BTC-USDC'] + }, + 'eth:usdt': { + minValueChangeForNewRound: 150, + useFeeds: [0,2,3,4], + pairNames: ['ETH-USDT', 'ETH/USDT', 'ETHUSDT', 'ETH-USDT'] }, 'eth:usd': { minValueChangeForNewRound: 150, - useFeeds: [0,1,2,3] + useFeeds: [0,1,2,3,4], + pairNames: ['ETH-USD', 'live_trades_ethusd', 'ETH/USD', 'ETHBUSD', 'ETH-USDC'] }, 'sol:usd': { minValueChangeForNewRound: 4, - useFeeds: [2,3,4] + useFeeds: [2,3,4], + pairNames: ['SOL/USD', 'SOLBUSD', 'SOL-USDT'] + // uses USDT for OKEx }, 'srm:usd': { minValueChangeForNewRound: 1, - useFeeds: [2,3,4] + useFeeds: [2,3,4], + pairNames: ['SRM/USD', 'SRMBUSD', 'SRM-USDT'] + // uses USDT for OKEx }, } @@ -77,7 +99,7 @@ export class PriceFeeder { } const useFeeds = (priceFeedMapping[name]) ? priceFeedMapping[name].useFeeds.map(x => this.feeds[x]) : this.feeds; - const feed = new AggregatedFeed(useFeeds, name) + const feed = new AggregatedFeed(useFeeds, priceFeedMapping[name].pairNames, name) const priceFeed = feed.medians() const minValueChangeForNewRound = priceFeedMapping[name].minValueChangeForNewRound || 100 diff --git a/src/feeds.ts b/src/feeds.ts index e161dcd..efa125d 100644 --- a/src/feeds.ts +++ b/src/feeds.ts @@ -97,6 +97,58 @@ export abstract class PriceFeed { abstract handleSubscribe(pair: string): Promise } +export class CoinBase extends PriceFeed { + protected log = log.child({ class: CoinBase.name }) + protected baseurl = "wss://ws-feed.pro.coinbase.com" + + parseMessage(data) { + const payload = JSON.parse(data) + + // { + // "type": "ticker", + // "sequence": 22772426228, + // "product_id": "BTC-USD", + // "price": "53784.59", + // "open_24h": "58795.78", + // "volume_24h": "35749.39437842", + // "low_24h": "53221", + // "high_24h": "58799.66", + // "volume_30d": "733685.27275521", + // "best_bid": "53784.58", + // "best_ask": "53784.59", + // "side": "buy", + // "time": "2021-03-16T06:26:06.791440Z", + // "trade_id": 145698988, + // "last_size": "0.00474597" + // } + + if (payload.type != "ticker") { + return + } + + const pair = payload.product_id as string + + const price: IPrice = { + source: CoinBase.name, + pair, + decimals: 2, + value: Math.floor(payload.price * 100), + } + + return price + } + + async handleSubscribe(pair: string) { + this.conn.send( + JSON.stringify({ + type: "subscribe", + product_ids: [pair], + channels: ["ticker"], + }) + ) + } +} + export class BitStamp extends PriceFeed { protected log = log.child({ class: BitStamp.name }) protected baseurl = "wss://ws.bitstamp.net" @@ -125,10 +177,7 @@ export class BitStamp extends PriceFeed { return } - const channel = (payload.channel as string).replace("live_trades_", "") - - // assume that the base symbol for the pair is 3 letters - const pair = channel.slice(0, 3) + ":" + channel.slice(3) + const pair = payload.channel as string const price: IPrice = { source: BitStamp.name, @@ -141,14 +190,11 @@ export class BitStamp extends PriceFeed { } async handleSubscribe(pair: string) { - // "btc:usd" => "BTCUSD" - const targetPair = pair.replace(":", "").toUpperCase() - this.conn.send( JSON.stringify({ event: "bts:subscribe", data: { - channel: `live_trades_${targetPair.replace("/", "").toLowerCase()}`, + channel: pair, }, }) ) @@ -180,7 +226,7 @@ export class FTX extends PriceFeed { return } - const pair = (payload.market as string).replace("/", ":").toLowerCase() + const pair = payload.market as string const price: IPrice = { source: FTX.name, @@ -193,70 +239,11 @@ export class FTX extends PriceFeed { } async handleSubscribe(pair: string) { - // "btc:usd" => "BTC-USD" - const targetPair = pair.replace(":", "/").toUpperCase() - this.conn.send( JSON.stringify({ op: "subscribe", channel: "ticker", - market: targetPair, - }) - ) - } -} - -export class CoinBase extends PriceFeed { - protected log = log.child({ class: CoinBase.name }) - protected baseurl = "wss://ws-feed.pro.coinbase.com" - - parseMessage(data) { - const payload = JSON.parse(data) - - // { - // "type": "ticker", - // "sequence": 22772426228, - // "product_id": "BTC-USD", - // "price": "53784.59", - // "open_24h": "58795.78", - // "volume_24h": "35749.39437842", - // "low_24h": "53221", - // "high_24h": "58799.66", - // "volume_30d": "733685.27275521", - // "best_bid": "53784.58", - // "best_ask": "53784.59", - // "side": "buy", - // "time": "2021-03-16T06:26:06.791440Z", - // "trade_id": 145698988, - // "last_size": "0.00474597" - // } - - if (payload.type != "ticker") { - return - } - - // "BTC-USD" => "btc:usd" - const pair = (payload.product_id as string).replace("-", ":").toLowerCase() - - const price: IPrice = { - source: CoinBase.name, - pair, - decimals: 2, - value: Math.floor(payload.price * 100), - } - - return price - } - - async handleSubscribe(pair: string) { - // "btc:usd" => "BTC-USD" - const targetPair = pair.replace(":", "-").toUpperCase() - - this.conn.send( - JSON.stringify({ - type: "subscribe", - product_ids: [targetPair], - channels: ["ticker"], + market: pair, }) ) } @@ -286,11 +273,8 @@ export class Binance extends PriceFeed { if (payload.e != "trade") { return } - // "btcbusd" => "btc:usd" - // assume that the base symbol for the pair is 3 letters - const baseCurrency = payload.s.slice(0, 3).toLowerCase(); - const quoteCurrency = payload.s.slice(3).toLowerCase(); - const pair = `${baseCurrency}:${quoteCurrency == 'busd' ? 'usd' : quoteCurrency}`; + + const pair = payload.s; const price: IPrice = { @@ -304,9 +288,7 @@ export class Binance extends PriceFeed { } async handleSubscribe(pair: string) { - // "btc:usd" => "btcbusd" - const [baseCurrency, quoteCurrency] = pair.split(':') - const targetPair = `${baseCurrency}${(quoteCurrency.toLowerCase() === 'usd' ? 'busd' : quoteCurrency)}@trade`.toLowerCase() + const targetPair = `${pair}@trade`.toLowerCase() this.conn.send( JSON.stringify({ method: "SUBSCRIBE", @@ -354,10 +336,8 @@ export class OKEx extends PriceFeed { return } - // "BTC-USDT" => "btc:usd" - const [baseCurrency, quoteCurrency] = (payload.data[0].instrument_id as string).toLowerCase().split('-'); - // assume that quote is always any form of usd/usdt/usdc so map to usd - const pair = `${baseCurrency}:${quoteCurrency.slice(0, 3)}`; + const pair = payload.data[0].instrument_id as string; + const price: IPrice = { source: OKEx.name, pair, @@ -369,9 +349,7 @@ export class OKEx extends PriceFeed { } async handleSubscribe(pair: string) { - // "btc:usd" => "BTC-USDT" - const [baseCurrency, quoteCurrency] = pair.split(':') - const targetPair = `spot/ticker:${baseCurrency.toUpperCase()}-${(quoteCurrency.toLowerCase() === 'usd' ? 'USDT' : quoteCurrency)}` + const targetPair = `spot/ticker:${pair}` this.conn.send( JSON.stringify({ "op": "subscribe", @@ -388,31 +366,30 @@ export class AggregatedFeed { public prices: IPrice[] = [] // assume that the feeds are already connected - constructor(public feeds: PriceFeed[], public pair: string) { + constructor(public feeds: PriceFeed[], public pairMappings: string[], public pair: string) { this.subscribe() } private subscribe() { const pair = this.pair + const pairMappings = this.pairMappings; - let i = 0 - for (let feed of this.feeds) { - feed.subscribe(pair) + let j = 0 - const index = i - i++ - - // store the price updates in the ith position of `this.prices` - feed.emitter.on(UPDATE, (price: IPrice) => { - if (price.pair != pair) { - return - } - - price.timestamp = Date.now() - this.prices[index] = price - - this.onPriceUpdate(price) - }) + for (let i = 0; i < this.feeds.length; i++) { + const feed = this.feeds[i]; + feed.subscribe(pairMappings[i]); + const index = j; + j++; + // store the price updates in the ith position of `this.prices` + feed.emitter.on(UPDATE, (price: IPrice) => { + if (price.pair != pairMappings[i]) { + return + } + price.timestamp = Date.now() + this.prices[index] = price + this.onPriceUpdate(price) + }) } }