From 14c72c9c6b45c7514917df29138f66602839ddf5 Mon Sep 17 00:00:00 2001 From: Ryan O'Hara-Reid Date: Fri, 24 Jul 2020 13:18:09 +1000 Subject: [PATCH] Currency System Update (#448) * initial update of currency system * WIP progress * Finish initial currency string error returns * fix whoopsie testing for non https insecureinos * Current WIP for getEnabledPairs check and error return * WIP continued * When getting enabled pairs throw error when item is not contained in available pairs list * More updates -WIP * Wip continued including potential interface * Current WIP * pairs manager pass * drop asset string and just use the map key, plus return some errors and create more work for myself. * clean and fixed a bug in currency.json, will not populate correctly without coinmarketcap api keys set. * purge logger references after merge * go mod tidy after merge * Pointer change WIP * fix some issues and added error returns to a few items (WIP) * WIP * Clean * Fix some linter issues * Fix more linter issues * even more linters * xtda nits * revert pointer change and rm field * Addr madcozbadd nits * fix linter issues: shadow declarations * Fix linter issues: gocritic huge things * linter issue fix * Addr nits * flush go mod files * after merge woops * fix shadow dec * Addr thrasher nits * addr nits * fix some issues * more fixes * RM println * Addr glorious nits * Add helper method for setting assets * add missing format directive * Addr nits * Actually process new futures contracts -_- derp * WIP for GRPC upgrade for pair management * update config pairs * finished disabling and enabling asset * linked update of tradable pairs to cli * fix oopsies * defer writing to file on program termination for currency storage system * update template * don't add disabled asset items to initial sync * Fix enable disabling a list of pairs and added in a slice error type so we can add whats allowable without throwing an error and return a report, also addressed some other nits * WIP on getting a channel to unsub * Wip track down unsubscribe bug and start creating streaming interface * purge websocket orderbook object and centralised updating routine for orderbook * general clean before interface implementation * stage one connection interface WIP * WIP * repackage wshandler WIP * find difference of subs and change signature of subscriber functions so we can batch subscriptions and unsubscription in exchanges that support it * design change on mange subscription routine WIP * integrated ZB with the new webosocket updates * WIP - okex conversion * integrate websocket upgrades for lakebtc, kraken, huobi, hitbtc, gateio, and WIP for coinbene * integrate another range of exchanges for websocket update * Added subscriber and unsubscriber methods to websocket functionality * fixed tests WIP * amalgamate cache setup with main websocket setup * reinstate exported fields traffic alert and shutdownC to accommodate gemini and lakebtc implementations * added in colon * Updated websocket auth handling as they werent getting passed through. Added a setter method for websocket URL due to the Binance generated auth key/listenKey. Fixed bug which stopped reconnection. * Fix subscribe candle bug Fix time conversion in candle Fix inititial candle history to datahandler Include funding to orderbook handling Include funding to trades Reduce code duplication in sub and unsub functions Added the ability to include funding currency websocket subscriptions validated all channels and added more items todo list (Auth items) * RM line * bitstamp pass * btcmarkets pass - still needs to implement unsubscriber functionality and pairs change test. * Batch outgoing subscriptions and fix unsubscribe bug * BTSE - bumped time to minute to reduce pinger calling by 75 calls per day. Fix authentication bug and add authentication pass into to-do. Batch outgoing subscription calls * fix type field and batch outgoing subs and unsubs for coinbasepro * Batch outgoing subs and unsubs * Fixes bug when matching return from authentication * Fix bug where params where being sent out of order due to map ,where depth items werent being subscribed too, where trying to subscribe to too many kline items caused error, where trying to get a nano secocond ID conflicted due to speed of generation. * Add websocket capability for currency pair change by utilizing full channel subscription list in subscribe function. * Add error handling * Fix public: time conversions, subscription list, stopped pushing heartbeat to data handler, aggregated list of connections. * hitBTC pass * returned nil instead of error due to period null bids and asks updates coming through. * Fix auth ping capture and reply. Added in interval handling for kline data. Added correct full trade data handling. Fix subscribe and unsubscribe. * Fix when websocket auth conn and token generation fail we don't try and auth sub. Fix bug between auth and normal connection id generation and matching. Batch outgoing payloads to increase efficiency. Updated matching functions to utilise channels instead of waitgroups and go routines. * RM debug output * rm func to get shutdown channel * Add unsubscriber functionality, added wsTicker type, removed return as this will impede data flow and cause reconnection when handling and processing data * okgroup WIP * *Added missing fields for websocket trades *Fix bug processing kline interval *Added fields for websocket ticker struct *Fix auth bug -Updated request and response matching param to interface so we can custom signature match. Stops auth subscribing before a reply is issued. -Updated channel inclusion of pair fo auth subs as this was missing. *Assortment of perfomance improvements * poloniex pass * send all trades to data handler, validated enabled and disable pairs * initial clean * centralised request matching mechanism * websocket main improvements WIP * WIP * Websocket management via gctcli WIP * GRPC expansion * Updated GCTCLI with websocket url and proxy setting functionality which flushes connection * Fix continuous spawning of routines bug on error with reconnection * Addr linter issues * fix subscription bug that I caused when I changed to a switch case * fix linter issue * fix woopsie * End of day WIP * Fix order submission REST, time conversion, order type conversion, orderID bugs * fix gateio test and unsubscribe bug * revert comment out code * websocketAPI changed to to true in configtest.json * fix race in gateio test * End of day WIP for websocket tests. * BugFix for binance when book isn't seeded. Updated websocket tests. Deprecated subscription manager. RM wrapper funcs. * Added string title to exchange name as they are saved as lower case in type, reinstated verbose check in websocket.go * Added verbosity check for setting websocket URL * fix bug where the asset had a mind of its own * purge dodgy coding * Fix tests, drop blocking chan in websocket Dial function * few more changes * race condition fix for websocket tests. * fix intermittent test failure due to underlying hash table storage * Address madcuzbad nit * RM superfluous printlines * Add quick top example with paramater fields * First pass Glorious nits * As per madcozbad suggestion return error when enabled pair not found in full return map. Add test. * addr madcozbadd nits * as per glorious suggestion rm'd loadedJSON field * adjusted ticker, added test and RM'd code that can never be executed * Addr nits and add in crypto rand genration for ID's * remove global channel declaration and rescoped as this was causing a lock * as per glorious suggestion restructured return error for websocket * addr glorious suggestions * fix linter issues * purge non-existent pair from testdata * add side field to struct and parse * addr glorious nits * Add verbosity to error returns and logs and fix string parsing in GCTRPC * fix speeling mistwake * Adds websocket functionality check before flushing websocket connection * Addr kraken panic and setting/flush websocket url stage one. * added websocket url check before setting with tests * Added in edge case test if by the time we call contains on available pairs it has been changed * remove error return for func * Continuation of tests * continuation of tests * Stop potential panic within pair creation function * Implement changes to upstream * rm sup comment * fix bug when subscribing and unsubscribing. Also add in boolean to determine there are currencies that need to be flushed via set pairs via gctcli * fix test * Fix linter issues * Fix tests * turn websocket off in config example * Fix issue where you cannot enable websocket when config is set to false, also added config websocket enable state saving * Introduced err var for same error returns * Add err var exchange base not found * restructure function * drop gctscript from generic response name * drop managesub delay const as its not being used * correctly implement websocket rate limiting for coinut * remove quotations * drop pair management check * fix spelling * return error in function to not update currency with unset role * amalagamted enable/disable into set function and added in pairstore fetch function * update error description * rm function * moved test function to sharedtestvals and move type to types.go * append delimiter onto currency delimiter strings * add test coverage * rm functions as they are set as methods in base * remove superfluous methods * Fix issue that would occur when a subscription errored and not appending successful subs * fix after rebase woopsie * fix linter issues * fix bug streamline code * fix linter issues * fix linter issues * fix case where it should not change ID if set but append new * fix whoopsie * fix websocket tests * fix readme, fix wrapper issues reporting template, go mod tidy * add test coverage * add test coverage and verified futures pariing * add in futures bypass as its not currently supported on BTSE until API update and implementation * removed downside/upside profit contract type as its no longer supported. Added in check in set config pairs to warn user of potential conflict and to manually remove or update. * If asset enabled add pair and increase code coverage * remove strings.title, set and fetch with strings.Lower but keep struct field exchangename unchanged. Streamline ticker and orderbook code. * Add code coverage * log error if setting default currency fails, add code coverage * address glorious nits * Addr xtda nits * fix linter issues * addr glorious nits * xtda nits * Addr glorious nits * add subscription protection and removed a superfluous wait call * fix test * fix whoopsie * addr xtda nits * addr glorious nits * Added asset types to subscriptions structs, also added in error handling for resubscription errors * consolidated rpc returned type and added in sucessful strings * dropped stream timing down to 100ms * DOC changes * proxy and url usage string additions * WIP * go mod tidy rides again * Addr nits * Addr nits, fix tests * fix wording * add in test case for currency matching * Add byte length check on outbound websocket payload subscriptions * addr thrasher nits * Addr madcozbadd nits * addr linter issues * Addr glorious nits by amalgamating function into one mega amazing function. * fix futures account subscription bug * addr glorious nits and reinstated wg.Wait() checks * changed string to currency delimiter string and setconnected by function --- README.md | 8 +- cmd/config/config.go | 5 +- cmd/exchange_template/exchange_template.go | 4 - cmd/exchange_template/wrapper_file.tmpl | 110 +- cmd/exchange_wrapper_issues/main.go | 16 +- cmd/exchange_wrapper_issues/report.tmpl | 14 +- cmd/exchange_wrapper_issues/types.go | 2 +- cmd/gctcli/commands.go | 334 +- cmd/gctcli/helpers.go | 20 + cmd/gctcli/main.go | 5 +- cmd/gctcli/pair_management.go | 456 + cmd/gctcli/websocket_management.go | 270 + common/common.go | 15 + common/common_test.go | 16 + config/config.go | 464 +- config/config_encryption.go | 6 - config/config_encryption_test.go | 14 - config/config_test.go | 465 +- config_example.json | 2 +- currency/code.go | 248 +- currency/code_test.go | 73 +- currency/code_types.go | 11 +- currency/coinmarketcap/coinmarketcap.go | 10 +- currency/coinmarketcap/coinmarketcap_test.go | 9 - currency/conversion.go | 12 +- currency/currencies.go | 24 +- currency/currencies_test.go | 15 + currency/currency.go | 23 +- currency/currency_types.go | 26 +- currency/manager.go | 155 +- currency/manager_test.go | 215 +- currency/manager_types.go | 4 +- currency/pair.go | 178 +- currency/pair_methods.go | 117 + currency/pair_test.go | 141 +- currency/pair_types.go | 12 + currency/pairs.go | 54 +- currency/pairs_test.go | 41 +- currency/storage.go | 194 +- currency/storage_test.go | 6 +- currency/storage_types.go | 2 +- currency/translation.go | 6 +- currency/translation_test.go | 16 +- docs/ADD_NEW_EXCHANGE.md | 362 +- engine/engine.go | 7 +- engine/events.go | 18 +- engine/events_test.go | 9 +- engine/exchange.go | 7 +- engine/fake_exchange_test.go | 37 +- engine/helpers.go | 65 +- engine/helpers_test.go | 313 +- engine/orders.go | 33 +- engine/orders_test.go | 21 +- engine/restful_server.go | 9 +- engine/routines.go | 166 +- engine/routines_test.go | 37 +- engine/rpcserver.go | 694 +- engine/syncer.go | 79 +- engine/websocket.go | 17 +- exchanges/alphapoint/alphapoint_websocket.go | 1 - exchanges/alphapoint/alphapoint_wrapper.go | 57 +- exchanges/asset/asset.go | 3 +- exchanges/binance/binance.go | 14 - exchanges/binance/binance_live_test.go | 1 + exchanges/binance/binance_mock_test.go | 2 +- exchanges/binance/binance_test.go | 17 +- exchanges/binance/binance_types.go | 192 +- exchanges/binance/binance_websocket.go | 469 +- exchanges/binance/binance_wrapper.go | 316 +- exchanges/bitfinex/bitfinex.go | 7 +- exchanges/bitfinex/bitfinex_test.go | 204 +- exchanges/bitfinex/bitfinex_types.go | 4 +- exchanges/bitfinex/bitfinex_websocket.go | 463 +- exchanges/bitfinex/bitfinex_wrapper.go | 282 +- exchanges/bitflyer/bitflyer_test.go | 19 +- exchanges/bitflyer/bitflyer_wrapper.go | 108 +- exchanges/bithumb/bithumb_test.go | 35 +- exchanges/bithumb/bithumb_wrapper.go | 118 +- exchanges/bitmex/bitmex.go | 2 - exchanges/bitmex/bitmex_test.go | 26 +- exchanges/bitmex/bitmex_websocket.go | 196 +- exchanges/bitmex/bitmex_wrapper.go | 296 +- exchanges/bitstamp/bitstamp.go | 2 - exchanges/bitstamp/bitstamp_live_test.go | 3 +- exchanges/bitstamp/bitstamp_mock_test.go | 3 +- exchanges/bitstamp/bitstamp_test.go | 14 +- exchanges/bitstamp/bitstamp_websocket.go | 146 +- exchanges/bitstamp/bitstamp_wrapper.go | 153 +- exchanges/bittrex/bittrex_test.go | 9 +- exchanges/bittrex/bittrex_wrapper.go | 220 +- exchanges/btcmarkets/btcmarkets.go | 12 +- exchanges/btcmarkets/btcmarkets_test.go | 27 +- exchanges/btcmarkets/btcmarkets_types.go | 20 +- exchanges/btcmarkets/btcmarkets_websocket.go | 215 +- exchanges/btcmarkets/btcmarkets_wrapper.go | 241 +- exchanges/btse/btse.go | 61 +- exchanges/btse/btse_test.go | 29 +- exchanges/btse/btse_types.go | 39 +- exchanges/btse/btse_websocket.go | 131 +- exchanges/btse/btse_wrapper.go | 296 +- exchanges/coinbasepro/coinbasepro.go | 2 - exchanges/coinbasepro/coinbasepro_test.go | 92 +- exchanges/coinbasepro/coinbasepro_types.go | 4 +- .../coinbasepro/coinbasepro_websocket.go | 205 +- exchanges/coinbasepro/coinbasepro_wrapper.go | 307 +- exchanges/coinbene/coinbene.go | 36 +- exchanges/coinbene/coinbene_test.go | 39 +- exchanges/coinbene/coinbene_websocket.go | 267 +- exchanges/coinbene/coinbene_wrapper.go | 318 +- exchanges/coinut/coinut.go | 4 +- exchanges/coinut/coinut_test.go | 19 +- exchanges/coinut/coinut_websocket.go | 417 +- exchanges/coinut/coinut_wrapper.go | 337 +- exchanges/exchange.go | 417 +- exchanges/exchange_test.go | 637 +- exchanges/exchange_types.go | 4 +- exchanges/exmo/exmo_wrapper.go | 126 +- exchanges/ftx/ftx.go | 2 - exchanges/ftx/ftx_test.go | 21 +- exchanges/ftx/ftx_websocket.go | 245 +- exchanges/ftx/ftx_wrapper.go | 238 +- exchanges/gateio/gateio.go | 2 - exchanges/gateio/gateio_test.go | 162 +- exchanges/gateio/gateio_types.go | 16 +- exchanges/gateio/gateio_websocket.go | 397 +- exchanges/gateio/gateio_wrapper.go | 218 +- exchanges/gemini/gemini.go | 5 +- exchanges/gemini/gemini_live_test.go | 3 +- exchanges/gemini/gemini_mock_test.go | 3 +- exchanges/gemini/gemini_test.go | 87 +- exchanges/gemini/gemini_websocket.go | 111 +- exchanges/gemini/gemini_wrapper.go | 167 +- exchanges/hitbtc/hitbtc.go | 2 - exchanges/hitbtc/hitbtc_test.go | 43 +- exchanges/hitbtc/hitbtc_types.go | 23 +- exchanges/hitbtc/hitbtc_websocket.go | 375 +- exchanges/hitbtc/hitbtc_wrapper.go | 276 +- exchanges/huobi/huobi.go | 5 +- exchanges/huobi/huobi_test.go | 55 +- exchanges/huobi/huobi_types.go | 34 +- exchanges/huobi/huobi_websocket.go | 380 +- exchanges/huobi/huobi_wrapper.go | 264 +- exchanges/interfaces.go | 26 +- exchanges/itbit/itbit_wrapper.go | 125 +- exchanges/kraken/kraken.go | 5 +- exchanges/kraken/kraken_test.go | 58 +- exchanges/kraken/kraken_types.go | 10 +- exchanges/kraken/kraken_websocket.go | 390 +- exchanges/kraken/kraken_wrapper.go | 333 +- exchanges/lakebtc/lakebtc_test.go | 28 +- exchanges/lakebtc/lakebtc_types.go | 13 +- exchanges/lakebtc/lakebtc_websocket.go | 167 +- exchanges/lakebtc/lakebtc_wrapper.go | 140 +- exchanges/lbank/lbank.go | 4 +- exchanges/lbank/lbank_test.go | 14 +- exchanges/lbank/lbank_wrapper.go | 178 +- .../localbitcoins/localbitcoins_wrapper.go | 80 +- exchanges/okcoin/okcoin_test.go | 51 +- exchanges/okcoin/okcoin_wrapper.go | 149 +- exchanges/okex/okex_test.go | 63 +- exchanges/okex/okex_wrapper.go | 320 +- exchanges/okgroup/okgroup.go | 4 +- exchanges/okgroup/okgroup_types.go | 31 +- exchanges/okgroup/okgroup_websocket.go | 472 +- exchanges/okgroup/okgroup_wrapper.go | 142 +- exchanges/order/order_test.go | 99 +- exchanges/order/order_types.go | 17 +- exchanges/order/orders.go | 39 +- exchanges/orderbook/orderbook.go | 91 +- exchanges/orderbook/orderbook_test.go | 57 +- exchanges/poloniex/poloniex.go | 2 - exchanges/poloniex/poloniex_live_test.go | 1 + exchanges/poloniex/poloniex_mock_test.go | 3 +- exchanges/poloniex/poloniex_test.go | 31 +- exchanges/poloniex/poloniex_websocket.go | 234 +- exchanges/poloniex/poloniex_wrapper.go | 222 +- exchanges/protocol/features.go | 61 +- .../sharedtestvalues/sharedtestvalues.go | 20 +- exchanges/stats/stats.go | 31 +- exchanges/stats/stats_test.go | 62 +- .../buffer/buffer.go} | 43 +- .../buffer/buffer_test.go} | 76 +- .../buffer/buffer_types.go} | 15 +- exchanges/stream/stream_match.go | 81 + exchanges/stream/stream_match_test.go | 50 + exchanges/stream/stream_types.go | 111 + exchanges/stream/websocket.go | 935 + exchanges/stream/websocket_connection.go | 314 + exchanges/stream/websocket_test.go | 957 + exchanges/stream/websocket_types.go | 132 + exchanges/ticker/ticker.go | 94 +- exchanges/ticker/ticker_test.go | 127 +- exchanges/websocket/wshandler/wshandler.go | 898 - .../websocket/wshandler/wshandler_test.go | 771 - .../websocket/wshandler/wshandler_types.go | 183 - exchanges/yobit/yobit_wrapper.go | 147 +- exchanges/zb/zb.go | 2 - exchanges/zb/zb_test.go | 32 +- exchanges/zb/zb_websocket.go | 311 +- exchanges/zb/zb_wrapper.go | 205 +- gctrpc/rpc.pb.go | 15023 ++++++---------- gctrpc/rpc.pb.gw.go | 854 +- gctrpc/rpc.proto | 180 +- gctrpc/rpc.swagger.json | 681 +- gctscript/README.md | 22 +- gctscript/modules/gct/exchange.go | 34 +- gctscript/wrappers/gct/exchange/exchange.go | 11 +- .../wrappers/gct/exchange/exchange_test.go | 23 +- gctscript/wrappers/validator/validator.go | 27 +- .../wrappers/validator/validator_test.go | 34 +- go.mod | 17 +- go.sum | 39 +- testdata/configtest.json | 4 +- 213 files changed, 23199 insertions(+), 19517 deletions(-) create mode 100644 cmd/gctcli/helpers.go create mode 100644 cmd/gctcli/pair_management.go create mode 100644 cmd/gctcli/websocket_management.go create mode 100644 currency/pair_methods.go create mode 100644 currency/pair_types.go rename exchanges/{websocket/wsorderbook/wsorderbook.go => stream/buffer/buffer.go} (81%) rename exchanges/{websocket/wsorderbook/wsorderbook_test.go => stream/buffer/buffer_test.go} (92%) rename exchanges/{websocket/wsorderbook/wsorderbook_types.go => stream/buffer/buffer_types.go} (65%) create mode 100644 exchanges/stream/stream_match.go create mode 100644 exchanges/stream/stream_match_test.go create mode 100644 exchanges/stream/stream_types.go create mode 100644 exchanges/stream/websocket.go create mode 100644 exchanges/stream/websocket_connection.go create mode 100644 exchanges/stream/websocket_test.go create mode 100644 exchanges/stream/websocket_types.go delete mode 100644 exchanges/websocket/wshandler/wshandler.go delete mode 100644 exchanges/websocket/wshandler/wshandler_test.go delete mode 100644 exchanges/websocket/wshandler/wshandler_types.go diff --git a/README.md b/README.md index f7eb9509..a89d917d 100644 --- a/README.md +++ b/README.md @@ -141,14 +141,14 @@ Binaries will be published once the codebase reaches a stable condition. |User|Contribution Amount| |--|--| -| [thrasher-](https://github.com/thrasher-) | 640 | +| [thrasher-](https://github.com/thrasher-) | 641 | | [shazbert](https://github.com/shazbert) | 191 | | [gloriousCode](https://github.com/gloriousCode) | 169 | -| [dependabot-preview[bot]](https://github.com/apps/dependabot-preview) | 48 | -| [xtda](https://github.com/xtda) | 43 | +| [dependabot-preview[bot]](https://github.com/apps/dependabot-preview) | 51 | +| [xtda](https://github.com/xtda) | 45 | | [ermalguni](https://github.com/ermalguni) | 14 | | [vadimzhukck](https://github.com/vadimzhukck) | 10 | -| [MadCozBadd](https://github.com/MadCozBadd) | 8 | +| [MadCozBadd](https://github.com/MadCozBadd) | 9 | | [140am](https://github.com/140am) | 8 | | [marcofranssen](https://github.com/marcofranssen) | 8 | | [dackroyd](https://github.com/dackroyd) | 5 | diff --git a/cmd/config/config.go b/cmd/config/config.go index b1e28e48..640caf64 100644 --- a/cmd/config/config.go +++ b/cmd/config/config.go @@ -1,6 +1,7 @@ package main import ( + "encoding/json" "flag" "io/ioutil" "log" @@ -49,9 +50,9 @@ func main() { if !config.ConfirmECS(fileData) && !encrypt { var result interface{} - errf := config.ConfirmConfigJSON(fileData, result) + errf := json.Unmarshal(fileData, &result) if errf != nil { - log.Fatal("File isn't in JSON format") + log.Fatal(errf) } log.Println("File is already decrypted. Encrypting..") encrypt = true diff --git a/cmd/exchange_template/exchange_template.go b/cmd/exchange_template/exchange_template.go index 770167f0..be8da2df 100644 --- a/cmd/exchange_template/exchange_template.go +++ b/cmd/exchange_template/exchange_template.go @@ -15,7 +15,6 @@ import ( "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/core" "github.com/thrasher-corp/gocryptotrader/currency" - "github.com/thrasher-corp/gocryptotrader/exchanges/asset" ) const ( @@ -147,9 +146,6 @@ func makeExchange(exch *exchange) error { newExchConfig.API.Credentials.Key = "Key" newExchConfig.API.Credentials.Secret = "Secret" newExchConfig.CurrencyPairs = ¤cy.PairsManager{ - AssetTypes: asset.Items{ - asset.Spot, - }, UseGlobalFormat: true, RequestFormat: ¤cy.PairFormat{ Uppercase: true, diff --git a/cmd/exchange_template/wrapper_file.tmpl b/cmd/exchange_template/wrapper_file.tmpl index 80b24037..e708df43 100644 --- a/cmd/exchange_template/wrapper_file.tmpl +++ b/cmd/exchange_template/wrapper_file.tmpl @@ -17,7 +17,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" "github.com/thrasher-corp/gocryptotrader/log" "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) @@ -30,13 +30,10 @@ func ({{.Variable}} *{{.CapitalName}}) GetDefaultConfig() (*config.ExchangeConfi exchCfg.HTTPTimeout = exchange.DefaultHTTPTimeout exchCfg.BaseCurrencies = {{.Variable}}.BaseCurrencies - err := {{.Variable}}.SetupDefaults(exchCfg) - if err != nil { - return nil, err - } + {{.Variable}}.SetupDefaults(exchCfg) if {{.Variable}}.Features.Supports.RESTCapabilities.AutoPairUpdates { - err = {{.Variable}}.UpdateTradablePairs(true) + err := {{.Variable}}.UpdateTradablePairs(true) if err != nil { return nil, err } @@ -51,20 +48,46 @@ func ({{.Variable}} *{{.CapitalName}}) SetDefaults() { {{.Variable}}.Verbose = true {{.Variable}}.API.CredentialsValidator.RequiresKey = true {{.Variable}}.API.CredentialsValidator.RequiresSecret = true - {{.Variable}}.CurrencyPairs = currency.PairsManager{ - AssetTypes: asset.Items{ - asset.Spot, - }, - UseGlobalFormat: true, - RequestFormat: ¤cy.PairFormat{ - Uppercase: true, - Delimiter: "-", - }, - ConfigFormat: ¤cy.PairFormat{ - Uppercase: true, - Delimiter: "-", - }, + + // If using only one pair format for request and configuration, across all + // supported asset types either SPOT and FUTURES etc. You can use the + // example below: + + // Request format denotes what the pair as a string will be, when you send + // a request to an exchange. + requestFmt := ¤cy.PairFormat{/*Set pair request formatting details here for e.g.*/ Uppercase: true, Delimiter: ":"} + // Config format denotes what the pair as a string will be, when saved to + // the config.json file. + configFmt := ¤cy.PairFormat{/*Set pair request formatting details here*/} + err := {{.Variable}}.SetGlobalPairsManager(requestFmt, configFmt, /*multiple assets can be set here using the asset package ie asset.Spot*/) + if err != nil { + log.Errorln(log.ExchangeSys, err) } + + // If assets require multiple differences in formating for request and + // configuration, another exchange method can be be used e.g. futures + // contracts require a dash as a delimiter rather than an underscore. You + // can use this example below: + + fmt1 := currency.PairStore{ + RequestFormat: ¤cy.PairFormat{Uppercase: true}, + ConfigFormat: ¤cy.PairFormat{Uppercase: true}, + } + + fmt2 := currency.PairStore{ + RequestFormat: ¤cy.PairFormat{Uppercase: true}, + ConfigFormat: ¤cy.PairFormat{Uppercase: true, Delimiter: ":"}, + } + + err = {{.Variable}}.StoreAssetPairFormat(asset.Spot, fmt1) + if err != nil { + log.Errorln(log.ExchangeSys, err) + } + err = {{.Variable}}.StoreAssetPairFormat(asset.Margin, fmt2) + if err != nil { + log.Errorln(log.ExchangeSys, err) + } + // Fill out the capabilities/features that the exchange supports {{.Variable}}.Features = exchange.Features{ Supports: exchange.FeaturesSupported{ @@ -91,7 +114,7 @@ func ({{.Variable}} *{{.CapitalName}}) SetDefaults() { {{.Variable}}.API.Endpoints.URLDefault = {{.Name}}APIURL {{.Variable}}.API.Endpoints.URL = {{.Variable}}.API.Endpoints.URLDefault - {{.Variable}}.Websocket = wshandler.New() + {{.Variable}}.Websocket = stream.New() {{.Variable}}.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit {{.Variable}}.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout {{.Variable}}.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit @@ -104,15 +127,12 @@ func ({{.Variable}} *{{.CapitalName}}) Setup(exch *config.ExchangeConfig) error return nil } - err := {{.Variable}}.SetupDefaults(exch) - if err != nil { - return err - } + {{.Variable}}.SetupDefaults(exch) // If websocket is supported, please fill out the following /* err = {{.Variable}}.Websocket.Setup( - &wshandler.WebsocketSetup{ + &stream.WebsocketSetup{ Enabled: exch.Features.Enabled.Websocket, Verbose: exch.Verbose, AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, @@ -129,7 +149,7 @@ func ({{.Variable}} *{{.CapitalName}}) Setup(exch *config.ExchangeConfig) error return err } - {{.Variable}}.WebsocketConn = &wshandler.WebsocketConnection{ + {{.Variable}}.WebsocketConn = &stream.WebsocketConnection{ ExchangeName: {{.Variable}}.Name, URL: {{.Variable}}.Websocket.GetWebsocketURL(), ProxyURL: {{.Variable}}.Websocket.GetProxyAddress(), @@ -195,8 +215,13 @@ func ({{.Variable}} *{{.CapitalName}}) UpdateTradablePairs(forceUpdate bool) err if err != nil { return err } - return {{.Variable}}.UpdatePairs(currency.NewPairsFromStrings(pairs), - asset.Spot, false, forceUpdate) + + p, err := currency.NewPairsFromStrings(pairs) + if err != nil { + return err + } + + return {{.Variable}}.UpdatePairs(p, asset.Spot, false, forceUpdate) } @@ -356,11 +381,6 @@ func ({{.Variable}} *{{.CapitalName}}) WithdrawFiatFundsToInternationalBank(with return nil, common.ErrNotYetImplemented } -// GetWebsocket returns a pointer to the exchange websocket -func ({{.Variable}} *{{.CapitalName}}) GetWebsocket() (*wshandler.Websocket, error) { - return nil, common.ErrNotYetImplemented -} - // GetActiveOrders retrieves any orders that are active/open func ({{.Variable}} *{{.CapitalName}}) GetActiveOrders(getOrdersRequest *order.GetOrdersRequest) ([]order.Detail, error) { return nil, common.ErrNotYetImplemented @@ -377,30 +397,6 @@ func ({{.Variable}} *{{.CapitalName}}) GetFeeByType(feeBuilder *exchange.FeeBuil return 0, common.ErrNotYetImplemented } -// SubscribeToWebsocketChannels appends to ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle subscribing -func ({{.Variable}} *{{.CapitalName}}) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - {{.Variable}}.Websocket.SubscribeToChannels(channels) - return nil -} - -// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle unsubscribing -func ({{.Variable}} *{{.CapitalName}}) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - {{.Variable}}.Websocket.RemoveSubscribedChannels(channels) - return nil -} - -// GetSubscriptions returns a copied list of subscriptions -func ({{.Variable}} *{{.CapitalName}}) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) { - return nil, common.ErrNotYetImplemented -} - -// AuthenticateWebsocket sends an authentication message to the websocket -func ({{.Variable}} *{{.CapitalName}}) AuthenticateWebsocket() error { - return common.ErrNotYetImplemented -} - // ValidateCredentials validates current credentials used for wrapper func ({{.Variable}} *{{.CapitalName}}) ValidateCredentials() error { _, err := {{.Variable}}.UpdateAccountInfo() diff --git a/cmd/exchange_wrapper_issues/main.go b/cmd/exchange_wrapper_issues/main.go index 62ae820d..5cb63322 100644 --- a/cmd/exchange_wrapper_issues/main.go +++ b/cmd/exchange_wrapper_issues/main.go @@ -296,10 +296,16 @@ func testWrappers(e exchange.IBotExchange, base *exchange.Base, config *Config) switch { case currencyPairOverride != "": - p = currency.NewPairFromString(currencyPairOverride) + var err error + p, err = currency.NewPairFromString(currencyPairOverride) + if err != nil { + log.Printf("%v Encountered error: '%v'", base.GetName(), err) + continue + } case len(base.Config.CurrencyPairs.Pairs[assetTypes[i]].Enabled) == 0: if len(base.Config.CurrencyPairs.Pairs[assetTypes[i]].Available) == 0 { - log.Printf("%v has no enabled or available currencies. Skipping", base.GetName()) + log.Printf("%v has no enabled or available currencies. Skipping", + base.GetName()) continue } p = base.Config.CurrencyPairs.Pairs[assetTypes[i]].Available.GetRandomPair() @@ -308,8 +314,8 @@ func testWrappers(e exchange.IBotExchange, base *exchange.Base, config *Config) } responseContainer := ExchangeAssetPairResponses{ - AssetType: assetTypes[i], - CurrencyPair: p, + AssetType: assetTypes[i], + Pair: p, } log.Printf("Setup config for %v %v %v", base.GetName(), assetTypes[i], p) @@ -858,7 +864,7 @@ func outputToConsole(exchangeResponses []ExchangeResponses) { log.Printf("%v Result: %v", exchangeResponses[i].ExchangeName, k) log.Printf("Function:\t%v", exchangeResponses[i].AssetPairResponses[j].EndpointResponses[k].Function) log.Printf("AssetType:\t%v", exchangeResponses[i].AssetPairResponses[j].AssetType) - log.Printf("Currency:\t%v\n", exchangeResponses[i].AssetPairResponses[j].CurrencyPair) + log.Printf("Currency:\t%v\n", exchangeResponses[i].AssetPairResponses[j].Pair) log.Printf("Wrapper Params:\t%s\n", exchangeResponses[i].AssetPairResponses[j].EndpointResponses[k].SentParams) if exchangeResponses[i].AssetPairResponses[j].EndpointResponses[k].Error != "" { totalErrors++ diff --git a/cmd/exchange_wrapper_issues/report.tmpl b/cmd/exchange_wrapper_issues/report.tmpl index 2fcede34..ad380698 100644 --- a/cmd/exchange_wrapper_issues/report.tmpl +++ b/cmd/exchange_wrapper_issues/report.tmpl @@ -69,10 +69,10 @@ {{ if eq $length 1 }} {{ else }}
-

{{ $assetPairResponses.AssetType }} {{ $assetPairResponses.CurrencyPair }} Results

+

{{ $assetPairResponses.AssetType }} {{ $assetPairResponses.Pair}} Results

@@ -108,18 +108,18 @@ {{ $assetPairResponses.AssetType }} - {{ $assetPairResponses.CurrencyPair }} + {{ $assetPairResponses.Pair}} {{ $endpointResponse.Function }} -
{{ $endpointResponse.SentParams | printf "%s" }} @@ -131,11 +131,11 @@ -
{{ $endpointResponse.Response | printf "%s" }} diff --git a/cmd/exchange_wrapper_issues/types.go b/cmd/exchange_wrapper_issues/types.go index 08b7e467..ebf518fe 100644 --- a/cmd/exchange_wrapper_issues/types.go +++ b/cmd/exchange_wrapper_issues/types.go @@ -58,7 +58,7 @@ type ExchangeResponses struct { type ExchangeAssetPairResponses struct { ErrorCount int64 `json:"errorCount"` AssetType asset.Item `json:"asset"` - CurrencyPair currency.Pair `json:"currency"` + Pair currency.Pair `json:"currency"` EndpointResponses []EndpointResponse `json:"responses"` } diff --git a/cmd/gctcli/commands.go b/cmd/gctcli/commands.go index cef30dae..f9374ca9 100644 --- a/cmd/gctcli/commands.go +++ b/cmd/gctcli/commands.go @@ -7,9 +7,7 @@ import ( "io/ioutil" "math" "os" - "os/exec" "path/filepath" - "runtime" "strconv" "strings" "time" @@ -566,7 +564,11 @@ func getTicker(c *cli.Context) error { } defer conn.Close() - p := currency.NewPairDelimiter(currencyPair, pairDelimiter) + p, err := currency.NewPairDelimiter(currencyPair, pairDelimiter) + if err != nil { + return err + } + client := gctrpc.NewGoCryptoTraderClient(conn) result, err := client.GetTicker(context.Background(), &gctrpc.GetTickerRequest{ @@ -679,7 +681,11 @@ func getOrderbook(c *cli.Context) error { } defer conn.Close() - p := currency.NewPairDelimiter(currencyPair, pairDelimiter) + p, err := currency.NewPairDelimiter(currencyPair, pairDelimiter) + if err != nil { + return err + } + client := gctrpc.NewGoCryptoTraderClient(conn) result, err := client.GetOrderbook(context.Background(), &gctrpc.GetOrderbookRequest{ @@ -1199,7 +1205,11 @@ func getOrders(c *cli.Context) error { } defer conn.Close() - p := currency.NewPairDelimiter(currencyPair, pairDelimiter) + p, err := currency.NewPairDelimiter(currencyPair, pairDelimiter) + if err != nil { + return err + } + client := gctrpc.NewGoCryptoTraderClient(conn) result, err := client.GetOrders(context.Background(), &gctrpc.GetOrdersRequest{ Exchange: exchangeName, @@ -1407,7 +1417,11 @@ func submitOrder(c *cli.Context) error { } defer conn.Close() - p := currency.NewPairDelimiter(currencyPair, pairDelimiter) + p, err := currency.NewPairDelimiter(currencyPair, pairDelimiter) + if err != nil { + return err + } + client := gctrpc.NewGoCryptoTraderClient(conn) result, err := client.SubmitOrder(context.Background(), &gctrpc.SubmitOrderRequest{ Exchange: exchangeName, @@ -1516,7 +1530,11 @@ func simulateOrder(c *cli.Context) error { } defer conn.Close() - p := currency.NewPairDelimiter(currencyPair, pairDelimiter) + p, err := currency.NewPairDelimiter(currencyPair, pairDelimiter) + if err != nil { + return err + } + client := gctrpc.NewGoCryptoTraderClient(conn) result, err := client.SimulateOrder(context.Background(), &gctrpc.SimulateOrderRequest{ Exchange: exchangeName, @@ -1618,7 +1636,11 @@ func whaleBomb(c *cli.Context) error { } defer conn.Close() - p := currency.NewPairDelimiter(currencyPair, pairDelimiter) + p, err := currency.NewPairDelimiter(currencyPair, pairDelimiter) + if err != nil { + return err + } + client := gctrpc.NewGoCryptoTraderClient(conn) result, err := client.WhaleBomb(context.Background(), &gctrpc.WhaleBombRequest{ Exchange: exchangeName, @@ -1750,7 +1772,11 @@ func cancelOrder(c *cli.Context) error { if !validPair(currencyPair) { return errInvalidPair } - p = currency.NewPairDelimiter(currencyPair, pairDelimiter) + var err error + p, err = currency.NewPairDelimiter(currencyPair, pairDelimiter) + if err != nil { + return err + } } conn, err := setupClient() @@ -1985,7 +2011,11 @@ func addEvent(c *cli.Context) error { } defer conn.Close() - p := currency.NewPairDelimiter(currencyPair, pairDelimiter) + p, err := currency.NewPairDelimiter(currencyPair, pairDelimiter) + if err != nil { + return err + } + client := gctrpc.NewGoCryptoTraderClient(conn) result, err := client.AddEvent(context.Background(), &gctrpc.AddEventRequest{ Exchange: exchangeName, @@ -2776,249 +2806,6 @@ func setLoggerDetails(c *cli.Context) error { return nil } -var getExchangePairsCommand = cli.Command{ - Name: "getexchangepairs", - Usage: "gets an exchanges supported currency pairs (available and enabled) plus asset types", - ArgsUsage: " ", - Action: getExchangePairs, - Flags: []cli.Flag{ - cli.StringFlag{ - Name: "exchange", - Usage: "the exchange to list of the currency pairs of", - }, - cli.StringFlag{ - Name: "asset", - Usage: "the asset type to filter by", - }, - }, -} - -func getExchangePairs(c *cli.Context) error { - if c.NArg() == 0 && c.NumFlags() == 0 { - cli.ShowCommandHelp(c, "getexchangepairs") - return nil - } - - var exchange string - var asset string - - if c.IsSet("exchange") { - exchange = c.String("exchange") - } else { - exchange = c.Args().First() - } - - if !validExchange(exchange) { - return errInvalidExchange - } - - if c.IsSet("asset") { - asset = c.String("asset") - } else { - asset = c.Args().Get(1) - } - - asset = strings.ToLower(asset) - if !validAsset(asset) { - return errInvalidAsset - } - - conn, err := setupClient() - if err != nil { - return err - } - defer conn.Close() - - client := gctrpc.NewGoCryptoTraderClient(conn) - result, err := client.GetExchangePairs(context.Background(), - &gctrpc.GetExchangePairsRequest{ - Exchange: exchange, - Asset: asset, - }, - ) - if err != nil { - return err - } - jsonOutput(result) - return nil -} - -var enableExchangePairCommand = cli.Command{ - Name: "enableexchangepair", - Usage: "enables an exchange currency pair", - ArgsUsage: " ", - Action: enableExchangePair, - Flags: []cli.Flag{ - cli.StringFlag{ - Name: "exchange", - Usage: "the exchange to enable the currency pair for", - }, - cli.StringFlag{ - Name: "pair", - Usage: "the currency pair to enable", - }, - cli.StringFlag{ - Name: "asset", - Usage: "the asset type to enable the currency pair for", - }, - }, -} - -func enableExchangePair(c *cli.Context) error { - if c.NArg() == 0 && c.NumFlags() == 0 { - cli.ShowCommandHelp(c, "enableexchangepair") - return nil - } - - var exchange string - var pair string - var asset string - - if c.IsSet("exchange") { - exchange = c.String("exchange") - } else { - exchange = c.Args().First() - } - - if !validExchange(exchange) { - return errInvalidExchange - } - - if c.IsSet("pair") { - pair = c.String("pair") - } else { - pair = c.Args().Get(1) - } - - if !validPair(pair) { - return errInvalidPair - } - - if c.IsSet("asset") { - asset = c.String("asset") - } else { - asset = c.Args().Get(2) - } - - asset = strings.ToLower(asset) - if !validAsset(asset) { - return errInvalidAsset - } - - conn, err := setupClient() - if err != nil { - return err - } - defer conn.Close() - - p := currency.NewPairDelimiter(pair, pairDelimiter) - client := gctrpc.NewGoCryptoTraderClient(conn) - result, err := client.EnableExchangePair(context.Background(), - &gctrpc.ExchangePairRequest{ - Exchange: exchange, - Pair: &gctrpc.CurrencyPair{ - Delimiter: p.Delimiter, - Base: p.Base.String(), - Quote: p.Quote.String(), - }, - AssetType: asset, - }, - ) - if err != nil { - return err - } - jsonOutput(result) - return nil -} - -var disableExchangePairCommand = cli.Command{ - Name: "disableexchangepair", - Usage: "disables a previously enabled exchange currency pair", - ArgsUsage: " ", - Action: disableExchangePair, - Flags: []cli.Flag{ - cli.StringFlag{ - Name: "exchange", - Usage: "the exchange to disable the currency pair for", - }, - cli.StringFlag{ - Name: "pair", - Usage: "the currency pair to disable", - }, - cli.StringFlag{ - Name: "asset", - Usage: "the asset type to disable the currency pair for", - }, - }, -} - -func disableExchangePair(c *cli.Context) error { - if c.NArg() == 0 && c.NumFlags() == 0 { - cli.ShowCommandHelp(c, "disableexchangepair") - return nil - } - - var exchange string - var pair string - var asset string - - if c.IsSet("exchange") { - exchange = c.String("exchange") - } else { - exchange = c.Args().First() - } - - if !validExchange(exchange) { - return errInvalidExchange - } - - if c.IsSet("pair") { - pair = c.String("pair") - } else { - pair = c.Args().Get(1) - } - - if !validPair(pair) { - return errInvalidPair - } - - if c.IsSet("asset") { - asset = c.String("asset") - } else { - asset = c.Args().Get(2) - } - - asset = strings.ToLower(asset) - if !validAsset(asset) { - return errInvalidAsset - } - - conn, err := setupClient() - if err != nil { - return err - } - defer conn.Close() - - p := currency.NewPairDelimiter(pair, pairDelimiter) - client := gctrpc.NewGoCryptoTraderClient(conn) - result, err := client.DisableExchangePair(context.Background(), - &gctrpc.ExchangePairRequest{ - Exchange: exchange, - Pair: &gctrpc.CurrencyPair{ - Delimiter: p.Delimiter, - Base: p.Base.String(), - Quote: p.Quote.String(), - }, - AssetType: asset, - }, - ) - if err != nil { - return err - } - jsonOutput(result) - return nil -} - var getOrderbookStreamCommand = cli.Command{ Name: "getorderbookstream", Usage: "gets the orderbook stream for a specific currency pair and exchange", @@ -3088,7 +2875,10 @@ func getOrderbookStream(c *cli.Context) error { } defer conn.Close() - p := currency.NewPairDelimiter(pair, pairDelimiter) + p, err := currency.NewPairDelimiter(pair, pairDelimiter) + if err != nil { + return err + } client := gctrpc.NewGoCryptoTraderClient(conn) result, err := client.GetOrderbookStream(context.Background(), @@ -3296,7 +3086,10 @@ func getTickerStream(c *cli.Context) error { } defer conn.Close() - p := currency.NewPairDelimiter(pair, pairDelimiter) + p, err := currency.NewPairDelimiter(pair, pairDelimiter) + if err != nil { + return err + } client := gctrpc.NewGoCryptoTraderClient(conn) result, err := client.GetTickerStream(context.Background(), @@ -3410,19 +3203,6 @@ func getExchangeTickerStream(c *cli.Context) error { } } -func clearScreen() error { - switch runtime.GOOS { - case "windows": - cmd := exec.Command("cmd", "/c", "cls") - cmd.Stdout = os.Stdout - return cmd.Run() - default: - cmd := exec.Command("clear") - cmd.Stdout = os.Stdout - return cmd.Run() - } -} - var getAuditEventCommand = cli.Command{ Name: "getauditevent", Usage: "gets audit events matching query parameters", @@ -3529,8 +3309,8 @@ func getAuditEvent(c *cli.Context) error { var uuid, filename, path string var gctScriptCommand = cli.Command{ - Name: "gctscript", - Usage: "execute gctscript command", + Name: "script", + Usage: "execute scripting management command", ArgsUsage: " ", Subcommands: []cli.Command{ { @@ -4022,7 +3802,10 @@ func getHistoricCandles(c *cli.Context) error { if !validPair(currencyPair) { return errInvalidPair } - p := currency.NewPairDelimiter(currencyPair, pairDelimiter) + p, err := currency.NewPairDelimiter(currencyPair, pairDelimiter) + if err != nil { + return err + } var assetType string if c.IsSet("asset") { @@ -4038,7 +3821,6 @@ func getHistoricCandles(c *cli.Context) error { if c.IsSet("rangesize") { candleRangeSize = c.Int64("rangesize") } else if c.Args().Get(3) != "" { - var err error candleRangeSize, err = strconv.ParseInt(c.Args().Get(3), 10, 64) if err != nil { return err @@ -4048,7 +3830,6 @@ func getHistoricCandles(c *cli.Context) error { if c.IsSet("granularity") { candleGranularity = c.Int64("granularity") } else if c.Args().Get(4) != "" { - var err error candleGranularity, err = strconv.ParseInt(c.Args().Get(4), 10, 64) if err != nil { return err @@ -4151,7 +3932,11 @@ func getHistoricCandlesExtended(c *cli.Context) error { if !validPair(currencyPair) { return errInvalidPair } - p := currency.NewPairDelimiter(currencyPair, pairDelimiter) + + p, err := currency.NewPairDelimiter(currencyPair, pairDelimiter) + if err != nil { + return err + } var assetType string if c.IsSet("asset") { @@ -4167,7 +3952,6 @@ func getHistoricCandlesExtended(c *cli.Context) error { if c.IsSet("interval") { candleGranularity = c.Int64("interval") } else if c.Args().Get(3) != "" { - var err error candleGranularity, err = strconv.ParseInt(c.Args().Get(3), 10, 64) if err != nil { return err diff --git a/cmd/gctcli/helpers.go b/cmd/gctcli/helpers.go new file mode 100644 index 00000000..8a1872d9 --- /dev/null +++ b/cmd/gctcli/helpers.go @@ -0,0 +1,20 @@ +package main + +import ( + "os" + "os/exec" + "runtime" +) + +func clearScreen() error { + switch runtime.GOOS { + case "windows": + cmd := exec.Command("cmd", "/c", "cls") + cmd.Stdout = os.Stdout + return cmd.Run() + default: + cmd := exec.Command("clear") + cmd.Stdout = os.Stdout + return cmd.Run() + } +} diff --git a/cmd/gctcli/main.go b/cmd/gctcli/main.go index 01f2b214..7b08ba54 100644 --- a/cmd/gctcli/main.go +++ b/cmd/gctcli/main.go @@ -127,9 +127,7 @@ func main() { withdrawalRequestCommand, getLoggerDetailsCommand, setLoggerDetailsCommand, - getExchangePairsCommand, - enableExchangePairCommand, - disableExchangePairCommand, + exchangePairManagerCommand, getOrderbookStreamCommand, getExchangeOrderbookStreamCommand, getTickerStreamCommand, @@ -138,6 +136,7 @@ func main() { getHistoricCandlesCommand, getHistoricCandlesExtendedCommand, gctScriptCommand, + websocketManagerCommand, } err := app.Run(os.Args) diff --git a/cmd/gctcli/pair_management.go b/cmd/gctcli/pair_management.go new file mode 100644 index 00000000..148ae0fd --- /dev/null +++ b/cmd/gctcli/pair_management.go @@ -0,0 +1,456 @@ +package main + +import ( + "context" + "strings" + + "github.com/thrasher-corp/gocryptotrader/currency" + "github.com/thrasher-corp/gocryptotrader/gctrpc" + "github.com/urfave/cli" +) + +var exchangePairManagerCommand = cli.Command{ + Name: "pair", + Usage: "execute exchange pair management command", + ArgsUsage: " ", + Subcommands: []cli.Command{ + { + Name: "get", + Usage: "returns all enabled and available pairs by asset type", + ArgsUsage: "", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "exchange", + Usage: "the exchange to act on", + }, + cli.StringFlag{ + Name: "asset", + Usage: "asset", + }, + }, + Action: getExchangePairs, + }, + { + Name: "disableasset", + Usage: "disables asset type", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "exchange", + Usage: "the exchange to act on", + }, + cli.StringFlag{ + Name: "asset", + Usage: "asset", + }, + }, + Action: enableDisableExchangeAsset, + }, + { + Name: "enableasset", + Usage: "enables asset type", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "exchange", + Usage: "the exchange to act on", + }, + cli.StringFlag{ + Name: "asset", + Usage: "asset", + }, + cli.BoolTFlag{ + Name: "enable", + Hidden: true, + }, + }, + Action: enableDisableExchangeAsset, + }, + { + Name: "disable", + Usage: "disable pairs by asset type", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "exchange", + Usage: "the exchange to act on", + }, + cli.StringFlag{ + Name: "pairs", + Usage: "either a single currency pair string or comma delimiter string of pairs e.g. \"BTC-USD,XRP-USD\"", + }, + cli.StringFlag{ + Name: "asset", + Usage: "asset", + }, + }, + Action: enableDisableExchangePair, + }, + { + Name: "enable", + Usage: "enable pairs by asset type", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "exchange", + Usage: "the exchange to act on", + }, + cli.StringFlag{ + Name: "pairs", + Usage: "either a single currency pair string or comma delimiter string of pairs e.g. \"BTC-USD,XRP-USD\"", + }, + cli.StringFlag{ + Name: "asset", + Usage: "asset", + }, + cli.BoolTFlag{ + Name: "enable", + Hidden: true, + }, + }, + Action: enableDisableExchangePair, + }, + { + Name: "enableall", + Usage: "enable all pairs", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "exchange", + Usage: "the exchange to act on", + }, + cli.BoolTFlag{ + Name: "enable", + Hidden: true, + }, + }, + Action: enableDisableAllExchangePairs, + }, + { + Name: "disableall", + Usage: "dissable all pairs", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "exchange", + Usage: "the exchange to act on", + }, + }, + Action: enableDisableAllExchangePairs, + }, + { + Name: "update", + Usage: "fetches supported pairs from the exchange and updates available pairs and removes unsupported enable pairs", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "exchange", + Usage: "the exchange to act on", + }, + }, + Action: updateExchangeSupportedPairs, + }, + { + Name: "getassets", + Usage: "fetches supported assets", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "exchange", + Usage: "the exchange to act on", + }, + }, + Action: getExchangeAssets, + }, + }, +} + +func enableDisableExchangePair(c *cli.Context) error { + enable := c.BoolT("enable") + if c.NArg() == 0 && c.NumFlags() == 0 { + if enable { + return cli.ShowCommandHelp(c, "enable") + } + + return cli.ShowCommandHelp(c, "disable") + } + + var exchange string + var pairs string + var asset string + + if c.IsSet("exchange") { + exchange = c.String("exchange") + } else { + exchange = c.Args().First() + } + + if !validExchange(exchange) { + return errInvalidExchange + } + + if c.IsSet("pairs") { + pairs = c.String("pairs") + } else { + pairs = c.Args().Get(1) + } + + if c.IsSet("asset") { + asset = c.String("asset") + } else { + asset = c.Args().Get(2) + } + + asset = strings.ToLower(asset) + if !validAsset(asset) { + return errInvalidAsset + } + + pairList := strings.Split(pairs, ",") + + var validPairs []*gctrpc.CurrencyPair + for i := range pairList { + if !validPair(pairList[i]) { + return errInvalidPair + } + + p, err := currency.NewPairFromString(pairList[i]) + if err != nil { + return err + } + + validPairs = append(validPairs, &gctrpc.CurrencyPair{ + Delimiter: p.Delimiter, + Base: p.Base.String(), + Quote: p.Quote.String(), + }) + } + + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + client := gctrpc.NewGoCryptoTraderClient(conn) + + result, err := client.SetExchangePair(context.Background(), + &gctrpc.SetExchangePairRequest{ + Exchange: exchange, + Pairs: validPairs, + AssetType: asset, + Enable: enable, + }, + ) + if err != nil { + return err + } + + jsonOutput(result) + return nil +} + +func getExchangePairs(c *cli.Context) error { + if c.NArg() == 0 && c.NumFlags() == 0 { + return cli.ShowSubcommandHelp(c) + } + + var exchange string + var asset string + + if c.IsSet("exchange") { + exchange = c.String("exchange") + } else { + exchange = c.Args().First() + } + + if !validExchange(exchange) { + return errInvalidExchange + } + + if c.IsSet("asset") { + asset = c.String("asset") + } else { + asset = c.Args().Get(1) + } + + asset = strings.ToLower(asset) + if !validAsset(asset) { + return errInvalidAsset + } + + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + client := gctrpc.NewGoCryptoTraderClient(conn) + result, err := client.GetExchangePairs(context.Background(), + &gctrpc.GetExchangePairsRequest{ + Exchange: exchange, + Asset: asset, + }, + ) + if err != nil { + return err + } + jsonOutput(result) + return nil +} + +func enableDisableExchangeAsset(c *cli.Context) error { + enable := c.BoolT("enable") + if c.NArg() == 0 && c.NumFlags() == 0 { + if enable { + return cli.ShowCommandHelp(c, "enableasset") + } + return cli.ShowCommandHelp(c, "disableasset") + } + + var exchange string + var asset string + + if c.IsSet("exchange") { + exchange = c.String("exchange") + } else { + exchange = c.Args().First() + } + + if !validExchange(exchange) { + return errInvalidExchange + } + + if c.IsSet("asset") { + asset = c.String("asset") + } else { + asset = c.Args().Get(1) + } + + asset = strings.ToLower(asset) + if !validAsset(asset) { + return errInvalidAsset + } + + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + client := gctrpc.NewGoCryptoTraderClient(conn) + result, err := client.SetExchangeAsset(context.Background(), + &gctrpc.SetExchangeAssetRequest{ + Exchange: exchange, + Asset: asset, + Enable: enable, + }, + ) + if err != nil { + return err + } + jsonOutput(result) + return nil +} + +func enableDisableAllExchangePairs(c *cli.Context) error { + enable := c.BoolT("enable") + if c.NArg() == 0 && c.NumFlags() == 0 { + if enable { + return cli.ShowCommandHelp(c, "enableall") + } + return cli.ShowCommandHelp(c, "disableall") + } + + var exchange string + if c.IsSet("exchange") { + exchange = c.String("exchange") + } else { + exchange = c.Args().First() + } + + if !validExchange(exchange) { + return errInvalidExchange + } + + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + client := gctrpc.NewGoCryptoTraderClient(conn) + result, err := client.SetAllExchangePairs(context.Background(), + &gctrpc.SetExchangeAllPairsRequest{ + Exchange: exchange, + Enable: enable, + }, + ) + if err != nil { + return err + } + jsonOutput(result) + return nil +} + +func updateExchangeSupportedPairs(c *cli.Context) error { + if c.NArg() == 0 && c.NumFlags() == 0 { + return cli.ShowSubcommandHelp(c) + } + + var exchange string + if c.IsSet("exchange") { + exchange = c.String("exchange") + } else { + exchange = c.Args().First() + } + + if !validExchange(exchange) { + return errInvalidExchange + } + + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + client := gctrpc.NewGoCryptoTraderClient(conn) + result, err := client.UpdateExchangeSupportedPairs(context.Background(), + &gctrpc.UpdateExchangeSupportedPairsRequest{ + Exchange: exchange, + }, + ) + if err != nil { + return err + } + jsonOutput(result) + return nil +} + +func getExchangeAssets(c *cli.Context) error { + if c.NArg() == 0 && c.NumFlags() == 0 { + return cli.ShowSubcommandHelp(c) + } + + var exchange string + if c.IsSet("exchange") { + exchange = c.String("exchange") + } else { + exchange = c.Args().First() + } + + if !validExchange(exchange) { + return errInvalidExchange + } + + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + client := gctrpc.NewGoCryptoTraderClient(conn) + result, err := client.GetExchangeAssets(context.Background(), + &gctrpc.GetExchangeAssetsRequest{ + Exchange: exchange, + }, + ) + if err != nil { + return err + } + jsonOutput(result) + return nil +} diff --git a/cmd/gctcli/websocket_management.go b/cmd/gctcli/websocket_management.go new file mode 100644 index 00000000..8b747e9c --- /dev/null +++ b/cmd/gctcli/websocket_management.go @@ -0,0 +1,270 @@ +package main + +import ( + "context" + "fmt" + + "github.com/thrasher-corp/gocryptotrader/gctrpc" + "github.com/urfave/cli" +) + +var websocketManagerCommand = cli.Command{ + Name: "websocket", + Usage: "execute websocket management command", + ArgsUsage: " ", + Subcommands: []cli.Command{ + { + Name: "getinfo", + Usage: "returns all exchange websocket information", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "exchange", + Usage: "the exchange to act on", + }, + }, + Action: getwebsocketInfo, + }, + { + Name: "disable", + Usage: "disables websocket connection for an exchange", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "exchange", + Usage: "the exchange to act on", + }, + }, + Action: enableDisableWebsocket, + }, + { + Name: "enable", + Usage: "enables websocket connection for an exchange", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "exchange", + Usage: "the exchange to act on", + }, + cli.BoolTFlag{ + Name: "enable", + Hidden: true, + }, + }, + Action: enableDisableWebsocket, + }, + { + Name: "getsubs", + Usage: "returns current subscriptions for an exchange", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "exchange", + Usage: "the exchange to act on", + }, + }, + Action: getSubscriptions, + }, + { + Name: "setproxy", + Usage: "sets exchange websocket proxy, flushes and reroutes connection", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "exchange", + Usage: "the exchange to act on", + }, + cli.StringFlag{ + Name: "proxy", + Usage: "proxy address to change to, if proxy string is not set, this will stop the utilization of the prior set proxy.", + }, + }, + Action: setProxy, + }, + { + Name: "seturl", + Usage: "sets exchange websocket endpoint URL and resets the websocket connection", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "exchange", + Usage: "the exchange to act on", + }, + cli.StringFlag{ + Name: "url", + Usage: "url string to change to, an empty string will set it back to the packaged defined default", + }, + }, + Action: setURL, + }, + }, +} + +func getwebsocketInfo(c *cli.Context) error { + if c.NArg() == 0 && c.NumFlags() == 0 { + return cli.ShowSubcommandHelp(c) + } + + var exchange string + if c.IsSet("exchange") { + exchange = c.String("exchange") + } else { + exchange = c.Args().First() + } + + if !validExchange(exchange) { + return fmt.Errorf("[%s] is not a valid exchange", exchange) + } + + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + client := gctrpc.NewGoCryptoTraderClient(conn) + result, err := client.WebsocketGetInfo(context.Background(), + &gctrpc.WebsocketGetInfoRequest{Exchange: exchange}) + if err != nil { + return err + } + jsonOutput(result) + return nil +} + +func enableDisableWebsocket(c *cli.Context) error { + enable := c.BoolT("enable") + if c.NArg() == 0 && c.NumFlags() == 0 { + return cli.ShowSubcommandHelp(c) + } + + var exchange string + if c.IsSet("exchange") { + exchange = c.String("exchange") + } else { + exchange = c.Args().First() + } + + if !validExchange(exchange) { + return fmt.Errorf("[%s] is not a valid exchange", exchange) + } + + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + client := gctrpc.NewGoCryptoTraderClient(conn) + result, err := client.WebsocketSetEnabled(context.Background(), + &gctrpc.WebsocketSetEnabledRequest{Exchange: exchange, Enable: enable}) + if err != nil { + return err + } + jsonOutput(result) + return nil +} + +func getSubscriptions(c *cli.Context) error { + if c.NArg() == 0 && c.NumFlags() == 0 { + return cli.ShowSubcommandHelp(c) + } + + var exchange string + if c.IsSet("exchange") { + exchange = c.String("exchange") + } else { + exchange = c.Args().First() + } + + if !validExchange(exchange) { + return fmt.Errorf("[%s] is not a valid exchange", exchange) + } + + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + client := gctrpc.NewGoCryptoTraderClient(conn) + result, err := client.WebsocketGetSubscriptions(context.Background(), + &gctrpc.WebsocketGetSubscriptionsRequest{Exchange: exchange}) + if err != nil { + return err + } + jsonOutput(result) + return nil +} + +func setProxy(c *cli.Context) error { + if c.NArg() == 0 && c.NumFlags() == 0 { + return cli.ShowSubcommandHelp(c) + } + + var exchange string + if c.IsSet("exchange") { + exchange = c.String("exchange") + } else { + exchange = c.Args().First() + } + + if !validExchange(exchange) { + return fmt.Errorf("[%s] is not a valid exchange", exchange) + } + + var proxy string + if c.IsSet("proxy") { + proxy = c.String("proxy") + } else { + proxy = c.Args().Get(1) + } + + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + client := gctrpc.NewGoCryptoTraderClient(conn) + result, err := client.WebsocketSetProxy(context.Background(), + &gctrpc.WebsocketSetProxyRequest{Exchange: exchange, Proxy: proxy}) + if err != nil { + return err + } + jsonOutput(result) + return nil +} + +func setURL(c *cli.Context) error { + if c.NArg() == 0 && c.NumFlags() == 0 { + return cli.ShowSubcommandHelp(c) + } + + var exchange string + if c.IsSet("exchange") { + exchange = c.String("exchange") + } else { + exchange = c.Args().First() + } + + if !validExchange(exchange) { + return fmt.Errorf("[%s] is not a valid exchange", exchange) + } + + var url string + if c.IsSet("url") { + url = c.String("url") + } else { + url = c.Args().Get(1) + } + + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + client := gctrpc.NewGoCryptoTraderClient(conn) + result, err := client.WebsocketSetURL(context.Background(), + &gctrpc.WebsocketSetURLRequest{Exchange: exchange, Url: url}) + if err != nil { + return err + } + jsonOutput(result) + return nil +} diff --git a/common/common.go b/common/common.go index 67ee52a9..dd587ae0 100644 --- a/common/common.go +++ b/common/common.go @@ -361,3 +361,18 @@ func InArray(val, array interface{}) (exists bool, index int) { } return } + +// Errors defines multiple errors +type Errors []error + +// Error implements error interface +func (e Errors) Error() string { + if len(e) == 0 { + return "" + } + var r string + for i := range e { + r += e[i].Error() + ", " + } + return r[:len(r)-2] +} diff --git a/common/common_test.go b/common/common_test.go index 31006441..391ba2b0 100644 --- a/common/common_test.go +++ b/common/common_test.go @@ -1,6 +1,7 @@ package common import ( + "errors" "net/url" "os" "os/user" @@ -547,3 +548,18 @@ func TestInArray(t *testing.T) { t.Errorf("found a non existent value in the slice") } } + +func TestErrors(t *testing.T) { + var test Errors + if test.Error() != "" { + t.Fatal("string should be nil") + } + test = append(test, errors.New("test1")) + if test.Error() != "test1" { + t.Fatal("does not match error") + } + test = append(test, errors.New("test2")) + if test.Error() != "test1, test2" { + t.Fatal("does not match error") + } +} diff --git a/config/config.go b/config/config.go index 5467373e..4f3bb2af 100644 --- a/config/config.go +++ b/config/config.go @@ -345,71 +345,46 @@ func (c *Config) GetExchangeAssetTypes(exchName string) (asset.Items, error) { return nil, fmt.Errorf("exchange %s currency pairs is nil", exchName) } - return exchCfg.CurrencyPairs.AssetTypes, nil + return exchCfg.CurrencyPairs.GetAssetTypes(), nil } // SupportsExchangeAssetType returns whether or not the exchange supports the supplied asset type -func (c *Config) SupportsExchangeAssetType(exchName string, assetType asset.Item) (bool, error) { +func (c *Config) SupportsExchangeAssetType(exchName string, assetType asset.Item) error { exchCfg, err := c.GetExchangeConfig(exchName) if err != nil { - return false, err + return err } if exchCfg.CurrencyPairs == nil { - return false, fmt.Errorf("exchange %s currency pairs is nil", exchName) + return fmt.Errorf("exchange %s currency pairs is nil", exchName) } if !asset.IsValid(assetType) { - return false, fmt.Errorf("exchange %s invalid asset types", exchName) + return fmt.Errorf("exchange %s invalid asset type %s", + exchName, + assetType) } - return exchCfg.CurrencyPairs.AssetTypes.Contains(assetType), nil -} - -// CheckExchangeAssetsConsistency checks the exchanges supported assets compared to the stored -// entries and removes any non supported -func (c *Config) CheckExchangeAssetsConsistency(exchName string) { - exchCfg, err := c.GetExchangeConfig(exchName) - if err != nil { - return - } - - exchangeAssetTypes, err := c.GetExchangeAssetTypes(exchName) - if err != nil { - return - } - - storedAssetTypes := exchCfg.CurrencyPairs.GetAssetTypes() - for x := range storedAssetTypes { - if !exchangeAssetTypes.Contains(storedAssetTypes[x]) { - log.Warnf(log.ConfigMgr, - "%s has non-needed stored asset type %v. Removing..\n", - exchName, storedAssetTypes[x]) - exchCfg.CurrencyPairs.Delete(storedAssetTypes[x]) - } + if !exchCfg.CurrencyPairs.GetAssetTypes().Contains(assetType) { + return fmt.Errorf("exchange %s unsupported asset type %s", + exchName, + assetType) } + return nil } // SetPairs sets the exchanges currency pairs func (c *Config) SetPairs(exchName string, assetType asset.Item, enabled bool, pairs currency.Pairs) error { - if len(pairs) == 0 { - return fmt.Errorf("pairs is nil") - } - exchCfg, err := c.GetExchangeConfig(exchName) if err != nil { return err } - supports, err := c.SupportsExchangeAssetType(exchName, assetType) + err = c.SupportsExchangeAssetType(exchName, assetType) if err != nil { return err } - if !supports { - return fmt.Errorf("exchange %s does not support asset type %v", exchName, assetType) - } - exchCfg.CurrencyPairs.StorePairs(assetType, pairs, enabled) return nil } @@ -421,16 +396,12 @@ func (c *Config) GetCurrencyPairConfig(exchName string, assetType asset.Item) (* return nil, err } - supports, err := c.SupportsExchangeAssetType(exchName, assetType) + err = c.SupportsExchangeAssetType(exchName, assetType) if err != nil { return nil, err } - if !supports { - return nil, fmt.Errorf("exchange %s does not support asset type %v", exchName, assetType) - } - - return exchCfg.CurrencyPairs.Get(assetType), nil + return exchCfg.CurrencyPairs.Get(assetType) } // CheckPairConfigFormats checks to see if the pair config format is valid @@ -508,60 +479,128 @@ func (c *Config) CheckPairConsistency(exchName string) error { return err } + var atLeastOneEnabled bool for x := range assetTypes { enabledPairs, err := c.GetEnabledPairs(exchName, assetTypes[x]) + if err == nil { + if len(enabledPairs) != 0 { + atLeastOneEnabled = true + continue + } + var enabled bool + enabled, err = c.AssetTypeEnabled(assetTypes[x], exchName) + if err != nil { + return err + } + + if !enabled { + continue + } + + var availPairs currency.Pairs + availPairs, err = c.GetAvailablePairs(exchName, assetTypes[x]) + if err != nil { + return err + } + + err = c.SetPairs(exchName, + assetTypes[x], + true, + currency.Pairs{availPairs.GetRandomPair()}) + if err != nil { + return err + } + atLeastOneEnabled = true + continue + } + + // On error an enabled pair is not found in the available pairs list + // so remove and report + availPairs, err := c.GetAvailablePairs(exchName, assetTypes[x]) if err != nil { return err } - availPairs, _ := c.GetAvailablePairs(exchName, assetTypes[x]) - if len(availPairs) == 0 { - continue - } - var pairs, pairsRemoved currency.Pairs - update := false - - if len(enabledPairs) > 0 { - for x := range enabledPairs { - if !availPairs.Contains(enabledPairs[x], true) { - update = true - pairsRemoved = append(pairsRemoved, enabledPairs[x]) - continue - } - pairs = append(pairs, enabledPairs[x]) + for x := range enabledPairs { + if !availPairs.Contains(enabledPairs[x], true) { + pairsRemoved = append(pairsRemoved, enabledPairs[x]) + continue } - } else { - update = true + pairs = append(pairs, enabledPairs[x]) } - if !update { + if len(pairsRemoved) == 0 { + return fmt.Errorf("check pair consistency fault for asset %s, conflict found but no pairs removed", + assetTypes[x]) + } + + // Flush corrupted/misspelled enabled pairs in config + err = c.SetPairs(exchName, assetTypes[x], true, pairs) + if err != nil { + return err + } + + log.Warnf(log.ConfigMgr, + "Exchange %s: [%v] Removing enabled pair(s) %v from enabled pairs list, as it isn't located in the available pairs list.\n", + exchName, + assetTypes[x], + pairsRemoved.Strings()) + + if len(pairs) != 0 { + atLeastOneEnabled = true continue } - if len(pairs) == 0 || len(enabledPairs) == 0 { - newPair := availPairs.GetRandomPair() - c.SetPairs(exchName, assetTypes[x], true, currency.Pairs{newPair}) - log.Warnf(log.ExchangeSys, "Exchange %s: [%v] No enabled pairs found in available pairs, randomly added %v pair.\n", - exchName, assetTypes[x], newPair) - continue - } else { - c.SetPairs(exchName, assetTypes[x], true, pairs) + enabled, err := c.AssetTypeEnabled(assetTypes[x], exchName) + if err != nil { + return err } - log.Warnf(log.ExchangeSys, "Exchange %s: [%v] Removing enabled pair(s) %v from enabled pairs as it isn't an available pair.\n", - exchName, assetTypes[x], pairsRemoved.Strings()) + + if !enabled { + continue + } + + err = c.SetPairs(exchName, + assetTypes[x], + true, + currency.Pairs{availPairs.GetRandomPair()}) + if err != nil { + return err + } + atLeastOneEnabled = true + } + + // If no pair is enabled across the entire range of assets, then atleast + // enable one and turn on the asset type + if !atLeastOneEnabled { + avail, err := c.GetAvailablePairs(exchName, assetTypes[0]) + if err != nil { + return err + } + + newPair := avail.GetRandomPair() + err = c.SetPairs(exchName, assetTypes[0], true, currency.Pairs{newPair}) + if err != nil { + return err + } + log.Warnf(log.ConfigMgr, + "Exchange %s: [%v] No enabled pairs found in available pairs list, randomly added %v pair.\n", + exchName, + assetTypes[0], + newPair) } return nil } // SupportsPair returns true or not whether the exchange supports the supplied // pair -func (c *Config) SupportsPair(exchName string, p currency.Pair, assetType asset.Item) (bool, error) { +func (c *Config) SupportsPair(exchName string, p currency.Pair, assetType asset.Item) bool { pairs, err := c.GetAvailablePairs(exchName, assetType) if err != nil { - return false, err + return false } - return pairs.Contains(p, false), nil + return pairs.Contains(p, false) } // GetPairFormat returns the exchanges pair config storage format @@ -571,25 +610,31 @@ func (c *Config) GetPairFormat(exchName string, assetType asset.Item) (currency. return currency.PairFormat{}, err } - supports, err := c.SupportsExchangeAssetType(exchName, assetType) + err = c.SupportsExchangeAssetType(exchName, assetType) if err != nil { return currency.PairFormat{}, err } - if !supports { - return currency.PairFormat{}, - fmt.Errorf("exchange %s does not support asset type %s", exchName, - assetType) - } - if exchCfg.CurrencyPairs.UseGlobalFormat { return *exchCfg.CurrencyPairs.ConfigFormat, nil } - p := exchCfg.CurrencyPairs.Get(assetType) + p, err := exchCfg.CurrencyPairs.Get(assetType) + if err != nil { + return currency.PairFormat{}, err + } + if p == nil { return currency.PairFormat{}, - fmt.Errorf("exchange %s pair store for asset type %s is nil", exchName, + fmt.Errorf("exchange %s pair store for asset type %s is nil", + exchName, + assetType) + } + + if p.ConfigFormat == nil { + return currency.PairFormat{}, + fmt.Errorf("exchange %s pair config format for asset type %s is nil", + exchName, assetType) } @@ -608,7 +653,11 @@ func (c *Config) GetAvailablePairs(exchName string, assetType asset.Item) (curre return nil, err } - pairs := exchCfg.CurrencyPairs.GetPairs(assetType, false) + pairs, err := exchCfg.CurrencyPairs.GetPairs(assetType, false) + if err != nil { + return nil, err + } + if pairs == nil { return nil, nil } @@ -618,7 +667,7 @@ func (c *Config) GetAvailablePairs(exchName string, assetType asset.Item) (curre } // GetEnabledPairs returns a list of currency pairs for a specifc exchange -func (c *Config) GetEnabledPairs(exchName string, assetType asset.Item) ([]currency.Pair, error) { +func (c *Config) GetEnabledPairs(exchName string, assetType asset.Item) (currency.Pairs, error) { exchCfg, err := c.GetExchangeConfig(exchName) if err != nil { return nil, err @@ -629,13 +678,19 @@ func (c *Config) GetEnabledPairs(exchName string, assetType asset.Item) ([]curre return nil, err } - pairs := exchCfg.CurrencyPairs.GetPairs(assetType, true) + pairs, err := exchCfg.CurrencyPairs.GetPairs(assetType, true) + if err != nil { + return pairs, err + } + if pairs == nil { return nil, nil } - return pairs.Format(pairFormat.Delimiter, pairFormat.Index, - pairFormat.Uppercase), nil + return pairs.Format(pairFormat.Delimiter, + pairFormat.Index, + pairFormat.Uppercase), + nil } // GetEnabledExchanges returns a list of enabled exchanges @@ -843,16 +898,6 @@ func (c *Config) CheckExchangeConfigValues() error { c.Exchanges[i].CurrencyPairs.ConfigFormat = c.Exchanges[i].ConfigCurrencyPairFormat c.Exchanges[i].CurrencyPairs.RequestFormat = c.Exchanges[i].RequestCurrencyPairFormat - if c.Exchanges[i].AssetTypes == nil { - c.Exchanges[i].CurrencyPairs.AssetTypes = asset.Items{ - asset.Spot, - } - } else { - c.Exchanges[i].CurrencyPairs.AssetTypes = asset.New( - strings.ToLower(*c.Exchanges[i].AssetTypes), - ) - } - var availPairs, enabledPairs currency.Pairs if c.Exchanges[i].AvailablePairs != nil { availPairs = *c.Exchanges[i].AvailablePairs @@ -865,8 +910,9 @@ func (c *Config) CheckExchangeConfigValues() error { c.Exchanges[i].CurrencyPairs.UseGlobalFormat = true c.Exchanges[i].CurrencyPairs.Store(asset.Spot, currency.PairStore{ - Available: availPairs, - Enabled: enabledPairs, + AssetEnabled: convert.BoolPtr(true), + Available: availPairs, + Enabled: enabledPairs, }, ) @@ -877,6 +923,50 @@ func (c *Config) CheckExchangeConfigValues() error { c.Exchanges[i].AssetTypes = nil c.Exchanges[i].AvailablePairs = nil c.Exchanges[i].EnabledPairs = nil + } else { + assets := c.Exchanges[i].CurrencyPairs.GetAssetTypes() + var atLeastOne bool + for index := range assets { + err := c.Exchanges[i].CurrencyPairs.IsAssetEnabled(assets[index]) + if err != nil { + // Checks if we have an old config without the ability to + // enable disable the entire asset + if err.Error() == "cannot ascertain if asset is enabled, variable is nil" { + log.Warnf(log.ConfigMgr, + "Exchange %s: upgrading config for asset type %s and setting enabled.\n", + c.Exchanges[i].Name, + assets[index]) + err = c.Exchanges[i].CurrencyPairs.SetAssetEnabled(assets[index], true) + if err != nil { + return err + } + atLeastOne = true + } + continue + } + atLeastOne = true + } + + if !atLeastOne { + if len(assets) == 0 { + c.Exchanges[i].Enabled = false + log.Warnf(log.ConfigMgr, + "%s no assets found, disabling...", + c.Exchanges[i].Name) + continue + } + + // turn on an asset if all disabled + log.Warnf(log.ConfigMgr, + "%s assets disabled, turning on asset %s", + c.Exchanges[i].Name, + assets[0]) + + err := c.Exchanges[i].CurrencyPairs.SetAssetEnabled(assets[0], true) + if err != nil { + return err + } + } } if c.Exchanges[i].Enabled { @@ -885,71 +975,101 @@ func (c *Config) CheckExchangeConfigValues() error { c.Exchanges[i].Enabled = false continue } - if (c.Exchanges[i].API.AuthenticatedSupport || c.Exchanges[i].API.AuthenticatedWebsocketSupport) && c.Exchanges[i].API.CredentialsValidator != nil { + if (c.Exchanges[i].API.AuthenticatedSupport || c.Exchanges[i].API.AuthenticatedWebsocketSupport) && + c.Exchanges[i].API.CredentialsValidator != nil { var failed bool - if c.Exchanges[i].API.CredentialsValidator.RequiresKey && (c.Exchanges[i].API.Credentials.Key == "" || c.Exchanges[i].API.Credentials.Key == DefaultAPIKey) { + if c.Exchanges[i].API.CredentialsValidator.RequiresKey && + (c.Exchanges[i].API.Credentials.Key == "" || c.Exchanges[i].API.Credentials.Key == DefaultAPIKey) { failed = true } - if c.Exchanges[i].API.CredentialsValidator.RequiresSecret && (c.Exchanges[i].API.Credentials.Secret == "" || c.Exchanges[i].API.Credentials.Secret == DefaultAPISecret) { + if c.Exchanges[i].API.CredentialsValidator.RequiresSecret && + (c.Exchanges[i].API.Credentials.Secret == "" || c.Exchanges[i].API.Credentials.Secret == DefaultAPISecret) { failed = true } - if c.Exchanges[i].API.CredentialsValidator.RequiresClientID && (c.Exchanges[i].API.Credentials.ClientID == DefaultAPIClientID || c.Exchanges[i].API.Credentials.ClientID == "") { + if c.Exchanges[i].API.CredentialsValidator.RequiresClientID && + (c.Exchanges[i].API.Credentials.ClientID == DefaultAPIClientID || c.Exchanges[i].API.Credentials.ClientID == "") { failed = true } if failed { c.Exchanges[i].API.AuthenticatedSupport = false c.Exchanges[i].API.AuthenticatedWebsocketSupport = false - log.Warnf(log.ExchangeSys, WarningExchangeAuthAPIDefaultOrEmptyValues, c.Exchanges[i].Name) + log.Warnf(log.ConfigMgr, WarningExchangeAuthAPIDefaultOrEmptyValues, c.Exchanges[i].Name) } } - if !c.Exchanges[i].Features.Supports.RESTCapabilities.AutoPairUpdates && !c.Exchanges[i].Features.Supports.WebsocketCapabilities.AutoPairUpdates { + if !c.Exchanges[i].Features.Supports.RESTCapabilities.AutoPairUpdates && + !c.Exchanges[i].Features.Supports.WebsocketCapabilities.AutoPairUpdates { lastUpdated := convert.UnixTimestampToTime(c.Exchanges[i].CurrencyPairs.LastUpdated) lastUpdated = lastUpdated.AddDate(0, 0, pairsLastUpdatedWarningThreshold) if lastUpdated.Unix() <= time.Now().Unix() { - log.Warnf(log.ExchangeSys, WarningPairsLastUpdatedThresholdExceeded, c.Exchanges[i].Name, pairsLastUpdatedWarningThreshold) + log.Warnf(log.ConfigMgr, + WarningPairsLastUpdatedThresholdExceeded, + c.Exchanges[i].Name, + pairsLastUpdatedWarningThreshold) } } if c.Exchanges[i].HTTPTimeout <= 0 { - log.Warnf(log.ExchangeSys, "Exchange %s HTTP Timeout value not set, defaulting to %v.\n", c.Exchanges[i].Name, defaultHTTPTimeout) + log.Warnf(log.ConfigMgr, + "Exchange %s HTTP Timeout value not set, defaulting to %v.\n", + c.Exchanges[i].Name, + defaultHTTPTimeout) c.Exchanges[i].HTTPTimeout = defaultHTTPTimeout } if c.Exchanges[i].WebsocketResponseCheckTimeout <= 0 { - log.Warnf(log.ExchangeSys, "Exchange %s Websocket response check timeout value not set, defaulting to %v.", - c.Exchanges[i].Name, defaultWebsocketResponseCheckTimeout) + log.Warnf(log.ConfigMgr, + "Exchange %s Websocket response check timeout value not set, defaulting to %v.", + c.Exchanges[i].Name, + defaultWebsocketResponseCheckTimeout) c.Exchanges[i].WebsocketResponseCheckTimeout = defaultWebsocketResponseCheckTimeout } if c.Exchanges[i].WebsocketResponseMaxLimit <= 0 { - log.Warnf(log.ExchangeSys, "Exchange %s Websocket response max limit value not set, defaulting to %v.", - c.Exchanges[i].Name, defaultWebsocketResponseMaxLimit) + log.Warnf(log.ConfigMgr, + "Exchange %s Websocket response max limit value not set, defaulting to %v.", + c.Exchanges[i].Name, + defaultWebsocketResponseMaxLimit) c.Exchanges[i].WebsocketResponseMaxLimit = defaultWebsocketResponseMaxLimit } if c.Exchanges[i].WebsocketTrafficTimeout <= 0 { - log.Warnf(log.ExchangeSys, "Exchange %s Websocket response traffic timeout value not set, defaulting to %v.", - c.Exchanges[i].Name, defaultWebsocketTrafficTimeout) + log.Warnf(log.ConfigMgr, + "Exchange %s Websocket response traffic timeout value not set, defaulting to %v.", + c.Exchanges[i].Name, + defaultWebsocketTrafficTimeout) c.Exchanges[i].WebsocketTrafficTimeout = defaultWebsocketTrafficTimeout } if c.Exchanges[i].WebsocketOrderbookBufferLimit <= 0 { - log.Warnf(log.ExchangeSys, "Exchange %s Websocket orderbook buffer limit value not set, defaulting to %v.", - c.Exchanges[i].Name, defaultWebsocketOrderbookBufferLimit) + log.Warnf(log.ConfigMgr, + "Exchange %s Websocket orderbook buffer limit value not set, defaulting to %v.", + c.Exchanges[i].Name, + defaultWebsocketOrderbookBufferLimit) c.Exchanges[i].WebsocketOrderbookBufferLimit = defaultWebsocketOrderbookBufferLimit } err := c.CheckPairConsistency(c.Exchanges[i].Name) if err != nil { - log.Errorf(log.ExchangeSys, "Exchange %s: CheckPairConsistency error: %s\n", c.Exchanges[i].Name, err) + log.Errorf(log.ConfigMgr, + "Exchange %s: CheckPairConsistency error: %s\n", + c.Exchanges[i].Name, + err) c.Exchanges[i].Enabled = false continue } - - c.CheckExchangeAssetsConsistency(c.Exchanges[i].Name) - + for x := range c.Exchanges[i].BankAccounts { + if !c.Exchanges[i].BankAccounts[x].Enabled { + continue + } + err := c.Exchanges[i].BankAccounts[x].Validate() + if err != nil { + c.Exchanges[i].BankAccounts[x].Enabled = false + log.Warnln(log.ConfigMgr, err.Error()) + } + } exchanges++ } } + if exchanges == 0 { return errors.New(ErrNoEnabledExchanges) } @@ -1104,8 +1224,8 @@ func (c *Config) RetrieveConfigCurrencyPairs(enabledOnly bool, assetType asset.I continue } - supports, _ := c.SupportsExchangeAssetType(c.Exchanges[x].Name, assetType) - if !supports { + err := c.SupportsExchangeAssetType(c.Exchanges[x].Name, assetType) + if err != nil { continue } @@ -1118,13 +1238,12 @@ func (c *Config) RetrieveConfigCurrencyPairs(enabledOnly bool, assetType asset.I } for x := range c.Exchanges { - supports, _ := c.SupportsExchangeAssetType(c.Exchanges[x].Name, assetType) - if !supports { + err := c.SupportsExchangeAssetType(c.Exchanges[x].Name, assetType) + if err != nil { continue } var pairs []currency.Pair - var err error if !c.Exchanges[x].Enabled && enabledOnly { pairs, err = c.GetEnabledPairs(c.Exchanges[x].Name, assetType) } else { @@ -1475,7 +1594,7 @@ func (c *Config) ReadConfig(configPath string, dryrun bool) error { } if !ConfirmECS(fileData) { - err = ConfirmConfigJSON(fileData, &c) + err = json.Unmarshal(fileData, c) if err != nil { return err } @@ -1493,38 +1612,39 @@ func (c *Config) ReadConfig(configPath string, dryrun bool) error { return c.SaveConfig(defaultPath, dryrun) } } - } else { - errCounter := 0 - for { - if errCounter >= maxAuthFailures { - return errors.New("failed to decrypt config after 3 attempts") - } - key, err := PromptForConfigKey(IsInitialSetup) - if err != nil { - log.Errorf(log.ConfigMgr, "PromptForConfigKey err: %s", err) - errCounter++ - continue - } + return nil + } - var f []byte - f = append(f, fileData...) - data, err := DecryptConfigFile(f, key) - if err != nil { - log.Errorf(log.ConfigMgr, "DecryptConfigFile err: %s", err) - errCounter++ - continue - } - - err = ConfirmConfigJSON(data, &c) - if err != nil { - if errCounter < maxAuthFailures { - log.Error(log.ConfigMgr, "Invalid password.") - } - errCounter++ - continue - } - break + errCounter := 0 + for { + if errCounter >= maxAuthFailures { + return errors.New("failed to decrypt config after 3 attempts") } + key, err := PromptForConfigKey(IsInitialSetup) + if err != nil { + log.Errorf(log.ConfigMgr, "PromptForConfigKey err: %s", err) + errCounter++ + continue + } + + var f []byte + f = append(f, fileData...) + data, err := DecryptConfigFile(f, key) + if err != nil { + log.Errorf(log.ConfigMgr, "DecryptConfigFile err: %s", err) + errCounter++ + continue + } + + err = json.Unmarshal(data, c) + if err != nil { + if errCounter < maxAuthFailures { + log.Error(log.ConfigMgr, "Invalid password.") + } + errCounter++ + continue + } + break } return nil } @@ -1611,12 +1731,16 @@ func (c *Config) CheckRemoteControlConfig() { func (c *Config) CheckConfig() error { err := c.CheckLoggerConfig() if err != nil { - log.Errorf(log.ConfigMgr, "Failed to configure logger, some logging features unavailable: %s\n", err) + log.Errorf(log.ConfigMgr, + "Failed to configure logger, some logging features unavailable: %s\n", + err) } err = c.checkDatabaseConfig() if err != nil { - log.Errorf(log.DatabaseMgr, "Failed to configure database: %v", err) + log.Errorf(log.DatabaseMgr, + "Failed to configure database: %v", + err) } err = c.CheckExchangeConfigValues() @@ -1626,7 +1750,9 @@ func (c *Config) CheckConfig() error { err = c.checkGCTScriptConfig() if err != nil { - log.Errorf(log.Global, "Failed to configure gctscript, feature has been disabled: %s\n", err) + log.Errorf(log.Global, + "Failed to configure gctscript, feature has been disabled: %s\n", + err) } c.CheckConnectionMonitorConfig() @@ -1641,7 +1767,9 @@ func (c *Config) CheckConfig() error { } if c.GlobalHTTPTimeout <= 0 { - log.Warnf(log.ConfigMgr, "Global HTTP Timeout value not set, defaulting to %v.\n", defaultHTTPTimeout) + log.Warnf(log.ConfigMgr, + "Global HTTP Timeout value not set, defaulting to %v.\n", + defaultHTTPTimeout) c.GlobalHTTPTimeout = defaultHTTPTimeout } @@ -1703,3 +1831,17 @@ func (c *Config) RemoveExchange(exchName string) bool { } return false } + +// AssetTypeEnabled checks to see if the asset type is enabled in configuration +func (c *Config) AssetTypeEnabled(a asset.Item, exch string) (bool, error) { + cfg, err := c.GetExchangeConfig(exch) + if err != nil { + return false, err + } + + err = cfg.CurrencyPairs.IsAssetEnabled(a) + if err != nil { + return false, nil + } + return true, nil +} diff --git a/config/config_encryption.go b/config/config_encryption.go index bf32e0e1..c2ba139a 100644 --- a/config/config_encryption.go +++ b/config/config_encryption.go @@ -5,7 +5,6 @@ import ( "crypto/aes" "crypto/cipher" "crypto/rand" - "encoding/json" "errors" "fmt" "io" @@ -167,11 +166,6 @@ func DecryptConfigFile(configData, key []byte) ([]byte, error) { return result, nil } -// ConfirmConfigJSON confirms JSON in file -func ConfirmConfigJSON(file []byte, result interface{}) error { - return json.Unmarshal(file, &result) -} - // ConfirmSalt checks whether the encrypted data contains a salt func ConfirmSalt(file []byte) bool { return bytes.Contains(file, []byte(SaltPrefix)) diff --git a/config/config_encryption_test.go b/config/config_encryption_test.go index 3eabe392..23ceef30 100644 --- a/config/config_encryption_test.go +++ b/config/config_encryption_test.go @@ -1,7 +1,6 @@ package config import ( - "io/ioutil" "testing" ) @@ -84,19 +83,6 @@ func TestDecryptConfigFile(t *testing.T) { } } -func TestConfirmConfigJSON(t *testing.T) { - var result interface{} - testConfirmJSON, err := ioutil.ReadFile(TestFile) - if err != nil { - t.Errorf("testConfirmJSON: %s", err) - } - - err = ConfirmConfigJSON(testConfirmJSON, &result) - if err != nil || result == nil { - t.Errorf("testConfirmJSON: %s", err) - } -} - func TestConfirmECS(t *testing.T) { t.Parallel() diff --git a/config/config_test.go b/config/config_test.go index 286bba71..82a814c4 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/thrasher-corp/gocryptotrader/common" + "github.com/thrasher-corp/gocryptotrader/common/convert" "github.com/thrasher-corp/gocryptotrader/connchecker" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/database" @@ -21,6 +22,7 @@ const ( defaultEnabledExchanges = 28 testFakeExchangeName = "Stampbit" testPair = "BTC-USD" + testString = "test" ) func TestGetCurrencyConfig(t *testing.T) { @@ -32,6 +34,22 @@ func TestGetCurrencyConfig(t *testing.T) { _ = cfg.GetCurrencyConfig() } +func TestGetClientBankAccounts(t *testing.T) { + cfg := GetConfig() + err := cfg.LoadConfig(TestFile, true) + if err != nil { + t.Fatal("GetExchangeBankAccounts LoadConfig error", err) + } + _, err = cfg.GetClientBankAccounts("Kraken", "USD") + if err != nil { + t.Error("GetExchangeBankAccounts error", err) + } + _, err = cfg.GetClientBankAccounts("noob exchange", "USD") + if err == nil { + t.Fatal("error cannot be nil") + } +} + func TestGetExchangeBankAccounts(t *testing.T) { cfg := GetConfig() err := cfg.LoadConfig(TestFile, true) @@ -48,6 +66,17 @@ func TestGetExchangeBankAccounts(t *testing.T) { } } +func TestCheckBankAccountConfig(t *testing.T) { + cfg := GetConfig() + err := cfg.LoadConfig(TestFile, true) + if err != nil { + t.Error("GetExchangeBankAccounts LoadConfig error", err) + } + + cfg.BankAccounts[0].Enabled = true + cfg.CheckBankAccountConfig() +} + func TestUpdateExchangeBankAccounts(t *testing.T) { cfg := GetConfig() err := cfg.LoadConfig(TestFile, true) @@ -84,7 +113,7 @@ func TestUpdateClientBankAccounts(t *testing.T) { if err != nil { t.Error("UpdateClientBankAccounts LoadConfig error", err) } - b := banking.Account{Enabled: false, BankName: "test", AccountNumber: "0234"} + b := banking.Account{Enabled: false, BankName: testString, AccountNumber: "0234"} err = cfg.UpdateClientBankAccounts(&b) if err != nil { t.Error("UpdateClientBankAccounts error", err) @@ -185,7 +214,7 @@ func TestPurgeExchangeCredentials(t *testing.T) { var c Config c.Exchanges = []ExchangeConfig{ { - Name: "test", + Name: testString, API: APIConfig{ AuthenticatedSupport: true, AuthenticatedWebsocketSupport: true, @@ -219,7 +248,7 @@ func TestPurgeExchangeCredentials(t *testing.T) { c.PurgeExchangeAPICredentials() - exchCfg, err := c.GetExchangeConfig("test") + exchCfg, err := c.GetExchangeConfig(testString) if err != nil { t.Error(err) } @@ -257,8 +286,8 @@ func TestUpdateCommunicationsConfig(t *testing.T) { if err != nil { t.Error("UpdateCommunicationsConfig LoadConfig error", err) } - cfg.UpdateCommunicationsConfig(&CommunicationsConfig{SlackConfig: SlackConfig{Name: "TEST"}}) - if cfg.Communications.SlackConfig.Name != "TEST" { + cfg.UpdateCommunicationsConfig(&CommunicationsConfig{SlackConfig: SlackConfig{Name: testString}}) + if cfg.Communications.SlackConfig.Name != testString { t.Error("UpdateCommunicationsConfig LoadConfig error") } } @@ -308,7 +337,7 @@ func TestCheckCommunicationsConfig(t *testing.T) { cfg.SMS = &SMSGlobalConfig{} cfg.Communications.SMSGlobalConfig.Name = "" cfg.CheckCommunicationsConfig() - if cfg.Communications.SMSGlobalConfig.Password != "test" { + if cfg.Communications.SMSGlobalConfig.Password != testString { t.Error("CheckCommunicationsConfig error:", err) } @@ -389,9 +418,9 @@ func TestGetExchangeAssetTypes(t *testing.T) { ExchangeConfig{ Name: testFakeExchangeName, CurrencyPairs: ¤cy.PairsManager{ - AssetTypes: asset.Items{ - asset.Spot, - asset.Futures, + Pairs: map[asset.Item]*currency.PairStore{ + asset.Spot: new(currency.PairStore), + asset.Futures: new(currency.PairStore), }, }, }, @@ -403,7 +432,7 @@ func TestGetExchangeAssetTypes(t *testing.T) { t.Error(err) } - if assets.JoinToString(",") != "spot,futures" { + if !assets.Contains(asset.Spot) || !assets.Contains(asset.Futures) { t.Error("unexpected results") } @@ -417,7 +446,7 @@ func TestGetExchangeAssetTypes(t *testing.T) { func TestSupportsExchangeAssetType(t *testing.T) { t.Parallel() var c Config - _, err := c.SupportsExchangeAssetType("void", asset.Spot) + err := c.SupportsExchangeAssetType("void", asset.Spot) if err == nil { t.Error("Expected error for non-existent exchange") } @@ -426,73 +455,30 @@ func TestSupportsExchangeAssetType(t *testing.T) { ExchangeConfig{ Name: testFakeExchangeName, CurrencyPairs: ¤cy.PairsManager{ - AssetTypes: asset.Items{ - asset.Spot, - asset.Futures, + Pairs: map[asset.Item]*currency.PairStore{ + asset.Spot: new(currency.PairStore), }, }, }, ) - supports, err := c.SupportsExchangeAssetType(testFakeExchangeName, asset.Spot) + err = c.SupportsExchangeAssetType(testFakeExchangeName, asset.Spot) if err != nil { t.Error(err) } - if !supports { - t.Error("exchange should support spot asset item") - } - - _, err = c.SupportsExchangeAssetType(testFakeExchangeName, "asdf") + err = c.SupportsExchangeAssetType(testFakeExchangeName, "asdf") if err == nil { t.Error("Expected error from invalid asset item") } c.Exchanges[0].CurrencyPairs = nil - _, err = c.SupportsExchangeAssetType(testFakeExchangeName, asset.Spot) + err = c.SupportsExchangeAssetType(testFakeExchangeName, asset.Spot) if err == nil { t.Error("Expected error from nil pair manager") } } -func TestCheckExchangeAssetsConsistency(t *testing.T) { - t.Parallel() - var c Config - // Test for non-existent exchange - c.CheckExchangeAssetsConsistency("void") - - c.Exchanges = append(c.Exchanges, - ExchangeConfig{ - Name: testFakeExchangeName, - }, - ) - - // Tests for nil currency pairs store but valid exchange name - c.CheckExchangeAssetsConsistency(testFakeExchangeName) - - // Simulate testing a diff between stored asset types (config loading) - // and pair store - c.Exchanges[0].CurrencyPairs = ¤cy.PairsManager{ - AssetTypes: asset.Items{ - asset.Spot, - asset.Futures, - asset.Index, - }, - } - c.Exchanges[0].CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore) - c.Exchanges[0].CurrencyPairs.Pairs[asset.PerpetualContract] = ¤cy.PairStore{} - c.CheckExchangeAssetsConsistency(testFakeExchangeName) - - supports, err := c.SupportsExchangeAssetType(testFakeExchangeName, asset.PerpetualContract) - if err != nil { - t.Error(err) - } - - if supports { - t.Error("perpetual contract should have been removed from the pair manager") - } -} - func TestSetPairs(t *testing.T) { t.Parallel() @@ -524,9 +510,8 @@ func TestSetPairs(t *testing.T) { } c.Exchanges[0].CurrencyPairs = ¤cy.PairsManager{ - AssetTypes: asset.Items{ - asset.Spot, - asset.Futures, + Pairs: map[asset.Item]*currency.PairStore{ + asset.Spot: new(currency.PairStore), }, } @@ -562,10 +547,6 @@ func TestGetCurrencyPairConfig(t *testing.T) { } pm := ¤cy.PairsManager{ - AssetTypes: asset.Items{ - asset.Spot, - asset.Futures, - }, Pairs: map[asset.Item]*currency.PairStore{ asset.Spot: { RequestFormat: ¤cy.PairFormat{ @@ -610,11 +591,6 @@ func TestCheckPairConfigFormats(t *testing.T) { c.Exchanges = append(c.Exchanges, ExchangeConfig{ Name: testFakeExchangeName, - CurrencyPairs: ¤cy.PairsManager{ - AssetTypes: asset.Items{ - asset.Item("wrong"), - }, - }, }, ) @@ -622,23 +598,40 @@ func TestCheckPairConfigFormats(t *testing.T) { t.Error("nil pair store should return an error") } - c.Exchanges[0].CurrencyPairs.AssetTypes = asset.Items{asset.Spot} - c.Exchanges[0].CurrencyPairs.Pairs = map[asset.Item]*currency.PairStore{ - asset.Spot: { - RequestFormat: ¤cy.PairFormat{}, - ConfigFormat: ¤cy.PairFormat{}, + c.Exchanges[0].CurrencyPairs = ¤cy.PairsManager{ + Pairs: map[asset.Item]*currency.PairStore{ + asset.Spot: {}, + asset.Futures: {}, }, - asset.Futures: { - RequestFormat: ¤cy.PairFormat{}, - ConfigFormat: ¤cy.PairFormat{}, + } + if err := c.CheckPairConfigFormats(testFakeExchangeName); err == nil { + t.Error("error cannot be nil") + } + + c.Exchanges[0].CurrencyPairs = ¤cy.PairsManager{ + Pairs: map[asset.Item]*currency.PairStore{ + asset.Spot: { + RequestFormat: ¤cy.PairFormat{}, + ConfigFormat: ¤cy.PairFormat{}, + }, + asset.Futures: { + RequestFormat: ¤cy.PairFormat{}, + ConfigFormat: ¤cy.PairFormat{}, + }, }, } if err := c.CheckPairConfigFormats(testFakeExchangeName); err != nil { t.Error("nil pairs should be okay to continue") } - + avail, err := currency.NewPairDelimiter(testPair, "-") + if err != nil { + t.Fatal(err) + } + enabled, err := currency.NewPairDelimiter("BTC~USD", "~") + if err != nil { + t.Fatal(err) + } // Test having a pair index and delimiter set at the same time throws an error - c.Exchanges[0].CurrencyPairs.AssetTypes = asset.Items{asset.Spot} c.Exchanges[0].CurrencyPairs.Pairs = map[asset.Item]*currency.PairStore{ asset.Spot: { RequestFormat: ¤cy.PairFormat{ @@ -651,10 +644,10 @@ func TestCheckPairConfigFormats(t *testing.T) { Index: "USD", }, Available: currency.Pairs{ - currency.NewPairDelimiter(testPair, "-"), + avail, }, Enabled: currency.Pairs{ - currency.NewPairDelimiter("BTC~USD", "~"), + enabled, }, }, } @@ -698,11 +691,6 @@ func TestCheckPairConsistency(t *testing.T) { c.Exchanges = append(c.Exchanges, ExchangeConfig{ Name: testFakeExchangeName, - CurrencyPairs: ¤cy.PairsManager{ - AssetTypes: asset.Items{ - asset.Spot, - }, - }, }, ) @@ -711,33 +699,71 @@ func TestCheckPairConsistency(t *testing.T) { t.Error("nil pair store should return an error") } - c.Exchanges[0].CurrencyPairs.Pairs = map[asset.Item]*currency.PairStore{ - asset.Spot: { - RequestFormat: ¤cy.PairFormat{ - Uppercase: false, - Delimiter: "_", - }, - ConfigFormat: ¤cy.PairFormat{ - Uppercase: true, - Delimiter: "_", - }, - Enabled: currency.Pairs{ - currency.NewPairDelimiter("BTC_USD", "_"), + enabled, err := currency.NewPairDelimiter("BTC_USD", "_") + if err != nil { + t.Fatal(err) + } + + c.Exchanges[0].CurrencyPairs = ¤cy.PairsManager{ + Pairs: map[asset.Item]*currency.PairStore{ + asset.Spot: { + RequestFormat: ¤cy.PairFormat{ + Uppercase: false, + Delimiter: "_", + }, + ConfigFormat: ¤cy.PairFormat{ + Uppercase: true, + Delimiter: "_", + }, + Enabled: currency.Pairs{ + enabled, + }, }, }, } // Test for nil avail pairs - if err := c.CheckPairConsistency(testFakeExchangeName); err != nil { - t.Error("nil available pairs should continue") + err = c.CheckPairConsistency(testFakeExchangeName) + if err != nil { + t.Error(err) + } + + p1, err := currency.NewPairDelimiter("LTC_USD", "_") + if err != nil { + t.Fatal(err) } // Test that enabled pair is not found in the available pairs c.Exchanges[0].CurrencyPairs.Pairs[asset.Spot].Available = currency.Pairs{ - currency.NewPairDelimiter("LTC_USD", "_"), + p1, } + + // LTC_USD is only found in the available pairs list and should therefor + // be added to the enabled pairs list due to the atLestOneEnabled code + err = c.CheckPairConsistency(testFakeExchangeName) + if err != nil { + t.Fatal(err) + } + + for _, item := range c.Exchanges[0].CurrencyPairs.Pairs[asset.Spot].Enabled { + if !item.Equal(p1) { + t.Fatal("LTC_USD should be contained in the enabled pairs list") + } + } + + p2, err := currency.NewPairDelimiter("BTC_USD", "_") + if err != nil { + t.Fatal(err) + } + + // Add the BTC_USD pair and see result + c.Exchanges[0].CurrencyPairs.Pairs[asset.Spot].Available = currency.Pairs{ + p1, + p2, + } + if err := c.CheckPairConsistency(testFakeExchangeName); err != nil { - t.Error("unexpected result") + t.Fatal(err) } // Test that an empty enabled pair is populated with an available pair @@ -746,10 +772,14 @@ func TestCheckPairConsistency(t *testing.T) { t.Error("unexpected result") } + if len(c.Exchanges[0].CurrencyPairs.Pairs[asset.Spot].Enabled) != 1 { + t.Fatal("should be populated with atleast one currency pair") + } + // Test that an invalid enabled pair is removed from the list c.Exchanges[0].CurrencyPairs.Pairs[asset.Spot].Enabled = currency.Pairs{ - currency.NewPairDelimiter("LTC_USD", "_"), - currency.NewPairDelimiter("BTC_USD", "_"), + p1, + p2, } if err := c.CheckPairConsistency(testFakeExchangeName); err != nil { t.Error("unexpected result") @@ -760,6 +790,48 @@ func TestCheckPairConsistency(t *testing.T) { if err := c.CheckPairConsistency(testFakeExchangeName); err != nil { t.Error("unexpected result") } + + c.Exchanges[0].CurrencyPairs.Pairs[asset.Spot].AssetEnabled = convert.BoolPtr(true) + c.Exchanges[0].CurrencyPairs.Pairs[asset.Spot].Enabled = currency.Pairs{} + + // Test no conflict and atleast one on enabled asset type + if err := c.CheckPairConsistency(testFakeExchangeName); err != nil { + t.Error("unexpected result") + } + + c.Exchanges[0].CurrencyPairs.Pairs[asset.Spot].AssetEnabled = convert.BoolPtr(true) + c.Exchanges[0].CurrencyPairs.Pairs[asset.Spot].Enabled = currency.Pairs{currency.NewPair(currency.DASH, currency.USD)} + + // Test with conflict and atleast one on enabled asset type + if err := c.CheckPairConsistency(testFakeExchangeName); err != nil { + t.Error("unexpected result") + } + + c.Exchanges[0].CurrencyPairs.Pairs[asset.Spot].AssetEnabled = convert.BoolPtr(false) + c.Exchanges[0].CurrencyPairs.Pairs[asset.Spot].Enabled = currency.Pairs{} + + // Test no conflict and atleast one on disabled asset type + if err := c.CheckPairConsistency(testFakeExchangeName); err != nil { + t.Error("unexpected result") + } + + c.Exchanges[0].CurrencyPairs.Pairs[asset.Spot].Enabled = currency.Pairs{ + currency.NewPair(currency.DASH, currency.USD), + p1, + p2, + } + + // Test with conflict and atleast one on disabled asset type + if err := c.CheckPairConsistency(testFakeExchangeName); err != nil { + t.Error("unexpected result") + } + + c.Exchanges[0].CurrencyPairs.Pairs[asset.Spot].AssetEnabled = nil + + // assetType enabled failure check + if err := c.CheckPairConsistency(testFakeExchangeName); err != nil { + t.Error("unexpected result") + } } func TestSupportsPair(t *testing.T) { @@ -772,17 +844,15 @@ func TestSupportsPair(t *testing.T) { } assetType := asset.Spot - _, err = cfg.SupportsPair("asdf", - currency.NewPair(currency.BTC, currency.USD), assetType) - if err == nil { + if cfg.SupportsPair("asdf", + currency.NewPair(currency.BTC, currency.USD), assetType) { t.Error( "TestSupportsPair. Expected error from Non-existent exchange", ) } - _, err = cfg.SupportsPair("Bitfinex", - currency.NewPair(currency.BTC, currency.USD), assetType) - if err != nil { + if !cfg.SupportsPair("Bitfinex", + currency.NewPair(currency.BTC, currency.USD), assetType) { t.Errorf( "TestSupportsPair. Incorrect values. Err: %s", err, ) @@ -809,7 +879,26 @@ func TestGetPairFormat(t *testing.T) { } c.Exchanges[0].CurrencyPairs = ¤cy.PairsManager{ - AssetTypes: asset.Items{asset.Spot}, + UseGlobalFormat: false, + RequestFormat: ¤cy.PairFormat{ + Uppercase: false, + Delimiter: "_", + }, + ConfigFormat: ¤cy.PairFormat{ + Uppercase: true, + Delimiter: "_", + }, + Pairs: map[asset.Item]*currency.PairStore{ + asset.Spot: nil, + }, + } + + _, err = c.GetPairFormat(testFakeExchangeName, asset.Spot) + if err == nil { + t.Error("Expected error from nil pair manager") + } + + c.Exchanges[0].CurrencyPairs = ¤cy.PairsManager{ UseGlobalFormat: true, RequestFormat: ¤cy.PairFormat{ Uppercase: false, @@ -819,6 +908,9 @@ func TestGetPairFormat(t *testing.T) { Uppercase: true, Delimiter: "_", }, + Pairs: map[asset.Item]*currency.PairStore{ + asset.Spot: new(currency.PairStore), + }, } _, err = c.GetPairFormat(testFakeExchangeName, asset.Item("invalid")) if err == nil { @@ -876,12 +968,8 @@ func TestGetAvailablePairs(t *testing.T) { c.Exchanges = append(c.Exchanges, ExchangeConfig{ - Name: testFakeExchangeName, - CurrencyPairs: ¤cy.PairsManager{ - AssetTypes: asset.Items{ - asset.Spot, - }, - }, + Name: testFakeExchangeName, + CurrencyPairs: ¤cy.PairsManager{}, }, ) @@ -923,12 +1011,8 @@ func TestGetEnabledPairs(t *testing.T) { c.Exchanges = append(c.Exchanges, ExchangeConfig{ - Name: testFakeExchangeName, - CurrencyPairs: ¤cy.PairsManager{ - AssetTypes: asset.Items{ - asset.Spot, - }, - }, + Name: testFakeExchangeName, + CurrencyPairs: ¤cy.PairsManager{}, }, ) @@ -953,6 +1037,11 @@ func TestGetEnabledPairs(t *testing.T) { c.Exchanges[0].CurrencyPairs.Pairs[asset.Spot].Enabled = currency.Pairs{ currency.NewPair(currency.BTC, currency.USD), } + + c.Exchanges[0].CurrencyPairs.Pairs[asset.Spot].Available = currency.Pairs{ + currency.NewPair(currency.BTC, currency.USD), + } + _, err = c.GetEnabledPairs(testFakeExchangeName, asset.Spot) if err != nil { t.Error(err) @@ -1183,16 +1272,15 @@ func TestCheckExchangeConfigValues(t *testing.T) { // Test API settings migration sptr := func(s string) *string { return &s } - bptr := func(b bool) *bool { return &b } int64ptr := func(i int64) *int64 { return &i } cfg.Exchanges[0].APIKey = sptr("awesomeKey") cfg.Exchanges[0].APISecret = sptr("meowSecret") cfg.Exchanges[0].ClientID = sptr("clientIDerino") cfg.Exchanges[0].APIAuthPEMKey = sptr("-----BEGIN EC PRIVATE KEY-----\nASDF\n-----END EC PRIVATE KEY-----\n") - cfg.Exchanges[0].APIAuthPEMKeySupport = bptr(true) - cfg.Exchanges[0].AuthenticatedAPISupport = bptr(true) - cfg.Exchanges[0].AuthenticatedWebsocketAPISupport = bptr(true) + cfg.Exchanges[0].APIAuthPEMKeySupport = convert.BoolPtr(true) + cfg.Exchanges[0].AuthenticatedAPISupport = convert.BoolPtr(true) + cfg.Exchanges[0].AuthenticatedWebsocketAPISupport = convert.BoolPtr(true) cfg.Exchanges[0].WebsocketURL = sptr("wss://1337") cfg.Exchanges[0].APIURL = sptr(APIURLNonDefaultMessage) cfg.Exchanges[0].APIURLSecondary = sptr(APIURLNonDefaultMessage) @@ -1230,8 +1318,8 @@ func TestCheckExchangeConfigValues(t *testing.T) { // Test feature and endpoint migrations migrations cfg.Exchanges[0].Features = nil - cfg.Exchanges[0].SupportsAutoPairUpdates = bptr(true) - cfg.Exchanges[0].Websocket = bptr(true) + cfg.Exchanges[0].SupportsAutoPairUpdates = convert.BoolPtr(true) + cfg.Exchanges[0].Websocket = convert.BoolPtr(true) cfg.Exchanges[0].API.Endpoints.URL = "" cfg.Exchanges[0].API.Endpoints.URLSecondary = "" cfg.Exchanges[0].API.Endpoints.WebsocketURL = "" @@ -1253,11 +1341,16 @@ func TestCheckExchangeConfigValues(t *testing.T) { t.Error("unexpected values") } + p1, err := currency.NewPairDelimiter(testPair, "-") + if err != nil { + t.Fatal(err) + } + // Test currency pair migration setupPairs := func(emptyAssets bool) { cfg.Exchanges[0].CurrencyPairs = nil p := currency.Pairs{ - currency.NewPairDelimiter(testPair, "-"), + p1, } cfg.Exchanges[0].PairsLastUpdated = int64ptr(1234567) @@ -1305,17 +1398,25 @@ func TestCheckExchangeConfigValues(t *testing.T) { t.Error("unexpected request format values") } - if cfg.Exchanges[0].CurrencyPairs.AssetTypes.JoinToString(",") != "spot" || + if !cfg.Exchanges[0].CurrencyPairs.GetAssetTypes().Contains(asset.Spot) || !cfg.Exchanges[0].CurrencyPairs.UseGlobalFormat { t.Error("unexpected results") } - pairs := cfg.Exchanges[0].CurrencyPairs.GetPairs(asset.Spot, true) + pairs, err := cfg.Exchanges[0].CurrencyPairs.GetPairs(asset.Spot, true) + if err != nil { + t.Fatal(err) + } + if len(pairs) == 0 || pairs.Join() != testPair { t.Error("pairs not set properly") } - pairs = cfg.Exchanges[0].CurrencyPairs.GetPairs(asset.Spot, false) + pairs, err = cfg.Exchanges[0].CurrencyPairs.GetPairs(asset.Spot, false) + if err != nil { + t.Fatal(err) + } + if len(pairs) == 0 || pairs.Join() != testPair { t.Error("pairs not set properly") } @@ -1334,24 +1435,10 @@ func TestCheckExchangeConfigValues(t *testing.T) { cfg.Exchanges[0].Features.Supports.RESTCapabilities.AutoPairUpdates = false cfg.Exchanges[0].Features.Supports.WebsocketCapabilities.AutoPairUpdates = false cfg.Exchanges[0].CurrencyPairs.LastUpdated = 0 - cfg.CheckExchangeConfigValues() - - // Test exchange pair consistency error - cfg.Exchanges[0].CurrencyPairs.UseGlobalFormat = false - backup := cfg.Exchanges[0].CurrencyPairs.Pairs[asset.Spot] - cfg.Exchanges[0].CurrencyPairs.Pairs[asset.Spot] = nil err = cfg.CheckExchangeConfigValues() if err != nil { t.Error(err) } - if cfg.Exchanges[0].Enabled { - t.Error("exchange should have been disabled") - } - - // Restore to previous state - cfg.Exchanges[0].Enabled = true - cfg.Exchanges[0].CurrencyPairs.UseGlobalFormat = true - cfg.Exchanges[0].CurrencyPairs.Pairs[asset.Spot] = backup // Test websocket and HTTP timeout values cfg.Exchanges[0].WebsocketResponseMaxLimit = 0 @@ -1419,6 +1506,10 @@ func TestCheckExchangeConfigValues(t *testing.T) { !cfg.Exchanges[0].API.AuthenticatedWebsocketSupport { t.Error("Expected AuthenticatedAPISupport and AuthenticatedWebsocketAPISupport to be false from invalid API keys") } + + // Make a sneaky copy for bank account testing + cpy := append(cfg.Exchanges[:0:0], cfg.Exchanges...) + // Test empty exchange name for an enabled exchange cfg.Exchanges[0].Enabled = true cfg.Exchanges[0].Name = "" @@ -1436,6 +1527,82 @@ func TestCheckExchangeConfigValues(t *testing.T) { if err == nil { t.Error("Expected error from no enabled exchanges") } + + cfg.Exchanges = cpy + // Check bank account validation for exchange + cfg.Exchanges[0].BankAccounts = []banking.Account{ + { + Enabled: true, + }, + } + + err = cfg.CheckExchangeConfigValues() + if err != nil { + t.Error(err) + } + + if cfg.Exchanges[0].BankAccounts[0].Enabled { + t.Fatal("bank aaccount details not provided this should disable") + } + + // Test international bank + cfg.Exchanges[0].BankAccounts[0].Enabled = true + cfg.Exchanges[0].BankAccounts[0].BankName = testString + cfg.Exchanges[0].BankAccounts[0].BankAddress = testString + cfg.Exchanges[0].BankAccounts[0].BankPostalCode = testString + cfg.Exchanges[0].BankAccounts[0].BankPostalCity = testString + cfg.Exchanges[0].BankAccounts[0].BankCountry = testString + cfg.Exchanges[0].BankAccounts[0].AccountName = testString + cfg.Exchanges[0].BankAccounts[0].SupportedCurrencies = "monopoly moneys" + cfg.Exchanges[0].BankAccounts[0].IBAN = "some iban" + cfg.Exchanges[0].BankAccounts[0].SWIFTCode = "some swifty" + + err = cfg.CheckExchangeConfigValues() + if err != nil { + t.Error(err) + } + + if !cfg.Exchanges[0].BankAccounts[0].Enabled { + t.Fatal("bank aaccount details provided this should not disable") + } + + // Test aussie bank + cfg.Exchanges[0].BankAccounts[0].Enabled = true + cfg.Exchanges[0].BankAccounts[0].BankName = testString + cfg.Exchanges[0].BankAccounts[0].BankAddress = testString + cfg.Exchanges[0].BankAccounts[0].BankPostalCode = testString + cfg.Exchanges[0].BankAccounts[0].BankPostalCity = testString + cfg.Exchanges[0].BankAccounts[0].BankCountry = testString + cfg.Exchanges[0].BankAccounts[0].AccountName = testString + cfg.Exchanges[0].BankAccounts[0].SupportedCurrencies = "AUD" + cfg.Exchanges[0].BankAccounts[0].BSBNumber = "some BSB nonsense" + cfg.Exchanges[0].BankAccounts[0].IBAN = "" + cfg.Exchanges[0].BankAccounts[0].SWIFTCode = "" + + err = cfg.CheckExchangeConfigValues() + if err != nil { + t.Error(err) + } + + if !cfg.Exchanges[0].BankAccounts[0].Enabled { + t.Fatal("bank account details provided this should not disable") + } + + cfg.Exchanges = nil + cfg.Exchanges = append(cfg.Exchanges, cpy[0]) + + cfg.Exchanges[0].CurrencyPairs.Pairs[asset.Spot].Enabled = nil + cfg.Exchanges[0].CurrencyPairs.Pairs[asset.Spot].AssetEnabled = convert.BoolPtr(false) + err = cfg.CheckExchangeConfigValues() + if err != nil { + t.Error(err) + } + + cfg.Exchanges[0].CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore) + err = cfg.CheckExchangeConfigValues() + if err == nil { + t.Error("err cannot be nil") + } } func TestRetrieveConfigCurrencyPairs(t *testing.T) { diff --git a/config_example.json b/config_example.json index 1b5216a2..1bd16444 100644 --- a/config_example.json +++ b/config_example.json @@ -449,7 +449,7 @@ "websocketCapabilities": {} }, "enabled": { - "autoPairUpdates": false, + "autoPairUpdates": true, "websocketAPI": false } }, diff --git a/currency/code.go b/currency/code.go index 634f2100..18aa61f8 100644 --- a/currency/code.go +++ b/currency/code.go @@ -6,8 +6,6 @@ import ( "fmt" "strings" "unicode" - - "github.com/thrasher-corp/gocryptotrader/common" ) func (r Role) String() string { @@ -68,24 +66,24 @@ func (b *BaseCodes) HasData() bool { // GetFullCurrencyData returns a type that is read to dump to file func (b *BaseCodes) GetFullCurrencyData() (File, error) { var file File - for _, i := range b.Items { - switch i.Role { + for i := range b.Items { + switch b.Items[i].Role { case Unset: - file.UnsetCurrency = append(file.UnsetCurrency, *i) + file.UnsetCurrency = append(file.UnsetCurrency, *b.Items[i]) case Fiat: - file.FiatCurrency = append(file.FiatCurrency, *i) + file.FiatCurrency = append(file.FiatCurrency, *b.Items[i]) case Cryptocurrency: - file.Cryptocurrency = append(file.Cryptocurrency, *i) + file.Cryptocurrency = append(file.Cryptocurrency, *b.Items[i]) case Token: - file.Token = append(file.Token, *i) + file.Token = append(file.Token, *b.Items[i]) case Contract: - file.Contracts = append(file.Contracts, *i) + file.Contracts = append(file.Contracts, *b.Items[i]) default: return file, errors.New("role undefined") } } - file.LastMainUpdate = b.LastMainUpdate + file.LastMainUpdate = b.LastMainUpdate.Unix() return file, nil } @@ -103,50 +101,11 @@ func (b *BaseCodes) GetCurrencies() Currencies { return currencies } -// UpdateCryptocurrency updates or registers a cryptocurrency -func (b *BaseCodes) UpdateCryptocurrency(fullName, symbol string, id int) error { - b.mtx.Lock() - defer b.mtx.Unlock() - for i := range b.Items { - if b.Items[i].Symbol != symbol { - continue - } - if b.Items[i].Role != Unset { - if b.Items[i].Role != Cryptocurrency { - if b.Items[i].FullName != "" { - if b.Items[i].FullName != fullName { - // multiple symbols found, break this and add the - // full context - this most likely won't occur for - // fiat but could occur for contracts. - break - } - } - return fmt.Errorf("role already defined in cryptocurrency %s as [%s]", - b.Items[i].Symbol, - b.Items[i].Role) - } - b.Items[i].FullName = fullName - b.Items[i].ID = id - return nil - } - - b.Items[i].Role = Cryptocurrency - b.Items[i].FullName = fullName - b.Items[i].ID = id - return nil +// UpdateCurrency updates or registers a currency/contract +func (b *BaseCodes) UpdateCurrency(fullName, symbol, blockchain string, id int, r Role) error { + if r == Unset { + return fmt.Errorf("role cannot be unset in update currency for %s", symbol) } - - b.Items = append(b.Items, &Item{ - FullName: fullName, - Symbol: symbol, - ID: id, - Role: Cryptocurrency, - }) - return nil -} - -// UpdateFiatCurrency updates or registers a fiat currency -func (b *BaseCodes) UpdateFiatCurrency(fullName, symbol string, id int) error { b.mtx.Lock() defer b.mtx.Unlock() for i := range b.Items { @@ -154,115 +113,31 @@ func (b *BaseCodes) UpdateFiatCurrency(fullName, symbol string, id int) error { continue } - if b.Items[i].Role != Unset { - if b.Items[i].Role != Fiat { - return fmt.Errorf("role already defined in fiat currency %s as [%s]", - b.Items[i].Symbol, - b.Items[i].Role) - } + if b.Items[i].Role == Unset { b.Items[i].FullName = fullName + b.Items[i].Role = r + b.Items[i].AssocChain = blockchain b.Items[i].ID = id return nil } - b.Items[i].Role = Fiat + if b.Items[i].Role != r { + // Captures same name currencies and duplicates to different roles + break + } + b.Items[i].FullName = fullName + b.Items[i].AssocChain = blockchain b.Items[i].ID = id return nil } b.Items = append(b.Items, &Item{ - FullName: fullName, - Symbol: symbol, - ID: id, - Role: Fiat, - }) - return nil -} - -// UpdateToken updates or registers a token -func (b *BaseCodes) UpdateToken(fullName, symbol, assocBlockchain string, id int) error { - b.mtx.Lock() - defer b.mtx.Unlock() - for i := range b.Items { - if b.Items[i].Symbol != symbol { - continue - } - - if b.Items[i].Role != Unset { - if b.Items[i].Role != Token { - if b.Items[i].FullName != "" { - if b.Items[i].FullName != fullName { - // multiple symbols found, break this and add the - // full context - this most likely won't occur for - // fiat but could occur for contracts. - break - } - } - return fmt.Errorf("role already defined in token %s as [%s]", - b.Items[i].Symbol, - b.Items[i].Role) - } - b.Items[i].FullName = fullName - b.Items[i].ID = id - b.Items[i].AssocChain = assocBlockchain - return nil - } - - b.Items[i].Role = Token - b.Items[i].FullName = fullName - b.Items[i].ID = id - b.Items[i].AssocChain = assocBlockchain - return nil - } - - b.Items = append(b.Items, &Item{ - FullName: fullName, Symbol: symbol, + FullName: fullName, + Role: r, + AssocChain: blockchain, ID: id, - Role: Token, - AssocChain: assocBlockchain, - }) - return nil -} - -// UpdateContract updates or registers a contract -func (b *BaseCodes) UpdateContract(fullName, symbol, assocExchange string) error { - b.mtx.Lock() - defer b.mtx.Unlock() - for i := range b.Items { - if b.Items[i].Symbol != symbol { - continue - } - - if b.Items[i].Role != Unset { - if b.Items[i].Role != Contract { - return fmt.Errorf("role already defined in contract %s as [%s]", - b.Items[i].Symbol, - b.Items[i].Role) - } - b.Items[i].FullName = fullName - if !common.StringDataContains(b.Items[i].AssocExchange, assocExchange) { - b.Items[i].AssocExchange = append(b.Items[i].AssocExchange, - assocExchange) - } - return nil - } - - b.Items[i].Role = Contract - b.Items[i].FullName = fullName - if !common.StringDataContains(b.Items[i].AssocExchange, assocExchange) { - b.Items[i].AssocExchange = append(b.Items[i].AssocExchange, - assocExchange) - } - return nil - } - - b.Items = append(b.Items, &Item{ - FullName: fullName, - Symbol: symbol, - Role: Contract, - AssocExchange: []string{assocExchange}, }) return nil } @@ -293,42 +168,38 @@ func (b *BaseCodes) Register(c string) Code { } } - newItem := Item{Symbol: NewUpperCode} - newCode := Code{ - Item: &newItem, + newItem := &Item{Symbol: NewUpperCode} + b.Items = append(b.Items, newItem) + + return Code{ + Item: newItem, UpperCase: format, } - - b.Items = append(b.Items, newCode.Item) - return newCode } // RegisterFiat registers a fiat currency from a string and returns a currency // code -func (b *BaseCodes) RegisterFiat(c string) (Code, error) { +func (b *BaseCodes) RegisterFiat(c string) Code { c = strings.ToUpper(c) b.mtx.Lock() defer b.mtx.Unlock() for i := range b.Items { if b.Items[i].Symbol == c { - if b.Items[i].Role != Unset { - if b.Items[i].Role != Fiat { - return Code{}, fmt.Errorf("register fiat error role already defined in fiat %s as [%s]", - b.Items[i].Symbol, - b.Items[i].Role) - } - return Code{Item: b.Items[i], UpperCase: true}, nil + if b.Items[i].Role == Unset { + b.Items[i].Role = Fiat } - b.Items[i].Role = Fiat - return Code{Item: b.Items[i], UpperCase: true}, nil + + if b.Items[i].Role != Fiat { + continue + } + return Code{Item: b.Items[i], UpperCase: true} } } item := &Item{Symbol: c, Role: Fiat} b.Items = append(b.Items, item) - - return Code{Item: item, UpperCase: true}, nil + return Code{Item: item, UpperCase: true} } // LoadItem sets item data @@ -336,41 +207,18 @@ func (b *BaseCodes) LoadItem(item *Item) error { b.mtx.Lock() defer b.mtx.Unlock() for i := range b.Items { - if b.Items[i].Symbol == item.Symbol { - if b.Items[i].Role == Unset { - b.Items[i].AssocChain = item.AssocChain - b.Items[i].AssocExchange = item.AssocExchange - b.Items[i].ID = item.ID - b.Items[i].Role = item.Role - b.Items[i].FullName = item.FullName - return nil - } - - if b.Items[i].FullName != "" { - if b.Items[i].FullName == item.FullName { - b.Items[i].AssocChain = item.AssocChain - b.Items[i].AssocExchange = item.AssocExchange - b.Items[i].ID = item.ID - b.Items[i].Role = item.Role - return nil - } - break - } - - if b.Items[i].ID == item.ID { - b.Items[i].AssocChain = item.AssocChain - b.Items[i].AssocExchange = item.AssocExchange - b.Items[i].FullName = item.FullName - b.Items[i].ID = item.ID - b.Items[i].Role = item.Role - return nil - } - - return fmt.Errorf("currency %s not found in currencycode list", - item.Symbol) + if b.Items[i].Symbol != item.Symbol || + (b.Items[i].Role != Unset && + item.Role != Unset && + b.Items[i].Role != item.Role) { + continue } + b.Items[i].AssocChain = item.AssocChain + b.Items[i].ID = item.ID + b.Items[i].Role = item.Role + b.Items[i].FullName = item.FullName + return nil } - b.Items = append(b.Items, item) return nil } diff --git a/currency/code_test.go b/currency/code_test.go index 1568a7e4..7ebb0aca 100644 --- a/currency/code_test.go +++ b/currency/code_test.go @@ -176,41 +176,51 @@ func TestBaseCode(t *testing.T) { false) } - err := main.UpdateContract("Bitcoin Perpetual", "XBTUSD", "Bitmex") + main.Register("XBTUSD") + + err := main.UpdateCurrency("Bitcoin Perpetual", + "XBTUSD", + "", + 0, + Contract) if err != nil { - t.Error("BaseCode UpdateContract error", err) + t.Fatal(err) } - err = main.UpdateCryptocurrency("Bitcoin", "BTC", 1337) + main.Register("BTC") + err = main.UpdateCurrency("Bitcoin", "BTC", "", 1337, Cryptocurrency) if err != nil { - t.Error("BaseCode UpdateContract error", err) + t.Fatal(err) } - err = main.UpdateFiatCurrency("Unreal Dollar", "AUD", 1111) + main.Register("AUD") + err = main.UpdateCurrency("Unreal Dollar", "AUD", "", 1111, Fiat) if err != nil { - t.Error("BaseCode UpdateContract error", err) + t.Fatal(err) } + if main.Items[5].FullName != "Unreal Dollar" { t.Error("Expected fullname to update for AUD") } - err = main.UpdateFiatCurrency("Australian Dollar", "AUD", 1336) + err = main.UpdateCurrency("Australian Dollar", "AUD", "", 1336, Fiat) if err != nil { - t.Error("BaseCode UpdateContract error", err) + t.Fatal(err) } main.Items[5].Role = Unset - err = main.UpdateFiatCurrency("Australian Dollar", "AUD", 1336) + err = main.UpdateCurrency("Australian Dollar", "AUD", "", 1336, Fiat) if err != nil { - t.Error("BaseCode UpdateContract error", err) + t.Fatal(err) } if main.Items[5].Role != Fiat { t.Error("Expected role to change to Fiat") } - err = main.UpdateToken("Populous", "PPT", "ETH", 1335) + main.Register("PPT") + err = main.UpdateCurrency("Populous", "PPT", "ETH", 1335, Token) if err != nil { - t.Error("BaseCode UpdateContract error", err) + t.Fatal(err) } contract := main.Register("XBTUSD") @@ -235,15 +245,12 @@ func TestBaseCode(t *testing.T) { true) } - err = main.LoadItem(&Item{ + main.LoadItem(&Item{ ID: 0, FullName: "Cardano", Role: Cryptocurrency, Symbol: "ADA", }) - if err != nil { - t.Error("BaseCode LoadItem() error", err) - } full, err := main.GetFullCurrencyData() if err != nil { @@ -275,8 +282,8 @@ func TestBaseCode(t *testing.T) { len(full.UnsetCurrency)) } - if !full.LastMainUpdate.IsZero() { - t.Errorf("BaseCode GetFullCurrencyData() error expected 0 but received %s", + if full.LastMainUpdate.(int64) != -62135596800 { + t.Errorf("BaseCode GetFullCurrencyData() error expected -62135596800 but received %d", full.LastMainUpdate) } @@ -295,41 +302,35 @@ func TestBaseCode(t *testing.T) { } main.Items[0].FullName = "Hello" - err = main.UpdateCryptocurrency("MEWOW", "CATS", 1338) + err = main.UpdateCurrency("MEWOW", "CATS", "", 1338, Cryptocurrency) if err != nil { - t.Error("BaseCode UpdateContract error", err) + t.Fatal(err) } if main.Items[0].FullName != "MEWOW" { t.Error("Fullname not updated") } - err = main.UpdateCryptocurrency("MEWOW", "CATS", 1338) + err = main.UpdateCurrency("MEWOW", "CATS", "", 1338, Cryptocurrency) if err != nil { - t.Error("BaseCode UpdateContract error", err) + t.Fatal(err) + } + err = main.UpdateCurrency("WOWCATS", "CATS", "", 3, Token) + if err != nil { + t.Fatal(err) } - main.Items[0].Role = Cryptocurrency - err = main.UpdateCryptocurrency("MEWOW", "CATS", 3) - if err != nil { - t.Error("BaseCode UpdateContract error", err) - } - if main.Items[0].ID != 3 { + // Creates a new item under a different currency role + if main.Items[9].ID != 3 { t.Error("ID not updated") } main.Items[0].Role = Unset - err = main.UpdateCryptocurrency("MEWOW", "CATS", 1338) + err = main.UpdateCurrency("MEWOW", "CATS", "", 1338, Cryptocurrency) if err != nil { - t.Error("BaseCode UpdateContract error", err) + t.Fatal(err) } if main.Items[0].ID != 1338 { t.Error("ID not updated") } - - main.Items[0].Role = Token - err = main.UpdateCryptocurrency("MEWOW", "CATS", 3) - if err == nil { - t.Error("Expecting cryptocurrency to already exist") - } } func TestCodeString(t *testing.T) { diff --git a/currency/code_types.go b/currency/code_types.go index c1b934ef..47800d4c 100644 --- a/currency/code_types.go +++ b/currency/code_types.go @@ -41,12 +41,11 @@ type Code struct { // Item defines a sub type containing the main attributes of a designated // currency code pointer type Item struct { - ID int `json:"id"` - FullName string `json:"fullName"` - Symbol string `json:"symbol"` - Role Role `json:"role"` - AssocChain string `json:"associatedBlockchain"` - AssocExchange []string `json:"associatedExchanges"` + ID int `json:"id,omitempty"` + FullName string `json:"fullName,omitempty"` + Symbol string `json:"symbol"` + Role Role `json:"-"` + AssocChain string `json:"associatedBlockchain,omitempty"` } // Const declarations for individual currencies/tokens/fiat diff --git a/currency/coinmarketcap/coinmarketcap.go b/currency/coinmarketcap/coinmarketcap.go index d664c181..e158ed28 100644 --- a/currency/coinmarketcap/coinmarketcap.go +++ b/currency/coinmarketcap/coinmarketcap.go @@ -8,7 +8,6 @@ package coinmarketcap import ( "context" "errors" - "fmt" "net/http" "net/url" "strconv" @@ -17,6 +16,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/exchanges/request" + "github.com/thrasher-corp/gocryptotrader/log" ) // SetDefaults sets default values for the exchange @@ -62,8 +62,8 @@ func (c *Coinmarketcap) GetCryptocurrencyInfo(currencyID ...int64) (CryptoCurren } var currStr []string - for _, d := range currencyID { - currStr = append(currStr, strconv.FormatInt(d, 10)) + for i := range currencyID { + currStr = append(currStr, strconv.FormatInt(currencyID[i], 10)) } val := url.Values{} @@ -667,7 +667,6 @@ func (c *Coinmarketcap) GetPriceConversion(amount float64, currencyID int64, atH func (c *Coinmarketcap) SendHTTPRequest(method, endpoint string, v url.Values, result interface{}) error { headers := make(map[string]string) headers["Accept"] = "application/json" - headers["Accept-Encoding"] = "deflate, gzip" headers["X-CMC_PRO_API_KEY"] = c.APIkey path := c.APIUrl + c.APIVersion + endpoint @@ -710,7 +709,8 @@ func (c *Coinmarketcap) SetAccountPlan(s string) error { case "enterprise": c.Plan = Enterprise default: - return fmt.Errorf("account plan %s not found", s) + log.Warnf(log.Global, "account plan %s not found, defaulting to basic", s) + c.Plan = Basic } return nil } diff --git a/currency/coinmarketcap/coinmarketcap_test.go b/currency/coinmarketcap/coinmarketcap_test.go index 4d878d73..93d54c5a 100644 --- a/currency/coinmarketcap/coinmarketcap_test.go +++ b/currency/coinmarketcap/coinmarketcap_test.go @@ -44,11 +44,6 @@ func TestSetup(t *testing.T) { if err := c.Setup(cfg); err != nil { t.Error(err) } - - cfg.AccountPlan = "meow" - if err := c.Setup(cfg); err == nil { - t.Error("expected err when invalid account plan is specified") - } } func TestCheckAccountPlan(t *testing.T) { @@ -405,8 +400,4 @@ func TestSetAccountPlan(t *testing.T) { } } } - - if err := c.SetAccountPlan("bra"); err == nil { - t.Error("SetAccountPlan() error cannot be nil") - } } diff --git a/currency/conversion.go b/currency/conversion.go index 363366f7..edeec928 100644 --- a/currency/conversion.go +++ b/currency/conversion.go @@ -110,22 +110,14 @@ func (c *ConversionRates) Update(m map[string]float64) error { var list []Code // Verification list, cross check all currencies coming in var mainBaseCurrency Code - for key, val := range m { - code1, err := storage.ValidateFiatCode(key[:3]) - if err != nil { - return err - } + code1 := storage.ValidateFiatCode(key[:3]) if mainBaseCurrency == (Code{}) { mainBaseCurrency = code1 } - code2, err := storage.ValidateFiatCode(key[3:]) - if err != nil { - return err - } - + code2 := storage.ValidateFiatCode(key[3:]) if code1 == code2 { // Get rid of same conversions continue } diff --git a/currency/currencies.go b/currency/currencies.go index 2647fbc1..caa6cb97 100644 --- a/currency/currencies.go +++ b/currency/currencies.go @@ -23,8 +23,8 @@ type Currencies []Code // Strings returns an array of currency strings func (c Currencies) Strings() []string { var list []string - for _, d := range c { - list = append(list, d.String()) + for i := range c { + list = append(list, c[i].String()) } return list } @@ -53,8 +53,9 @@ func (c *Currencies) UnmarshalJSON(d []byte) error { } var allTheCurrencies Currencies - for _, data := range strings.Split(configCurrencies, ",") { - allTheCurrencies = append(allTheCurrencies, NewCode(data)) + curr := strings.Split(configCurrencies, ",") + for i := range curr { + allTheCurrencies = append(allTheCurrencies, NewCode(curr[i])) } *c = allTheCurrencies @@ -72,17 +73,14 @@ func (c Currencies) Match(other Currencies) bool { return false } - for _, d := range c { - var found bool - for i := range other { - if d == other[i] { - found = true - break +match: + for x := range c { + for y := range other { + if c[x] == other[y] { + continue match } } - if !found { - return false - } + return false } return true } diff --git a/currency/currencies_test.go b/currency/currencies_test.go index d97df832..f6d950c9 100644 --- a/currency/currencies_test.go +++ b/currency/currencies_test.go @@ -47,3 +47,18 @@ func TestCurrenciesMarshalJSON(t *testing.T) { expected, string(encoded)) } } + +func TestMatch(t *testing.T) { + matchString := []string{"btc", "usd", "ltc", "bro", "things"} + c := NewCurrenciesFromStringArray(matchString) + + if !c.Match(NewCurrenciesFromStringArray(matchString)) { + t.Fatal("should match") + } + if c.Match(NewCurrenciesFromStringArray([]string{"btc", "usd", "ltc", "bro"})) { + t.Fatal("should not match") + } + if c.Match(NewCurrenciesFromStringArray([]string{"btc", "usd", "ltc", "bro", "garbo"})) { + t.Fatal("should not match") + } +} diff --git a/currency/currency.go b/currency/currency.go index 475f4642..3415bf42 100644 --- a/currency/currency.go +++ b/currency/currency.go @@ -71,9 +71,14 @@ func GetTotalMarketCryptocurrencies() ([]Code, error) { return storage.GetTotalMarketCryptocurrencies() } -// RunStorageUpdater runs a new foreign exchange updater instance -func RunStorageUpdater(o BotOverrides, m *MainConfiguration, filepath string, v bool) error { - return storage.RunUpdater(o, m, filepath, v) +// RunStorageUpdater runs a new foreign exchange updater instance +func RunStorageUpdater(o BotOverrides, m *MainConfiguration, filepath string) error { + return storage.RunUpdater(o, m, filepath) +} + +// ShutdownStorageUpdater cleanly shuts down and saves to currency.json +func ShutdownStorageUpdater() error { + return storage.Shutdown() } // CopyPairFormat copies the pair format from a list of pairs once matched @@ -100,17 +105,23 @@ func FormatPairs(pairs []string, delimiter, index string) (Pairs, error) { continue } var p Pair + var err error if delimiter != "" { - p = NewPairDelimiter(pairs[x], delimiter) + p, err = NewPairDelimiter(pairs[x], delimiter) + if err != nil { + return nil, err + } } else { if index != "" { - var err error p, err = NewPairFromIndex(pairs[x], index) if err != nil { return Pairs{}, err } } else { - p = NewPairFromStrings(pairs[x][0:3], pairs[x][3:]) + p, err = NewPairFromStrings(pairs[x][0:3], pairs[x][3:]) + if err != nil { + return Pairs{}, err + } } } result = append(result, p) diff --git a/currency/currency_types.go b/currency/currency_types.go index 641a6e1a..83b5b077 100644 --- a/currency/currency_types.go +++ b/currency/currency_types.go @@ -53,10 +53,24 @@ type FXSettings struct { // File defines a full currency file generated by the currency storage // analysis system type File struct { - LastMainUpdate time.Time `json:"lastMainUpdate"` - Cryptocurrency []Item `json:"cryptocurrencies"` - FiatCurrency []Item `json:"fiatCurrencies"` - UnsetCurrency []Item `json:"unsetCurrencies"` - Contracts []Item `json:"contracts"` - Token []Item `json:"tokens"` + LastMainUpdate interface{} `json:"lastMainUpdate"` + Cryptocurrency []Item `json:"cryptocurrencies"` + FiatCurrency []Item `json:"fiatCurrencies"` + UnsetCurrency []Item `json:"unsetCurrencies"` + Contracts []Item `json:"contracts"` + Token []Item `json:"tokens"` } + +// Const here are packaged defined delimiters +const ( + UnderscoreDelimiter = "_" + DashDelimiter = "-" + ForwardSlashDelimiter = "/" + ColonDelimiter = ":" +) + +// delimiters is a delimiter list +var delimiters = []string{UnderscoreDelimiter, + DashDelimiter, + ForwardSlashDelimiter, + ColonDelimiter} diff --git a/currency/manager.go b/currency/manager.go index c77aaa36..9485d93c 100644 --- a/currency/manager.go +++ b/currency/manager.go @@ -2,14 +2,16 @@ package currency import ( "errors" + "fmt" + "github.com/thrasher-corp/gocryptotrader/common/convert" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" ) // GetAssetTypes returns a list of stored asset types func (p *PairsManager) GetAssetTypes() asset.Items { - p.m.Lock() - defer p.m.Unlock() + p.m.RLock() + defer p.m.RUnlock() var assetTypes asset.Items for k := range p.Pairs { assetTypes = append(assetTypes, k) @@ -18,28 +20,23 @@ func (p *PairsManager) GetAssetTypes() asset.Items { } // Get gets the currency pair config based on the asset type -func (p *PairsManager) Get(a asset.Item) *PairStore { - p.m.Lock() - defer p.m.Unlock() +func (p *PairsManager) Get(a asset.Item) (*PairStore, error) { + p.m.RLock() + defer p.m.RUnlock() c, ok := p.Pairs[a] if !ok { - return nil + return nil, + fmt.Errorf("cannot get pair store, asset type %s not supported", a) } - return c + return c, nil } // Store stores a new currency pair config based on its asset type func (p *PairsManager) Store(a asset.Item, ps PairStore) { p.m.Lock() - if p.Pairs == nil { p.Pairs = make(map[asset.Item]*PairStore) } - - if !p.AssetTypes.Contains(a) { - p.AssetTypes = append(p.AssetTypes, a) - } - p.Pairs[a] = &ps p.m.Unlock() } @@ -51,37 +48,35 @@ func (p *PairsManager) Delete(a asset.Item) { if p.Pairs == nil { return } - - _, ok := p.Pairs[a] - if !ok { - return - } - delete(p.Pairs, a) } // GetPairs gets a list of stored pairs based on the asset type and whether // they're enabled or not -func (p *PairsManager) GetPairs(a asset.Item, enabled bool) Pairs { - p.m.Lock() - defer p.m.Unlock() +func (p *PairsManager) GetPairs(a asset.Item, enabled bool) (Pairs, error) { + p.m.RLock() + defer p.m.RUnlock() if p.Pairs == nil { - return nil + return nil, nil } c, ok := p.Pairs[a] if !ok { - return nil + return nil, nil } - var pairs Pairs if enabled { - pairs = c.Enabled - } else { - pairs = c.Available + for i := range c.Enabled { + if !c.Available.Contains(c.Enabled[i], true) { + return c.Enabled, + fmt.Errorf("enabled pair %s of asset type %s not contained in available list", + c.Enabled[i], + a) + } + } + return c.Enabled, nil } - - return pairs + return c.Available, nil } // StorePairs stores a list of pairs based on the asset type and whether @@ -96,7 +91,8 @@ func (p *PairsManager) StorePairs(a asset.Item, pairs Pairs, enabled bool) { c, ok := p.Pairs[a] if !ok { - c = new(PairStore) + p.Pairs[a] = new(PairStore) + c = p.Pairs[a] } if enabled { @@ -104,8 +100,6 @@ func (p *PairsManager) StorePairs(a asset.Item, pairs Pairs, enabled bool) { } else { c.Available = pairs } - - p.Pairs[a] = c } // DisablePair removes the pair from the enabled pairs list if found @@ -113,17 +107,9 @@ func (p *PairsManager) DisablePair(a asset.Item, pair Pair) error { p.m.Lock() defer p.m.Unlock() - if p.Pairs == nil { - return errors.New("pair manager not initialised") - } - - c, ok := p.Pairs[a] - if !ok { - return errors.New("asset type not found") - } - - if c == nil { - return errors.New("currency store is nil") + c, err := p.getPairStore(a) + if err != nil { + return err } if !c.Enabled.Contains(pair, true) { @@ -140,27 +126,82 @@ func (p *PairsManager) EnablePair(a asset.Item, pair Pair) error { p.m.Lock() defer p.m.Unlock() - if p.Pairs == nil { - return errors.New("pair manager not initialised") - } - - c, ok := p.Pairs[a] - if !ok { - return errors.New("asset type not found") - } - - if c == nil { - return errors.New("currency store is nil") + c, err := p.getPairStore(a) + if err != nil { + return err } if !c.Available.Contains(pair, true) { - return errors.New("specified pair was not found in the list of available pairs") + return fmt.Errorf("%s pair was not found in the list of available pairs", + pair) } if c.Enabled.Contains(pair, true) { - return errors.New("specified pair is already enabled") + return fmt.Errorf("%s pair is already enabled", pair) } c.Enabled = c.Enabled.Add(pair) return nil } + +// IsAssetEnabled checks to see if an asset is enabled +func (p *PairsManager) IsAssetEnabled(a asset.Item) error { + p.m.RLock() + defer p.m.RUnlock() + + c, err := p.getPairStore(a) + if err != nil { + return err + } + + if c.AssetEnabled == nil { + return errors.New("cannot ascertain if asset is enabled, variable is nil") + } + + if !*c.AssetEnabled { + return errors.New("asset not enabled") + } + return nil +} + +// SetAssetEnabled sets if an asset is enabled or disabled for first run +func (p *PairsManager) SetAssetEnabled(a asset.Item, enabled bool) error { + p.m.Lock() + defer p.m.Unlock() + + c, err := p.getPairStore(a) + if err != nil { + return err + } + + if c.AssetEnabled == nil { + c.AssetEnabled = convert.BoolPtr(enabled) + return nil + } + + if !*c.AssetEnabled && !enabled { + return errors.New("asset already disabled") + } else if *c.AssetEnabled && enabled { + return errors.New("asset already enabled") + } + + *c.AssetEnabled = enabled + return nil +} + +func (p *PairsManager) getPairStore(a asset.Item) (*PairStore, error) { + if p.Pairs == nil { + return nil, errors.New("pair manager not initialised") + } + + c, ok := p.Pairs[a] + if !ok { + return nil, errors.New("asset type not found") + } + + if c == nil { + return nil, errors.New("currency store is nil") + } + + return c, nil +} diff --git a/currency/manager_test.go b/currency/manager_test.go index 2d2f0bbd..554c4f00 100644 --- a/currency/manager_test.go +++ b/currency/manager_test.go @@ -8,11 +8,21 @@ import ( var p PairsManager -func initTest() { +func initTest(t *testing.T) { + spotAvailable, err := NewPairsFromStrings([]string{"BTC-USD", "LTC-USD"}) + if err != nil { + t.Fatal(err) + } + + spotEnabled, err := NewPairsFromStrings([]string{"BTC-USD"}) + if err != nil { + t.Fatal(err) + } + p.Store(asset.Spot, PairStore{ - Available: NewPairsFromStrings([]string{"BTC-USD", "LTC-USD"}), - Enabled: NewPairsFromStrings([]string{"BTC-USD"}), + Available: spotAvailable, + Enabled: spotEnabled, RequestFormat: &PairFormat{ Uppercase: true, }, @@ -25,7 +35,7 @@ func initTest() { } func TestGetAssetTypes(t *testing.T) { - initTest() + initTest(t) a := p.GetAssetTypes() if len(a) == 0 { @@ -38,22 +48,34 @@ func TestGetAssetTypes(t *testing.T) { } func TestGet(t *testing.T) { - initTest() + initTest(t) - if p.Get(asset.Spot) == nil { - t.Error("Spot assets shouldn't be nil") + _, err := p.Get(asset.Spot) + if err != nil { + t.Error(err) } - if p.Get(asset.Futures) != nil { + _, err = p.Get(asset.Futures) + if err == nil { t.Error("Futures should be nil") } } func TestStore(t *testing.T) { + availPairs, err := NewPairsFromStrings([]string{"BTC-USD", "LTC-USD"}) + if err != nil { + t.Fatal(err) + } + + enabledPairs, err := NewPairsFromStrings([]string{"BTC-USD"}) + if err != nil { + t.Fatal(err) + } + p.Store(asset.Futures, PairStore{ - Available: NewPairsFromStrings([]string{"BTC-USD", "LTC-USD"}), - Enabled: NewPairsFromStrings([]string{"BTC-USD"}), + Available: availPairs, + Enabled: enabledPairs, RequestFormat: &PairFormat{ Uppercase: true, }, @@ -64,7 +86,12 @@ func TestStore(t *testing.T) { }, ) - if p.Get(asset.Futures) == nil { + f, err := p.Get(asset.Futures) + if err != nil { + t.Fatal(err) + } + + if f == nil { t.Error("Futures assets shouldn't be nil") } } @@ -73,67 +100,131 @@ func TestDelete(t *testing.T) { p.Pairs = nil p.Delete(asset.Spot) - p.Store(asset.Spot, - PairStore{ - Available: NewPairsFromStrings([]string{"BTC-USD"}), - }, - ) + btcusdPairs, err := NewPairsFromStrings([]string{"BTC-USD"}) + if err != nil { + t.Fatal(err) + } + + p.Store(asset.Spot, PairStore{ + Available: btcusdPairs, + }) + p.Delete(asset.UpsideProfitContract) - if p.Get(asset.Spot) == nil { + spotPS, err := p.Get(asset.Spot) + if err != nil { + t.Fatal(err) + } + + if spotPS == nil { t.Error("AssetTypeSpot should exist") } p.Delete(asset.Spot) - if p.Get(asset.Spot) != nil { + + if _, err := p.Get(asset.Spot); err == nil { t.Error("Delete should have deleted AssetTypeSpot") } } func TestGetPairs(t *testing.T) { p.Pairs = nil - pairs := p.GetPairs(asset.Spot, true) + pairs, err := p.GetPairs(asset.Spot, true) + if err != nil { + t.Fatal(err) + } + if pairs != nil { t.Fatal("pairs shouldn't be populated") } - initTest() - pairs = p.GetPairs(asset.Spot, true) + initTest(t) + pairs, err = p.GetPairs(asset.Spot, true) + if err != nil { + t.Fatal(err) + } if pairs == nil { t.Fatal("pairs should be populated") } - pairs = p.GetPairs("blah", true) + pairs, err = p.GetPairs("blah", true) + if err != nil { + t.Fatal(err) + } + if pairs != nil { t.Fatal("pairs shouldn't be populated") } + + superfluous := NewPair(DASH, USDT) + newPairs := p.Pairs[asset.Spot].Enabled.Add(superfluous) + p.Pairs[asset.Spot].Enabled = newPairs + + _, err = p.GetPairs(asset.Spot, true) + if err == nil { + t.Fatal("error cannot be nil") + } } func TestStorePairs(t *testing.T) { p.Pairs = nil - p.StorePairs(asset.Spot, NewPairsFromStrings([]string{"ETH-USD"}), false) - pairs := p.GetPairs(asset.Spot, false) - if !pairs.Contains(NewPairFromString("ETH-USD"), true) { + + ethusdPairs, err := NewPairsFromStrings([]string{"ETH-USD"}) + if err != nil { + t.Fatal(err) + } + + p.StorePairs(asset.Spot, ethusdPairs, false) + pairs, err := p.GetPairs(asset.Spot, false) + if err != nil { + t.Fatal(err) + } + + ethusd, err := NewPairFromString("ETH-USD") + if err != nil { + t.Fatal(err) + } + + if !pairs.Contains(ethusd, true) { t.Errorf("TestStorePairs failed, unexpected result") } - initTest() - p.StorePairs(asset.Spot, NewPairsFromStrings([]string{"ETH-USD"}), false) - pairs = p.GetPairs(asset.Spot, false) + initTest(t) + p.StorePairs(asset.Spot, ethusdPairs, false) + pairs, err = p.GetPairs(asset.Spot, false) + if err != nil { + t.Fatal(err) + } + if pairs == nil { t.Errorf("pairs should be populated") } - if !pairs.Contains(NewPairFromString("ETH-USD"), true) { + if !pairs.Contains(ethusd, true) { t.Errorf("TestStorePairs failed, unexpected result") } - p.StorePairs(asset.Futures, NewPairsFromStrings([]string{"ETH-KRW"}), true) - pairs = p.GetPairs(asset.Futures, true) + ethkrwPairs, err := NewPairsFromStrings([]string{"ETH-KRW"}) + if err != nil { + t.Error(err) + } + + p.StorePairs(asset.Futures, ethkrwPairs, true) + p.StorePairs(asset.Futures, ethkrwPairs, false) + pairs, err = p.GetPairs(asset.Futures, true) + if err != nil { + t.Fatal(err) + } + if pairs == nil { t.Errorf("pairs futures should be populated") } - if !pairs.Contains(NewPairFromString("ETH-KRW"), true) { + ethkrw, err := NewPairFromString("ETH-KRW") + if err != nil { + t.Error(err) + } + + if !pairs.Contains(ethkrw, true) { t.Errorf("TestStorePairs failed, unexpected result") } } @@ -146,7 +237,7 @@ func TestDisablePair(t *testing.T) { } // Test asset type which doesn't exist - initTest() + initTest(t) if err := p.DisablePair(asset.Futures, Pair{}); err == nil { t.Error("unexpected result") } @@ -158,7 +249,7 @@ func TestDisablePair(t *testing.T) { } // Test disabling a pair which isn't enabled - initTest() + initTest(t) if err := p.DisablePair(asset.Spot, NewPair(LTC, USD)); err == nil { t.Error("unexpected result") } @@ -177,7 +268,7 @@ func TestEnablePair(t *testing.T) { } // Test asset type which doesn't exist - initTest() + initTest(t) if err := p.EnablePair(asset.Futures, Pair{}); err == nil { t.Error("unexpected result") } @@ -189,7 +280,7 @@ func TestEnablePair(t *testing.T) { } // Test enabling a pair which isn't in the list of available pairs - initTest() + initTest(t) if err := p.EnablePair(asset.Spot, NewPair(ETH, USD)); err == nil { t.Error("unexpected result") } @@ -204,3 +295,55 @@ func TestEnablePair(t *testing.T) { t.Error("unexpected result") } } + +func TestIsAssetEnabled_SetAssetEnabled(t *testing.T) { + p.Pairs = nil + // Test enabling a pair when the pair manager is not initialised + err := p.IsAssetEnabled(asset.Spot) + if err == nil { + t.Error("unexpected result") + } + + err = p.SetAssetEnabled(asset.Spot, true) + if err == nil { + t.Fatal("unexpected result") + } + + // Test asset type which doesn't exist + initTest(t) + + err = p.IsAssetEnabled(asset.Spot) + if err == nil { + t.Error("unexpected result") + } + + err = p.SetAssetEnabled(asset.Spot, false) + if err != nil { + t.Fatal(err) + } + + err = p.SetAssetEnabled(asset.Spot, false) + if err == nil { + t.Fatal("unexpected result") + } + + err = p.IsAssetEnabled(asset.Spot) + if err == nil { + t.Error("unexpected result") + } + + err = p.SetAssetEnabled(asset.Spot, true) + if err != nil { + t.Fatal(err) + } + + err = p.SetAssetEnabled(asset.Spot, true) + if err == nil { + t.Fatal("unexpected result") + } + + err = p.IsAssetEnabled(asset.Spot) + if err != nil { + t.Error("unexpected result") + } +} diff --git a/currency/manager_types.go b/currency/manager_types.go index 456cea10..ad2540ca 100644 --- a/currency/manager_types.go +++ b/currency/manager_types.go @@ -12,13 +12,13 @@ type PairsManager struct { ConfigFormat *PairFormat `json:"configFormat,omitempty"` UseGlobalFormat bool `json:"useGlobalFormat,omitempty"` LastUpdated int64 `json:"lastUpdated,omitempty"` - AssetTypes asset.Items `json:"assetTypes"` Pairs map[asset.Item]*PairStore `json:"pairs"` - m sync.Mutex + m sync.RWMutex } // PairStore stores a currency pair store type PairStore struct { + AssetEnabled *bool `json:"assetEnabled"` Enabled Pairs `json:"enabled"` Available Pairs `json:"available"` RequestFormat *PairFormat `json:"requestFormat,omitempty"` diff --git a/currency/pair.go b/currency/pair.go index bf4ff95b..ccc9e985 100644 --- a/currency/pair.go +++ b/currency/pair.go @@ -1,15 +1,24 @@ package currency import ( - "encoding/json" "fmt" "strings" ) // NewPairDelimiter splits the desired currency string at delimeter, the returns // a Pair struct -func NewPairDelimiter(currencyPair, delimiter string) Pair { +func NewPairDelimiter(currencyPair, delimiter string) (Pair, error) { + if !strings.Contains(currencyPair, delimiter) { + return Pair{}, + fmt.Errorf("delimiter: [%s] not found in currencypair string", delimiter) + } result := strings.Split(currencyPair, delimiter) + if len(result) < 2 { + return Pair{}, + fmt.Errorf("supplied pair: [%s] cannot be split with %s", + currencyPair, + delimiter) + } if len(result) > 2 { result[1] = strings.Join(result[1:], delimiter) } @@ -17,15 +26,24 @@ func NewPairDelimiter(currencyPair, delimiter string) Pair { Delimiter: delimiter, Base: NewCode(result[0]), Quote: NewCode(result[1]), - } + }, nil } // NewPairFromStrings returns a CurrencyPair without a delimiter -func NewPairFromStrings(baseCurrency, quoteCurrency string) Pair { - return Pair{ - Base: NewCode(baseCurrency), - Quote: NewCode(quoteCurrency), +func NewPairFromStrings(base, quote string) (Pair, error) { + if strings.Contains(base, " ") { + return Pair{}, + fmt.Errorf("cannot create pair, invalid base currency string [%s]", + base) } + + if strings.Contains(quote, " ") { + return Pair{}, + fmt.Errorf("cannot create pair, invalid quote currency string [%s]", + quote) + } + + return Pair{Base: NewCode(base), Quote: NewCode(quote)}, nil } // NewPair returns a currency pair from currency codes @@ -55,23 +73,24 @@ func NewPairFromIndex(currencyPair, index string) (Pair, error) { } if i == 0 { return NewPairFromStrings(currencyPair[0:len(index)], - currencyPair[len(index):]), - nil + currencyPair[len(index):]) } - return NewPairFromStrings(currencyPair[0:i], currencyPair[i:]), nil + return NewPairFromStrings(currencyPair[0:i], currencyPair[i:]) } // NewPairFromString converts currency string into a new CurrencyPair // with or without delimeter -func NewPairFromString(currencyPair string) Pair { - delimiters := []string{"_", "-", "/", ":"} - var delimiter string - for _, x := range delimiters { - if strings.Contains(currencyPair, x) { - delimiter = x - return NewPairDelimiter(currencyPair, delimiter) +func NewPairFromString(currencyPair string) (Pair, error) { + for x := range delimiters { + if strings.Contains(currencyPair, delimiters[x]) { + return NewPairDelimiter(currencyPair, delimiters[x]) } } + if len(currencyPair) < 3 { + return Pair{}, + fmt.Errorf("cannot create pair from %s string", + currencyPair) + } return NewPairFromStrings(currencyPair[0:3], currencyPair[3:]) } @@ -79,129 +98,12 @@ func NewPairFromString(currencyPair string) Pair { // with a specific format. This is helpful for exchanges which // provide currency pairs with no delimiter so we can match it with a list and // apply the same format -func NewPairFromFormattedPairs(currencyPair string, pairs Pairs, pairFmt PairFormat) Pair { +func NewPairFromFormattedPairs(currencyPair string, pairs Pairs, pairFmt PairFormat) (Pair, error) { for x := range pairs { - if strings.EqualFold(pairs[x].Format(pairFmt.Delimiter, - pairFmt.Uppercase).String(), currencyPair) { - return pairs[x] + fPair := pairs[x].Format(pairFmt.Delimiter, pairFmt.Uppercase) + if strings.EqualFold(fPair.String(), currencyPair) { + return pairs[x], nil } } return NewPairFromString(currencyPair) } - -// String returns a currency pair string -func (p Pair) String() string { - return p.Base.String() + p.Delimiter + p.Quote.String() -} - -// Lower converts the pair object to lowercase -func (p Pair) Lower() Pair { - return Pair{ - Delimiter: p.Delimiter, - Base: p.Base.Lower(), - Quote: p.Quote.Lower(), - } -} - -// Upper converts the pair object to uppercase -func (p Pair) Upper() Pair { - return Pair{ - Delimiter: p.Delimiter, - Base: p.Base.Upper(), - Quote: p.Quote.Upper(), - } -} - -// UnmarshalJSON comforms type to the umarshaler interface -func (p *Pair) UnmarshalJSON(d []byte) error { - var pair string - err := json.Unmarshal(d, &pair) - if err != nil { - return err - } - - *p = NewPairFromString(pair) - return nil -} - -// MarshalJSON conforms type to the marshaler interface -func (p Pair) MarshalJSON() ([]byte, error) { - return json.Marshal(p.String()) -} - -// Format changes the currency based on user preferences overriding the default -// String() display -func (p Pair) Format(delimiter string, uppercase bool) Pair { - p.Delimiter = delimiter - - if uppercase { - return p.Upper() - } - return p.Lower() -} - -// Equal compares two currency pairs and returns whether or not they are equal -func (p Pair) Equal(cPair Pair) bool { - return strings.EqualFold(p.Base.String(), cPair.Base.String()) && - strings.EqualFold(p.Quote.String(), cPair.Quote.String()) -} - -// EqualIncludeReciprocal compares two currency pairs and returns whether or not -// they are the same including reciprocal currencies. -func (p Pair) EqualIncludeReciprocal(cPair Pair) bool { - if p.Base.Item == cPair.Base.Item && - p.Quote.Item == cPair.Quote.Item || - p.Base.Item == cPair.Quote.Item && - p.Quote.Item == cPair.Base.Item { - return true - } - return false -} - -// IsCryptoPair checks to see if the pair is a crypto pair e.g. BTCLTC -func (p Pair) IsCryptoPair() bool { - return storage.IsCryptocurrency(p.Base) && - storage.IsCryptocurrency(p.Quote) -} - -// IsCryptoFiatPair checks to see if the pair is a crypto fiat pair e.g. BTCUSD -func (p Pair) IsCryptoFiatPair() bool { - return storage.IsCryptocurrency(p.Base) && - storage.IsFiatCurrency(p.Quote) || - storage.IsFiatCurrency(p.Base) && - storage.IsCryptocurrency(p.Quote) -} - -// IsFiatPair checks to see if the pair is a fiat pair e.g. EURUSD -func (p Pair) IsFiatPair() bool { - return storage.IsFiatCurrency(p.Base) && storage.IsFiatCurrency(p.Quote) -} - -// IsInvalid checks invalid pair if base and quote are the same -func (p Pair) IsInvalid() bool { - return p.Base.Item == p.Quote.Item -} - -// Swap turns the currency pair into its reciprocal -func (p Pair) Swap() Pair { - p.Base, p.Quote = p.Quote, p.Base - return p -} - -// IsEmpty returns whether or not the pair is empty or is missing a currency -// code -func (p Pair) IsEmpty() bool { - return p.Base.IsEmpty() || p.Quote.IsEmpty() -} - -// ContainsCurrency checks to see if a pair contains a specific currency -func (p Pair) ContainsCurrency(c Code) bool { - return p.Base.Item == c.Item || p.Quote.Item == c.Item -} - -// Pair holds currency pair information -type Pair struct { - Delimiter string `json:"delimiter"` - Base Code `json:"base"` - Quote Code `json:"quote"` -} diff --git a/currency/pair_methods.go b/currency/pair_methods.go new file mode 100644 index 00000000..f377d07c --- /dev/null +++ b/currency/pair_methods.go @@ -0,0 +1,117 @@ +package currency + +import ( + "encoding/json" + "strings" +) + +// String returns a currency pair string +func (p Pair) String() string { + return p.Base.String() + p.Delimiter + p.Quote.String() +} + +// Lower converts the pair object to lowercase +func (p Pair) Lower() Pair { + return Pair{ + Delimiter: p.Delimiter, + Base: p.Base.Lower(), + Quote: p.Quote.Lower(), + } +} + +// Upper converts the pair object to uppercase +func (p Pair) Upper() Pair { + return Pair{ + Delimiter: p.Delimiter, + Base: p.Base.Upper(), + Quote: p.Quote.Upper(), + } +} + +// UnmarshalJSON comforms type to the umarshaler interface +func (p *Pair) UnmarshalJSON(d []byte) error { + var pair string + err := json.Unmarshal(d, &pair) + if err != nil { + return err + } + + newPair, err := NewPairFromString(pair) + if err != nil { + return err + } + + p.Base = newPair.Base + p.Quote = newPair.Quote + p.Delimiter = newPair.Delimiter + return nil +} + +// MarshalJSON conforms type to the marshaler interface +func (p Pair) MarshalJSON() ([]byte, error) { + return json.Marshal(p.String()) +} + +// Format changes the currency based on user preferences overriding the default +// String() display +func (p Pair) Format(delimiter string, uppercase bool) Pair { + newP := Pair{Base: p.Base, Quote: p.Quote, Delimiter: delimiter} + if uppercase { + return newP.Upper() + } + return newP.Lower() +} + +// Equal compares two currency pairs and returns whether or not they are equal +func (p Pair) Equal(cPair Pair) bool { + return strings.EqualFold(p.Base.String(), cPair.Base.String()) && + strings.EqualFold(p.Quote.String(), cPair.Quote.String()) +} + +// EqualIncludeReciprocal compares two currency pairs and returns whether or not +// they are the same including reciprocal currencies. +func (p Pair) EqualIncludeReciprocal(cPair Pair) bool { + if (p.Base.Item == cPair.Base.Item && p.Quote.Item == cPair.Quote.Item) || + (p.Base.Item == cPair.Quote.Item && p.Quote.Item == cPair.Base.Item) { + return true + } + return false +} + +// IsCryptoPair checks to see if the pair is a crypto pair e.g. BTCLTC +func (p Pair) IsCryptoPair() bool { + return storage.IsCryptocurrency(p.Base) && + storage.IsCryptocurrency(p.Quote) +} + +// IsCryptoFiatPair checks to see if the pair is a crypto fiat pair e.g. BTCUSD +func (p Pair) IsCryptoFiatPair() bool { + return (storage.IsCryptocurrency(p.Base) && storage.IsFiatCurrency(p.Quote)) || + (storage.IsFiatCurrency(p.Base) && storage.IsCryptocurrency(p.Quote)) +} + +// IsFiatPair checks to see if the pair is a fiat pair e.g. EURUSD +func (p Pair) IsFiatPair() bool { + return storage.IsFiatCurrency(p.Base) && storage.IsFiatCurrency(p.Quote) +} + +// IsInvalid checks invalid pair if base and quote are the same +func (p Pair) IsInvalid() bool { + return p.Base.Item == p.Quote.Item +} + +// Swap turns the currency pair into its reciprocal +func (p Pair) Swap() Pair { + return Pair{Base: p.Quote, Quote: p.Base} +} + +// IsEmpty returns whether or not the pair is empty or is missing a currency +// code +func (p Pair) IsEmpty() bool { + return p.Base.IsEmpty() && p.Quote.IsEmpty() +} + +// ContainsCurrency checks to see if a pair contains a specific currency +func (p Pair) ContainsCurrency(c Code) bool { + return p.Base.Item == c.Item || p.Quote.Item == c.Item +} diff --git a/currency/pair_test.go b/currency/pair_test.go index f429f27a..56511f4f 100644 --- a/currency/pair_test.go +++ b/currency/pair_test.go @@ -12,21 +12,35 @@ const ( func TestLower(t *testing.T) { t.Parallel() - pair := NewPairFromString(defaultPair) + pair, err := NewPairFromString(defaultPair) + if err != nil { + t.Fatal(err) + } actual := pair.Lower() - expected := NewPairFromString(defaultPair).Lower() - if actual != expected { + expected, err := NewPairFromString(defaultPair) + if err != nil { + t.Fatal(err) + } + + if actual.String() != expected.Lower().String() { t.Errorf("Lower(): %s was not equal to expected value: %s", - actual, expected) + actual, + expected.Lower()) } } func TestUpper(t *testing.T) { t.Parallel() - pair := NewPairFromString(defaultPair) + pair, err := NewPairFromString(defaultPair) + if err != nil { + t.Fatal(err) + } actual := pair.Upper() - expected := NewPairFromString(defaultPair) - if actual != expected { + expected, err := NewPairFromString(defaultPair) + if err != nil { + t.Fatal(err) + } + if actual.String() != expected.String() { t.Errorf("Upper(): %s was not equal to expected value: %s", actual, expected) } @@ -34,7 +48,10 @@ func TestUpper(t *testing.T) { func TestPairUnmarshalJSON(t *testing.T) { var unmarshalHere Pair - configPair := NewPairDelimiter("btc_usd", "_") + configPair, err := NewPairDelimiter("btc_usd", "_") + if err != nil { + t.Fatal(err) + } encoded, err := json.Marshal(configPair) if err != nil { @@ -59,9 +76,9 @@ func TestPairUnmarshalJSON(t *testing.T) { func TestPairMarshalJSON(t *testing.T) { quickstruct := struct { - Pair Pair `json:"superPair"` + Pair *Pair `json:"superPair"` }{ - Pair{Base: BTC, Quote: USD, Delimiter: "-"}, + &Pair{Base: BTC, Quote: USD, Delimiter: "-"}, } encoded, err := json.Marshal(quickstruct) @@ -158,7 +175,14 @@ func TestPair(t *testing.T) { func TestDisplay(t *testing.T) { t.Parallel() - pair := NewPairDelimiter(defaultPairWDelimiter, "-") + _, err := NewPairDelimiter(defaultPairWDelimiter, "wow") + if err == nil { + t.Fatal("error cannot be nil") + } + pair, err := NewPairDelimiter(defaultPairWDelimiter, "-") + if err != nil { + t.Fatal(err) + } actual := pair.String() expected := defaultPairWDelimiter if actual != expected { @@ -319,7 +343,24 @@ func TestNewPairWithDelimiter(t *testing.T) { func TestNewPairDelimiter(t *testing.T) { t.Parallel() - pair := NewPairDelimiter(defaultPairWDelimiter, "-") + _, err := NewPairDelimiter("", "") + if err == nil { + t.Fatal("error cannot be nil") + } + _, err = NewPairDelimiter("BTC_USD", "wow") + if err == nil { + t.Fatal("error cannot be nil") + } + + _, err = NewPairDelimiter("BTC_USD", " ") + if err == nil { + t.Fatal("error cannot be nil") + } + + pair, err := NewPairDelimiter(defaultPairWDelimiter, "-") + if err != nil { + t.Fatal(err) + } actual := pair.String() expected := defaultPairWDelimiter if actual != expected { @@ -338,7 +379,10 @@ func TestNewPairDelimiter(t *testing.T) { ) } - pair = NewPairDelimiter("BTC-MOVE-0626", "-") + pair, err = NewPairDelimiter("BTC-MOVE-0626", "-") + if err != nil { + t.Fatal(err) + } actual = pair.String() expected = "BTC-MOVE-0626" if actual != expected { @@ -348,7 +392,10 @@ func TestNewPairDelimiter(t *testing.T) { ) } - pair = NewPairDelimiter("fBTC-USDT", "-") + pair, err = NewPairDelimiter("fBTC-USDT", "-") + if err != nil { + t.Fatal(err) + } actual = pair.String() expected = "fBTC-USDT" if actual != expected { @@ -404,7 +451,10 @@ func TestNewPairFromIndex(t *testing.T) { func TestNewPairFromString(t *testing.T) { t.Parallel() pairStr := defaultPairWDelimiter - pair := NewPairFromString(pairStr) + pair, err := NewPairFromString(pairStr) + if err != nil { + t.Fatal(err) + } actual := pair.String() expected := defaultPairWDelimiter if actual != expected { @@ -415,7 +465,10 @@ func TestNewPairFromString(t *testing.T) { } pairStr = defaultPair - pair = NewPairFromString(pairStr) + pair, err = NewPairFromString(pairStr) + if err != nil { + t.Fatal(err) + } actual = pair.String() expected = defaultPair if actual != expected { @@ -428,23 +481,45 @@ func TestNewPairFromString(t *testing.T) { func TestNewPairFromFormattedPairs(t *testing.T) { t.Parallel() + p1, err := NewPairDelimiter("BTC-USDT", "-") + if err != nil { + t.Fatal(err) + } + p2, err := NewPairDelimiter("LTC-USD", "-") + if err != nil { + t.Fatal(err) + } pairs := Pairs{ - NewPairDelimiter("BTC-USDT", "-"), - NewPairDelimiter("LTC-USD", "-"), + p1, + p2, + } + + p, err := NewPairFromFormattedPairs("BTCUSDT", pairs, PairFormat{ + Uppercase: true, + }) + if err != nil { + t.Fatal(err) } - p := NewPairFromFormattedPairs("BTCUSDT", pairs, PairFormat{Uppercase: true}) if p.String() != "BTC-USDT" { t.Error("TestNewPairFromFormattedPairs: Expected currency was not found") } - p = NewPairFromFormattedPairs("btcusdt", pairs, PairFormat{Uppercase: false}) + p, err = NewPairFromFormattedPairs("btcusdt", pairs, PairFormat{Uppercase: false}) + if err != nil { + t.Fatal(err) + } + if p.String() != "BTC-USDT" { t.Error("TestNewPairFromFormattedPairs: Expected currency was not found") } // Now a wrong one, will default to NewPairFromString - p = NewPairFromFormattedPairs("ethusdt", pairs, PairFormat{}) + p, err = NewPairFromFormattedPairs("ethusdt", pairs, PairFormat{}) + if err != nil { + t.Fatal(err) + } + if p.String() != "ethusdt" && p.Base.String() != "eth" { t.Error("TestNewPairFromFormattedPairs: Expected currency was not found") } @@ -514,29 +589,43 @@ func TestCopyPairFormat(t *testing.T) { t.Error("TestCopyPairFormat: Expected pair was not found") } - result = CopyPairFormat(NewPair(ETH, USD), pairs, true) + np := NewPair(ETH, USD) + result = CopyPairFormat(np, pairs, true) if result.String() != "" { t.Error("TestCopyPairFormat: Unexpected non empty pair returned") } } func TestFindPairDifferences(t *testing.T) { - pairList := NewPairsFromStrings([]string{defaultPairWDelimiter, "ETH-USD", "LTC-USD"}) + pairList, err := NewPairsFromStrings([]string{defaultPairWDelimiter, "ETH-USD", "LTC-USD"}) + if err != nil { + t.Fatal(err) + } + + dash, err := NewPairsFromStrings([]string{"DASH-USD"}) + if err != nil { + t.Fatal(err) + } // Test new pair update - newPairs, removedPairs := pairList.FindDifferences(NewPairsFromStrings([]string{"DASH-USD"})) + newPairs, removedPairs := pairList.FindDifferences(dash) if len(newPairs) != 1 && len(removedPairs) != 3 { t.Error("TestFindPairDifferences: Unexpected values") } + emptyPairsList, err := NewPairsFromStrings([]string{""}) + if err != nil { + t.Fatal(err) + } + // Test that we don't allow empty strings for new pairs - newPairs, removedPairs = pairList.FindDifferences(NewPairsFromStrings([]string{""})) + newPairs, removedPairs = pairList.FindDifferences(emptyPairsList) if len(newPairs) != 0 && len(removedPairs) != 3 { t.Error("TestFindPairDifferences: Unexpected values") } // Test that we don't allow empty strings for new pairs - newPairs, removedPairs = NewPairsFromStrings([]string{""}).FindDifferences(pairList) + newPairs, removedPairs = emptyPairsList.FindDifferences(pairList) if len(newPairs) != 3 && len(removedPairs) != 0 { t.Error("TestFindPairDifferences: Unexpected values") } diff --git a/currency/pair_types.go b/currency/pair_types.go new file mode 100644 index 00000000..263da25f --- /dev/null +++ b/currency/pair_types.go @@ -0,0 +1,12 @@ +package currency + +// Pair holds currency pair information +type Pair struct { + ID string `json:"id"` + Delimiter string `json:"delimiter,omitempty"` + Base Code `json:"base,omitempty"` + Quote Code `json:"quote,omitempty"` +} + +// Pairs defines a list of pairs +type Pairs []Pair diff --git a/currency/pairs.go b/currency/pairs.go index effe2167..8abbd948 100644 --- a/currency/pairs.go +++ b/currency/pairs.go @@ -10,16 +10,21 @@ import ( // NewPairsFromStrings takes in currency pair strings and returns a currency // pair list -func NewPairsFromStrings(pairs []string) Pairs { - var ps Pairs - for _, p := range pairs { - if p == "" { +func NewPairsFromStrings(pairs []string) (Pairs, error) { + var newPairs Pairs + for i := range pairs { + if pairs[i] == "" { continue } - ps = append(ps, NewPairFromString(p)) + newPair, err := NewPairFromString(pairs[i]) + if err != nil { + return nil, err + } + + newPairs = append(newPairs, newPair) } - return ps + return newPairs, nil } // Strings returns a slice of strings referring to each currency pair @@ -79,8 +84,13 @@ func (p *Pairs) UnmarshalJSON(d []byte) error { } var allThePairs Pairs - for _, data := range strings.Split(pairs, ",") { - allThePairs = append(allThePairs, NewPairFromString(data)) + oldPairs := strings.Split(pairs, ",") + for i := range oldPairs { + pair, err := NewPairFromString(oldPairs[i]) + if err != nil { + return err + } + allThePairs = append(allThePairs, pair) } *p = allThePairs @@ -101,21 +111,16 @@ func (p Pairs) Upper() Pairs { return upper } -// Slice exposes the underlying type -func (p Pairs) Slice() []Pair { - return p -} - // Contains checks to see if a specified pair exists inside a currency pair // array func (p Pairs) Contains(check Pair, exact bool) bool { - for _, pair := range p.Slice() { + for i := range p { if exact { - if pair.Equal(check) { + if p[i].Equal(check) { return true } } else { - if pair.EqualIncludeReciprocal(check) { + if p[i].EqualIncludeReciprocal(check) { return true } } @@ -127,11 +132,11 @@ func (p Pairs) Contains(check Pair, exact bool) bool { // and removes it from the list of pairs func (p Pairs) RemovePairsByFilter(filter Code) Pairs { var pairs Pairs - for _, pair := range p.Slice() { - if pair.ContainsCurrency(filter) { + for i := range p { + if p[i].ContainsCurrency(filter) { continue } - pairs = append(pairs, pair) + pairs = append(pairs, p[i]) } return pairs } @@ -167,12 +172,12 @@ func (p Pairs) FindDifferences(pairs Pairs) (newPairs, removedPairs Pairs) { newPairs = append(newPairs, pairs[x]) } } - for _, oldPair := range p { - if oldPair.String() == "" { + for x := range p { + if p[x].String() == "" { continue } - if !pairs.Contains(oldPair, true) { - removedPairs = append(removedPairs, oldPair) + if !pairs.Contains(p[x], true) { + removedPairs = append(removedPairs, p[x]) } } return @@ -188,6 +193,3 @@ func (p Pairs) GetRandomPair() Pair { return p[rand.Intn(pairsLen)] } - -// Pairs defines a list of pairs -type Pairs []Pair diff --git a/currency/pairs_test.go b/currency/pairs_test.go index 1af69a8f..87429817 100644 --- a/currency/pairs_test.go +++ b/currency/pairs_test.go @@ -6,7 +6,10 @@ import ( ) func TestPairsUpper(t *testing.T) { - pairs := NewPairsFromStrings([]string{"btc_usd", "btc_aud", "btc_ltc"}) + pairs, err := NewPairsFromStrings([]string{"btc_usd", "btc_aud", "btc_ltc"}) + if err != nil { + t.Fatal(err) + } expected := "BTC_USD,BTC_AUD,BTC_LTC" if pairs.Upper().Join() != expected { @@ -16,7 +19,10 @@ func TestPairsUpper(t *testing.T) { } func TestPairsString(t *testing.T) { - pairs := NewPairsFromStrings([]string{"btc_usd", "btc_aud", "btc_ltc"}) + pairs, err := NewPairsFromStrings([]string{"btc_usd", "btc_aud", "btc_ltc"}) + if err != nil { + t.Fatal(err) + } expected := []string{"btc_usd", "btc_aud", "btc_ltc"} for i, p := range pairs { @@ -28,7 +34,10 @@ func TestPairsString(t *testing.T) { } func TestPairsJoin(t *testing.T) { - pairs := NewPairsFromStrings([]string{"btc_usd", "btc_aud", "btc_ltc"}) + pairs, err := NewPairsFromStrings([]string{"btc_usd", "btc_aud", "btc_ltc"}) + if err != nil { + t.Fatal(err) + } expected := "btc_usd,btc_aud,btc_ltc" if pairs.Join() != expected { @@ -38,7 +47,10 @@ func TestPairsJoin(t *testing.T) { } func TestPairsFormat(t *testing.T) { - pairs := NewPairsFromStrings([]string{"btc_usd", "btc_aud", "btc_ltc"}) + pairs, err := NewPairsFromStrings([]string{"btc_usd", "btc_aud", "btc_ltc"}) + if err != nil { + t.Fatal(err) + } expected := "BTC-USD,BTC-AUD,BTC-LTC" if pairs.Format("-", "", true).Join() != expected { @@ -57,7 +69,10 @@ func TestPairsFormat(t *testing.T) { expected, pairs.Format(":", "KRW", true).Join()) } - pairs = NewPairsFromStrings([]string{"DASHKRW", "BTCKRW"}) + pairs, err = NewPairsFromStrings([]string{"DASHKRW", "BTCKRW"}) + if err != nil { + t.Fatal(err) + } expected = "dash-krw,btc-krw" if pairs.Format("-", "KRW", false).Join() != expected { t.Errorf("Pairs Join() error expected %s but received %s", @@ -106,10 +121,15 @@ func TestPairsUnmarshalJSON(t *testing.T) { } func TestPairsMarshalJSON(t *testing.T) { + pairs, err := NewPairsFromStrings([]string{"btc_usd", "btc_aud", "btc_ltc"}) + if err != nil { + t.Fatal(err) + } + quickstruct := struct { Pairs Pairs `json:"soManyPairs"` }{ - Pairs: NewPairsFromStrings([]string{"btc_usd", "btc_aud", "btc_ltc"}), + Pairs: pairs, } encoded, err := json.Marshal(quickstruct) @@ -176,12 +196,21 @@ func TestContains(t *testing.T) { var pairs = Pairs{ NewPair(BTC, USD), NewPair(LTC, USD), + NewPair(USD, ZRX), } if !pairs.Contains(NewPair(BTC, USD), true) { t.Errorf("TestContains: Expected pair was not found") } + if pairs.Contains(NewPair(USD, BTC), true) { + t.Errorf("TestContains: Unexpected pair was found") + } + + if !pairs.Contains(NewPair(USD, BTC), false) { + t.Errorf("TestContains: Expected pair was not found") + } + if pairs.Contains(NewPair(ETH, USD), false) { t.Errorf("TestContains: Non-existent pair was found") } diff --git a/currency/storage.go b/currency/storage.go index 43b59f2c..4f5c7d60 100644 --- a/currency/storage.go +++ b/currency/storage.go @@ -23,8 +23,14 @@ func init() { func (s *Storage) SetDefaults() { s.defaultBaseCurrency = USD s.baseCurrency = s.defaultBaseCurrency - s.SetDefaultFiatCurrencies(USD, AUD, EUR, CNY) - s.SetDefaultCryptocurrencies(BTC, LTC, ETH, DOGE, DASH, XRP, XMR) + err := s.SetDefaultFiatCurrencies(USD, AUD, EUR, CNY) + if err != nil { + log.Errorf(log.Global, "Currency Storage: Setting default fiat currencies error: %s", err) + } + err = s.SetDefaultCryptocurrencies(BTC, LTC, ETH, DOGE, DASH, XRP, XMR) + if err != nil { + log.Errorf(log.Global, "Currency Storage: Setting default cryptocurrencies error: %s", err) + } s.SetupConversionRates() s.fiatExchangeMarkets = forexprovider.NewDefaultFXProvider() } @@ -33,8 +39,9 @@ func (s *Storage) SetDefaults() { // dump file and keep foreign exchange rates updated as fast as possible without // triggering rate limiters, it will also run a full cryptocurrency check // through coin market cap and expose analytics for exchange services -func (s *Storage) RunUpdater(overrides BotOverrides, settings *MainConfiguration, filePath string, verbose bool) error { +func (s *Storage) RunUpdater(overrides BotOverrides, settings *MainConfiguration, filePath string) error { s.mtx.Lock() + s.shutdown = make(chan struct{}) if !settings.Cryptocurrencies.HasData() { s.mtx.Unlock() @@ -48,17 +55,17 @@ func (s *Storage) RunUpdater(overrides BotOverrides, settings *MainConfiguration } s.baseCurrency = settings.FiatDisplayCurrency log.Debugf(log.Global, - "Fiat display currency: %s.\n", s.baseCurrency) + "Fiat display currency: %s.\n", + s.baseCurrency) if settings.CryptocurrencyProvider.Enabled { - log.Debugln( - log.Global, + log.Debugln(log.Global, "Setting up currency analysis system with Coinmarketcap...") c := &coinmarketcap.Coinmarketcap{} c.SetDefaults() err := c.Setup(coinmarketcap.Settings{ Name: settings.CryptocurrencyProvider.Name, - Enabled: true, + Enabled: settings.CryptocurrencyProvider.Enabled, AccountPlan: settings.CryptocurrencyProvider.AccountPlan, APIkey: settings.CryptocurrencyProvider.APIkey, Verbose: settings.CryptocurrencyProvider.Verbose, @@ -174,16 +181,34 @@ func (s *Storage) SetupConversionRates() { // SetDefaultFiatCurrencies assigns the default fiat currency list and adds it // to the running list -func (s *Storage) SetDefaultFiatCurrencies(c ...Code) { +func (s *Storage) SetDefaultFiatCurrencies(c ...Code) error { + for i := range c { + err := s.currencyCodes.UpdateCurrency("", c[i].String(), "", 0, Fiat) + if err != nil { + return err + } + } s.defaultFiatCurrencies = append(s.defaultFiatCurrencies, c...) s.fiatCurrencies = append(s.fiatCurrencies, c...) + return nil } // SetDefaultCryptocurrencies assigns the default cryptocurrency list and adds // it to the running list -func (s *Storage) SetDefaultCryptocurrencies(c ...Code) { +func (s *Storage) SetDefaultCryptocurrencies(c ...Code) error { + for i := range c { + err := s.currencyCodes.UpdateCurrency("", + c[i].String(), + "", + 0, + Cryptocurrency) + if err != nil { + return err + } + } s.defaultCryptoCurrencies = append(s.defaultCryptoCurrencies, c...) s.cryptocurrencies = append(s.cryptocurrencies, c...) + return nil } // SetupForexProviders sets up a new instance of the forex providers @@ -228,7 +253,7 @@ func (s *Storage) ForeignExchangeUpdater() { for { select { - case <-s.shutdownC: + case <-s.shutdown: return case <-SeedForeignExchangeTick.C: @@ -248,36 +273,29 @@ func (s *Storage) ForeignExchangeUpdater() { // SeedCurrencyAnalysisData sets a new instance of a coinmarketcap data. func (s *Storage) SeedCurrencyAnalysisData() error { - b, err := ioutil.ReadFile(s.path) - if err != nil { - err = s.FetchCurrencyAnalysisData() + if s.currencyCodes.LastMainUpdate.IsZero() { + b, err := ioutil.ReadFile(s.path) if err != nil { - return s.WriteCurrencyDataToFile(s.path, false) + return s.FetchCurrencyAnalysisData() + } + var f *File + err = json.Unmarshal(b, &f) + if err != nil { + return err + } + err = s.LoadFileCurrencyData(f) + if err != nil { + return err } - - return s.WriteCurrencyDataToFile(s.path, true) - } - - var fromFile File - err = json.Unmarshal(b, &fromFile) - if err != nil { - return err - } - - err = s.LoadFileCurrencyData(&fromFile) - if err != nil { - return err } // Based on update delay update the file - if fromFile.LastMainUpdate.After(fromFile.LastMainUpdate.Add(s.currencyFileUpdateDelay)) || - fromFile.LastMainUpdate.IsZero() { - err = s.FetchCurrencyAnalysisData() + if time.Now().After(s.currencyCodes.LastMainUpdate.Add(s.currencyFileUpdateDelay)) || + s.currencyCodes.LastMainUpdate.IsZero() { + err := s.FetchCurrencyAnalysisData() if err != nil { - return s.WriteCurrencyDataToFile(s.path, false) + return err } - - return s.WriteCurrencyDataToFile(s.path, true) } return nil @@ -304,7 +322,7 @@ func (s *Storage) WriteCurrencyDataToFile(path string, mainUpdate bool) error { if mainUpdate { t := time.Now() - data.LastMainUpdate = t + data.LastMainUpdate = t.Unix() s.currencyCodes.LastMainUpdate = t } @@ -320,41 +338,62 @@ func (s *Storage) WriteCurrencyDataToFile(path string, mainUpdate bool) error { // LoadFileCurrencyData loads currencies into the currency codes func (s *Storage) LoadFileCurrencyData(f *File) error { for i := range f.Contracts { - err := s.currencyCodes.LoadItem(&f.Contracts[i]) + contract := f.Contracts[i] + contract.Role = Contract + err := s.currencyCodes.LoadItem(&contract) if err != nil { return err } } for i := range f.Cryptocurrency { - err := s.currencyCodes.LoadItem(&f.Cryptocurrency[i]) + crypto := f.Cryptocurrency[i] + crypto.Role = Cryptocurrency + err := s.currencyCodes.LoadItem(&crypto) if err != nil { return err } } for i := range f.Token { - err := s.currencyCodes.LoadItem(&f.Token[i]) + token := f.Token[i] + token.Role = Token + err := s.currencyCodes.LoadItem(&token) if err != nil { return err } } for i := range f.FiatCurrency { - err := s.currencyCodes.LoadItem(&f.FiatCurrency[i]) + fiat := f.FiatCurrency[i] + fiat.Role = Fiat + err := s.currencyCodes.LoadItem(&fiat) if err != nil { return err } } for i := range f.UnsetCurrency { - err := s.currencyCodes.LoadItem(&f.UnsetCurrency[i]) + unset := f.UnsetCurrency[i] + unset.Role = Unset + err := s.currencyCodes.LoadItem(&unset) if err != nil { return err } } - s.currencyCodes.LastMainUpdate = f.LastMainUpdate + switch t := f.LastMainUpdate.(type) { + case string: + parseT, err := time.Parse(time.RFC3339Nano, t) + if err != nil { + return err + } + s.currencyCodes.LastMainUpdate = parseT + case float64: + s.currencyCodes.LastMainUpdate = time.Unix(int64(t), 0) + default: + return errors.New("unhandled type conversion for LastMainUpdate time") + } return nil } @@ -372,19 +411,22 @@ func (s *Storage) UpdateCurrencies() error { } if m[x].Platform.Symbol != "" { - err := s.currencyCodes.UpdateToken(m[x].Name, + err = s.currencyCodes.UpdateCurrency(m[x].Name, m[x].Symbol, m[x].Platform.Symbol, - m[x].ID) + m[x].ID, + Token) if err != nil { return err } continue } - err := s.currencyCodes.UpdateCryptocurrency(m[x].Name, + err = s.currencyCodes.UpdateCurrency(m[x].Name, m[x].Symbol, - m[x].ID) + "", + m[x].ID, + Cryptocurrency) if err != nil { return err } @@ -452,8 +494,7 @@ func (s *Storage) GetExchangeRates() (Conversions, error) { func (s *Storage) SeedForeignExchangeRates() error { s.fxRates.mtx.Lock() defer s.fxRates.mtx.Unlock() - rates, err := s.fiatExchangeMarkets.GetCurrencyData( - s.baseCurrency.String(), + rates, err := s.fiatExchangeMarkets.GetCurrencyData(s.baseCurrency.String(), s.fiatCurrencies.Strings()) if err != nil { return err @@ -463,15 +504,7 @@ func (s *Storage) SeedForeignExchangeRates() error { // UpdateForeignExchangeRates sets exchange rates on the FX map func (s *Storage) updateExchangeRates(m map[string]float64) error { - err := s.fxRates.Update(m) - if err != nil { - return err - } - - if s.path != "" { - return s.WriteCurrencyDataToFile(s.path, false) - } - return nil + return s.fxRates.Update(m) } // SetupCryptoProvider sets congiguration parameters and starts a new instance @@ -502,9 +535,9 @@ func (s *Storage) GetTotalMarketCryptocurrencies() (Currencies, error) { // IsDefaultCurrency returns if a currency is a default currency func (s *Storage) IsDefaultCurrency(c Code) bool { - t, _ := GetTranslation(c) for i := range s.defaultFiatCurrencies { - if s.defaultFiatCurrencies[i].Match(c) || s.defaultFiatCurrencies[i].Match(t) { + if s.defaultFiatCurrencies[i].Match(c) || + s.defaultFiatCurrencies[i].Match(GetTranslation(c)) { return true } } @@ -514,9 +547,9 @@ func (s *Storage) IsDefaultCurrency(c Code) bool { // IsDefaultCryptocurrency returns if a cryptocurrency is a default // cryptocurrency func (s *Storage) IsDefaultCryptocurrency(c Code) bool { - t, _ := GetTranslation(c) - for _, d := range s.defaultCryptoCurrencies { - if d.Match(c) || d.Match(t) { + for i := range s.defaultCryptoCurrencies { + if s.defaultCryptoCurrencies[i].Match(c) || + s.defaultCryptoCurrencies[i].Match(GetTranslation(c)) { return true } } @@ -534,9 +567,9 @@ func (s *Storage) IsFiatCurrency(c Code) bool { return false } - t, _ := GetTranslation(c) - for _, d := range s.fiatCurrencies { - if d.Match(c) || d.Match(t) { + for i := range s.fiatCurrencies { + if s.fiatCurrencies[i].Match(c) || + s.fiatCurrencies[i].Match(GetTranslation(c)) { return true } } @@ -555,9 +588,9 @@ func (s *Storage) IsCryptocurrency(c Code) bool { return false } - t, _ := GetTranslation(c) - for _, d := range s.cryptocurrencies { - if d.Match(c) || d.Match(t) { + for i := range s.cryptocurrencies { + if s.cryptocurrencies[i].Match(c) || + s.cryptocurrencies[i].Match(GetTranslation(c)) { return true } } @@ -573,15 +606,12 @@ func (s *Storage) ValidateCode(newCode string) Code { // ValidateFiatCode validates a fiat currency string and returns a currency // code -func (s *Storage) ValidateFiatCode(newCode string) (Code, error) { - c, err := s.currencyCodes.RegisterFiat(newCode) - if err != nil { - return c, err - } +func (s *Storage) ValidateFiatCode(newCode string) Code { + c := s.currencyCodes.RegisterFiat(newCode) if !s.fiatCurrencies.Contains(c) { s.fiatCurrencies = append(s.fiatCurrencies, c) } - return c, nil + return c } // ValidateCryptoCode validates a cryptocurrency string and returns a currency @@ -637,9 +667,9 @@ func (s *Storage) GetBaseCurrency() Code { // UpdateEnabledCryptoCurrencies appends new cryptocurrencies to the enabled // currency list func (s *Storage) UpdateEnabledCryptoCurrencies(c Currencies) { - for _, i := range c { - if !s.cryptocurrencies.Contains(i) { - s.cryptocurrencies = append(s.cryptocurrencies, i) + for i := range c { + if !s.cryptocurrencies.Contains(c[i]) { + s.cryptocurrencies = append(s.cryptocurrencies, c[i]) } } } @@ -647,9 +677,10 @@ func (s *Storage) UpdateEnabledCryptoCurrencies(c Currencies) { // UpdateEnabledFiatCurrencies appends new fiat currencies to the enabled // currency list func (s *Storage) UpdateEnabledFiatCurrencies(c Currencies) { - for _, i := range c { - if !s.fiatCurrencies.Contains(i) && !s.cryptocurrencies.Contains(i) { - s.fiatCurrencies = append(s.fiatCurrencies, i) + for i := range c { + if !s.fiatCurrencies.Contains(c[i]) && + !s.cryptocurrencies.Contains(c[i]) { + s.fiatCurrencies = append(s.fiatCurrencies, c[i]) } } } @@ -709,3 +740,10 @@ func (s *Storage) NewConversion(from, to Code) (Conversion, error) { func (s *Storage) IsVerbose() bool { return s.Verbose } + +// Shutdown shuts down the currency storage system and saves to currency.json +func (s *Storage) Shutdown() error { + close(s.shutdown) + s.wg.Wait() + return s.WriteCurrencyDataToFile(s.path, true) +} diff --git a/currency/storage_test.go b/currency/storage_test.go index b9e562bf..699f3d7d 100644 --- a/currency/storage_test.go +++ b/currency/storage_test.go @@ -6,7 +6,7 @@ func TestRunUpdater(t *testing.T) { var newStorage Storage emptyMainConfig := MainConfiguration{} - err := newStorage.RunUpdater(BotOverrides{}, &emptyMainConfig, "", false) + err := newStorage.RunUpdater(BotOverrides{}, &emptyMainConfig, "") if err == nil { t.Fatal("storage RunUpdater() error cannot be nil") } @@ -16,12 +16,12 @@ func TestRunUpdater(t *testing.T) { FiatDisplayCurrency: USD, } - err = newStorage.RunUpdater(BotOverrides{}, &mainConfig, "", false) + err = newStorage.RunUpdater(BotOverrides{}, &mainConfig, "") if err == nil { t.Fatal("storage RunUpdater() error cannot be nil") } - err = newStorage.RunUpdater(BotOverrides{}, &mainConfig, "/bla", false) + err = newStorage.RunUpdater(BotOverrides{}, &mainConfig, "/bla") if err != nil { t.Fatal("storage RunUpdater() error", err) } diff --git a/currency/storage_types.go b/currency/storage_types.go index 032be5db..64020db0 100644 --- a/currency/storage_types.go +++ b/currency/storage_types.go @@ -56,7 +56,7 @@ type Storage struct { foreignExchangeUpdateDelay time.Duration mtx sync.Mutex wg sync.WaitGroup - shutdownC chan struct{} + shutdown chan struct{} updaterRunning bool Verbose bool } diff --git a/currency/translation.go b/currency/translation.go index c2db7430..5b9075b0 100644 --- a/currency/translation.go +++ b/currency/translation.go @@ -2,12 +2,12 @@ package currency // GetTranslation returns similar strings for a particular currency if not found // returns the code back -func GetTranslation(currency Code) (Code, bool) { +func GetTranslation(currency Code) Code { val, ok := translations[currency] if !ok { - return currency, ok + return currency } - return val, ok + return val } var translations = map[Code]Code{ diff --git a/currency/translation_test.go b/currency/translation_test.go index 5124597b..95415b38 100644 --- a/currency/translation_test.go +++ b/currency/translation_test.go @@ -5,29 +5,21 @@ import "testing" func TestGetTranslation(t *testing.T) { currencyPair := NewPair(BTC, USD) expected := XBT - actual, ok := GetTranslation(currencyPair.Base) - if !ok { - t.Error("GetTranslation: failed to retrieve translation for BTC") - } - + actual := GetTranslation(currencyPair.Base) if expected != actual { t.Error("GetTranslation: translation result was different to expected result") } currencyPair.Base = NEO - _, ok = GetTranslation(currencyPair.Base) - if ok { + actual = GetTranslation(currencyPair.Base) + if actual != currencyPair.Base { t.Error("GetTranslation: no error on non translatable currency") } expected = BTC currencyPair.Base = XBT - actual, ok = GetTranslation(currencyPair.Base) - if !ok { - t.Error("GetTranslation: failed to retrieve translation for BTC") - } - + actual = GetTranslation(currencyPair.Base) if expected != actual { t.Error("GetTranslation: translation result was different to expected result") } diff --git a/docs/ADD_NEW_EXCHANGE.md b/docs/ADD_NEW_EXCHANGE.md index 169c44c8..ae69630a 100644 --- a/docs/ADD_NEW_EXCHANGE.md +++ b/docs/ADD_NEW_EXCHANGE.md @@ -49,7 +49,7 @@ Find out which asset types are supported by the exchange and add them to the pai config.GetDefaultFilePath() ``` -```go +```js { "name": "FTX", "enabled": true, @@ -61,37 +61,35 @@ config.GetDefaultFilePath() "websocketOrderbookBufferLimit": 5, "baseCurrencies": "USD", "currencyPairs": { - "assetTypes": [ - "spot", - "futures" - ], - "pairs": { - "futures": { - "enabled": "BTC-PERP", - "available": "BTC-PERP", - "requestFormat": { - "uppercase": true, - "delimiter": "-" - }, - "configFormat": { - "uppercase": true, - "delimiter": "-" - } + "pairs": { + "futures": { + "assetEnabled": true, + "enabled": "BTC-PERP", + "available": "BTC-PERP", + "requestFormat": { + "uppercase": true, + "delimiter": "-" }, - "spot": { - "enabled": "BTC/USD", - "available": "BTC/USD", - "requestFormat": { - "uppercase": true, - "delimiter": "/" - }, - "configFormat": { - "uppercase": true, - "delimiter": "/" - } + "configFormat": { + "uppercase": true, + "delimiter": "-" + } + }, + "spot": { + "assetEnabled": true, + "enabled": "BTC/USD", + "available": "BTC/USD", + "requestFormat": { + "uppercase": true, + "delimiter": "/" + }, + "configFormat": { + "uppercase": true, + "delimiter": "/" } } - }, + } + }, "api": { "authenticatedSupport": false, "authenticatedWebsocketApiSupport": false, @@ -177,6 +175,16 @@ Similar to the configs, spot support is inbuilt but other asset types will need Delimiter: "-", }, } + + err := f.StoreAssetPairFormat(asset.Spot, spot) + if err != nil { + log.Errorln(log.ExchangeSys, err) + } + + err = f.StoreAssetPairFormat(asset.Futures, futures) + if err != nil { + log.Errorln(log.ExchangeSys, err) + } ``` ### Document the addition of the new exchange (FTX exchange is used as an example below): @@ -645,16 +653,6 @@ The currency package contains many helper functions to format and process curren ### Websocket addition if exchange supports it: -#### Add websocket to exchange struct in ftx.go - -```go -// FTX is the overarching type across this package -type FTX struct { - exchange.Base - WebsocketConn *wshandler.WebsocketConnection // Add this line -} -``` - #### Websocket Setup: - Set the websocket url in ftx_websocket.go that is provided in the documentation: @@ -672,27 +670,35 @@ func (f *FTX) WsConnect() error { return errors.New(wshandler.WebsocketNotEnabled) } var dialer websocket.Dialer - err := f.WebsocketConn.Dial(&dialer, http.Header{}) + err := f.Websocket.Conn.Dial(&dialer, http.Header{}) if err != nil { return err } - f.WebsocketConn.SetupPingHandler(wshandler.WebsocketPingHandler{ + // Can set up custom ping handler per websocket connection. + f.Websocket.Conn.SetupPingHandler(wshandler.WebsocketPingHandler{ MessageType: websocket.PingMessage, Delay: ftxWebsocketTimer, }) if f.Verbose { log.Debugf(log.ExchangeSys, "%s Connected to Websocket.\n", f.Name) } + // This reader routine is called prior to initiating a subscription for + // efficient processing. go f.wsReadData() if f.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) { - err := f.WsAuth() + err = f.WsAuth() if err != nil { f.Websocket.DataHandler <- err f.Websocket.SetCanUseAuthenticatedEndpoints(false) } } - f.GenerateDefaultSubscriptions() - return nil + // Generates the default subscription set, based off enabled pairs. + subs, err := f.GenerateDefaultSubscriptions() + if err != nil { + return err + } + // Finally subscribes to each individual channel. + return f.Websocket.SubscribeToChannels(subs) } ``` @@ -700,22 +706,44 @@ func (f *FTX) WsConnect() error { ```go // GenerateDefaultSubscriptions generates default subscription -func (f *FTX) GenerateDefaultSubscriptions() { - var channels = []string{wsTicker, wsTrades, wsOrderbook, wsMarkets, wsFills, wsOrders} - var subscriptions []wshandler.WebsocketChannelSubscription - for a := range f.CurrencyPairs.AssetTypes { - pairs := f.GetEnabledPairs(f.CurrencyPairs.AssetTypes[a]) +func (f *FTX) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, error) { + var subscriptions []stream.ChannelSubscription + subscriptions = append(subscriptions, stream.ChannelSubscription{ + Channel: wsMarkets, + }) + // Ranges over available channels, pairs and asset types to produce a full + // subscription list. + var channels = []string{wsTicker, wsTrades, wsOrderbook} + assets := f.GetAssetTypes() + for a := range assets { + pairs, err := f.GetEnabledPairs(assets[a]) + if err != nil { + return nil, err + } for z := range pairs { - newPair := currency.NewPairWithDelimiter(pairs[z].Base.String(), pairs[z].Quote.String(), "-") + newPair := currency.NewPairWithDelimiter(pairs[z].Base.String(), + pairs[z].Quote.String(), + "-") for x := range channels { - subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{ - Channel: channels[x], - Currency: newPair, - }) + subscriptions = append(subscriptions, + stream.ChannelSubscription{ + Channel: channels[x], + Currency: newPair, + Asset: assets[a], + }) } } } - f.Websocket.SubscribeToChannels(subscriptions) + // Appends authenticated channels to the subscription list + if f.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) { + var authchan = []string{wsOrders, wsFills} + for x := range authchan { + subscriptions = append(subscriptions, stream.ChannelSubscription{ + Channel: authchan[x], + }) + } + } + return subscriptions, nil } ``` @@ -753,22 +781,47 @@ type WsSub struct { ```go // Subscribe sends a websocket message to receive data from the channel -func (f *FTX) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error { - var sub WsSub - a, err := f.GetPairAssetType(channelToSubscribe.Currency) - if err != nil { - return err +func (f *FTX) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error { + // For subscriptions we try to batch as much as possible to limit the amount + // of connection usage but sometimes this is not supported on the exchange + // API. + var errs common.Errors // This is an array of errors useful in the event that one channel subscription errors but we can subscribe to the next iteration. +channels: + for i := range channelsToSubscribe { + // Type we declared above to send via our websocket connection. + var sub WsSub + sub.Channel = channelsToSubscribe[i].Channel + sub.Operation = subscribe + + switch channelsToSubscribe[i].Channel { + case wsFills, wsOrders, wsMarkets: + // Authenticated wsFills && wsOrders or wsMarkets which is a channel subscription for the full set of tradable markets do not need a currency pair association. + default: + a, err := f.GetPairAssetType(channelsToSubscribe[i].Currency) + if err != nil { + errs = append(errs, err) + continue channels + } + // Ensures our outbound currency pair is formatted correctly, sometimes our configuration format is different from what our request format needs to be. + formattedPair, err := f.FormatExchangeCurrency(channelsToSubscribe[i].Currency, a) + if err != nil { + errs = append(errs, err) + continue channels + } + sub.Market = formattedPair.String() + } + err := f.Websocket.Conn.SendJSONMessage(sub) + if err != nil { + errs = append(errs, err) + continue + } + // When we have a successful subscription, we can alert our internal management system of the success. + f.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe[i]) } - switch channelToSubscribe.Channel { - case wsFills, wsOrders: - sub.Operation = "subscribe" - sub.Channel = channelToSubscribe.Channel - default: - sub.Operation = "subscribe" - sub.Channel = channelToSubscribe.Channel - sub.Market = f.FormatExchangeCurrency(channelToSubscribe.Currency, a).String() + if errs != nil { + return errs } - return f.WebsocketConn.SendJSONMessage(sub) + return nil } ``` @@ -782,7 +835,7 @@ Run gocryptotrader with the following settings enabled in config }, "enabled": { "autoPairUpdates": true, - "websocketAPI": true + "websocketAPI": true // <- Change this to true if it is false ``` #### Handle websocket data: @@ -800,13 +853,12 @@ func (f *FTX) wsReadData() { case <-f.Websocket.ShutdownC: return default: - resp, err := f.WebsocketConn.ReadMessage() - if err != nil { - f.Websocket.ReadMessageErrors <- err + resp := f.Websocket.Conn.ReadMessage() + if resp.Raw == nil { return } - f.Websocket.TrafficAlert <- struct{}{} - err = f.wsHandleData(resp.Raw) + + err := f.wsHandleData(resp.Raw) if err != nil { f.Websocket.DataHandler <- err } @@ -963,7 +1015,7 @@ func (f *FTX) WsAuth() error { Time: intNonce, }, } - return f.WebsocketConn.SendJSONMessage(req) + return f.Websocket.Conn.SendJSONMessage(req) } ``` @@ -971,16 +1023,42 @@ func (f *FTX) WsAuth() error { ```go // Unsubscribe sends a websocket message to stop receiving data from the channel -func (f *FTX) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error { - var unSub WsSub - a, err := f.GetPairAssetType(channelToSubscribe.Currency) - if err != nil { - return err +func (f *FTX) Unsubscribe(channelsToUnsubscribe []stream.ChannelSubscription) error { + // As with subscribing we want to batch as much as possible, but sometimes this cannot be achieved due to API shortfalls. + var errs common.Errors +channels: + for i := range channelsToUnsubscribe { + var unSub WsSub + unSub.Operation = unsubscribe + unSub.Channel = channelsToUnsubscribe[i].Channel + switch channelsToUnsubscribe[i].Channel { + case wsFills, wsOrders, wsMarkets: + default: + a, err := f.GetPairAssetType(channelsToUnsubscribe[i].Currency) + if err != nil { + errs = append(errs, err) + continue channels + } + + formattedPair, err := f.FormatExchangeCurrency(channelsToUnsubscribe[i].Currency, a) + if err != nil { + errs = append(errs, err) + continue channels + } + unSub.Market = formattedPair.String() + } + err := f.Websocket.Conn.SendJSONMessage(unSub) + if err != nil { + errs = append(errs, err) + continue + } + // When we have a successful unsubscription, we can alert our internal management system of the success. + f.Websocket.RemoveSuccessfulUnsubscriptions(channelsToUnsubscribe[i]) } - unSub.Operation = "unsubscribe" - unSub.Channel = channelToSubscribe.Channel - unSub.Market = f.FormatExchangeCurrency(channelToSubscribe.Currency, a).String() - return f.WebsocketConn.SendJSONMessage(unSub) + if errs != nil { + return errs + } + return nil } ``` @@ -989,24 +1067,53 @@ func (f *FTX) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubscript Add websocket functionality if supported to Setup: ```go - err = f.Websocket.Setup( - &wshandler.WebsocketSetup{ - Enabled: exch.Features.Enabled.Websocket, - Verbose: exch.Verbose, - AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, - WebsocketTimeout: exch.WebsocketTrafficTimeout, - DefaultURL: ftxWSURL, - ExchangeName: exch.Name, - RunningURL: exch.API.Endpoints.WebsocketURL, - Connector: f.WsConnect, - Subscriber: f.Subscribe, - UnSubscriber: f.Unsubscribe, - Features: &f.Features.Supports.WebsocketCapabilities, - }) +// Setup takes in the supplied exchange configuration details and sets params +func (f *FTX) Setup(exch *config.ExchangeConfig) error { + if !exch.Enabled { + f.SetEnabled(false) + return nil + } + + err := f.SetupDefaults(exch) if err != nil { return err - } - ``` + } + + // Websocket details setup below + err = f.Websocket.Setup(&stream.WebsocketSetup{ + Enabled: exch.Features.Enabled.Websocket, + Verbose: exch.Verbose, + AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, + WebsocketTimeout: exch.WebsocketTrafficTimeout, + DefaultURL: ftxWSURL, // Default ws endpoint so we can roll back via CLI if needed. + ExchangeName: exch.Name, // Sets websocket name to the exchange name. + RunningURL: exch.API.Endpoints.WebsocketURL, + Connector: f.WsConnect, // Connector function outlined above. + Subscriber: f.Subscribe, // Subscriber function outlined above. + UnSubscriber: f.Unsubscribe, // Unsubscriber function outlined above. + GenerateSubscriptions: f.GenerateDefaultSubscriptions, // GenerateDefaultSubscriptions function outlined above. + Features: &f.Features.Supports.WebsocketCapabilities, // Defines the capabilities of the websocket outlined in supported features struct. This allows the websocket connection to be flushed appropriately if we have a pair/asset enable/disable change. This is outlined below. + + // Orderbook buffer specific variables for processing orderbook updates via websocket feed. + OrderbookBufferLimit: exch.WebsocketOrderbookBufferLimit, + // Other orderbook buffer vars: + // BufferEnabled bool + // SortBuffer bool + // SortBufferByUpdateIDs bool + // UpdateEntriesByID bool + }) + if err != nil { + return err + } + // Sets up a new connection for the websocket, there are two separate connections denoted by the ConnectionSetup struct auth bool. + return f.Websocket.SetupNewConnection(stream.ConnectionSetup{ + ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout, + ResponseMaxLimit: exch.WebsocketResponseMaxLimit, + // RateLimit int64 rudimentary rate limit that sleeps connection in milliseconds before sending designated payload + // Authenticated bool sets if the connection is dedicated for an authenticated websocket stream which can be accessed from the Websocket field variable AuthConn e.g. f.Websocket.AuthConn + }) +} +``` Below are the features supported by FTX API protocol: @@ -1053,32 +1160,35 @@ Below are the features supported by FTX API protocol: Initially the functions return nil or common.ErrNotYetImplemented ```go -// GetWebsocket returns a pointer to the exchange websocket -func (f *FTX) GetWebsocket() (*wshandler.Websocket, error) { - return f.Websocket, nil -} - -// SubscribeToWebsocketChannels appends to ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle subscribing -func (f *FTX) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - f.Websocket.SubscribeToChannels(channels) - return nil -} - -// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle unsubscribing -func (f *FTX) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - f.Websocket.RemoveSubscribedChannels(channels) - return nil -} - -// GetSubscriptions returns a copied list of subscriptions -func (f *FTX) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) { - return f.Websocket.GetSubscriptions(), nil -} - // AuthenticateWebsocket sends an authentication message to the websocket func (f *FTX) AuthenticateWebsocket() error { return f.WsAuth() } -``` \ No newline at end of file +``` + + +## Last but not least - Live testing + +### Live testing websocket via [gctcli](../cmd/gctcli/main.go) + +Please test all `websocket` commands below whilst a GoCryptoTrader instance is running and with the exchange websocket setting enabled: + +- `getinfo` to ensure fetching websocket information is possible (that the websocket connection is enabled, connected and is running). +- `disable/enable` to ensure disabling/enabling a websocket connection disconnects/connects accordingly. +- `getsubs` to ensure the subscriptions are in sync with the exchange's config settings or by manual subscriptions added/removed via `gctcli`. +- `setproxy` to ensure that a proxy can be set and resets the websocket connection accordingly. +- `seturl` to ensure that a new websocket URL can be set in the event of an API endpoint change whilst an instance of GoCryptoTrader is already running. + +Please test all `pair` commands to disable and enable different assets types to witness subscriptions and unsubscriptions: + +- `get` to ensure correct enabled and disabled pairs for a supported asset type. +- `disableasset` to ensure disabling of entire asset class and associated unsubscriptions. +- `enableasset` to ensure correct enabling of entire asset class and associated subscriptions. +- `disable` to ensure correct disabling of pair(s) and and associated unsubscriptions. +- `enable` to ensure correct enabling of pair(s) and associated subscriptions. +- `enableall` to ensure correct enabling of all pairs for an asset type and associated subscriptions. +- `disableall` to ensure correct disabling of all pairs for an asset type and associated unsubscriptions. + +## Open a PR + +Submitting a PR is easy and all are welcome additions to the public repository. Submit via github.com/thrasher-corp/gocryptotrader or contact our team via slack for more information. \ No newline at end of file diff --git a/engine/engine.go b/engine/engine.go index e01ec2d8..303c3dbc 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -385,8 +385,7 @@ func (e *Engine) Start() error { CurrencyDelay: e.Config.Currency.CurrencyFileUpdateDuration, FxRateDelay: e.Config.Currency.ForeignExchangeUpdateDuration, }, - e.Settings.DataDir, - e.Settings.Verbose) + e.Settings.DataDir) if err != nil { gctlog.Errorf(gctlog.Global, "Currency updater system failed to start %v", err) } @@ -514,6 +513,10 @@ func (e *Engine) Stop() { } } + if err := currency.ShutdownStorageUpdater(); err != nil { + gctlog.Errorf(gctlog.Global, "Currency storage system. Error: %v", err) + } + if !e.Settings.EnableDryRun { err := e.Config.SaveConfig(e.Settings.ConfigFile, false) if err != nil { diff --git a/engine/events.go b/engine/events.go index 993b5101..f614f9fb 100644 --- a/engine/events.go +++ b/engine/events.go @@ -72,7 +72,7 @@ var Events []*Event // Add adds an event to the Events chain and returns an index/eventID // and an error -func Add(exchange, item string, condition EventConditionParams, currencyPair currency.Pair, asset asset.Item, action string) (int64, error) { +func Add(exchange, item string, condition EventConditionParams, p currency.Pair, a asset.Item, action string) (int64, error) { err := IsValidEvent(exchange, item, condition, action) if err != nil { return 0, err @@ -89,8 +89,8 @@ func Add(exchange, item string, condition EventConditionParams, currencyPair cur evt.Exchange = exchange evt.Item = item evt.Condition = condition - evt.Pair = currencyPair - evt.Asset = asset + evt.Pair = p + evt.Asset = a evt.Action = action evt.Executed = false Events = append(Events, evt) @@ -99,8 +99,8 @@ func Add(exchange, item string, condition EventConditionParams, currencyPair cur // Remove deletes and event by its ID func Remove(eventID int64) bool { - for i, x := range Events { - if x.ID == eventID { + for i := range Events { + if Events[i].ID == eventID { Events = append(Events[:i], Events[i+1:]...) return true } @@ -112,9 +112,8 @@ func Remove(eventID int64) bool { // events that have been executed. func GetEventCounter() (total, executed int) { total = len(Events) - - for _, x := range Events { - if x.Executed { + for i := range Events { + if Events[i].Executed { executed++ } } @@ -126,11 +125,10 @@ func (e *Event) ExecuteAction() bool { if strings.Contains(e.Action, ",") { action := strings.Split(e.Action, ",") if action[0] == ActionSMSNotify { - message := fmt.Sprintf("Event triggered: %s\n", e.String()) if action[1] == "ALL" { Bot.CommsManager.PushEvent(base.Event{ Type: "event", - Message: message, + Message: "Event triggered: " + e.String(), }) } } diff --git a/engine/events_test.go b/engine/events_test.go index ea1d9ce8..5846d3ca 100644 --- a/engine/events_test.go +++ b/engine/events_test.go @@ -138,10 +138,11 @@ func TestProcessTicker(t *testing.T) { // now populate it with a 0 entry tick := ticker.Price{ - Pair: currency.NewPair(currency.BTC, currency.USD), - Last: 0, + Pair: currency.NewPair(currency.BTC, currency.USD), + ExchangeName: e.Exchange, + AssetType: e.Asset, } - if err := ticker.ProcessTicker(e.Exchange, &tick, e.Asset); err != nil { + if err := ticker.ProcessTicker(&tick); err != nil { t.Fatal("unexpected result:", err) } if r := e.processTicker(); r { @@ -150,7 +151,7 @@ func TestProcessTicker(t *testing.T) { // now populate it with a number > 0 tick.Last = 1337 - if err := ticker.ProcessTicker(e.Exchange, &tick, e.Asset); err != nil { + if err := ticker.ProcessTicker(&tick); err != nil { t.Fatal("unexpected result:", err) } if r := e.processTicker(); !r { diff --git a/engine/exchange.go b/engine/exchange.go index e42ca7c3..5c677ef9 100644 --- a/engine/exchange.go +++ b/engine/exchange.go @@ -6,6 +6,7 @@ import ( "sync" "github.com/thrasher-corp/gocryptotrader/common" + "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/binance" "github.com/thrasher-corp/gocryptotrader/exchanges/bitfinex" @@ -237,7 +238,11 @@ func LoadExchange(name string, useWG bool, wg *sync.WaitGroup) error { dryrunParamInteraction("enableallpairs") assets := exchCfg.CurrencyPairs.GetAssetTypes() for x := range assets { - pairs := exchCfg.CurrencyPairs.GetPairs(assets[x], false) + var pairs currency.Pairs + pairs, err = exchCfg.CurrencyPairs.GetPairs(assets[x], false) + if err != nil { + return err + } exchCfg.CurrencyPairs.StorePairs(assets[x], pairs, true) } } diff --git a/engine/fake_exchange_test.go b/engine/fake_exchange_test.go index d7f00df6..26905ceb 100644 --- a/engine/fake_exchange_test.go +++ b/engine/fake_exchange_test.go @@ -12,8 +12,8 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/kline" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) @@ -89,11 +89,11 @@ func (h *FakePassingExchange) FetchTradablePairs(_ asset.Item) ([]string, error) } func (h *FakePassingExchange) UpdateTradablePairs(_ bool) error { return nil } -func (h *FakePassingExchange) GetEnabledPairs(_ asset.Item) currency.Pairs { - return currency.Pairs{} +func (h *FakePassingExchange) GetEnabledPairs(_ asset.Item) (currency.Pairs, error) { + return currency.Pairs{}, nil } -func (h *FakePassingExchange) GetAvailablePairs(_ asset.Item) currency.Pairs { - return currency.Pairs{} +func (h *FakePassingExchange) GetAvailablePairs(_ asset.Item) (currency.Pairs, error) { + return currency.Pairs{}, nil } func (h *FakePassingExchange) FetchAccountInfo() (account.Holdings, error) { return account.Holdings{}, nil @@ -142,6 +142,11 @@ func (h *FakePassingExchange) GetOrderHistory(_ *order.GetOrdersRequest) ([]orde return nil, nil } func (h *FakePassingExchange) GetActiveOrders(_ *order.GetOrdersRequest) ([]order.Detail, error) { + pair, err := currency.NewPairFromString("BTCUSD") + if err != nil { + return nil, err + } + return []order.Detail{ { Price: 1337, @@ -153,25 +158,25 @@ func (h *FakePassingExchange) GetActiveOrders(_ *order.GetOrdersRequest) ([]orde Status: order.Active, AssetType: asset.Spot, Date: time.Now(), - Pair: currency.NewPairFromString("BTCUSD"), + Pair: pair, }, }, nil } -func (h *FakePassingExchange) SetHTTPClientUserAgent(_ string) {} -func (h *FakePassingExchange) GetHTTPClientUserAgent() string { return "" } -func (h *FakePassingExchange) SetClientProxyAddress(_ string) error { return nil } -func (h *FakePassingExchange) SupportsWebsocket() bool { return true } -func (h *FakePassingExchange) SupportsREST() bool { return true } -func (h *FakePassingExchange) IsWebsocketEnabled() bool { return true } -func (h *FakePassingExchange) GetWebsocket() (*wshandler.Websocket, error) { return nil, nil } -func (h *FakePassingExchange) SubscribeToWebsocketChannels(_ []wshandler.WebsocketChannelSubscription) error { +func (h *FakePassingExchange) SetHTTPClientUserAgent(_ string) {} +func (h *FakePassingExchange) GetHTTPClientUserAgent() string { return "" } +func (h *FakePassingExchange) SetClientProxyAddress(_ string) error { return nil } +func (h *FakePassingExchange) SupportsWebsocket() bool { return true } +func (h *FakePassingExchange) SupportsREST() bool { return true } +func (h *FakePassingExchange) IsWebsocketEnabled() bool { return true } +func (h *FakePassingExchange) GetWebsocket() (*stream.Websocket, error) { return nil, nil } +func (h *FakePassingExchange) SubscribeToWebsocketChannels(_ []stream.ChannelSubscription) error { return nil } -func (h *FakePassingExchange) UnsubscribeToWebsocketChannels(_ []wshandler.WebsocketChannelSubscription) error { +func (h *FakePassingExchange) UnsubscribeToWebsocketChannels(_ []stream.ChannelSubscription) error { return nil } func (h *FakePassingExchange) AuthenticateWebsocket() error { return nil } -func (h *FakePassingExchange) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) { +func (h *FakePassingExchange) GetSubscriptions() ([]stream.ChannelSubscription, error) { return nil, nil } func (h *FakePassingExchange) GetDefaultConfig() (*config.ExchangeConfig, error) { return nil, nil } diff --git a/engine/helpers.go b/engine/helpers.go index cab09c92..93d873fa 100644 --- a/engine/helpers.go +++ b/engine/helpers.go @@ -292,8 +292,7 @@ func MapCurrenciesByExchange(p currency.Pairs, enabledExchangesOnly bool, assetT continue } exchName := Bot.Config.Exchanges[y].Name - success, err := Bot.Config.SupportsPair(exchName, p[x], assetType) - if err != nil || !success { + if !Bot.Config.SupportsPair(exchName, p[x], assetType) { continue } @@ -324,14 +323,10 @@ func GetExchangeNamesByCurrency(p currency.Pair, enabled bool, assetType asset.I } exchName := Bot.Config.Exchanges[x].Name - success, err := Bot.Config.SupportsPair(exchName, p, assetType) - if err != nil { + if !Bot.Config.SupportsPair(exchName, p, assetType) { continue } - - if success { - exchanges = append(exchanges, exchName) - } + exchanges = append(exchanges, exchName) } return exchanges } @@ -404,19 +399,18 @@ func GetRelatableCurrencies(p currency.Pair, incOrig, incUSDT bool) currency.Pai addPair(p) } - first, ok := currency.GetTranslation(p.Base) - if ok { + first := currency.GetTranslation(p.Base) + if first != p.Base { addPair(currency.NewPair(first, p.Quote)) - var second currency.Code - second, ok = currency.GetTranslation(p.Quote) - if ok { + second := currency.GetTranslation(p.Quote) + if second != p.Quote { addPair(currency.NewPair(first, second)) } } - second, ok := currency.GetTranslation(p.Quote) - if ok { + second := currency.GetTranslation(p.Quote) + if second != p.Quote { addPair(currency.NewPair(p.Base, second)) } } @@ -483,8 +477,8 @@ func GetCollatedExchangeAccountInfoByCoin(accounts []account.Holdings) map[curre // GetExchangeHighestPriceByCurrencyPair returns the exchange with the highest // price for a given currency pair and asset type -func GetExchangeHighestPriceByCurrencyPair(p currency.Pair, assetType asset.Item) (string, error) { - result := stats.SortExchangesByPrice(p, assetType, true) +func GetExchangeHighestPriceByCurrencyPair(p currency.Pair, a asset.Item) (string, error) { + result := stats.SortExchangesByPrice(p, a, true) if len(result) == 0 { return "", fmt.Errorf("no stats for supplied currency pair and asset type") } @@ -717,7 +711,14 @@ func GetAllActiveTickers() []EnabledExchangeCurrencies { exchangeTicker.ExchangeName = exchName for y := range assets { - currencies := exchanges[x].GetEnabledPairs(assets[y]) + currencies, err := exchanges[x].GetEnabledPairs(assets[y]) + if err != nil { + log.Errorf(log.ExchangeSys, + "Exchange %s could not retrieve enabled currencies. Err: %s\n", + exchName, + err) + continue + } for z := range currencies { tp, err := exchanges[x].FetchTicker(currencies[z], assets[y]) if err != nil { @@ -739,19 +740,25 @@ func GetAllEnabledExchangeAccountInfo() AllEnabledExchangeAccounts { var response AllEnabledExchangeAccounts exchanges := GetExchanges() for x := range exchanges { - if !exchanges[x].GetAuthenticatedAPISupport(exchange.RestAuthentication) { - if Bot.Settings.Verbose { - log.Debugf(log.ExchangeSys, "GetAllEnabledExchangeAccountInfo: Skippping %s due to disabled authenticated API support.\n", exchanges[x].GetName()) + if exchanges[x] != nil && exchanges[x].IsEnabled() { + if !exchanges[x].GetAuthenticatedAPISupport(exchange.RestAuthentication) { + if Bot.Settings.Verbose { + log.Debugf(log.ExchangeSys, + "GetAllEnabledExchangeAccountInfo: Skippping %s due to disabled authenticated API support.\n", + exchanges[x].GetName()) + } + continue } - continue + accountInfo, err := exchanges[x].FetchAccountInfo() + if err != nil { + log.Errorf(log.ExchangeSys, + "Error encountered retrieving exchange account info for %s. Error %s\n", + exchanges[x].GetName(), + err) + continue + } + response.Data = append(response.Data, accountInfo) } - accountInfo, err := exchanges[x].FetchAccountInfo() - if err != nil { - log.Errorf(log.ExchangeSys, "Error encountered retrieving exchange account info for %s. Error %s\n", - exchanges[x].GetName(), err) - continue - } - response.Data = append(response.Data, accountInfo) } return response } diff --git a/engine/helpers_test.go b/engine/helpers_test.go index b4a83283..30e404d1 100644 --- a/engine/helpers_test.go +++ b/engine/helpers_test.go @@ -171,136 +171,225 @@ func TestGetSpecificAvailablePairs(t *testing.T) { assetType := asset.Spot result := GetSpecificAvailablePairs(true, true, true, false, assetType) - if !result.Contains(currency.NewPairFromStrings("BTC", "USD"), true) { + btsusd, err := currency.NewPairFromStrings("BTC", "USD") + if err != nil { + t.Fatal(err) + } + + if !result.Contains(btsusd, true) { t.Fatal("Unexpected result") } - if !result.Contains(currency.NewPairFromStrings("BTC", "USDT"), false) { + btcusdt, err := currency.NewPairFromStrings("BTC", "USDT") + if err != nil { + t.Fatal(err) + } + + if !result.Contains(btcusdt, false) { t.Fatal("Unexpected result") } result = GetSpecificAvailablePairs(true, true, false, false, assetType) - if result.Contains(currency.NewPairFromStrings("BTC", "USDT"), false) { + if result.Contains(btcusdt, false) { t.Fatal("Unexpected result") } + ltcbtc, err := currency.NewPairFromStrings("LTC", "BTC") + if err != nil { + t.Fatal(err) + } + result = GetSpecificAvailablePairs(true, false, false, true, assetType) - if !result.Contains(currency.NewPairFromStrings("LTC", "BTC"), false) { + if !result.Contains(ltcbtc, false) { t.Fatal("Unexpected result") } } func TestIsRelatablePairs(t *testing.T) { SetupTestHelpers(t) + xbtusd, err := currency.NewPairFromStrings("XBT", "USD") + if err != nil { + t.Fatal(err) + } + + btcusd, err := currency.NewPairFromStrings("BTC", "USD") + if err != nil { + t.Fatal(err) + } // Test relational pairs with similar names - result := IsRelatablePairs(currency.NewPairFromStrings("XBT", "USD"), - currency.NewPairFromStrings("BTC", "USD"), false) + result := IsRelatablePairs(xbtusd, btcusd, false) if !result { t.Fatal("Unexpected result") } // Test relational pairs with similar names reversed - result = IsRelatablePairs(currency.NewPairFromStrings("BTC", "USD"), - currency.NewPairFromStrings("XBT", "USD"), false) + result = IsRelatablePairs(btcusd, xbtusd, false) if !result { t.Fatal("Unexpected result") } + btcusdt, err := currency.NewPairFromStrings("BTC", "USDT") + if err != nil { + t.Fatal(err) + } + // Test relational pairs with similar names but with Tether support disabled - result = IsRelatablePairs(currency.NewPairFromStrings("XBT", "USD"), - currency.NewPairFromStrings("BTC", "USDT"), false) + result = IsRelatablePairs(xbtusd, btcusdt, false) if result { t.Fatal("Unexpected result") } + xbtusdt, err := currency.NewPairFromStrings("XBT", "USDT") + if err != nil { + t.Fatal(err) + } + // Test relational pairs with similar names but with Tether support enabled - result = IsRelatablePairs(currency.NewPairFromStrings("XBT", "USDT"), - currency.NewPairFromStrings("BTC", "USD"), true) + result = IsRelatablePairs(xbtusdt, btcusd, true) if !result { t.Fatal("Unexpected result") } + aeusdt, err := currency.NewPairFromStrings("AE", "USDT") + if err != nil { + t.Fatal(err) + } + + usdtae, err := currency.NewPairDelimiter("USDT-AE", "-") + if err != nil { + t.Fatal(err) + } + // Test relational pairs with different ordering, a delimiter and with // Tether support enabled - result = IsRelatablePairs(currency.NewPairFromStrings("AE", "USDT"), - currency.NewPairDelimiter("USDT-AE", "-"), true) + result = IsRelatablePairs(aeusdt, usdtae, true) if !result { t.Fatal("Unexpected result") } // Test relational pairs with different ordering, a delimiter and with // Tether support disabled - result = IsRelatablePairs(currency.NewPairFromStrings("AE", "USDT"), - currency.NewPairDelimiter("USDT-AE", "-"), false) + result = IsRelatablePairs(aeusdt, usdtae, false) if !result { t.Fatal("Unexpected result") } + xbteur, err := currency.NewPairFromStrings("XBT", "EUR") + if err != nil { + t.Fatal(err) + } + + btcaud, err := currency.NewPairFromStrings("BTC", "AUD") + if err != nil { + t.Fatal(err) + } + // Test relationl pairs with similar names and different fiat currencies - result = IsRelatablePairs(currency.NewPairFromStrings("XBT", "EUR"), - currency.NewPairFromStrings("BTC", "AUD"), false) + result = IsRelatablePairs(xbteur, btcaud, false) if !result { t.Fatal("Unexpected result") } + usdbtc, err := currency.NewPairFromStrings("USD", "BTC") + if err != nil { + t.Fatal(err) + } + + btceur, err := currency.NewPairFromStrings("BTC", "EUR") + if err != nil { + t.Fatal(err) + } + // Test relationl pairs with similar names, different fiat currencies and // with different ordering - result = IsRelatablePairs(currency.NewPairFromStrings("USD", "BTC"), - currency.NewPairFromStrings("BTC", "EUR"), false) + result = IsRelatablePairs(usdbtc, btceur, false) if !result { // Is this really expected result??? t.Fatal("Unexpected result") } // Test relationl pairs with similar names, different fiat currencies and // with Tether enabled - result = IsRelatablePairs(currency.NewPairFromStrings("USD", "BTC"), - currency.NewPairFromStrings("BTC", "USDT"), true) + result = IsRelatablePairs(usdbtc, btcusdt, true) if !result { t.Fatal("Unexpected result") } + ltcbtc, err := currency.NewPairFromStrings("LTC", "BTC") + if err != nil { + t.Fatal(err) + } + + btcltc, err := currency.NewPairFromStrings("BTC", "LTC") + if err != nil { + t.Fatal(err) + } + // Test relationl crypto pairs with similar names - result = IsRelatablePairs(currency.NewPairFromStrings("LTC", "BTC"), - currency.NewPairFromStrings("BTC", "LTC"), false) + result = IsRelatablePairs(ltcbtc, btcltc, false) if !result { t.Fatal("Unexpected result") } + ltceth, err := currency.NewPairFromStrings("LTC", "ETH") + if err != nil { + t.Fatal(err) + } + + btceth, err := currency.NewPairFromStrings("BTC", "ETH") + if err != nil { + t.Fatal(err) + } + // Test relationl crypto pairs with similar different pairs - result = IsRelatablePairs(currency.NewPairFromStrings("LTC", "ETH"), - currency.NewPairFromStrings("BTC", "ETH"), false) + result = IsRelatablePairs(ltceth, btceth, false) if result { t.Fatal("Unexpected result") } // Test relationl crypto pairs with similar different pairs and with USDT // enabled - result = IsRelatablePairs(currency.NewPairFromStrings("USDT", "USD"), - currency.NewPairFromStrings("BTC", "USD"), true) + usdtusd, err := currency.NewPairFromStrings("USDT", "USD") + if err != nil { + t.Fatal(err) + } + + result = IsRelatablePairs(usdtusd, btcusd, true) if result { t.Fatal("Unexpected result") } + xbtltc, err := currency.NewPairFromStrings("XBT", "LTC") + if err != nil { + t.Fatal(err) + } + // Test relationl crypto pairs with with similar names - result = IsRelatablePairs(currency.NewPairFromStrings("XBT", "LTC"), - currency.NewPairFromStrings("BTC", "LTC"), false) + result = IsRelatablePairs(xbtltc, btcltc, false) if !result { t.Fatal("Unexpected result") } + ltcxbt, err := currency.NewPairFromStrings("LTC", "XBT") + if err != nil { + t.Fatal(err) + } + // Test relationl crypto pairs with different ordering and similar names - result = IsRelatablePairs(currency.NewPairFromStrings("LTC", "XBT"), - currency.NewPairFromStrings("BTC", "LTC"), false) + result = IsRelatablePairs(ltcxbt, btcltc, false) if !result { t.Fatal("Unexpected result") } // Test edge case between two pairs when currency translations were causing // non-relational pairs to be relatable - result = IsRelatablePairs(currency.NewPairFromStrings("EUR", "USD"), - currency.NewPairFromStrings("BTC", "USD"), false) + eurusd, err := currency.NewPairFromStrings("EUR", "USD") + if err != nil { + t.Fatal(err) + } + + result = IsRelatablePairs(eurusd, btcusd, false) if result { t.Fatal("Unexpected result") } @@ -308,44 +397,80 @@ func TestIsRelatablePairs(t *testing.T) { func TestGetRelatableCryptocurrencies(t *testing.T) { SetupTestHelpers(t) - p := GetRelatableCryptocurrencies(currency.NewPairFromStrings("BTC", "LTC")) - if p.Contains(currency.NewPairFromStrings("BTC", "LTC"), true) { + btcltc, err := currency.NewPairFromStrings("BTC", "LTC") + if err != nil { + t.Fatal(err) + } + + btcbtc, err := currency.NewPairFromStrings("BTC", "BTC") + if err != nil { + t.Fatal(err) + } + + ltcltc, err := currency.NewPairFromStrings("LTC", "LTC") + if err != nil { + t.Fatal(err) + } + + btceth, err := currency.NewPairFromStrings("BTC", "ETH") + if err != nil { + t.Fatal(err) + } + + p := GetRelatableCryptocurrencies(btcltc) + if p.Contains(btcltc, true) { t.Fatal("Unexpected result") } - if p.Contains(currency.NewPairFromStrings("BTC", "BTC"), true) { + if p.Contains(btcbtc, true) { t.Fatal("Unexpected result") } - if p.Contains(currency.NewPairFromStrings("LTC", "LTC"), true) { + if p.Contains(ltcltc, true) { t.Fatal("Unexpected result") } - if !p.Contains(currency.NewPairFromStrings("BTC", "ETH"), true) { + if !p.Contains(btceth, true) { t.Fatal("Unexpected result") } - p = GetRelatableCryptocurrencies(currency.NewPairFromStrings("BTC", "LTC")) - if p.Contains(currency.NewPairFromStrings("BTC", "LTC"), true) { + p = GetRelatableCryptocurrencies(btcltc) + if p.Contains(btcltc, true) { t.Fatal("Unexpected result") } - if p.Contains(currency.NewPairFromStrings("BTC", "BTC"), true) { + if p.Contains(btcbtc, true) { t.Fatal("Unexpected result") } - if p.Contains(currency.NewPairFromStrings("LTC", "LTC"), true) { + if p.Contains(ltcltc, true) { t.Fatal("Unexpected result") } - if !p.Contains(currency.NewPairFromStrings("BTC", "ETH"), true) { + if !p.Contains(btceth, true) { t.Fatal("Unexpected result") } } func TestGetRelatableFiatCurrencies(t *testing.T) { SetupTestHelpers(t) - p := GetRelatableFiatCurrencies(currency.NewPairFromStrings("BTC", "USD")) - if !p.Contains(currency.NewPairFromStrings("BTC", "EUR"), true) { + + btsusd, err := currency.NewPairFromStrings("BTC", "USD") + if err != nil { + t.Fatal(err) + } + + btceur, err := currency.NewPairFromStrings("BTC", "EUR") + if err != nil { + t.Fatal(err) + } + + p := GetRelatableFiatCurrencies(btsusd) + if !p.Contains(btceur, true) { t.Fatal("Unexpected result") } - p = GetRelatableFiatCurrencies(currency.NewPairFromStrings("BTC", "USD")) - if !p.Contains(currency.NewPairFromStrings("BTC", "ZAR"), true) { + btczar, err := currency.NewPairFromStrings("BTC", "ZAR") + if err != nil { + t.Fatal(err) + } + + p = GetRelatableFiatCurrencies(btsusd) + if !p.Contains(btczar, true) { t.Fatal("Unexpected result") } } @@ -373,21 +498,36 @@ func TestGetExchangeNamesByCurrency(t *testing.T) { SetupTestHelpers(t) assetType := asset.Spot - result := GetExchangeNamesByCurrency(currency.NewPairFromStrings("BTC", "USD"), + btsusd, err := currency.NewPairFromStrings("BTC", "USD") + if err != nil { + t.Fatal(err) + } + + btcjpy, err := currency.NewPairFromStrings("BTC", "JPY") + if err != nil { + t.Fatal(err) + } + + blahjpy, err := currency.NewPairFromStrings("blah", "JPY") + if err != nil { + t.Fatal(err) + } + + result := GetExchangeNamesByCurrency(btsusd, true, assetType) if !common.StringDataCompare(result, "Bitstamp") { t.Fatal("Unexpected result") } - result = GetExchangeNamesByCurrency(currency.NewPairFromStrings("BTC", "JPY"), + result = GetExchangeNamesByCurrency(btcjpy, true, assetType) if !common.StringDataCompare(result, "Bitflyer") { t.Fatal("Unexpected result") } - result = GetExchangeNamesByCurrency(currency.NewPairFromStrings("blah", "JPY"), + result = GetExchangeNamesByCurrency(blahjpy, true, assetType) if len(result) > 0 { @@ -415,9 +555,12 @@ func TestGetSpecificOrderbook(t *testing.T) { t.Fatal("Unexpected result", err) } - ob, err := GetSpecificOrderbook(currency.NewPairFromString("BTCUSD"), - "Bitstamp", - asset.Spot) + btsusd, err := currency.NewPairFromStrings("BTC", "USD") + if err != nil { + t.Fatal(err) + } + + ob, err := GetSpecificOrderbook(btsusd, "Bitstamp", asset.Spot) if err != nil { t.Fatal(err) } @@ -426,9 +569,12 @@ func TestGetSpecificOrderbook(t *testing.T) { t.Fatal("Unexpected result") } - _, err = GetSpecificOrderbook(currency.NewPairFromStrings("ETH", "LTC"), - "Bitstamp", - asset.Spot) + ethltc, err := currency.NewPairFromStrings("ETH", "LTC") + if err != nil { + t.Fatal(err) + } + + _, err = GetSpecificOrderbook(ethltc, "Bitstamp", asset.Spot) if err == nil { t.Fatal("Unexpected result") } @@ -440,16 +586,21 @@ func TestGetSpecificTicker(t *testing.T) { SetupTestHelpers(t) LoadExchange("Bitstamp", false, nil) - p := currency.NewPairFromStrings("BTC", "USD") - err := ticker.ProcessTicker("Bitstamp", - &ticker.Price{Pair: p, Last: 1000}, - asset.Spot) + p, err := currency.NewPairFromStrings("BTC", "USD") + if err != nil { + t.Fatal(err) + } + + err = ticker.ProcessTicker(&ticker.Price{ + Pair: p, + Last: 1000, + AssetType: asset.Spot, + ExchangeName: "Bitstamp"}) if err != nil { t.Fatal("ProcessTicker error", err) } - tick, err := GetSpecificTicker(currency.NewPairFromStrings("BTC", "USD"), "Bitstamp", - asset.Spot) + tick, err := GetSpecificTicker(p, "Bitstamp", asset.Spot) if err != nil { t.Fatal(err) } @@ -458,8 +609,12 @@ func TestGetSpecificTicker(t *testing.T) { t.Fatal("Unexpected result") } - _, err = GetSpecificTicker(currency.NewPairFromStrings("ETH", "LTC"), "Bitstamp", - asset.Spot) + ethltc, err := currency.NewPairFromStrings("ETH", "LTC") + if err != nil { + t.Fatal(err) + } + + _, err = GetSpecificTicker(ethltc, "Bitstamp", asset.Spot) if err == nil { t.Fatal("Unexpected result") } @@ -530,7 +685,11 @@ func TestGetCollatedExchangeAccountInfoByCoin(t *testing.T) { func TestGetExchangeHighestPriceByCurrencyPair(t *testing.T) { SetupTestHelpers(t) - p := currency.NewPairFromStrings("BTC", "USD") + p, err := currency.NewPairFromStrings("BTC", "USD") + if err != nil { + t.Fatal(err) + } + stats.Add("Bitfinex", p, asset.Spot, 1000, 10000) stats.Add("Bitstamp", p, asset.Spot, 1337, 10000) exchangeName, err := GetExchangeHighestPriceByCurrencyPair(p, asset.Spot) @@ -542,8 +701,12 @@ func TestGetExchangeHighestPriceByCurrencyPair(t *testing.T) { t.Error("Unexpected result") } - _, err = GetExchangeHighestPriceByCurrencyPair(currency.NewPairFromStrings("BTC", "AUD"), - asset.Spot) + btcaud, err := currency.NewPairFromStrings("BTC", "AUD") + if err != nil { + t.Fatal(err) + } + + _, err = GetExchangeHighestPriceByCurrencyPair(btcaud, asset.Spot) if err == nil { t.Error("Unexpected result") } @@ -552,7 +715,11 @@ func TestGetExchangeHighestPriceByCurrencyPair(t *testing.T) { func TestGetExchangeLowestPriceByCurrencyPair(t *testing.T) { SetupTestHelpers(t) - p := currency.NewPairFromStrings("BTC", "USD") + p, err := currency.NewPairFromStrings("BTC", "USD") + if err != nil { + t.Fatal(err) + } + stats.Add("Bitfinex", p, asset.Spot, 1000, 10000) stats.Add("Bitstamp", p, asset.Spot, 1337, 10000) exchangeName, err := GetExchangeLowestPriceByCurrencyPair(p, asset.Spot) @@ -564,8 +731,12 @@ func TestGetExchangeLowestPriceByCurrencyPair(t *testing.T) { t.Error("Unexpected result") } - _, err = GetExchangeLowestPriceByCurrencyPair(currency.NewPairFromStrings("BTC", "AUD"), - asset.Spot) + btcaud, err := currency.NewPairFromStrings("BTC", "AUD") + if err != nil { + t.Fatal(err) + } + + _, err = GetExchangeLowestPriceByCurrencyPair(btcaud, asset.Spot) if err == nil { t.Error("Unexpected reuslt") } diff --git a/engine/orders.go b/engine/orders.go index ca91e898..15578ca5 100644 --- a/engine/orders.go +++ b/engine/orders.go @@ -389,18 +389,45 @@ func (o *orderManager) Submit(newOrder *order.Submit) (*orderSubmitResponse, err func (o *orderManager) processOrders() { authExchanges := GetAuthAPISupportedExchanges() for x := range authExchanges { - log.Debugf(log.OrderMgr, "Order manager: Processing orders for exchange %v.", authExchanges[x]) + log.Debugf(log.OrderMgr, + "Order manager: Processing orders for exchange %v.", + authExchanges[x]) + exch := GetExchangeByName(authExchanges[x]) supportedAssets := exch.GetAssetTypes() for y := range supportedAssets { + pairs, err := exch.GetEnabledPairs(supportedAssets[y]) + if err != nil { + log.Errorf(log.OrderMgr, + "Order manager: Unable to get enabled pairs for %s and asset type %s: %s", + authExchanges[x], + supportedAssets[y], + err) + continue + } + + if len(pairs) == 0 { + if Bot.Settings.Verbose { + log.Debugf(log.OrderMgr, + "Order manager: No pairs enabled for %s and asset type %s, skipping...", + authExchanges[x], + supportedAssets[y]) + } + continue + } + req := order.GetOrdersRequest{ Side: order.AnySide, Type: order.AnyType, - Pairs: exch.GetEnabledPairs(supportedAssets[y]), + Pairs: pairs, } result, err := exch.GetActiveOrders(&req) if err != nil { - log.Warnf(log.OrderMgr, "Order manager: Unable to get active orders: %s", err) + log.Warnf(log.OrderMgr, + "Order manager: Unable to get active orders for %s and asset type %s: %s", + authExchanges[x], + supportedAssets[y], + err) continue } diff --git a/engine/orders_test.go b/engine/orders_test.go index 1e8a0838..f799ace1 100644 --- a/engine/orders_test.go +++ b/engine/orders_test.go @@ -254,6 +254,11 @@ func TestCancelOrder(t *testing.T) { t.Error("Expected error due to no order found") } + pair, err := currency.NewPairFromString("BTCUSD") + if err != nil { + t.Fatal(err) + } + cancel := &order.Cancel{ Exchange: fakePassExchange, ID: "TestCancelOrder", @@ -261,7 +266,7 @@ func TestCancelOrder(t *testing.T) { Status: order.New, AssetType: asset.Spot, Date: time.Now(), - Pair: currency.NewPairFromString("BTCUSD"), + Pair: pair, } err = Bot.OrderManager.Cancel(cancel) if err != nil { @@ -326,9 +331,14 @@ func TestSubmit(t *testing.T) { t.Error("Expected error from validation") } + pair, err := currency.NewPairFromString("BTCUSD") + if err != nil { + t.Fatal(err) + } + Bot.OrderManager.cfg.EnforceLimitConfig = true Bot.OrderManager.cfg.AllowMarketOrders = false - o.Pair = currency.NewPairFromString("BTCUSD") + o.Pair = pair o.AssetType = asset.Spot o.Side = order.Buy o.Amount = 1 @@ -351,8 +361,13 @@ func TestSubmit(t *testing.T) { t.Error("Expected fail due to order exchange not found in allowed list") } + failPair, err := currency.NewPairFromString("BTCAUD") + if err != nil { + t.Fatal(err) + } + Bot.OrderManager.cfg.AllowedExchanges = nil - Bot.OrderManager.cfg.AllowedPairs = currency.Pairs{currency.NewPairFromString("BTCAUD")} + Bot.OrderManager.cfg.AllowedPairs = currency.Pairs{failPair} _, err = Bot.OrderManager.Submit(o) if err == nil { t.Error("Expected fail due to order pair not found in allowed list") diff --git a/engine/restful_server.go b/engine/restful_server.go index f2f3fc02..cd6cb7e1 100644 --- a/engine/restful_server.go +++ b/engine/restful_server.go @@ -66,7 +66,14 @@ func GetAllActiveOrderbooks() []EnabledExchangeOrderbooks { exchangeOB.ExchangeName = exchName for y := range assets { - currencies := exchanges[x].GetEnabledPairs(assets[y]) + currencies, err := exchanges[x].GetEnabledPairs(assets[y]) + if err != nil { + log.Errorf(log.RESTSys, + "Exchange %s could not retrieve enabled currencies. Err: %s\n", + exchName, + err) + continue + } for z := range currencies { ob, err := exchanges[x].FetchOrderbook(currencies[z], assets[y]) if err != nil { diff --git a/engine/routines.go b/engine/routines.go index bc9e60a4..e2d13354 100644 --- a/engine/routines.go +++ b/engine/routines.go @@ -8,12 +8,11 @@ import ( "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/currency" - "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/stats" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" "github.com/thrasher-corp/gocryptotrader/log" ) @@ -57,26 +56,29 @@ func printConvertCurrencyFormat(origCurrency currency.Code, origPrice float64) s ) } -func printTickerSummary(result *ticker.Price, p currency.Pair, assetType asset.Item, exchangeName, protocol string, err error) { +func printTickerSummary(result *ticker.Price, protocol string, err error) { if err != nil { - log.Errorf(log.Ticker, "Failed to get %s %s %s %s ticker. Error: %s\n", - exchangeName, + if err == common.ErrNotYetImplemented { + log.Warnf(log.Ticker, "Failed to get %s ticker. Error: %s\n", + protocol, + err) + return + } + log.Errorf(log.Ticker, "Failed to get %s ticker. Error: %s\n", protocol, - p, - assetType, err) return } - stats.Add(exchangeName, p, assetType, result.Last, result.Volume) - if p.Quote.IsFiatCurrency() && - p.Quote != Bot.Config.Currency.FiatDisplayCurrency { - origCurrency := p.Quote.Upper() + stats.Add(result.ExchangeName, result.Pair, result.AssetType, result.Last, result.Volume) + if result.Pair.Quote.IsFiatCurrency() && + result.Pair.Quote != Bot.Config.Currency.FiatDisplayCurrency { + origCurrency := result.Pair.Quote.Upper() log.Infof(log.Ticker, "%s %s %s %s: TICKER: Last %s Ask %s Bid %s High %s Low %s Volume %.8f\n", - exchangeName, + result.ExchangeName, protocol, - FormatCurrency(p), - strings.ToUpper(assetType.String()), + FormatCurrency(result.Pair), + strings.ToUpper(result.AssetType.String()), printConvertCurrencyFormat(origCurrency, result.Last), printConvertCurrencyFormat(origCurrency, result.Ask), printConvertCurrencyFormat(origCurrency, result.Bid), @@ -84,13 +86,13 @@ func printTickerSummary(result *ticker.Price, p currency.Pair, assetType asset.I printConvertCurrencyFormat(origCurrency, result.Low), result.Volume) } else { - if p.Quote.IsFiatCurrency() && - p.Quote == Bot.Config.Currency.FiatDisplayCurrency { + if result.Pair.Quote.IsFiatCurrency() && + result.Pair.Quote == Bot.Config.Currency.FiatDisplayCurrency { log.Infof(log.Ticker, "%s %s %s %s: TICKER: Last %s Ask %s Bid %s High %s Low %s Volume %.8f\n", - exchangeName, + result.ExchangeName, protocol, - FormatCurrency(p), - strings.ToUpper(assetType.String()), + FormatCurrency(result.Pair), + strings.ToUpper(result.AssetType.String()), printCurrencyFormat(result.Last), printCurrencyFormat(result.Ask), printCurrencyFormat(result.Bid), @@ -99,10 +101,10 @@ func printTickerSummary(result *ticker.Price, p currency.Pair, assetType asset.I result.Volume) } else { log.Infof(log.Ticker, "%s %s %s %s: TICKER: Last %.8f Ask %.8f Bid %.8f High %.8f Low %.8f Volume %.8f\n", - exchangeName, + result.ExchangeName, protocol, - FormatCurrency(p), - strings.ToUpper(assetType.String()), + FormatCurrency(result.Pair), + strings.ToUpper(result.AssetType.String()), result.Last, result.Ask, result.Bid, @@ -113,13 +115,16 @@ func printTickerSummary(result *ticker.Price, p currency.Pair, assetType asset.I } } -func printOrderbookSummary(result *orderbook.Base, p currency.Pair, assetType asset.Item, exchangeName, protocol string, err error) { +func printOrderbookSummary(result *orderbook.Base, protocol string, err error) { if err != nil { - log.Errorf(log.OrderBook, "Failed to get %s %s %s orderbook of type %s. Error: %s\n", - exchangeName, + if err == common.ErrNotYetImplemented { + log.Warnf(log.Ticker, "Failed to get %s ticker. Error: %s\n", + protocol, + err) + return + } + log.Errorf(log.OrderBook, "Failed to get %s orderbook. Error: %s\n", protocol, - p, - assetType, err) return } @@ -127,53 +132,53 @@ func printOrderbookSummary(result *orderbook.Base, p currency.Pair, assetType as bidsAmount, bidsValue := result.TotalBidsAmount() asksAmount, asksValue := result.TotalAsksAmount() - if p.Quote.IsFiatCurrency() && - p.Quote != Bot.Config.Currency.FiatDisplayCurrency { - origCurrency := p.Quote.Upper() + if result.Pair.Quote.IsFiatCurrency() && + result.Pair.Quote != Bot.Config.Currency.FiatDisplayCurrency { + origCurrency := result.Pair.Quote.Upper() log.Infof(log.OrderBook, "%s %s %s %s: ORDERBOOK: Bids len: %d Amount: %f %s. Total value: %s Asks len: %d Amount: %f %s. Total value: %s\n", - exchangeName, + result.ExchangeName, protocol, - FormatCurrency(p), - strings.ToUpper(assetType.String()), + FormatCurrency(result.Pair), + strings.ToUpper(result.AssetType.String()), len(result.Bids), bidsAmount, - p.Base, + result.Pair.Base, printConvertCurrencyFormat(origCurrency, bidsValue), len(result.Asks), asksAmount, - p.Base, + result.Pair.Base, printConvertCurrencyFormat(origCurrency, asksValue), ) } else { - if p.Quote.IsFiatCurrency() && - p.Quote == Bot.Config.Currency.FiatDisplayCurrency { + if result.Pair.Quote.IsFiatCurrency() && + result.Pair.Quote == Bot.Config.Currency.FiatDisplayCurrency { log.Infof(log.OrderBook, "%s %s %s %s: ORDERBOOK: Bids len: %d Amount: %f %s. Total value: %s Asks len: %d Amount: %f %s. Total value: %s\n", - exchangeName, + result.ExchangeName, protocol, - FormatCurrency(p), - strings.ToUpper(assetType.String()), + FormatCurrency(result.Pair), + strings.ToUpper(result.AssetType.String()), len(result.Bids), bidsAmount, - p.Base, + result.Pair.Base, printCurrencyFormat(bidsValue), len(result.Asks), asksAmount, - p.Base, + result.Pair.Base, printCurrencyFormat(asksValue), ) } else { log.Infof(log.OrderBook, "%s %s %s %s: ORDERBOOK: Bids len: %d Amount: %f %s. Total value: %f Asks len: %d Amount: %f %s. Total value: %f\n", - exchangeName, + result.ExchangeName, protocol, - FormatCurrency(p), - strings.ToUpper(assetType.String()), + FormatCurrency(result.Pair), + strings.ToUpper(result.AssetType.String()), len(result.Bids), bidsAmount, - p.Base, + result.Pair.Base, bidsValue, len(result.Asks), asksAmount, - p.Base, + result.Pair.Base, asksValue, ) } @@ -212,28 +217,27 @@ func WebsocketRoutine() { ) } - // TO-DO: expose IsConnected() and IsConnecting so this can be simplified - if exchanges[i].IsWebsocketEnabled() { - ws, err := exchanges[i].GetWebsocket() - if err != nil { - log.Errorf( - log.WebsocketMgr, - "Exchange %s GetWebsocket error: %s\n", - exchanges[i].GetName(), - err, - ) - return - } + ws, err := exchanges[i].GetWebsocket() + if err != nil { + log.Errorf( + log.WebsocketMgr, + "Exchange %s GetWebsocket error: %s\n", + exchanges[i].GetName(), + err, + ) + return + } - // Exchange sync manager might have already started ws - // service or is in the process of connecting, so check - if ws.IsConnected() || ws.IsConnecting() { - return - } + // Exchange sync manager might have already started ws + // service or is in the process of connecting, so check + if ws.IsConnected() || ws.IsConnecting() { + return + } - // Data handler routine - go WebsocketDataReceiver(ws) + // Data handler routine + go WebsocketDataReceiver(ws) + if ws.IsEnabled() { err = ws.Connect() if err != nil { log.Errorf(log.WebsocketMgr, "%v\n", err) @@ -254,7 +258,7 @@ var wg sync.WaitGroup // WebsocketDataReceiver handles websocket data coming from a websocket feed // associated with an exchange -func WebsocketDataReceiver(ws *wshandler.Websocket) { +func WebsocketDataReceiver(ws *stream.Websocket) { wg.Add(1) defer wg.Done() @@ -262,7 +266,7 @@ func WebsocketDataReceiver(ws *wshandler.Websocket) { select { case <-shutdowner: return - case data := <-ws.DataHandler: + case data := <-ws.ToRoutine: err := WebsocketDataHandler(ws.GetName(), data) if err != nil { log.Error(log.WebsocketMgr, err) @@ -278,12 +282,13 @@ func WebsocketDataHandler(exchName string, data interface{}) error { return fmt.Errorf("routines.go - exchange %s nil data sent to websocket", exchName) } + switch d := data.(type) { case string: log.Info(log.WebsocketMgr, d) case error: return fmt.Errorf("routines.go exchange %s websocket error - %s", exchName, data) - case wshandler.TradeData: + case stream.TradeData: if Bot.Settings.Verbose { log.Infof(log.WebsocketMgr, "%s websocket %s %s trade updated %+v", exchName, @@ -291,7 +296,7 @@ func WebsocketDataHandler(exchName string, data interface{}) error { d.AssetType, d) } - case wshandler.FundingData: + case stream.FundingData: if Bot.Settings.Verbose { log.Infof(log.WebsocketMgr, "%s websocket %s %s funding updated %+v", exchName, @@ -307,9 +312,9 @@ func WebsocketDataHandler(exchName string, data interface{}) error { SyncItemTicker, nil) } - err := ticker.ProcessTicker(exchName, d, d.AssetType) - printTickerSummary(d, d.Pair, d.AssetType, exchName, "websocket", err) - case wshandler.KlineData: + err := ticker.ProcessTicker(d) + printTickerSummary(d, "websocket", err) + case stream.KlineData: if Bot.Settings.Verbose { log.Infof(log.WebsocketMgr, "%s websocket %s %s kline updated %+v", exchName, @@ -317,22 +322,15 @@ func WebsocketDataHandler(exchName string, data interface{}) error { d.AssetType, d) } - case wshandler.WebsocketOrderbookUpdate: + case *orderbook.Base: if Bot.Settings.EnableExchangeSyncManager && Bot.ExchangeCurrencyPairManager != nil { Bot.ExchangeCurrencyPairManager.update(exchName, d.Pair, - d.Asset, + d.AssetType, SyncItemOrderbook, nil) } - - if Bot.Settings.Verbose { - log.Infof(log.WebsocketMgr, - "%s websocket %s %s orderbook updated", - exchName, - FormatCurrency(d.Pair), - d.Asset) - } + printOrderbookSummary(d, "websocket", nil) case *order.Detail: if !Bot.OrderManager.orderStore.exists(d) { err := Bot.OrderManager.orderStore.Add(d) @@ -356,7 +354,7 @@ func WebsocketDataHandler(exchName string, data interface{}) error { od.UpdateOrderFromModify(d) case order.ClassificationError: return errors.New(d.Error()) - case wshandler.UnhandledMessageWarning: + case stream.UnhandledMessageWarning: log.Warn(log.WebsocketMgr, d.Message) default: if Bot.Settings.Verbose { diff --git a/engine/routines_test.go b/engine/routines_test.go index ac200633..b54d1645 100644 --- a/engine/routines_test.go +++ b/engine/routines_test.go @@ -5,19 +5,16 @@ import ( "testing" "time" + "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/exchanges/order" + "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" ) func TestWebsocketDataHandlerProcess(t *testing.T) { - ws := wshandler.New() - err := ws.Setup(&wshandler.WebsocketSetup{Enabled: true}) - if err != nil { - t.Error(err) - } - ws.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride() + ws := sharedtestvalues.NewTestWebsocket() go WebsocketDataReceiver(ws) ws.DataHandler <- "string" time.Sleep(time.Second) @@ -36,11 +33,11 @@ func TestHandleData(t *testing.T) { if err == nil { t.Error("Expected nil data error") } - err = WebsocketDataHandler(exchName, wshandler.TradeData{}) + err = WebsocketDataHandler(exchName, stream.TradeData{}) if err != nil { t.Error(err) } - err = WebsocketDataHandler(exchName, wshandler.FundingData{}) + err = WebsocketDataHandler(exchName, stream.FundingData{}) if err != nil { t.Error(err) } @@ -48,11 +45,7 @@ func TestHandleData(t *testing.T) { if err != nil { t.Error(err) } - err = WebsocketDataHandler(exchName, wshandler.KlineData{}) - if err != nil { - t.Error(err) - } - err = WebsocketDataHandler(exchName, wshandler.WebsocketOrderbookUpdate{}) + err = WebsocketDataHandler(exchName, stream.KlineData{}) if err != nil { t.Error(err) } @@ -107,7 +100,9 @@ func TestHandleData(t *testing.T) { t.Error(err) } - err = WebsocketDataHandler(exchName, wshandler.UnhandledMessageWarning{Message: "there's an issue here's a tissue"}) + err = WebsocketDataHandler(exchName, stream.UnhandledMessageWarning{ + Message: "there's an issue here's a tissue"}, + ) if err != nil { t.Error(err) } @@ -124,4 +119,16 @@ func TestHandleData(t *testing.T) { if err.Error() != classificationError.Error() { t.Errorf("Problem formatting error. Expected %v Received %v", classificationError.Error(), err.Error()) } + + err = WebsocketDataHandler(exchName, &orderbook.Base{ + ExchangeName: fakePassExchange, + Pair: currency.NewPair(currency.BTC, currency.USD), + }) + if err != nil { + t.Error(err) + } + err = WebsocketDataHandler(exchName, "this is a test string") + if err != nil { + t.Error(err) + } } diff --git a/engine/rpcserver.go b/engine/rpcserver.go index 75d4787d..c66b8b2f 100644 --- a/engine/rpcserver.go +++ b/engine/rpcserver.go @@ -2,6 +2,7 @@ package engine import ( "context" + "encoding/json" "errors" "fmt" "io/ioutil" @@ -52,6 +53,11 @@ const ( errDispatchSystem = "dispatch system offline" ) +var ( + errExchangeNotLoaded = errors.New("exchange is not loaded/doesn't exist") + errExchangeBaseNotFound = errors.New("cannot get exchange base") +) + // RPCServer struct type RPCServer struct{} @@ -165,7 +171,7 @@ func StartRPCRESTProxy() { } // GetInfo returns info about the current GoCryptoTrader session -func (s *RPCServer) GetInfo(ctx context.Context, r *gctrpc.GetInfoRequest) (*gctrpc.GetInfoResponse, error) { +func (s *RPCServer) GetInfo(_ context.Context, r *gctrpc.GetInfoRequest) (*gctrpc.GetInfoResponse, error) { d := time.Since(Bot.Uptime) resp := gctrpc.GetInfoResponse{ Uptime: d.String(), @@ -187,24 +193,32 @@ func (s *RPCServer) GetInfo(ctx context.Context, r *gctrpc.GetInfoRequest) (*gct } // GetSubsystems returns a list of subsystems and their status -func (s *RPCServer) GetSubsystems(ctx context.Context, r *gctrpc.GetSubsystemsRequest) (*gctrpc.GetSusbsytemsResponse, error) { +func (s *RPCServer) GetSubsystems(_ context.Context, r *gctrpc.GetSubsystemsRequest) (*gctrpc.GetSusbsytemsResponse, error) { return &gctrpc.GetSusbsytemsResponse{SubsystemsStatus: GetSubsystemsStatus()}, nil } // EnableSubsystem enables a engine subsytem -func (s *RPCServer) EnableSubsystem(ctx context.Context, r *gctrpc.GenericSubsystemRequest) (*gctrpc.GenericSubsystemResponse, error) { +func (s *RPCServer) EnableSubsystem(_ context.Context, r *gctrpc.GenericSubsystemRequest) (*gctrpc.GenericResponse, error) { err := SetSubsystem(r.Subsystem, true) - return &gctrpc.GenericSubsystemResponse{}, err + if err != nil { + return nil, err + } + return &gctrpc.GenericResponse{Status: MsgStatusSuccess, + Data: fmt.Sprintf("subsystem %s enabled", r.Subsystem)}, nil } // DisableSubsystem disables a engine subsytem -func (s *RPCServer) DisableSubsystem(ctx context.Context, r *gctrpc.GenericSubsystemRequest) (*gctrpc.GenericSubsystemResponse, error) { +func (s *RPCServer) DisableSubsystem(_ context.Context, r *gctrpc.GenericSubsystemRequest) (*gctrpc.GenericResponse, error) { err := SetSubsystem(r.Subsystem, false) - return &gctrpc.GenericSubsystemResponse{}, err + if err != nil { + return nil, err + } + return &gctrpc.GenericResponse{Status: MsgStatusSuccess, + Data: fmt.Sprintf("subsystem %s disabled", r.Subsystem)}, nil } // GetRPCEndpoints returns a list of API endpoints -func (s *RPCServer) GetRPCEndpoints(ctx context.Context, r *gctrpc.GetRPCEndpointsRequest) (*gctrpc.GetRPCEndpointsResponse, error) { +func (s *RPCServer) GetRPCEndpoints(_ context.Context, r *gctrpc.GetRPCEndpointsRequest) (*gctrpc.GetRPCEndpointsResponse, error) { endpoints := GetRPCEndpoints() var resp gctrpc.GetRPCEndpointsResponse resp.Endpoints = make(map[string]*gctrpc.RPCEndpoint) @@ -218,7 +232,7 @@ func (s *RPCServer) GetRPCEndpoints(ctx context.Context, r *gctrpc.GetRPCEndpoin } // GetCommunicationRelayers returns the status of the engines communication relayers -func (s *RPCServer) GetCommunicationRelayers(ctx context.Context, r *gctrpc.GetCommunicationRelayersRequest) (*gctrpc.GetCommunicationRelayersResponse, error) { +func (s *RPCServer) GetCommunicationRelayers(_ context.Context, r *gctrpc.GetCommunicationRelayersRequest) (*gctrpc.GetCommunicationRelayersResponse, error) { relayers, err := Bot.CommsManager.GetStatus() if err != nil { return nil, err @@ -237,38 +251,44 @@ func (s *RPCServer) GetCommunicationRelayers(ctx context.Context, r *gctrpc.GetC // GetExchanges returns a list of exchanges // Param is whether or not you wish to list enabled exchanges -func (s *RPCServer) GetExchanges(ctx context.Context, r *gctrpc.GetExchangesRequest) (*gctrpc.GetExchangesResponse, error) { +func (s *RPCServer) GetExchanges(_ context.Context, r *gctrpc.GetExchangesRequest) (*gctrpc.GetExchangesResponse, error) { exchanges := strings.Join(GetExchangeNames(r.Enabled), ",") return &gctrpc.GetExchangesResponse{Exchanges: exchanges}, nil } // DisableExchange disables an exchange -func (s *RPCServer) DisableExchange(ctx context.Context, r *gctrpc.GenericExchangeNameRequest) (*gctrpc.GenericExchangeNameResponse, error) { +func (s *RPCServer) DisableExchange(_ context.Context, r *gctrpc.GenericExchangeNameRequest) (*gctrpc.GenericResponse, error) { err := UnloadExchange(r.Exchange) - return &gctrpc.GenericExchangeNameResponse{}, err + if err != nil { + return nil, err + } + return &gctrpc.GenericResponse{Status: MsgStatusSuccess}, nil } // EnableExchange enables an exchange -func (s *RPCServer) EnableExchange(ctx context.Context, r *gctrpc.GenericExchangeNameRequest) (*gctrpc.GenericExchangeNameResponse, error) { +func (s *RPCServer) EnableExchange(_ context.Context, r *gctrpc.GenericExchangeNameRequest) (*gctrpc.GenericResponse, error) { err := LoadExchange(r.Exchange, false, nil) - return &gctrpc.GenericExchangeNameResponse{}, err + if err != nil { + return nil, err + } + return &gctrpc.GenericResponse{Status: MsgStatusSuccess}, nil } // GetExchangeOTPCode retrieves an exchanges OTP code -func (s *RPCServer) GetExchangeOTPCode(ctx context.Context, r *gctrpc.GenericExchangeNameRequest) (*gctrpc.GetExchangeOTPReponse, error) { +func (s *RPCServer) GetExchangeOTPCode(_ context.Context, r *gctrpc.GenericExchangeNameRequest) (*gctrpc.GetExchangeOTPReponse, error) { result, err := GetExchangeoOTPByName(r.Exchange) return &gctrpc.GetExchangeOTPReponse{OtpCode: result}, err } // GetExchangeOTPCodes retrieves OTP codes for all exchanges which have an // OTP secret installed -func (s *RPCServer) GetExchangeOTPCodes(ctx context.Context, r *gctrpc.GetExchangeOTPsRequest) (*gctrpc.GetExchangeOTPsResponse, error) { +func (s *RPCServer) GetExchangeOTPCodes(_ context.Context, r *gctrpc.GetExchangeOTPsRequest) (*gctrpc.GetExchangeOTPsResponse, error) { result, err := GetExchangeOTPs() return &gctrpc.GetExchangeOTPsResponse{OtpCodes: result}, err } // GetExchangeInfo gets info for a specific exchange -func (s *RPCServer) GetExchangeInfo(ctx context.Context, r *gctrpc.GenericExchangeNameRequest) (*gctrpc.GetExchangeInfoResponse, error) { +func (s *RPCServer) GetExchangeInfo(_ context.Context, r *gctrpc.GenericExchangeNameRequest) (*gctrpc.GetExchangeInfoResponse, error) { exchCfg, err := Bot.Config.GetExchangeConfig(r.Exchange) if err != nil { return nil, err @@ -286,11 +306,16 @@ func (s *RPCServer) GetExchangeInfo(ctx context.Context, r *gctrpc.GenericExchan } resp.SupportedAssets = make(map[string]*gctrpc.PairsSupported) - for x := range exchCfg.CurrencyPairs.AssetTypes { - a := exchCfg.CurrencyPairs.AssetTypes[x] - resp.SupportedAssets[a.String()] = &gctrpc.PairsSupported{ - EnabledPairs: exchCfg.CurrencyPairs.Get(a).Enabled.Join(), - AvailablePairs: exchCfg.CurrencyPairs.Get(a).Available.Join(), + assets := exchCfg.CurrencyPairs.GetAssetTypes() + for i := range assets { + ps, err := exchCfg.CurrencyPairs.Get(assets[i]) + if err != nil { + return nil, err + } + + resp.SupportedAssets[assets[i].String()] = &gctrpc.PairsSupported{ + EnabledPairs: ps.Enabled.Join(), + AvailablePairs: ps.Available.Join(), } } return resp, nil @@ -298,13 +323,12 @@ func (s *RPCServer) GetExchangeInfo(ctx context.Context, r *gctrpc.GenericExchan // GetTicker returns the ticker for a specified exchange, currency pair and // asset type -func (s *RPCServer) GetTicker(ctx context.Context, r *gctrpc.GetTickerRequest) (*gctrpc.TickerResponse, error) { - t, err := GetSpecificTicker( - currency.Pair{ - Delimiter: r.Pair.Delimiter, - Base: currency.NewCode(r.Pair.Base), - Quote: currency.NewCode(r.Pair.Quote), - }, +func (s *RPCServer) GetTicker(_ context.Context, r *gctrpc.GetTickerRequest) (*gctrpc.TickerResponse, error) { + t, err := GetSpecificTicker(currency.Pair{ + Delimiter: r.Pair.Delimiter, + Base: currency.NewCode(r.Pair.Base), + Quote: currency.NewCode(r.Pair.Quote), + }, r.Exchange, asset.Item(r.AssetType), ) @@ -329,7 +353,7 @@ func (s *RPCServer) GetTicker(ctx context.Context, r *gctrpc.GetTickerRequest) ( // GetTickers returns a list of tickers for all enabled exchanges and all // enabled currency pairs -func (s *RPCServer) GetTickers(ctx context.Context, r *gctrpc.GetTickersRequest) (*gctrpc.GetTickersResponse, error) { +func (s *RPCServer) GetTickers(_ context.Context, r *gctrpc.GetTickersRequest) (*gctrpc.GetTickersResponse, error) { activeTickers := GetAllActiveTickers() var tickers []*gctrpc.Tickers @@ -362,13 +386,12 @@ func (s *RPCServer) GetTickers(ctx context.Context, r *gctrpc.GetTickersRequest) // GetOrderbook returns an orderbook for a specific exchange, currency pair // and asset type -func (s *RPCServer) GetOrderbook(ctx context.Context, r *gctrpc.GetOrderbookRequest) (*gctrpc.OrderbookResponse, error) { - ob, err := GetSpecificOrderbook( - currency.Pair{ - Delimiter: r.Pair.Delimiter, - Base: currency.NewCode(r.Pair.Base), - Quote: currency.NewCode(r.Pair.Quote), - }, +func (s *RPCServer) GetOrderbook(_ context.Context, r *gctrpc.GetOrderbookRequest) (*gctrpc.OrderbookResponse, error) { + ob, err := GetSpecificOrderbook(currency.Pair{ + Delimiter: r.Pair.Delimiter, + Base: currency.NewCode(r.Pair.Base), + Quote: currency.NewCode(r.Pair.Quote), + }, r.Exchange, asset.Item(r.AssetType), ) @@ -405,7 +428,7 @@ func (s *RPCServer) GetOrderbook(ctx context.Context, r *gctrpc.GetOrderbookRequ // GetOrderbooks returns a list of orderbooks for all enabled exchanges and all // enabled currency pairs -func (s *RPCServer) GetOrderbooks(ctx context.Context, r *gctrpc.GetOrderbooksRequest) (*gctrpc.GetOrderbooksResponse, error) { +func (s *RPCServer) GetOrderbooks(_ context.Context, r *gctrpc.GetOrderbooksRequest) (*gctrpc.GetOrderbooksResponse, error) { activeOrderbooks := GetAllActiveOrderbooks() var orderbooks []*gctrpc.Orderbooks @@ -448,10 +471,10 @@ func (s *RPCServer) GetOrderbooks(ctx context.Context, r *gctrpc.GetOrderbooksRe } // GetAccountInfo returns an account balance for a specific exchange -func (s *RPCServer) GetAccountInfo(ctx context.Context, r *gctrpc.GetAccountInfoRequest) (*gctrpc.GetAccountInfoResponse, error) { +func (s *RPCServer) GetAccountInfo(_ context.Context, r *gctrpc.GetAccountInfoRequest) (*gctrpc.GetAccountInfoResponse, error) { exch := GetExchangeByName(r.Exchange) if exch == nil { - return nil, errors.New("exchange is not loaded/doesn't exist") + return nil, errExchangeNotLoaded } resp, err := exch.FetchAccountInfo() @@ -484,7 +507,7 @@ func (s *RPCServer) GetAccountInfoStream(r *gctrpc.GetAccountInfoRequest, stream exch := GetExchangeByName(r.Exchange) if exch == nil { - return errors.New("exchange is not loaded/doesn't exist") + return errExchangeNotLoaded } initAcc, err := exch.FetchAccountInfo() @@ -558,12 +581,12 @@ func (s *RPCServer) GetAccountInfoStream(r *gctrpc.GetAccountInfoRequest, stream } // GetConfig returns the bots config -func (s *RPCServer) GetConfig(ctx context.Context, r *gctrpc.GetConfigRequest) (*gctrpc.GetConfigResponse, error) { +func (s *RPCServer) GetConfig(_ context.Context, r *gctrpc.GetConfigRequest) (*gctrpc.GetConfigResponse, error) { return &gctrpc.GetConfigResponse{}, common.ErrNotYetImplemented } // GetPortfolio returns the portfolio details -func (s *RPCServer) GetPortfolio(ctx context.Context, r *gctrpc.GetPortfolioRequest) (*gctrpc.GetPortfolioResponse, error) { +func (s *RPCServer) GetPortfolio(_ context.Context, r *gctrpc.GetPortfolioRequest) (*gctrpc.GetPortfolioResponse, error) { var addrs []*gctrpc.PortfolioAddress botAddrs := Bot.Portfolio.Addresses @@ -584,7 +607,7 @@ func (s *RPCServer) GetPortfolio(ctx context.Context, r *gctrpc.GetPortfolioRequ } // GetPortfolioSummary returns the portfolio summary -func (s *RPCServer) GetPortfolioSummary(ctx context.Context, r *gctrpc.GetPortfolioSummaryRequest) (*gctrpc.GetPortfolioSummaryResponse, error) { +func (s *RPCServer) GetPortfolioSummary(_ context.Context, r *gctrpc.GetPortfolioSummaryRequest) (*gctrpc.GetPortfolioSummaryResponse, error) { result := Bot.Portfolio.GetPortfolioSummary() var resp gctrpc.GetPortfolioSummaryResponse @@ -640,22 +663,30 @@ func (s *RPCServer) GetPortfolioSummary(ctx context.Context, r *gctrpc.GetPortfo } // AddPortfolioAddress adds an address to the portfolio manager -func (s *RPCServer) AddPortfolioAddress(ctx context.Context, r *gctrpc.AddPortfolioAddressRequest) (*gctrpc.AddPortfolioAddressResponse, error) { - err := Bot.Portfolio.AddAddress(r.Address, r.Description, currency.NewCode(r.CoinType), r.Balance) +func (s *RPCServer) AddPortfolioAddress(_ context.Context, r *gctrpc.AddPortfolioAddressRequest) (*gctrpc.GenericResponse, error) { + err := Bot.Portfolio.AddAddress(r.Address, + r.Description, + currency.NewCode(r.CoinType), + r.Balance) if err != nil { return nil, err } - return &gctrpc.AddPortfolioAddressResponse{}, err + return &gctrpc.GenericResponse{Status: MsgStatusSuccess}, nil } // RemovePortfolioAddress removes an address from the portfolio manager -func (s *RPCServer) RemovePortfolioAddress(ctx context.Context, r *gctrpc.RemovePortfolioAddressRequest) (*gctrpc.RemovePortfolioAddressResponse, error) { - err := Bot.Portfolio.RemoveAddress(r.Address, r.Description, currency.NewCode(r.CoinType)) - return &gctrpc.RemovePortfolioAddressResponse{}, err +func (s *RPCServer) RemovePortfolioAddress(_ context.Context, r *gctrpc.RemovePortfolioAddressRequest) (*gctrpc.GenericResponse, error) { + err := Bot.Portfolio.RemoveAddress(r.Address, + r.Description, + currency.NewCode(r.CoinType)) + if err != nil { + return nil, err + } + return &gctrpc.GenericResponse{Status: MsgStatusSuccess}, nil } // GetForexProviders returns a list of available forex providers -func (s *RPCServer) GetForexProviders(ctx context.Context, r *gctrpc.GetForexProvidersRequest) (*gctrpc.GetForexProvidersResponse, error) { +func (s *RPCServer) GetForexProviders(_ context.Context, r *gctrpc.GetForexProvidersRequest) (*gctrpc.GetForexProvidersResponse, error) { providers := Bot.Config.GetForexProviders() if len(providers) == 0 { return nil, fmt.Errorf("forex providers is empty") @@ -677,7 +708,7 @@ func (s *RPCServer) GetForexProviders(ctx context.Context, r *gctrpc.GetForexPro } // GetForexRates returns a list of forex rates -func (s *RPCServer) GetForexRates(ctx context.Context, r *gctrpc.GetForexRatesRequest) (*gctrpc.GetForexRatesResponse, error) { +func (s *RPCServer) GetForexRates(_ context.Context, r *gctrpc.GetForexRatesRequest) (*gctrpc.GetForexRatesResponse, error) { rates, err := currency.GetExchangeRates() if err != nil { return nil, err @@ -712,10 +743,10 @@ func (s *RPCServer) GetForexRates(ctx context.Context, r *gctrpc.GetForexRatesRe // GetOrders returns all open orders, filtered by exchange, currency pair or // asset type -func (s *RPCServer) GetOrders(ctx context.Context, r *gctrpc.GetOrdersRequest) (*gctrpc.GetOrdersResponse, error) { +func (s *RPCServer) GetOrders(_ context.Context, r *gctrpc.GetOrdersRequest) (*gctrpc.GetOrdersResponse, error) { exch := GetExchangeByName(r.Exchange) if exch == nil { - return nil, errors.New("exchange is not loaded/doesn't exist") + return nil, errExchangeNotLoaded } resp, err := exch.GetActiveOrders(&order.GetOrdersRequest{ @@ -749,10 +780,10 @@ func (s *RPCServer) GetOrders(ctx context.Context, r *gctrpc.GetOrdersRequest) ( } // GetOrder returns order information based on exchange and order ID -func (s *RPCServer) GetOrder(ctx context.Context, r *gctrpc.GetOrderRequest) (*gctrpc.OrderDetails, error) { +func (s *RPCServer) GetOrder(_ context.Context, r *gctrpc.GetOrderRequest) (*gctrpc.OrderDetails, error) { exch := GetExchangeByName(r.Exchange) if exch == nil { - return nil, errors.New("exchange is not loaded/doesn't exist") + return nil, errExchangeNotLoaded } result, err := exch.GetOrderInfo(r.OrderId) if err != nil { @@ -791,14 +822,18 @@ func (s *RPCServer) GetOrder(ctx context.Context, r *gctrpc.GetOrderRequest) (*g // SubmitOrder submits an order specified by exchange, currency pair and asset // type -func (s *RPCServer) SubmitOrder(ctx context.Context, r *gctrpc.SubmitOrderRequest) (*gctrpc.SubmitOrderResponse, error) { +func (s *RPCServer) SubmitOrder(_ context.Context, r *gctrpc.SubmitOrderRequest) (*gctrpc.SubmitOrderResponse, error) { exch := GetExchangeByName(r.Exchange) if exch == nil { - return nil, errors.New("exchange is not loaded/doesn't exist") + return nil, errExchangeNotLoaded } - p := currency.NewPairFromStrings(r.Pair.Base, r.Pair.Quote) - resp, err := Bot.OrderManager.Submit(&order.Submit{ + p, err := currency.NewPairFromStrings(r.Pair.Base, r.Pair.Quote) + if err != nil { + return nil, err + } + + submission := &order.Submit{ Pair: p, Side: order.Side(r.Side), Type: order.Type(r.OrderType), @@ -806,8 +841,9 @@ func (s *RPCServer) SubmitOrder(ctx context.Context, r *gctrpc.SubmitOrderReques Price: r.Price, ClientID: r.ClientId, Exchange: r.Exchange, - }) + } + resp, err := exch.SubmitOrder(submission) if err != nil { return &gctrpc.SubmitOrderResponse{}, err } @@ -820,13 +856,17 @@ func (s *RPCServer) SubmitOrder(ctx context.Context, r *gctrpc.SubmitOrderReques // SimulateOrder simulates an order specified by exchange, currency pair and asset // type -func (s *RPCServer) SimulateOrder(ctx context.Context, r *gctrpc.SimulateOrderRequest) (*gctrpc.SimulateOrderResponse, error) { +func (s *RPCServer) SimulateOrder(_ context.Context, r *gctrpc.SimulateOrderRequest) (*gctrpc.SimulateOrderResponse, error) { exch := GetExchangeByName(r.Exchange) if exch == nil { - return nil, errors.New("exchange is not loaded/doesn't exist") + return nil, errExchangeNotLoaded + } + + p, err := currency.NewPairFromStrings(r.Pair.Base, r.Pair.Quote) + if err != nil { + return nil, err } - p := currency.NewPairFromStrings(r.Pair.Base, r.Pair.Quote) o, err := exch.FetchOrderbook(p, asset.Spot) if err != nil { return nil, err @@ -857,13 +897,17 @@ func (s *RPCServer) SimulateOrder(ctx context.Context, r *gctrpc.SimulateOrderRe // WhaleBomb finds the amount required to reach a specific price target for a given exchange, pair // and asset type -func (s *RPCServer) WhaleBomb(ctx context.Context, r *gctrpc.WhaleBombRequest) (*gctrpc.SimulateOrderResponse, error) { +func (s *RPCServer) WhaleBomb(_ context.Context, r *gctrpc.WhaleBombRequest) (*gctrpc.SimulateOrderResponse, error) { exch := GetExchangeByName(r.Exchange) if exch == nil { - return nil, errors.New("exchange is not loaded/doesn't exist") + return nil, errExchangeNotLoaded + } + + p, err := currency.NewPairFromStrings(r.Pair.Base, r.Pair.Quote) + if err != nil { + return nil, err } - p := currency.NewPairFromStrings(r.Pair.Base, r.Pair.Quote) o, err := exch.FetchOrderbook(p, asset.Spot) if err != nil { return nil, err @@ -894,35 +938,43 @@ func (s *RPCServer) WhaleBomb(ctx context.Context, r *gctrpc.WhaleBombRequest) ( // CancelOrder cancels an order specified by exchange, currency pair and asset // type -func (s *RPCServer) CancelOrder(ctx context.Context, r *gctrpc.CancelOrderRequest) (*gctrpc.CancelOrderResponse, error) { +func (s *RPCServer) CancelOrder(_ context.Context, r *gctrpc.CancelOrderRequest) (*gctrpc.GenericResponse, error) { exch := GetExchangeByName(r.Exchange) if exch == nil { - return nil, errors.New("exchange is not loaded/doesn't exist") + return nil, errExchangeNotLoaded } - err := exch.CancelOrder(&order.Cancel{ + p, err := currency.NewPairFromStrings(r.Pair.Base, r.Pair.Quote) + if err != nil { + return nil, err + } + + err = exch.CancelOrder(&order.Cancel{ AccountID: r.AccountId, ID: r.OrderId, Side: order.Side(r.Side), WalletAddress: r.WalletAddress, - Pair: currency.NewPairFromStrings(r.Pair.Base, r.Pair.Quote), + Pair: p, }) - - return &gctrpc.CancelOrderResponse{}, err + if err != nil { + return nil, err + } + return &gctrpc.GenericResponse{Status: MsgStatusSuccess, + Data: fmt.Sprintf("order %s cancelled", r.OrderId)}, nil } // CancelAllOrders cancels all orders, filterable by exchange -func (s *RPCServer) CancelAllOrders(ctx context.Context, r *gctrpc.CancelAllOrdersRequest) (*gctrpc.CancelAllOrdersResponse, error) { +func (s *RPCServer) CancelAllOrders(_ context.Context, r *gctrpc.CancelAllOrdersRequest) (*gctrpc.CancelAllOrdersResponse, error) { return &gctrpc.CancelAllOrdersResponse{}, common.ErrNotYetImplemented } // GetEvents returns the stored events list -func (s *RPCServer) GetEvents(ctx context.Context, r *gctrpc.GetEventsRequest) (*gctrpc.GetEventsResponse, error) { +func (s *RPCServer) GetEvents(_ context.Context, r *gctrpc.GetEventsRequest) (*gctrpc.GetEventsResponse, error) { return &gctrpc.GetEventsResponse{}, common.ErrNotYetImplemented } // AddEvent adds an event -func (s *RPCServer) AddEvent(ctx context.Context, r *gctrpc.AddEventRequest) (*gctrpc.AddEventResponse, error) { +func (s *RPCServer) AddEvent(_ context.Context, r *gctrpc.AddEventRequest) (*gctrpc.AddEventResponse, error) { evtCondition := EventConditionParams{ CheckBids: r.ConditionParams.CheckBids, CheckBidsAndAsks: r.ConditionParams.CheckBidsAndAsks, @@ -943,17 +995,20 @@ func (s *RPCServer) AddEvent(ctx context.Context, r *gctrpc.AddEventRequest) (*g } // RemoveEvent removes an event, specified by an event ID -func (s *RPCServer) RemoveEvent(ctx context.Context, r *gctrpc.RemoveEventRequest) (*gctrpc.RemoveEventResponse, error) { - Remove(r.Id) - return &gctrpc.RemoveEventResponse{}, nil +func (s *RPCServer) RemoveEvent(_ context.Context, r *gctrpc.RemoveEventRequest) (*gctrpc.GenericResponse, error) { + if !Remove(r.Id) { + return nil, fmt.Errorf("event %d not removed", r.Id) + } + return &gctrpc.GenericResponse{Status: MsgStatusSuccess, + Data: fmt.Sprintf("event %d removed", r.Id)}, nil } // GetCryptocurrencyDepositAddresses returns a list of cryptocurrency deposit // addresses specified by an exchange -func (s *RPCServer) GetCryptocurrencyDepositAddresses(ctx context.Context, r *gctrpc.GetCryptocurrencyDepositAddressesRequest) (*gctrpc.GetCryptocurrencyDepositAddressesResponse, error) { +func (s *RPCServer) GetCryptocurrencyDepositAddresses(_ context.Context, r *gctrpc.GetCryptocurrencyDepositAddressesRequest) (*gctrpc.GetCryptocurrencyDepositAddressesResponse, error) { exch := GetExchangeByName(r.Exchange) if exch == nil { - return nil, errors.New("exchange is not loaded/doesn't exist") + return nil, errExchangeNotLoaded } result, err := GetCryptocurrencyDepositAddressesByExchange(r.Exchange) @@ -962,10 +1017,10 @@ func (s *RPCServer) GetCryptocurrencyDepositAddresses(ctx context.Context, r *gc // GetCryptocurrencyDepositAddress returns a cryptocurrency deposit address // specified by exchange and cryptocurrency -func (s *RPCServer) GetCryptocurrencyDepositAddress(ctx context.Context, r *gctrpc.GetCryptocurrencyDepositAddressRequest) (*gctrpc.GetCryptocurrencyDepositAddressResponse, error) { +func (s *RPCServer) GetCryptocurrencyDepositAddress(_ context.Context, r *gctrpc.GetCryptocurrencyDepositAddressRequest) (*gctrpc.GetCryptocurrencyDepositAddressResponse, error) { exch := GetExchangeByName(r.Exchange) if exch == nil { - return nil, errors.New("exchange is not loaded/doesn't exist") + return nil, errExchangeNotLoaded } addr, err := GetExchangeCryptocurrencyDepositAddress(r.Exchange, "", currency.NewCode(r.Cryptocurrency)) @@ -974,10 +1029,10 @@ func (s *RPCServer) GetCryptocurrencyDepositAddress(ctx context.Context, r *gctr // WithdrawCryptocurrencyFunds withdraws cryptocurrency funds specified by // exchange -func (s *RPCServer) WithdrawCryptocurrencyFunds(ctx context.Context, r *gctrpc.WithdrawCryptoRequest) (*gctrpc.WithdrawResponse, error) { +func (s *RPCServer) WithdrawCryptocurrencyFunds(_ context.Context, r *gctrpc.WithdrawCryptoRequest) (*gctrpc.WithdrawResponse, error) { exch := GetExchangeByName(r.Exchange) if exch == nil { - return nil, errors.New("exchange is not loaded/doesn't exist") + return nil, errExchangeNotLoaded } request := &withdraw.Request{ @@ -1004,17 +1059,20 @@ func (s *RPCServer) WithdrawCryptocurrencyFunds(ctx context.Context, r *gctrpc.W } // WithdrawFiatFunds withdraws fiat funds specified by exchange -func (s *RPCServer) WithdrawFiatFunds(ctx context.Context, r *gctrpc.WithdrawFiatRequest) (*gctrpc.WithdrawResponse, error) { +func (s *RPCServer) WithdrawFiatFunds(_ context.Context, r *gctrpc.WithdrawFiatRequest) (*gctrpc.WithdrawResponse, error) { exch := GetExchangeByName(r.Exchange) if exch == nil { - return nil, errors.New("exchange is not loaded/doesn't exist") + return nil, errExchangeNotLoaded } var bankAccount *banking.Account - bankAccount, err := banking.GetBankAccountByID(r.BankAccountId) if err != nil { - bankAccount, err = exch.GetBase().GetExchangeBankAccounts(r.BankAccountId, r.Currency) + base := exch.GetBase() + if base == nil { + return nil, errExchangeBaseNotFound + } + bankAccount, err = base.GetExchangeBankAccounts(r.BankAccountId, r.Currency) if err != nil { return nil, err } @@ -1041,7 +1099,7 @@ func (s *RPCServer) WithdrawFiatFunds(ctx context.Context, r *gctrpc.WithdrawFia } // WithdrawalEventByID returns previous withdrawal request details -func (s *RPCServer) WithdrawalEventByID(ctx context.Context, r *gctrpc.WithdrawalEventByIDRequest) (*gctrpc.WithdrawalEventByIDResponse, error) { +func (s *RPCServer) WithdrawalEventByID(_ context.Context, r *gctrpc.WithdrawalEventByIDRequest) (*gctrpc.WithdrawalEventByIDResponse, error) { if !Bot.Config.Database.Enabled { return nil, database.ErrDatabaseSupportDisabled } @@ -1103,7 +1161,7 @@ func (s *RPCServer) WithdrawalEventByID(ctx context.Context, r *gctrpc.Withdrawa } // WithdrawalEventsByExchange returns previous withdrawal request details by exchange -func (s *RPCServer) WithdrawalEventsByExchange(ctx context.Context, r *gctrpc.WithdrawalEventsByExchangeRequest) (*gctrpc.WithdrawalEventsByExchangeResponse, error) { +func (s *RPCServer) WithdrawalEventsByExchange(_ context.Context, r *gctrpc.WithdrawalEventsByExchangeRequest) (*gctrpc.WithdrawalEventsByExchangeResponse, error) { if !Bot.Config.Database.Enabled { return nil, database.ErrDatabaseSupportDisabled } @@ -1124,7 +1182,7 @@ func (s *RPCServer) WithdrawalEventsByExchange(ctx context.Context, r *gctrpc.Wi } // WithdrawalEventsByDate returns previous withdrawal request details by exchange -func (s *RPCServer) WithdrawalEventsByDate(ctx context.Context, r *gctrpc.WithdrawalEventsByDateRequest) (*gctrpc.WithdrawalEventsByExchangeResponse, error) { +func (s *RPCServer) WithdrawalEventsByDate(_ context.Context, r *gctrpc.WithdrawalEventsByDateRequest) (*gctrpc.WithdrawalEventsByExchangeResponse, error) { UTCStartTime, err := time.Parse(common.SimpleTimeFormat, r.Start) if err != nil { return nil, err @@ -1143,7 +1201,7 @@ func (s *RPCServer) WithdrawalEventsByDate(ctx context.Context, r *gctrpc.Withdr } // GetLoggerDetails returns a loggers details -func (s *RPCServer) GetLoggerDetails(ctx context.Context, r *gctrpc.GetLoggerDetailsRequest) (*gctrpc.GetLoggerDetailsResponse, error) { +func (s *RPCServer) GetLoggerDetails(_ context.Context, r *gctrpc.GetLoggerDetailsRequest) (*gctrpc.GetLoggerDetailsResponse, error) { levels, err := log.Level(r.Logger) if err != nil { return nil, err @@ -1158,7 +1216,7 @@ func (s *RPCServer) GetLoggerDetails(ctx context.Context, r *gctrpc.GetLoggerDet } // SetLoggerDetails sets a loggers details -func (s *RPCServer) SetLoggerDetails(ctx context.Context, r *gctrpc.SetLoggerDetailsRequest) (*gctrpc.GetLoggerDetailsResponse, error) { +func (s *RPCServer) SetLoggerDetails(_ context.Context, r *gctrpc.SetLoggerDetailsRequest) (*gctrpc.GetLoggerDetailsResponse, error) { levels, err := log.SetLevel(r.Logger, r.Level) if err != nil { return nil, err @@ -1173,7 +1231,7 @@ func (s *RPCServer) SetLoggerDetails(ctx context.Context, r *gctrpc.SetLoggerDet } // GetExchangePairs returns a list of exchange supported assets and related pairs -func (s *RPCServer) GetExchangePairs(ctx context.Context, r *gctrpc.GetExchangePairsRequest) (*gctrpc.GetExchangePairsResponse, error) { +func (s *RPCServer) GetExchangePairs(_ context.Context, r *gctrpc.GetExchangePairsRequest) (*gctrpc.GetExchangePairsResponse, error) { exchCfg, err := Bot.Config.GetExchangeConfig(r.Exchange) if err != nil { return nil, err @@ -1188,84 +1246,105 @@ func (s *RPCServer) GetExchangePairs(ctx context.Context, r *gctrpc.GetExchangeP resp.SupportedAssets = make(map[string]*gctrpc.PairsSupported) assetTypes := exchCfg.CurrencyPairs.GetAssetTypes() for x := range assetTypes { - a := assetTypes[x] - if r.Asset != "" && !strings.EqualFold(a.String(), r.Asset) { + if r.Asset != "" && !strings.EqualFold(assetTypes[x].String(), r.Asset) { continue } - resp.SupportedAssets[a.String()] = &gctrpc.PairsSupported{ - AvailablePairs: exchCfg.CurrencyPairs.Get(a).Available.Join(), - EnabledPairs: exchCfg.CurrencyPairs.Get(a).Enabled.Join(), + + ps, err := exchCfg.CurrencyPairs.Get(assetTypes[x]) + if err != nil { + return nil, err + } + + resp.SupportedAssets[assetTypes[x].String()] = &gctrpc.PairsSupported{ + AvailablePairs: ps.Available.Join(), + EnabledPairs: ps.Enabled.Join(), } } return &resp, nil } -// EnableExchangePair enables the specified pair on an exchange -func (s *RPCServer) EnableExchangePair(ctx context.Context, r *gctrpc.ExchangePairRequest) (*gctrpc.GenericExchangeNameResponse, error) { +// SetExchangePair enables/disabled the specified pair(s) on an exchange +func (s *RPCServer) SetExchangePair(_ context.Context, r *gctrpc.SetExchangePairRequest) (*gctrpc.GenericResponse, error) { exchCfg, err := Bot.Config.GetExchangeConfig(r.Exchange) if err != nil { return nil, err } - if r.AssetType != "" && - !exchCfg.CurrencyPairs.GetAssetTypes().Contains(asset.Item(r.AssetType)) { + if r.AssetType == "" { + return nil, errors.New("asset type must be specified") + } + + if !exchCfg.CurrencyPairs.GetAssetTypes().Contains(asset.Item(r.AssetType)) { return nil, errors.New("specified asset type does not exist") } - // Default to spot asset type unless set - a := asset.Spot - if r.AssetType != "" { - a = asset.Item(r.AssetType) + a := asset.Item(r.AssetType) + + exch := GetExchangeByName(r.Exchange) + if exch == nil { + return nil, errExchangeNotLoaded + } + + base := exch.GetBase() + if base == nil { + return nil, errExchangeBaseNotFound } pairFmt, err := Bot.Config.GetPairFormat(r.Exchange, a) if err != nil { return nil, err } + var pass bool + var newErrors common.Errors + for i := range r.Pairs { + var p currency.Pair + p, err = currency.NewPairFromStrings(r.Pairs[i].Base, r.Pairs[i].Quote) + if err != nil { + return nil, err + } - p := currency.NewPairFromStrings(r.Pair.Base, r.Pair.Quote).Format( - pairFmt.Delimiter, pairFmt.Uppercase) - err = exchCfg.CurrencyPairs.EnablePair(a, p) - if err != nil { - return nil, err - } - err = GetExchangeByName(r.Exchange).GetBase().CurrencyPairs.EnablePair( - asset.Item(r.AssetType), p) - return &gctrpc.GenericExchangeNameResponse{}, err -} + if r.Enable { + err = exchCfg.CurrencyPairs.EnablePair(a, + p.Format(pairFmt.Delimiter, pairFmt.Uppercase)) + if err != nil { + newErrors = append(newErrors, err) + continue + } + err = base.CurrencyPairs.EnablePair(asset.Item(r.AssetType), p) + if err != nil { + newErrors = append(newErrors, err) + continue + } + pass = true + continue + } -// DisableExchangePair disables the specified pair on an exchange -func (s *RPCServer) DisableExchangePair(ctx context.Context, r *gctrpc.ExchangePairRequest) (*gctrpc.GenericExchangeNameResponse, error) { - exchCfg, err := Bot.Config.GetExchangeConfig(r.Exchange) - if err != nil { - return nil, err + err = exchCfg.CurrencyPairs.DisablePair(asset.Item(r.AssetType), + p.Format(pairFmt.Delimiter, pairFmt.Uppercase)) + if err != nil { + newErrors = append(newErrors, err) + continue + } + err = base.CurrencyPairs.DisablePair(asset.Item(r.AssetType), p) + if err != nil { + newErrors = append(newErrors, err) + continue + } + pass = true } - if r.AssetType != "" && - !exchCfg.CurrencyPairs.GetAssetTypes().Contains(asset.Item(r.AssetType)) { - return nil, errors.New("specified asset type does not exist") + if exch.IsWebsocketEnabled() && pass && base.Websocket.IsConnected() { + err = exch.FlushWebsocketChannels() + if err != nil { + newErrors = append(newErrors, err) + } } - // Default to spot asset type unless set - a := asset.Spot - if r.AssetType != "" { - a = asset.Item(r.AssetType) + if newErrors != nil { + return nil, newErrors } - pairFmt, err := Bot.Config.GetPairFormat(r.Exchange, a) - if err != nil { - return nil, err - } - - p := currency.NewPairFromStrings(r.Pair.Base, r.Pair.Quote).Format( - pairFmt.Delimiter, pairFmt.Uppercase) - err = exchCfg.CurrencyPairs.DisablePair(asset.Item(r.AssetType), p) - if err != nil { - return nil, err - } - err = GetExchangeByName(r.Exchange).GetBase().CurrencyPairs.DisablePair( - asset.Item(r.AssetType), p) - return &gctrpc.GenericExchangeNameResponse{}, err + return &gctrpc.GenericResponse{Status: MsgStatusSuccess}, nil } // GetOrderbookStream streams the requested updated orderbook @@ -1282,7 +1361,10 @@ func (s *RPCServer) GetOrderbookStream(r *gctrpc.GetOrderbookStreamRequest, stre return errors.New(errAssetTypeUnset) } - p := currency.NewPairFromStrings(r.Pair.Base, r.Pair.Quote) + p, err := currency.NewPairFromStrings(r.Pair.Base, r.Pair.Quote) + if err != nil { + return err + } pipe, err := orderbook.SubscribeOrderbook(r.Exchange, p, asset.Item(r.AssetType)) if err != nil { @@ -1388,7 +1470,10 @@ func (s *RPCServer) GetTickerStream(r *gctrpc.GetTickerStreamRequest, stream gct return errors.New(errAssetTypeUnset) } - p := currency.NewPairFromStrings(r.Pair.Base, r.Pair.Quote) + p, err := currency.NewPairFromStrings(r.Pair.Base, r.Pair.Quote) + if err != nil { + return err + } pipe, err := ticker.SubscribeTicker(r.Exchange, p, asset.Item(r.AssetType)) if err != nil { @@ -1465,7 +1550,7 @@ func (s *RPCServer) GetExchangeTickerStream(r *gctrpc.GetExchangeTickerStreamReq } // GetAuditEvent returns matching audit events from database -func (s *RPCServer) GetAuditEvent(ctx context.Context, r *gctrpc.GetAuditEventRequest) (*gctrpc.GetAuditEventResponse, error) { +func (s *RPCServer) GetAuditEvent(_ context.Context, r *gctrpc.GetAuditEventRequest) (*gctrpc.GetAuditEventResponse, error) { UTCStartTime, err := time.Parse(common.SimpleTimeFormat, r.StartDate) if err != nil { return nil, err @@ -1513,7 +1598,7 @@ func (s *RPCServer) GetAuditEvent(ctx context.Context, r *gctrpc.GetAuditEventRe } // GetHistoricCandles returns historical candles for a given exchange -func (s *RPCServer) GetHistoricCandles(ctx context.Context, req *gctrpc.GetHistoricCandlesRequest) (*gctrpc.GetHistoricCandlesResponse, error) { +func (s *RPCServer) GetHistoricCandles(_ context.Context, req *gctrpc.GetHistoricCandlesRequest) (*gctrpc.GetHistoricCandlesResponse, error) { if req.Exchange == "" { return nil, errors.New(errExchangeNameUnset) } @@ -1577,7 +1662,7 @@ func (s *RPCServer) GetHistoricCandles(ctx context.Context, req *gctrpc.GetHisto } // GCTScriptStatus returns a slice of current running scripts that includes next run time and uuid -func (s *RPCServer) GCTScriptStatus(ctx context.Context, r *gctrpc.GCTScriptStatusRequest) (*gctrpc.GCTScriptStatusResponse, error) { +func (s *RPCServer) GCTScriptStatus(_ context.Context, r *gctrpc.GCTScriptStatusRequest) (*gctrpc.GCTScriptStatusResponse, error) { if !gctscript.GCTScriptConfig.Enabled { return &gctrpc.GCTScriptStatusResponse{Status: gctscript.ErrScriptingDisabled.Error()}, nil } @@ -1605,7 +1690,7 @@ func (s *RPCServer) GCTScriptStatus(ctx context.Context, r *gctrpc.GCTScriptStat } // GCTScriptQuery queries a running script and returns script running information -func (s *RPCServer) GCTScriptQuery(ctx context.Context, r *gctrpc.GCTScriptQueryRequest) (*gctrpc.GCTScriptQueryResponse, error) { +func (s *RPCServer) GCTScriptQuery(_ context.Context, r *gctrpc.GCTScriptQueryRequest) (*gctrpc.GCTScriptQueryResponse, error) { if !gctscript.GCTScriptConfig.Enabled { return &gctrpc.GCTScriptQueryResponse{Status: gctscript.ErrScriptingDisabled.Error()}, nil } @@ -1636,9 +1721,9 @@ func (s *RPCServer) GCTScriptQuery(ctx context.Context, r *gctrpc.GCTScriptQuery } // GCTScriptExecute execute a script -func (s *RPCServer) GCTScriptExecute(ctx context.Context, r *gctrpc.GCTScriptExecuteRequest) (*gctrpc.GCTScriptGenericResponse, error) { +func (s *RPCServer) GCTScriptExecute(_ context.Context, r *gctrpc.GCTScriptExecuteRequest) (*gctrpc.GenericResponse, error) { if !gctscript.GCTScriptConfig.Enabled { - return &gctrpc.GCTScriptGenericResponse{Status: gctscript.ErrScriptingDisabled.Error()}, nil + return &gctrpc.GenericResponse{Status: gctscript.ErrScriptingDisabled.Error()}, nil } if r.Script.Path == "" { @@ -1647,13 +1732,13 @@ func (s *RPCServer) GCTScriptExecute(ctx context.Context, r *gctrpc.GCTScriptExe gctVM := gctscript.New() if gctVM == nil { - return &gctrpc.GCTScriptGenericResponse{Status: MsgStatusError, Data: "unable to create VM instance"}, nil + return &gctrpc.GenericResponse{Status: MsgStatusError, Data: "unable to create VM instance"}, nil } script := filepath.Join(r.Script.Path, r.Script.Name) err := gctVM.Load(script) if err != nil { - return &gctrpc.GCTScriptGenericResponse{ + return &gctrpc.GenericResponse{ Status: MsgStatusError, Data: err.Error(), }, nil @@ -1661,21 +1746,21 @@ func (s *RPCServer) GCTScriptExecute(ctx context.Context, r *gctrpc.GCTScriptExe go gctVM.CompileAndRun() - return &gctrpc.GCTScriptGenericResponse{ + return &gctrpc.GenericResponse{ Status: MsgStatusOK, Data: gctVM.ShortName() + " (" + gctVM.ID.String() + ") executed", }, nil } // GCTScriptStop terminate a running script -func (s *RPCServer) GCTScriptStop(ctx context.Context, r *gctrpc.GCTScriptStopRequest) (*gctrpc.GCTScriptGenericResponse, error) { +func (s *RPCServer) GCTScriptStop(_ context.Context, r *gctrpc.GCTScriptStopRequest) (*gctrpc.GenericResponse, error) { if !gctscript.GCTScriptConfig.Enabled { - return &gctrpc.GCTScriptGenericResponse{Status: gctscript.ErrScriptingDisabled.Error()}, nil + return &gctrpc.GenericResponse{Status: gctscript.ErrScriptingDisabled.Error()}, nil } UUID, err := uuid.FromString(r.Script.UUID) if err != nil { - return &gctrpc.GCTScriptGenericResponse{Status: MsgStatusError, Data: err.Error()}, nil + return &gctrpc.GenericResponse{Status: MsgStatusError, Data: err.Error()}, nil } if v, f := gctscript.AllVMSync.Load(UUID); f { @@ -1684,15 +1769,15 @@ func (s *RPCServer) GCTScriptStop(ctx context.Context, r *gctrpc.GCTScriptStopRe if err != nil { status = " " + err.Error() } - return &gctrpc.GCTScriptGenericResponse{Status: MsgStatusOK, Data: v.(*gctscript.VM).ID.String() + status}, nil + return &gctrpc.GenericResponse{Status: MsgStatusOK, Data: v.(*gctscript.VM).ID.String() + status}, nil } - return &gctrpc.GCTScriptGenericResponse{Status: MsgStatusError, Data: "no running script found"}, nil + return &gctrpc.GenericResponse{Status: MsgStatusError, Data: "no running script found"}, nil } // GCTScriptUpload upload a new script to ScriptPath -func (s *RPCServer) GCTScriptUpload(ctx context.Context, r *gctrpc.GCTScriptUploadRequest) (*gctrpc.GCTScriptGenericResponse, error) { +func (s *RPCServer) GCTScriptUpload(_ context.Context, r *gctrpc.GCTScriptUploadRequest) (*gctrpc.GenericResponse, error) { if !gctscript.GCTScriptConfig.Enabled { - return &gctrpc.GCTScriptGenericResponse{Status: gctscript.ErrScriptingDisabled.Error()}, nil + return &gctrpc.GenericResponse{Status: gctscript.ErrScriptingDisabled.Error()}, nil } fPath := filepath.Join(gctscript.ScriptPath, r.ScriptName) @@ -1743,7 +1828,7 @@ func (s *RPCServer) GCTScriptUpload(ctx context.Context, r *gctrpc.GCTScriptUplo files, errExtract := archive.UnZip(fPath, filepath.Join(gctscript.ScriptPath, r.ScriptName[:len(r.ScriptName)-4])) if errExtract != nil { log.Errorf(log.Global, "Failed to archive zip file %v", errExtract) - return &gctrpc.GCTScriptGenericResponse{Status: MsgStatusError, Data: errExtract.Error()}, nil + return &gctrpc.GenericResponse{Status: MsgStatusError, Data: errExtract.Error()}, nil } var failedFiles []string for x := range files { @@ -1761,7 +1846,7 @@ func (s *RPCServer) GCTScriptUpload(ctx context.Context, r *gctrpc.GCTScriptUplo if err != nil { log.Errorf(log.GCTScriptMgr, "Failed to remove file %v (%v), manual deletion required", filepath.Base(fPath), err) } - return &gctrpc.GCTScriptGenericResponse{Status: ErrScriptFailedValidation, Data: strings.Join(failedFiles, ", ")}, nil + return &gctrpc.GenericResponse{Status: ErrScriptFailedValidation, Data: strings.Join(failedFiles, ", ")}, nil } } else { err = gctscript.Validate(fPath) @@ -1770,18 +1855,18 @@ func (s *RPCServer) GCTScriptUpload(ctx context.Context, r *gctrpc.GCTScriptUplo if errRemove != nil { log.Errorf(log.GCTScriptMgr, "Failed to remove file %v, manual deletion required: %v", filepath.Base(fPath), errRemove) } - return &gctrpc.GCTScriptGenericResponse{Status: ErrScriptFailedValidation, Data: err.Error()}, nil + return &gctrpc.GenericResponse{Status: ErrScriptFailedValidation, Data: err.Error()}, nil } } - return &gctrpc.GCTScriptGenericResponse{ + return &gctrpc.GenericResponse{ Status: MsgStatusOK, Data: fmt.Sprintf("script %s written", newFile.Name()), }, nil } // GCTScriptReadScript read a script and return contents -func (s *RPCServer) GCTScriptReadScript(ctx context.Context, r *gctrpc.GCTScriptReadScriptRequest) (*gctrpc.GCTScriptQueryResponse, error) { +func (s *RPCServer) GCTScriptReadScript(_ context.Context, r *gctrpc.GCTScriptReadScriptRequest) (*gctrpc.GCTScriptQueryResponse, error) { if !gctscript.GCTScriptConfig.Enabled { return &gctrpc.GCTScriptQueryResponse{Status: gctscript.ErrScriptingDisabled.Error()}, nil } @@ -1832,39 +1917,296 @@ func (s *RPCServer) GCTScriptListAll(context.Context, *gctrpc.GCTScriptListAllRe } // GCTScriptStopAll stops all running scripts -func (s *RPCServer) GCTScriptStopAll(context.Context, *gctrpc.GCTScriptStopAllRequest) (*gctrpc.GCTScriptGenericResponse, error) { +func (s *RPCServer) GCTScriptStopAll(context.Context, *gctrpc.GCTScriptStopAllRequest) (*gctrpc.GenericResponse, error) { if !gctscript.GCTScriptConfig.Enabled { - return &gctrpc.GCTScriptGenericResponse{Status: gctscript.ErrScriptingDisabled.Error()}, nil + return &gctrpc.GenericResponse{Status: gctscript.ErrScriptingDisabled.Error()}, nil } err := gctscript.ShutdownAll() if err != nil { - return &gctrpc.GCTScriptGenericResponse{Status: "error", Data: err.Error()}, nil + return &gctrpc.GenericResponse{Status: "error", Data: err.Error()}, nil } - return &gctrpc.GCTScriptGenericResponse{ + return &gctrpc.GenericResponse{ Status: MsgStatusOK, Data: "all running scripts have been stopped", }, nil } // GCTScriptAutoLoadToggle adds or removes an entry to the autoload list -func (s *RPCServer) GCTScriptAutoLoadToggle(ctx context.Context, r *gctrpc.GCTScriptAutoLoadRequest) (*gctrpc.GCTScriptGenericResponse, error) { +func (s *RPCServer) GCTScriptAutoLoadToggle(_ context.Context, r *gctrpc.GCTScriptAutoLoadRequest) (*gctrpc.GenericResponse, error) { if !gctscript.GCTScriptConfig.Enabled { - return &gctrpc.GCTScriptGenericResponse{Status: gctscript.ErrScriptingDisabled.Error()}, nil + return &gctrpc.GenericResponse{Status: gctscript.ErrScriptingDisabled.Error()}, nil } if r.Status { err := gctscript.Autoload(r.Script, true) if err != nil { - return &gctrpc.GCTScriptGenericResponse{Status: "error", Data: err.Error()}, nil + return &gctrpc.GenericResponse{Status: "error", Data: err.Error()}, nil } - return &gctrpc.GCTScriptGenericResponse{Status: "success", Data: "script " + r.Script + " removed from autoload list"}, nil + return &gctrpc.GenericResponse{Status: "success", Data: "script " + r.Script + " removed from autoload list"}, nil } err := gctscript.Autoload(r.Script, false) if err != nil { - return &gctrpc.GCTScriptGenericResponse{Status: "error", Data: err.Error()}, nil + return &gctrpc.GenericResponse{Status: "error", Data: err.Error()}, nil } - return &gctrpc.GCTScriptGenericResponse{Status: "success", Data: "script " + r.Script + " added to autoload list"}, nil + return &gctrpc.GenericResponse{Status: "success", Data: "script " + r.Script + " added to autoload list"}, nil +} + +// SetExchangeAsset enables or disables an exchanges asset type +func (s *RPCServer) SetExchangeAsset(_ context.Context, r *gctrpc.SetExchangeAssetRequest) (*gctrpc.GenericResponse, error) { + exch := GetExchangeByName(r.Exchange) + if exch == nil { + return nil, errExchangeNotLoaded + } + + exchCfg, err := Bot.Config.GetExchangeConfig(r.Exchange) + if err != nil { + return nil, err + } + + base := exch.GetBase() + if base == nil { + return nil, errExchangeBaseNotFound + } + + if r.Asset == "" { + return nil, errors.New("asset type must be specified") + } + + a := asset.Item(r.Asset) + err = base.CurrencyPairs.SetAssetEnabled(a, r.Enable) + if err != nil { + return nil, err + } + err = exchCfg.CurrencyPairs.SetAssetEnabled(a, r.Enable) + if err != nil { + return nil, err + } + + return &gctrpc.GenericResponse{Status: MsgStatusSuccess}, nil +} + +// SetAllExchangePairs enables or disables an exchanges pairs +func (s *RPCServer) SetAllExchangePairs(_ context.Context, r *gctrpc.SetExchangeAllPairsRequest) (*gctrpc.GenericResponse, error) { + exch := GetExchangeByName(r.Exchange) + if exch == nil { + return nil, errExchangeNotLoaded + } + + exchCfg, err := Bot.Config.GetExchangeConfig(r.Exchange) + if err != nil { + return nil, err + } + + base := exch.GetBase() + if base == nil { + return nil, errExchangeBaseNotFound + } + + assets := base.CurrencyPairs.GetAssetTypes() + + if r.Enable { + for i := range assets { + var pairs currency.Pairs + pairs, err = base.CurrencyPairs.GetPairs(assets[i], false) + if err != nil { + return nil, err + } + exchCfg.CurrencyPairs.StorePairs(assets[i], pairs, true) + base.CurrencyPairs.StorePairs(assets[i], pairs, true) + } + } else { + for i := range assets { + exchCfg.CurrencyPairs.StorePairs(assets[i], nil, true) + base.CurrencyPairs.StorePairs(assets[i], nil, true) + } + } + + if exch.IsWebsocketEnabled() && base.Websocket.IsConnected() { + err = exch.FlushWebsocketChannels() + if err != nil { + return nil, err + } + } + + return &gctrpc.GenericResponse{Status: MsgStatusSuccess}, nil +} + +// UpdateExchangeSupportedPairs forces an update of the supported pairs which +// will update the available pairs list and remove any assets that are disabled +// by the exchange +func (s *RPCServer) UpdateExchangeSupportedPairs(_ context.Context, r *gctrpc.UpdateExchangeSupportedPairsRequest) (*gctrpc.GenericResponse, error) { + exch := GetExchangeByName(r.Exchange) + if exch == nil { + return nil, errExchangeNotLoaded + } + + base := exch.GetBase() + if base == nil { + return nil, errExchangeBaseNotFound + } + + if !base.GetEnabledFeatures().AutoPairUpdates { + return nil, + errors.New("cannot auto pair update for exchange, a manual update is needed") + } + + err := exch.UpdateTradablePairs(false) + if err != nil { + return nil, err + } + + if exch.IsWebsocketEnabled() { + err = exch.FlushWebsocketChannels() + if err != nil { + return nil, err + } + } + return &gctrpc.GenericResponse{Status: MsgStatusSuccess}, nil +} + +// GetExchangeAssets returns the supported asset types +func (s *RPCServer) GetExchangeAssets(_ context.Context, r *gctrpc.GetExchangeAssetsRequest) (*gctrpc.GetExchangeAssetsResponse, error) { + exch := GetExchangeByName(r.Exchange) + if exch == nil { + return nil, errExchangeNotLoaded + } + + return &gctrpc.GetExchangeAssetsResponse{ + Assets: exch.GetAssetTypes().JoinToString(","), + }, nil +} + +// WebsocketGetInfo returns websocket connection information +func (s *RPCServer) WebsocketGetInfo(_ context.Context, r *gctrpc.WebsocketGetInfoRequest) (*gctrpc.WebsocketGetInfoResponse, error) { + exch := GetExchangeByName(r.Exchange) + if exch == nil { + return nil, errExchangeNotLoaded + } + + w, err := exch.GetWebsocket() + if err != nil { + return nil, err + } + + return &gctrpc.WebsocketGetInfoResponse{ + Exchange: exch.GetName(), + Supported: exch.SupportsWebsocket(), + Enabled: exch.IsWebsocketEnabled(), + Authenticated: w.CanUseAuthenticatedEndpoints(), + RunningUrl: w.GetWebsocketURL(), + ProxyAddress: w.GetProxyAddress(), + }, nil +} + +// WebsocketSetEnabled enables or disables the websocket client +func (s *RPCServer) WebsocketSetEnabled(_ context.Context, r *gctrpc.WebsocketSetEnabledRequest) (*gctrpc.GenericResponse, error) { + exch := GetExchangeByName(r.Exchange) + if exch == nil { + return nil, errExchangeNotLoaded + } + + w, err := exch.GetWebsocket() + if err != nil { + return nil, fmt.Errorf("websocket not supported for exchange %s", r.Exchange) + } + + exchCfg, err := Bot.Config.GetExchangeConfig(r.Exchange) + if err != nil { + return nil, err + } + + if r.Enable { + err = w.Enable() + if err != nil { + return nil, err + } + + exchCfg.Features.Enabled.Websocket = true + return &gctrpc.GenericResponse{Status: MsgStatusSuccess, Data: "websocket enabled"}, nil + } + + err = w.Disable() + if err != nil { + return nil, err + } + exchCfg.Features.Enabled.Websocket = false + return &gctrpc.GenericResponse{Status: MsgStatusSuccess, Data: "websocket disabled"}, nil +} + +// WebsocketGetSubscriptions returns websocket subscription analysis +func (s *RPCServer) WebsocketGetSubscriptions(_ context.Context, r *gctrpc.WebsocketGetSubscriptionsRequest) (*gctrpc.WebsocketGetSubscriptionsResponse, error) { + exch := GetExchangeByName(r.Exchange) + if exch == nil { + return nil, errExchangeNotLoaded + } + + w, err := exch.GetWebsocket() + if err != nil { + return nil, fmt.Errorf("websocket not supported for exchange %s", r.Exchange) + } + + payload := new(gctrpc.WebsocketGetSubscriptionsResponse) + payload.Exchange = exch.GetName() + subs := w.GetSubscriptions() + for i := range subs { + params, err := json.Marshal(subs[i].Params) + if err != nil { + return nil, err + } + payload.Subscriptions = append(payload.Subscriptions, + &gctrpc.WebsocketSubscription{ + Channel: subs[i].Channel, + Currency: subs[i].Currency.String(), + Asset: subs[i].Asset.String(), + Params: string(params), + }) + } + return payload, nil +} + +// WebsocketSetProxy sets client websocket connection proxy +func (s *RPCServer) WebsocketSetProxy(_ context.Context, r *gctrpc.WebsocketSetProxyRequest) (*gctrpc.GenericResponse, error) { + exch := GetExchangeByName(r.Exchange) + if exch == nil { + return nil, errExchangeNotLoaded + } + + w, err := exch.GetWebsocket() + if err != nil { + return nil, fmt.Errorf("websocket not supported for exchange %s", r.Exchange) + } + + err = w.SetProxyAddress(r.Proxy) + if err != nil { + return nil, err + } + return &gctrpc.GenericResponse{Status: MsgStatusSuccess, + Data: fmt.Sprintf("new proxy has been set [%s] for %s websocket connection", + r.Exchange, + r.Proxy)}, nil +} + +// WebsocketSetURL sets exchange websocket client connection URL +func (s *RPCServer) WebsocketSetURL(_ context.Context, r *gctrpc.WebsocketSetURLRequest) (*gctrpc.GenericResponse, error) { + exch := GetExchangeByName(r.Exchange) + if exch == nil { + return nil, errExchangeNotLoaded + } + + w, err := exch.GetWebsocket() + if err != nil { + return nil, fmt.Errorf("websocket not supported for exchange %s", r.Exchange) + } + + err = w.SetWebsocketURL(r.Url, false, true) + if err != nil { + return nil, err + } + return &gctrpc.GenericResponse{Status: MsgStatusSuccess, + Data: fmt.Sprintf("new URL has been set [%s] for %s websocket connection", + r.Exchange, + r.Url)}, nil } diff --git a/engine/syncer.go b/engine/syncer.go index a634da41..bf4f8e8d 100644 --- a/engine/syncer.go +++ b/engine/syncer.go @@ -200,16 +200,18 @@ func (e *ExchangeCurrencyPairSyncer) update(exchangeName string, p currency.Pair } switch syncType { - case SyncItemOrderbook, SyncItemTrade, SyncItemTicker: - if !e.Cfg.SyncOrderbook && syncType == SyncItemOrderbook { + case SyncItemOrderbook: + if !e.Cfg.SyncOrderbook { return } - if !e.Cfg.SyncTicker && syncType == SyncItemTicker { + case SyncItemTicker: + if !e.Cfg.SyncTicker { return } - if !e.Cfg.SyncTrades && syncType == SyncItemTrade { + case SyncItemTrade: + if !e.Cfg.SyncTrades { return } default: @@ -236,7 +238,10 @@ func (e *ExchangeCurrencyPairSyncer) update(exchangeName string, p currency.Pair if atomic.LoadInt32(&e.initSyncCompleted) != 1 && !origHadData { removedCounter++ log.Debugf(log.SyncMgr, "%s ticker sync complete %v [%d/%d].\n", - exchangeName, FormatCurrency(p).String(), removedCounter, createdCounter) + exchangeName, + FormatCurrency(p).String(), + removedCounter, + createdCounter) e.initSyncWG.Done() } @@ -251,7 +256,10 @@ func (e *ExchangeCurrencyPairSyncer) update(exchangeName string, p currency.Pair if atomic.LoadInt32(&e.initSyncCompleted) != 1 && !origHadData { removedCounter++ log.Debugf(log.SyncMgr, "%s orderbook sync complete %v [%d/%d].\n", - exchangeName, FormatCurrency(p).String(), removedCounter, createdCounter) + exchangeName, + FormatCurrency(p).String(), + removedCounter, + createdCounter) e.initSyncWG.Done() } @@ -266,7 +274,10 @@ func (e *ExchangeCurrencyPairSyncer) update(exchangeName string, p currency.Pair if atomic.LoadInt32(&e.initSyncCompleted) != 1 && !origHadData { removedCounter++ log.Debugf(log.SyncMgr, "%s trade sync complete %v [%d/%d].\n", - exchangeName, FormatCurrency(p).String(), removedCounter, createdCounter) + exchangeName, + FormatCurrency(p).String(), + removedCounter, + createdCounter) e.initSyncWG.Done() } } @@ -276,7 +287,8 @@ func (e *ExchangeCurrencyPairSyncer) update(exchangeName string, p currency.Pair func (e *ExchangeCurrencyPairSyncer) worker() { cleanup := func() { - log.Debugln(log.SyncMgr, "Exchange CurrencyPairSyncer worker shutting down.") + log.Debugln(log.SyncMgr, + "Exchange CurrencyPairSyncer worker shutting down.") } defer cleanup() @@ -293,8 +305,10 @@ func (e *ExchangeCurrencyPairSyncer) worker() { if exchanges[x].SupportsWebsocket() && exchanges[x].IsWebsocketEnabled() { ws, err := exchanges[x].GetWebsocket() if err != nil { - log.Errorf(log.SyncMgr, "%s unable to get websocket pointer. Err: %s\n", - exchangeName, err) + log.Errorf(log.SyncMgr, + "%s unable to get websocket pointer. Err: %s\n", + exchangeName, + err) usingREST = true } @@ -308,7 +322,17 @@ func (e *ExchangeCurrencyPairSyncer) worker() { } for y := range assetTypes { - enabledPairs := exchanges[x].GetEnabledPairs(assetTypes[y]) + if exchanges[x].GetBase().CurrencyPairs.IsAssetEnabled(assetTypes[y]) != nil { + continue + } + enabledPairs, err := exchanges[x].GetEnabledPairs(assetTypes[y]) + if err != nil { + log.Errorf(log.SyncMgr, + "%s failed to get enabled pairs. Err: %s\n", + exchangeName, + err) + continue + } for i := range enabledPairs { if atomic.LoadInt32(&e.shutdown) == 1 { return @@ -410,7 +434,7 @@ func (e *ExchangeCurrencyPairSyncer) worker() { } else { result, err = exchanges[x].UpdateTicker(c.Pair, c.AssetType) } - printTickerSummary(result, c.Pair, c.AssetType, exchangeName, "REST", err) + printTickerSummary(result, "REST", err) if err == nil { if Bot.Config.RemoteControl.WebsocketRPC.Enabled { relayWebsocketEvent(result, "ticker_update", c.AssetType.String(), exchangeName) @@ -449,7 +473,7 @@ func (e *ExchangeCurrencyPairSyncer) worker() { e.setProcessing(c.Exchange, c.Pair, c.AssetType, SyncItemOrderbook, true) result, err := exchanges[x].UpdateOrderbook(c.Pair, c.AssetType) - printOrderbookSummary(result, c.Pair, c.AssetType, exchangeName, "REST", err) + printOrderbookSummary(result, "REST", err) if err == nil { if Bot.Config.RemoteControl.WebsocketRPC.Enabled { relayWebsocketEvent(result, "orderbook_update", c.AssetType.String(), exchangeName) @@ -497,8 +521,10 @@ func (e *ExchangeCurrencyPairSyncer) Start() { if supportsWebsocket && exchanges[x].IsWebsocketEnabled() { ws, err := exchanges[x].GetWebsocket() if err != nil { - log.Errorf(log.SyncMgr, "%s failed to get websocket. Err: %s\n", - exchangeName, err) + log.Errorf(log.SyncMgr, + "%s failed to get websocket. Err: %s\n", + exchangeName, + err) usingREST = true } @@ -507,8 +533,10 @@ func (e *ExchangeCurrencyPairSyncer) Start() { err = ws.Connect() if err != nil { - log.Errorf(log.SyncMgr, "%s websocket failed to connect. Err: %s\n", - exchangeName, err) + log.Errorf(log.SyncMgr, + "%s websocket failed to connect. Err: %s\n", + exchangeName, + err) usingREST = true } else { usingWebsocket = true @@ -521,7 +549,22 @@ func (e *ExchangeCurrencyPairSyncer) Start() { } for y := range assetTypes { - enabledPairs := exchanges[x].GetEnabledPairs(assetTypes[y]) + if exchanges[x].GetBase().CurrencyPairs.IsAssetEnabled(assetTypes[y]) != nil { + log.Warnf(log.SyncMgr, + "%s asset type %s is disabled, fetching enabled pairs is paused", + exchangeName, + assetTypes[y]) + continue + } + + enabledPairs, err := exchanges[x].GetEnabledPairs(assetTypes[y]) + if err != nil { + log.Errorf(log.SyncMgr, + "%s failed to get enabled pairs. Err: %s\n", + exchangeName, + err) + continue + } for i := range enabledPairs { if e.exists(exchangeName, enabledPairs[i], assetTypes[y]) { continue diff --git a/engine/websocket.go b/engine/websocket.go index e277e788..99cef467 100644 --- a/engine/websocket.go +++ b/engine/websocket.go @@ -351,8 +351,14 @@ func wsGetTicker(client *WebsocketClient, data interface{}) error { return err } - result, err := GetSpecificTicker(currency.NewPairFromString(tickerReq.Currency), - tickerReq.Exchange, asset.Item(tickerReq.AssetType)) + p, err := currency.NewPairFromString(tickerReq.Currency) + if err != nil { + return err + } + + result, err := GetSpecificTicker(p, + tickerReq.Exchange, + asset.Item(tickerReq.AssetType)) if err != nil { wsResp.Error = err.Error() @@ -383,7 +389,12 @@ func wsGetOrderbook(client *WebsocketClient, data interface{}) error { return err } - result, err := GetSpecificOrderbook(currency.NewPairFromString(orderbookReq.Currency), + p, err := currency.NewPairFromString(orderbookReq.Currency) + if err != nil { + return err + } + + result, err := GetSpecificOrderbook(p, orderbookReq.Exchange, asset.Item(orderbookReq.AssetType)) if err != nil { diff --git a/exchanges/alphapoint/alphapoint_websocket.go b/exchanges/alphapoint/alphapoint_websocket.go index dfb65979..54a9c913 100644 --- a/exchanges/alphapoint/alphapoint_websocket.go +++ b/exchanges/alphapoint/alphapoint_websocket.go @@ -40,7 +40,6 @@ func (a *Alphapoint) WebsocketClient() { for a.Enabled { msgType, resp, err := a.WebsocketConn.ReadMessage() if err != nil { - a.Websocket.ReadMessageErrors <- err log.Error(log.ExchangeSys, err) break } diff --git a/exchanges/alphapoint/alphapoint_wrapper.go b/exchanges/alphapoint/alphapoint_wrapper.go index 0eb6fe15..bb2eaeb7 100644 --- a/exchanges/alphapoint/alphapoint_wrapper.go +++ b/exchanges/alphapoint/alphapoint_wrapper.go @@ -16,7 +16,6 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) @@ -35,10 +34,6 @@ func (a *Alphapoint) SetDefaults() { a.API.CredentialsValidator.RequiresKey = true a.API.CredentialsValidator.RequiresSecret = true - a.CurrencyPairs.AssetTypes = asset.Items{ - asset.Spot, - } - a.Features = exchange.Features{ Supports: exchange.FeaturesSupported{ REST: true, @@ -129,23 +124,24 @@ func (a *Alphapoint) FetchAccountInfo() (account.Holdings, error) { // UpdateTicker updates and returns the ticker for a currency pair func (a *Alphapoint) UpdateTicker(p currency.Pair, assetType asset.Item) (*ticker.Price, error) { - tickerPrice := new(ticker.Price) tick, err := a.GetTicker(p.String()) if err != nil { - return tickerPrice, err + return nil, err } - tickerPrice.Pair = p - tickerPrice.Ask = tick.Ask - tickerPrice.Bid = tick.Bid - tickerPrice.Low = tick.Low - tickerPrice.High = tick.High - tickerPrice.Volume = tick.Volume - tickerPrice.Last = tick.Last - - err = ticker.ProcessTicker(a.Name, tickerPrice, assetType) + err = ticker.ProcessTicker(&ticker.Price{ + Pair: p, + Ask: tick.Ask, + Bid: tick.Bid, + Low: tick.Low, + High: tick.High, + Volume: tick.Volume, + Last: tick.Last, + ExchangeName: a.Name, + AssetType: assetType, + }) if err != nil { - return tickerPrice, err + return nil, err } return ticker.GetTicker(a.Name, p, assetType) @@ -313,11 +309,6 @@ func (a *Alphapoint) WithdrawFiatFundsToInternationalBank(withdrawRequest *withd return "", common.ErrNotYetImplemented } -// GetWebsocket returns a pointer to the exchange websocket -func (a *Alphapoint) GetWebsocket() (*wshandler.Websocket, error) { - return nil, common.ErrNotYetImplemented -} - // GetFeeByType returns an estimate of fee based on type of transaction func (a *Alphapoint) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { return 0, common.ErrFunctionNotSupported @@ -406,28 +397,6 @@ func (a *Alphapoint) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detai return orders, nil } -// SubscribeToWebsocketChannels appends to ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle subscribing -func (a *Alphapoint) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - return common.ErrFunctionNotSupported -} - -// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle unsubscribing -func (a *Alphapoint) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - return common.ErrFunctionNotSupported -} - -// GetSubscriptions returns a copied list of subscriptions -func (a *Alphapoint) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) { - return nil, common.ErrFunctionNotSupported -} - -// AuthenticateWebsocket sends an authentication message to the websocket -func (a *Alphapoint) AuthenticateWebsocket() error { - return common.ErrFunctionNotSupported -} - // ValidateCredentials validates current credentials used for wrapper // functionality func (a *Alphapoint) ValidateCredentials() error { diff --git a/exchanges/asset/asset.go b/exchanges/asset/asset.go index 43947812..5e80d0f7 100644 --- a/exchanges/asset/asset.go +++ b/exchanges/asset/asset.go @@ -14,6 +14,7 @@ type Items []Item const ( Spot = Item("spot") Margin = Item("margin") + MarginFunding = Item("marginfunding") Index = Item("index") Binary = Item("binary") PerpetualContract = Item("perpetualcontract") @@ -26,6 +27,7 @@ const ( var supported = Items{ Spot, Margin, + MarginFunding, Index, Binary, PerpetualContract, @@ -66,7 +68,6 @@ func (a Items) Contains(i Item) bool { return true } } - return false } diff --git a/exchanges/binance/binance.go b/exchanges/binance/binance.go index 66fcf280..33987279 100644 --- a/exchanges/binance/binance.go +++ b/exchanges/binance/binance.go @@ -17,9 +17,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/common/crypto" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" - "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/request" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" "github.com/thrasher-corp/gocryptotrader/log" ) @@ -63,7 +61,6 @@ const ( // Binance is the overarching type across the Bithumb package type Binance struct { exchange.Base - WebsocketConn *wshandler.WebsocketConnection // Valid string list that is required by the exchange validLimits []int @@ -569,17 +566,6 @@ func (b *Binance) CheckLimit(limit int) error { return errors.New("incorrect limit values - valid values are 5, 10, 20, 50, 100, 500, 1000") } -// CheckSymbol checks value against a variable list -func (b *Binance) CheckSymbol(symbol string, assetType asset.Item) error { - enPairs := b.GetAvailablePairs(assetType) - for x := range enPairs { - if b.FormatExchangeCurrency(enPairs[x], assetType).String() == symbol { - return nil - } - } - return errors.New("incorrect symbol values - please check available pairs in configuration") -} - // SetValues sets the default valid values func (b *Binance) SetValues() { b.validLimits = []int{5, 10, 20, 50, 100, 500, 1000, 5000} diff --git a/exchanges/binance/binance_live_test.go b/exchanges/binance/binance_live_test.go index 643af815..b20f29e4 100644 --- a/exchanges/binance/binance_live_test.go +++ b/exchanges/binance/binance_live_test.go @@ -29,6 +29,7 @@ func TestMain(m *testing.M) { binanceConfig.API.Credentials.Key = apiKey binanceConfig.API.Credentials.Secret = apiSecret b.SetDefaults() + b.Websocket = sharedtestvalues.NewTestWebsocket() err = b.Setup(binanceConfig) if err != nil { log.Fatal("Binance setup error", err) diff --git a/exchanges/binance/binance_mock_test.go b/exchanges/binance/binance_mock_test.go index 55974656..0bec281b 100644 --- a/exchanges/binance/binance_mock_test.go +++ b/exchanges/binance/binance_mock_test.go @@ -33,6 +33,7 @@ func TestMain(m *testing.M) { binanceConfig.API.Credentials.Key = apiKey binanceConfig.API.Credentials.Secret = apiSecret b.SetDefaults() + b.Websocket = sharedtestvalues.NewTestWebsocket() err = b.Setup(binanceConfig) if err != nil { log.Fatal("Binance setup error", err) @@ -45,7 +46,6 @@ func TestMain(m *testing.M) { b.HTTPClient = newClient b.API.Endpoints.URL = serverDetails - b.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride() log.Printf(sharedtestvalues.MockTesting, b.Name, b.API.Endpoints.URL) os.Exit(m.Run()) } diff --git a/exchanges/binance/binance_test.go b/exchanges/binance/binance_test.go index bfd2fb69..6fa58ab7 100644 --- a/exchanges/binance/binance_test.go +++ b/exchanges/binance/binance_test.go @@ -206,7 +206,7 @@ func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { var feeBuilder = setFeeBuilder() b.GetFeeByType(feeBuilder) - if !areTestAPIKeysSet() { + if !areTestAPIKeysSet() || mockTests { if feeBuilder.FeeType != exchange.OfflineTradeFee { t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, feeBuilder.FeeType) } @@ -439,6 +439,7 @@ func TestCancelExchangeOrder(t *testing.T) { WalletAddress: core.BitcoinDonationAddress, AccountID: "1", Pair: currency.NewPair(currency.LTC, currency.BTC), + AssetType: asset.Spot, } err := b.CancelOrder(orderCancellation) @@ -904,10 +905,13 @@ func TestExecutionTypeToOrderStatus(t *testing.T) { } func TestGetHistoricCandles(t *testing.T) { - currencyPair := currency.NewPairFromString("BTCUSDT") + currencyPair, err := currency.NewPairFromString("BTCUSDT") + if err != nil { + t.Fatal(err) + } startTime := time.Unix(1546300800, 0) end := time.Unix(1577836799, 0) - _, err := b.GetHistoricCandles(currencyPair, asset.Spot, startTime, end, kline.OneDay) + _, err = b.GetHistoricCandles(currencyPair, asset.Spot, startTime, end, kline.OneDay) if err != nil { t.Fatal(err) } @@ -919,10 +923,13 @@ func TestGetHistoricCandles(t *testing.T) { } func TestGetHistoricCandlesExtended(t *testing.T) { - currencyPair := currency.NewPairFromString("BTCUSDT") + currencyPair, err := currency.NewPairFromString("BTCUSDT") + if err != nil { + t.Fatal(err) + } startTime := time.Unix(1546300800, 0) end := time.Unix(1577836799, 0) - _, err := b.GetHistoricCandlesExtended(currencyPair, asset.Spot, startTime, end, kline.OneDay) + _, err = b.GetHistoricCandlesExtended(currencyPair, asset.Spot, startTime, end, kline.OneDay) if err != nil { t.Fatal(err) } diff --git a/exchanges/binance/binance_types.go b/exchanges/binance/binance_types.go index 9a9aff26..b3516e33 100644 --- a/exchanges/binance/binance_types.go +++ b/exchanges/binance/binance_types.go @@ -25,15 +25,19 @@ type ExchangeInfo struct { } `json:"rateLimits"` ExchangeFilters interface{} `json:"exchangeFilters"` Symbols []struct { - Symbol string `json:"symbol"` - Status string `json:"status"` - BaseAsset string `json:"baseAsset"` - BaseAssetPrecision int `json:"baseAssetPrecision"` - QuoteAsset string `json:"quoteAsset"` - QuotePrecision int `json:"quotePrecision"` - OrderTypes []string `json:"orderTypes"` - IcebergAllowed bool `json:"icebergAllowed"` - Filters []struct { + Symbol string `json:"symbol"` + Status string `json:"status"` + BaseAsset string `json:"baseAsset"` + BaseAssetPrecision int `json:"baseAssetPrecision"` + QuoteAsset string `json:"quoteAsset"` + QuotePrecision int `json:"quotePrecision"` + OrderTypes []string `json:"orderTypes"` + IcebergAllowed bool `json:"icebergAllowed"` + OCOAllowed bool `json:"ocoAllowed"` + QuoteOrderQtyMarketAllowed bool `json:"quoteOrderQtyMarketAllowed"` + IsSpotTradingAllowed bool `json:"isSpotTradingAllowed"` + IsMarginTradingAllowed bool `json:"isMarginTradingAllowed"` + Filters []struct { FilterType string `json:"filterType"` MinPrice float64 `json:"minPrice,string"` MaxPrice float64 `json:"maxPrice,string"` @@ -594,89 +598,111 @@ type UserAccountStream struct { } type wsAccountInfo struct { - CanDeposit bool `json:"D"` - CanTrade bool `json:"T"` - CanWithdraw bool `json:"W"` - EventTime int64 `json:"E"` - LastUpdated int64 `json:"u"` - BuyerCommission float64 `json:"b"` - MakerCommission float64 `json:"m"` - SellerCommission float64 `json:"s"` - TakerCommission float64 `json:"t"` - EventType string `json:"e"` - Currencies []struct { - Asset string `json:"a"` - Available float64 `json:"f,string"` - Locked float64 `json:"l,string"` - } `json:"B"` + Stream string `json:"stream"` + Data struct { + CanDeposit bool `json:"D"` + CanTrade bool `json:"T"` + CanWithdraw bool `json:"W"` + EventTime int64 `json:"E"` + LastUpdated int64 `json:"u"` + BuyerCommission float64 `json:"b"` + MakerCommission float64 `json:"m"` + SellerCommission float64 `json:"s"` + TakerCommission float64 `json:"t"` + EventType string `json:"e"` + Currencies []struct { + Asset string `json:"a"` + Available float64 `json:"f,string"` + Locked float64 `json:"l,string"` + } `json:"B"` + } `json:"data"` } type wsAccountPosition struct { - Currencies []struct { - Asset string `json:"a"` - Available float64 `json:"f,string"` - Locked float64 `json:"l,string"` - } `json:"B"` - EventTime int64 `json:"E"` - LastUpdated int64 `json:"u"` - EventType string `json:"e"` + Stream string `json:"stream"` + Data struct { + Currencies []struct { + Asset string `json:"a"` + Available float64 `json:"f,string"` + Locked float64 `json:"l,string"` + } `json:"B"` + EventTime int64 `json:"E"` + LastUpdated int64 `json:"u"` + EventType string `json:"e"` + } `json:"data"` } type wsBalanceUpdate struct { - EventTime int64 `json:"E"` - ClearTime int64 `json:"T"` - BalanceDelta float64 `json:"d,string"` - Asset string `json:"a"` - EventType string `json:"e"` + Stream string `json:"stream"` + Data struct { + EventTime int64 `json:"E"` + ClearTime int64 `json:"T"` + BalanceDelta float64 `json:"d,string"` + Asset string `json:"a"` + EventType string `json:"e"` + } `json:"data"` } type wsOrderUpdate struct { - ClientOrderID string `json:"C"` - EventTime int64 `json:"E"` - IcebergQuantity float64 `json:"F,string"` - LastExecutedPrice float64 `json:"L,string"` - CommissionAsset float64 `json:"N"` - OrderCreationTime int64 `json:"O"` - StopPrice float64 `json:"P,string"` - QuoteOrderQuantity float64 `json:"Q,string"` - Side string `json:"S"` - TransactionTime int64 `json:"T"` - OrderStatus string `json:"X"` - LastQuoteAssetTransactedQuantity float64 `json:"Y,string"` - CumulativeQuoteTransactedQuantity float64 `json:"Z,string"` - CancelledClientOrderID string `json:"c"` - EventType string `json:"e"` - TimeInForce string `json:"f"` - OrderListID int64 `json:"g"` - OrderID int64 `json:"i"` - LastExecutedQuantity float64 `json:"l,string"` - IsMaker bool `json:"m"` - Commission float64 `json:"n,string"` - OrderType string `json:"o"` - Price float64 `json:"p,string"` - Quantity float64 `json:"q,string"` - RejectionReason string `json:"r"` - Symbol string `json:"s"` - TradeID int64 `json:"t"` - IsOnOrderBook bool `json:"w"` - CurrentExecutionType string `json:"x"` - CumulativeFilledQuantity float64 `json:"z,string"` + Stream string `json:"stream"` + Data struct { + ClientOrderID string `json:"C"` + EventTime int64 `json:"E"` + IcebergQuantity float64 `json:"F,string"` + LastExecutedPrice float64 `json:"L,string"` + CommissionAsset float64 `json:"N"` + OrderCreationTime int64 `json:"O"` + StopPrice float64 `json:"P,string"` + QuoteOrderQuantity float64 `json:"Q,string"` + Side string `json:"S"` + TransactionTime int64 `json:"T"` + OrderStatus string `json:"X"` + LastQuoteAssetTransactedQuantity float64 `json:"Y,string"` + CumulativeQuoteTransactedQuantity float64 `json:"Z,string"` + CancelledClientOrderID string `json:"c"` + EventType string `json:"e"` + TimeInForce string `json:"f"` + OrderListID int64 `json:"g"` + OrderID int64 `json:"i"` + LastExecutedQuantity float64 `json:"l,string"` + IsMaker bool `json:"m"` + Commission float64 `json:"n,string"` + OrderType string `json:"o"` + Price float64 `json:"p,string"` + Quantity float64 `json:"q,string"` + RejectionReason string `json:"r"` + Symbol string `json:"s"` + TradeID int64 `json:"t"` + IsOnOrderBook bool `json:"w"` + CurrentExecutionType string `json:"x"` + CumulativeFilledQuantity float64 `json:"z,string"` + } `json:"data"` } -type wsListStauts struct { - ListClientOrderID string `json:"C"` - EventTime int64 `json:"E"` - ListOrderStatus string `json:"L"` - Orders []struct { - ClientOrderID string `json:"c"` - OrderID int64 `json:"i"` - Symbol string `json:"s"` - } `json:"O"` - TransactionTime int64 `json:"T"` - ContingencyType string `json:"c"` - EventType string `json:"e"` - OrderListID int64 `json:"g"` - ListStatusType string `json:"l"` - RejectionReason string `json:"r"` - Symbol string `json:"s"` +type wsListStatus struct { + Stream string `json:"stream"` + Data struct { + ListClientOrderID string `json:"C"` + EventTime int64 `json:"E"` + ListOrderStatus string `json:"L"` + Orders []struct { + ClientOrderID string `json:"c"` + OrderID int64 `json:"i"` + Symbol string `json:"s"` + } `json:"O"` + TransactionTime int64 `json:"T"` + ContingencyType string `json:"c"` + EventType string `json:"e"` + OrderListID int64 `json:"g"` + ListStatusType string `json:"l"` + RejectionReason string `json:"r"` + Symbol string `json:"s"` + } `json:"data"` +} + +// WsPayload defines the payload through the websocket connection +type WsPayload struct { + Method string `json:"method"` + Params []string `json:"params"` + ID int64 `json:"id"` } diff --git a/exchanges/binance/binance_websocket.go b/exchanges/binance/binance_websocket.go index 260af14d..2b58b288 100644 --- a/exchanges/binance/binance_websocket.go +++ b/exchanges/binance/binance_websocket.go @@ -14,23 +14,23 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream/buffer" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook" "github.com/thrasher-corp/gocryptotrader/log" ) const ( - binanceDefaultWebsocketURL = "wss://stream.binance.com:9443" + binanceDefaultWebsocketURL = "wss://stream.binance.com:9443/stream" pingDelay = time.Minute * 9 ) var listenKey string -// WsConnect intiates a websocket connection +// WsConnect initiates a websocket connection func (b *Binance) WsConnect() error { if !b.Websocket.IsEnabled() || !b.IsEnabled() { - return errors.New(wshandler.WebsocketNotEnabled) + return errors.New(stream.WebsocketNotEnabled) } var dialer websocket.Dialer @@ -39,54 +39,43 @@ func (b *Binance) WsConnect() error { listenKey, err = b.GetWsAuthStreamKey() if err != nil { b.Websocket.SetCanUseAuthenticatedEndpoints(false) - log.Errorf(log.ExchangeSys, "%v unable to connect to authenticated Websocket. Error: %s", b.Name, err) + log.Errorf(log.ExchangeSys, + "%v unable to connect to authenticated Websocket. Error: %s", + b.Name, + err) + } else { + // cleans on failed connection + clean := strings.Split(b.Websocket.GetWebsocketURL(), "?streams=") + authPayload := clean[0] + "?streams=" + listenKey + err = b.Websocket.SetWebsocketURL(authPayload, false, false) + if err != nil { + return err + } } } - pairs := b.GetEnabledPairs(asset.Spot).Strings() - tick := strings.ToLower( - strings.Replace( - strings.Join(pairs, "@ticker/"), "-", "", -1)) + "@ticker" - trade := strings.ToLower( - strings.Replace( - strings.Join(pairs, "@trade/"), "-", "", -1)) + "@trade" - kline := strings.ToLower( - strings.Replace( - strings.Join(pairs, "@kline_1m/"), "-", "", -1)) + "@kline_1m" - depth := strings.ToLower( - strings.Replace( - strings.Join(pairs, "@depth/"), "-", "", -1)) + "@depth" - - wsurl := b.Websocket.GetWebsocketURL() + - "/stream?streams=" + - tick + - "/" + - trade + - "/" + - kline + - "/" + - depth - if listenKey != "" { - wsurl += "/" + - listenKey - } - - b.WebsocketConn.URL = wsurl - b.WebsocketConn.Verbose = b.Verbose - - err = b.WebsocketConn.Dial(&dialer, http.Header{}) + err = b.Websocket.Conn.Dial(&dialer, http.Header{}) if err != nil { return fmt.Errorf("%v - Unable to connect to Websocket. Error: %s", b.Name, err) } - b.WebsocketConn.SetupPingHandler(wshandler.WebsocketPingHandler{ + + if b.Websocket.CanUseAuthenticatedEndpoints() { + go b.KeepAuthKeyAlive() + } + + b.Websocket.Conn.SetupPingHandler(stream.PingHandler{ UseGorillaHandler: true, MessageType: websocket.PongMessage, Delay: pingDelay, }) - enabledPairs := b.GetEnabledPairs(asset.Spot) + enabledPairs, err := b.GetEnabledPairs(asset.Spot) + if err != nil { + return err + } + for i := range enabledPairs { err = b.SeedLocalCache(enabledPairs[i]) if err != nil { @@ -95,17 +84,19 @@ func (b *Binance) WsConnect() error { } go b.wsReadData() - go b.KeepAuthKeyAlive() - return nil + + subs, err := b.GenerateSubscriptions() + if err != nil { + return err + } + return b.Websocket.SubscribeToChannels(subs) } // KeepAuthKeyAlive will continuously send messages to // keep the WS auth key active func (b *Binance) KeepAuthKeyAlive() { b.Websocket.Wg.Add(1) - defer func() { - b.Websocket.Wg.Done() - }() + defer b.Websocket.Wg.Done() ticks := time.NewTicker(time.Minute * 30) for { select { @@ -116,7 +107,8 @@ func (b *Binance) KeepAuthKeyAlive() { err := b.MaintainWsAuthStreamKey() if err != nil { b.Websocket.DataHandler <- err - log.Warnf(log.ExchangeSys, b.Name+" - Unable to renew auth websocket token, may experience shutdown") + log.Warnf(log.ExchangeSys, + b.Name+" - Unable to renew auth websocket token, may experience shutdown") } } } @@ -125,25 +117,16 @@ func (b *Binance) KeepAuthKeyAlive() { // wsReadData receives and passes on websocket messages for processing func (b *Binance) wsReadData() { b.Websocket.Wg.Add(1) - defer func() { - b.Websocket.Wg.Done() - }() - for { - select { - case <-b.Websocket.ShutdownC: - return + defer b.Websocket.Wg.Done() - default: - resp, err := b.WebsocketConn.ReadMessage() - if err != nil { - b.Websocket.ReadMessageErrors <- err - return - } - b.Websocket.TrafficAlert <- struct{}{} - err = b.wsHandleData(resp.Raw) - if err != nil { - b.Websocket.DataHandler <- err - } + for { + resp := b.Websocket.Conn.ReadMessage() + if resp.Raw == nil { + return + } + err := b.wsHandleData(resp.Raw) + if err != nil { + b.Websocket.DataHandler <- err } } } @@ -163,112 +146,122 @@ func (b *Binance) wsHandleData(respRaw []byte) error { return nil } } - if e, ok := multiStreamData["e"].(string); ok { - switch e { - case "outboundAccountInfo": - var data wsAccountInfo - err := json.Unmarshal(respRaw, &data) - if err != nil { - return fmt.Errorf("%v - Could not convert to outboundAccountInfo structure %s", - b.Name, - err) - } - b.Websocket.DataHandler <- data - case "outboundAccountPosition": - var data wsAccountPosition - err := json.Unmarshal(respRaw, &data) - if err != nil { - return fmt.Errorf("%v - Could not convert to outboundAccountPosition structure %s", - b.Name, - err) - } - b.Websocket.DataHandler <- data - case "balanceUpdate": - var data wsBalanceUpdate - err := json.Unmarshal(respRaw, &data) - if err != nil { - return fmt.Errorf("%v - Could not convert to balanceUpdate structure %s", - b.Name, - err) - } - b.Websocket.DataHandler <- data - case "executionReport": - var data wsOrderUpdate - err := json.Unmarshal(respRaw, &data) - if err != nil { - return fmt.Errorf("%v - Could not convert to executionReport structure %s", - b.Name, - err) - } - var orderID = strconv.FormatInt(data.OrderID, 10) - oType, err := order.StringToOrderType(data.OrderType) - if err != nil { - b.Websocket.DataHandler <- order.ClassificationError{ - Exchange: b.Name, - OrderID: orderID, - Err: err, + if newdata, ok := multiStreamData["data"].(map[string]interface{}); ok { + if e, ok := newdata["e"].(string); ok { + switch e { + case "outboundAccountInfo": + var data wsAccountInfo + err := json.Unmarshal(respRaw, &data) + if err != nil { + return fmt.Errorf("%v - Could not convert to outboundAccountInfo structure %s", + b.Name, + err) } - } - var oSide order.Side - oSide, err = order.StringToOrderSide(data.Side) - if err != nil { - b.Websocket.DataHandler <- order.ClassificationError{ - Exchange: b.Name, - OrderID: orderID, - Err: err, + b.Websocket.DataHandler <- data + case "outboundAccountPosition": + var data wsAccountPosition + err := json.Unmarshal(respRaw, &data) + if err != nil { + return fmt.Errorf("%v - Could not convert to outboundAccountPosition structure %s", + b.Name, + err) } - } - var oStatus order.Status - oStatus, err = stringToOrderStatus(data.CurrentExecutionType) - if err != nil { - b.Websocket.DataHandler <- order.ClassificationError{ - Exchange: b.Name, - OrderID: orderID, - Err: err, + b.Websocket.DataHandler <- data + case "balanceUpdate": + var data wsBalanceUpdate + err := json.Unmarshal(respRaw, &data) + if err != nil { + return fmt.Errorf("%v - Could not convert to balanceUpdate structure %s", + b.Name, + err) } + b.Websocket.DataHandler <- data + case "executionReport": + var data wsOrderUpdate + err := json.Unmarshal(respRaw, &data) + if err != nil { + return fmt.Errorf("%v - Could not convert to executionReport structure %s", + b.Name, + err) + } + var orderID = strconv.FormatInt(data.Data.OrderID, 10) + oType, err := order.StringToOrderType(data.Data.OrderType) + if err != nil { + b.Websocket.DataHandler <- order.ClassificationError{ + Exchange: b.Name, + OrderID: orderID, + Err: err, + } + } + var oSide order.Side + oSide, err = order.StringToOrderSide(data.Data.Side) + if err != nil { + b.Websocket.DataHandler <- order.ClassificationError{ + Exchange: b.Name, + OrderID: orderID, + Err: err, + } + } + var oStatus order.Status + oStatus, err = stringToOrderStatus(data.Data.CurrentExecutionType) + if err != nil { + b.Websocket.DataHandler <- order.ClassificationError{ + Exchange: b.Name, + OrderID: orderID, + Err: err, + } + } + var p currency.Pair + var a asset.Item + p, a, err = b.GetRequestFormattedPairAndAssetType(data.Data.Symbol) + if err != nil { + return err + } + b.Websocket.DataHandler <- &order.Detail{ + Price: data.Data.Price, + Amount: data.Data.Quantity, + ExecutedAmount: data.Data.CumulativeFilledQuantity, + RemainingAmount: data.Data.Quantity - data.Data.CumulativeFilledQuantity, + Exchange: b.Name, + ID: orderID, + Type: oType, + Side: oSide, + Status: oStatus, + AssetType: a, + Date: time.Unix(0, data.Data.OrderCreationTime*int64(time.Millisecond)), + Pair: p, + } + case "listStatus": + var data wsListStatus + err := json.Unmarshal(respRaw, &data) + if err != nil { + return fmt.Errorf("%v - Could not convert to listStatus structure %s", + b.Name, + err) + } + b.Websocket.DataHandler <- data } - var p currency.Pair - var a asset.Item - p, a, err = b.GetRequestFormattedPairAndAssetType(data.Symbol) - if err != nil { - return err - } - b.Websocket.DataHandler <- &order.Detail{ - Price: data.Price, - Amount: data.Quantity, - ExecutedAmount: data.CumulativeFilledQuantity, - RemainingAmount: data.Quantity - data.CumulativeFilledQuantity, - Exchange: b.Name, - ID: orderID, - Type: oType, - Side: oSide, - Status: oStatus, - AssetType: a, - Date: time.Unix(0, data.OrderCreationTime*int64(time.Millisecond)), - Pair: p, - } - case "listStatus": - var data wsListStauts - err := json.Unmarshal(respRaw, &data) - if err != nil { - return fmt.Errorf("%v - Could not convert to listStatus structure %s", - b.Name, - err) - } - b.Websocket.DataHandler <- data - default: - b.Websocket.DataHandler <- wshandler.UnhandledMessageWarning{Message: b.Name + wshandler.UnhandledMessage + string(respRaw)} - return nil } } - if stream, ok := multiStreamData["stream"].(string); ok { - streamType := strings.Split(stream, "@") + if wsStream, ok := multiStreamData["stream"].(string); ok { + streamType := strings.Split(wsStream, "@") if len(streamType) > 1 { if data, ok := multiStreamData["data"]; ok { rawData, err := json.Marshal(data) if err != nil { return err } + + pairs, err := b.GetEnabledPairs(asset.Spot) + if err != nil { + return err + } + + format, err := b.GetPairFormat(asset.Spot, true) + if err != nil { + return err + } + switch streamType[1] { case "trade": var trade TradeStream @@ -293,14 +286,18 @@ func (b *Binance) wsHandleData(respRaw []byte) error { err) } - b.Websocket.DataHandler <- wshandler.TradeData{ - CurrencyPair: currency.NewPairFromFormattedPairs(trade.Symbol, b.GetEnabledPairs(asset.Spot), - b.GetPairFormat(asset.Spot, true)), - Timestamp: time.Unix(0, trade.TimeStamp*int64(time.Millisecond)), - Price: price, - Amount: amount, - Exchange: b.Name, - AssetType: asset.Spot, + pair, err := currency.NewPairFromFormattedPairs(trade.Symbol, pairs, format) + if err != nil { + return err + } + + b.Websocket.DataHandler <- stream.TradeData{ + CurrencyPair: pair, + Timestamp: time.Unix(0, trade.TimeStamp*int64(time.Millisecond)), + Price: price, + Amount: amount, + Exchange: b.Name, + AssetType: asset.Spot, } case "ticker": var t TickerStream @@ -311,6 +308,11 @@ func (b *Binance) wsHandleData(respRaw []byte) error { err.Error()) } + pair, err := currency.NewPairFromFormattedPairs(t.Symbol, pairs, format) + if err != nil { + return err + } + b.Websocket.DataHandler <- &ticker.Price{ ExchangeName: b.Name, Open: t.OpenPrice, @@ -324,8 +326,7 @@ func (b *Binance) wsHandleData(respRaw []byte) error { Last: t.LastPrice, LastUpdated: time.Unix(0, t.EventTime*int64(time.Millisecond)), AssetType: asset.Spot, - Pair: currency.NewPairFromFormattedPairs(t.Symbol, b.GetEnabledPairs(asset.Spot), - b.GetPairFormat(asset.Spot, true)), + Pair: pair, } case "kline_1m", "kline_3m", "kline_5m", "kline_15m", "kline_30m", "kline_1h", "kline_2h", "kline_4h", "kline_6h", "kline_8h", "kline_12h", "kline_1d", "kline_3d", "kline_1w", "kline_1M": @@ -337,10 +338,14 @@ func (b *Binance) wsHandleData(respRaw []byte) error { err) } - b.Websocket.DataHandler <- wshandler.KlineData{ - Timestamp: time.Unix(0, kline.EventTime*int64(time.Millisecond)), - Pair: currency.NewPairFromFormattedPairs(kline.Symbol, b.GetEnabledPairs(asset.Spot), - b.GetPairFormat(asset.Spot, true)), + pair, err := currency.NewPairFromFormattedPairs(kline.Symbol, pairs, format) + if err != nil { + return err + } + + b.Websocket.DataHandler <- stream.KlineData{ + Timestamp: time.Unix(0, kline.EventTime*int64(time.Millisecond)), + Pair: pair, AssetType: asset.Spot, Exchange: b.Name, StartTime: time.Unix(0, kline.Kline.StartTime*int64(time.Millisecond)), @@ -361,22 +366,16 @@ func (b *Binance) wsHandleData(respRaw []byte) error { err) } - err = b.UpdateLocalCache(&depth) + err = b.UpdateLocalBuffer(&depth) if err != nil { return fmt.Errorf("%v - UpdateLocalCache error: %s", b.Name, err) } - - currencyPair := currency.NewPairFromFormattedPairs(depth.Pair, b.GetEnabledPairs(asset.Spot), - b.GetPairFormat(asset.Spot, true)) - b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{ - Pair: currencyPair, - Asset: asset.Spot, - Exchange: b.Name, - } default: - b.Websocket.DataHandler <- wshandler.UnhandledMessageWarning{Message: b.Name + wshandler.UnhandledMessage + string(respRaw)} + b.Websocket.DataHandler <- stream.UnhandledMessageWarning{ + Message: b.Name + stream.UnhandledMessage + string(respRaw), + } } } } @@ -403,11 +402,15 @@ func stringToOrderStatus(status string) (order.Status, error) { // SeedLocalCache seeds depth data func (b *Binance) SeedLocalCache(p currency.Pair) error { - ob, err := b.GetOrderBook( - OrderBookDataRequestParams{ - Symbol: b.FormatExchangeCurrency(p, asset.Spot).String(), - Limit: 1000, - }) + fPair, err := b.FormatExchangeCurrency(p, asset.Spot) + if err != nil { + return err + } + + ob, err := b.GetOrderBook(OrderBookDataRequestParams{ + Symbol: fPair.String(), + Limit: 1000, + }) if err != nil { return err } @@ -439,11 +442,34 @@ func (b *Binance) SeedLocalCacheWithBook(p currency.Pair, orderbookNew *OrderBoo return b.Websocket.Orderbook.LoadSnapshot(&newOrderBook) } -// UpdateLocalCache updates and returns the most recent iteration of the orderbook -func (b *Binance) UpdateLocalCache(wsdp *WebsocketDepthStream) error { - currencyPair := currency.NewPairFromFormattedPairs(wsdp.Pair, b.GetEnabledPairs(asset.Spot), - b.GetPairFormat(asset.Spot, true)) +// UpdateLocalBuffer updates and returns the most recent iteration of the orderbook +func (b *Binance) UpdateLocalBuffer(wsdp *WebsocketDepthStream) error { + enabledPairs, err := b.GetEnabledPairs(asset.Spot) + if err != nil { + return err + } + + format, err := b.GetPairFormat(asset.Spot, true) + if err != nil { + return err + } + + currencyPair, err := currency.NewPairFromFormattedPairs(wsdp.Pair, + enabledPairs, + format) + if err != nil { + return err + } + currentBook := b.Websocket.Orderbook.GetOrderbook(currencyPair, asset.Spot) + if currentBook == nil { + // Used when a pair/s is enabled while connected + err = b.SeedLocalCache(currencyPair) + if err != nil { + return err + } + currentBook = b.Websocket.Orderbook.GetOrderbook(currencyPair, asset.Spot) + } // Drop any event where u is <= lastUpdateId in the snapshot. // The first processed event should have U <= lastUpdateId+1 AND u >= lastUpdateId+1. @@ -479,7 +505,7 @@ func (b *Binance) UpdateLocalCache(wsdp *WebsocketDepthStream) error { updateAsk = append(updateAsk, orderbook.Item{Price: p, Amount: a}) } - return b.Websocket.Orderbook.Update(&wsorderbook.WebsocketOrderbookUpdate{ + return b.Websocket.Orderbook.Update(&buffer.Update{ Bids: updateBid, Asks: updateAsk, Pair: currencyPair, @@ -487,3 +513,62 @@ func (b *Binance) UpdateLocalCache(wsdp *WebsocketDepthStream) error { Asset: asset.Spot, }) } + +// GenerateSubscriptions generates the default subscription set +func (b *Binance) GenerateSubscriptions() ([]stream.ChannelSubscription, error) { + var channels = []string{"@ticker", "@trade", "@kline_1m", "@depth@100ms"} + var subscriptions []stream.ChannelSubscription + assets := b.GetAssetTypes() + for x := range assets { + pairs, err := b.GetEnabledPairs(assets[x]) + if err != nil { + return nil, err + } + + for y := range pairs { + for z := range channels { + lp := pairs[y].Lower() + lp.Delimiter = "" + subscriptions = append(subscriptions, stream.ChannelSubscription{ + Channel: lp.String() + channels[z], + Currency: pairs[y], + Asset: assets[x], + }) + } + } + } + return subscriptions, nil +} + +// Subscribe subscribes to a set of channels +func (b *Binance) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error { + payload := WsPayload{ + Method: "SUBSCRIBE", + } + + for i := range channelsToSubscribe { + payload.Params = append(payload.Params, channelsToSubscribe[i].Channel) + } + err := b.Websocket.Conn.SendJSONMessage(payload) + if err != nil { + return err + } + b.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe...) + return nil +} + +// Unsubscribe unsubscribes from a set of channels +func (b *Binance) Unsubscribe(channelsToUnsubscribe []stream.ChannelSubscription) error { + payload := WsPayload{ + Method: "UNSUBSCRIBE", + } + for i := range channelsToUnsubscribe { + payload.Params = append(payload.Params, channelsToUnsubscribe[i].Channel) + } + err := b.Websocket.Conn.SendJSONMessage(payload) + if err != nil { + return err + } + b.Websocket.RemoveSuccessfulUnsubscriptions(channelsToUnsubscribe...) + return nil +} diff --git a/exchanges/binance/binance_wrapper.go b/exchanges/binance/binance_wrapper.go index d29c9039..ce6e5891 100644 --- a/exchanges/binance/binance_wrapper.go +++ b/exchanges/binance/binance_wrapper.go @@ -18,8 +18,8 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" "github.com/thrasher-corp/gocryptotrader/log" "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) @@ -56,21 +56,23 @@ func (b *Binance) SetDefaults() { b.API.CredentialsValidator.RequiresSecret = true b.SetValues() - b.CurrencyPairs = currency.PairsManager{ - AssetTypes: asset.Items{ - asset.Spot, - }, - - UseGlobalFormat: true, - RequestFormat: ¤cy.PairFormat{ - Uppercase: true, - }, + fmt1 := currency.PairStore{ + RequestFormat: ¤cy.PairFormat{Uppercase: true}, ConfigFormat: ¤cy.PairFormat{ - Delimiter: "-", + Delimiter: currency.DashDelimiter, Uppercase: true, }, } + err := b.StoreAssetPairFormat(asset.Spot, fmt1) + if err != nil { + log.Errorln(log.ExchangeSys, err) + } + err = b.StoreAssetPairFormat(asset.Margin, fmt1) + if err != nil { + log.Errorln(log.ExchangeSys, err) + } + b.Features = exchange.Features{ Supports: exchange.FeaturesSupported{ REST: true, @@ -145,7 +147,7 @@ func (b *Binance) SetDefaults() { b.API.Endpoints.URLDefault = apiURL b.API.Endpoints.URL = b.API.Endpoints.URLDefault - b.Websocket = wshandler.New() + b.Websocket = stream.New() b.API.Endpoints.WebsocketURL = binanceDefaultWebsocketURL b.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit b.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout @@ -164,40 +166,31 @@ func (b *Binance) Setup(exch *config.ExchangeConfig) error { return err } - err = b.Websocket.Setup( - &wshandler.WebsocketSetup{ - Enabled: exch.Features.Enabled.Websocket, - Verbose: exch.Verbose, - AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, - WebsocketTimeout: exch.WebsocketTrafficTimeout, - DefaultURL: binanceDefaultWebsocketURL, - ExchangeName: exch.Name, - RunningURL: exch.API.Endpoints.WebsocketURL, - Connector: b.WsConnect, - Features: &b.Features.Supports.WebsocketCapabilities, - }) - + err = b.Websocket.Setup(&stream.WebsocketSetup{ + Enabled: exch.Features.Enabled.Websocket, + Verbose: exch.Verbose, + AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, + WebsocketTimeout: exch.WebsocketTrafficTimeout, + DefaultURL: binanceDefaultWebsocketURL, + ExchangeName: exch.Name, + RunningURL: exch.API.Endpoints.WebsocketURL, + Connector: b.WsConnect, + Subscriber: b.Subscribe, + UnSubscriber: b.Unsubscribe, + GenerateSubscriptions: b.GenerateSubscriptions, + Features: &b.Features.Supports.WebsocketCapabilities, + OrderbookBufferLimit: exch.WebsocketOrderbookBufferLimit, + SortBuffer: true, + SortBufferByUpdateIDs: true, + }) if err != nil { return err } - b.WebsocketConn = &wshandler.WebsocketConnection{ - ExchangeName: b.Name, - URL: b.Websocket.GetWebsocketURL(), - ProxyURL: b.Websocket.GetProxyAddress(), - Verbose: b.Verbose, + return b.Websocket.SetupNewConnection(stream.ConnectionSetup{ ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout, ResponseMaxLimit: exch.WebsocketResponseMaxLimit, - } - - b.Websocket.Orderbook.Setup( - exch.WebsocketOrderbookBufferLimit, - false, - true, - true, - false, - exch.Name) - return nil + }) } // Start starts the Binance go routine @@ -221,30 +214,59 @@ func (b *Binance) Run() { } forceUpdate := false - delim := b.GetPairFormat(asset.Spot, false).Delimiter - if !common.StringDataContains(b.GetEnabledPairs(asset.Spot).Strings(), delim) || - !common.StringDataContains(b.GetAvailablePairs(asset.Spot).Strings(), delim) { - enabledPairs := currency.NewPairsFromStrings( - []string{currency.BTC.String() + delim + currency.USDT.String()}, - ) - log.Warn(log.ExchangeSys, - "Available pairs for Binance reset due to config upgrade, please enable the ones you would like to use again") - forceUpdate = true + format, err := b.GetPairFormat(asset.Spot, false) + if err != nil { + log.Errorf(log.ExchangeSys, "%s failed to get enabled currencies. Err %s\n", + b.Name, + err) + return + } + pairs, err := b.GetEnabledPairs(asset.Spot) + if err != nil { + log.Errorf(log.ExchangeSys, "%s failed to get enabled currencies. Err %s\n", + b.Name, + err) + return + } - err := b.UpdatePairs(enabledPairs, asset.Spot, true, true) + avail, err := b.GetAvailablePairs(asset.Spot) + if err != nil { + log.Errorf(log.ExchangeSys, "%s failed to get available currencies. Err %s\n", + b.Name, + err) + return + } + + if !common.StringDataContains(pairs.Strings(), format.Delimiter) || + !common.StringDataContains(avail.Strings(), format.Delimiter) { + var enabledPairs currency.Pairs + enabledPairs, err = currency.NewPairsFromStrings([]string{ + currency.BTC.String() + + format.Delimiter + + currency.USDT.String()}) if err != nil { - log.Errorf(log.ExchangeSys, - "%s failed to update currencies. Err: %s\n", + log.Errorf(log.ExchangeSys, "%s failed to update currencies. Err %s\n", b.Name, err) + } else { + log.Warn(log.ExchangeSys, + "Available pairs for Binance reset due to config upgrade, please enable the ones you would like to use again") + forceUpdate = true + + err = b.UpdatePairs(enabledPairs, asset.Spot, true, true) + if err != nil { + log.Errorf(log.ExchangeSys, + "%s failed to update currencies. Err: %s\n", + b.Name, + err) + } } } if !b.GetEnabledFeatures().AutoPairUpdates && !forceUpdate { return } - - err := b.UpdateTradablePairs(forceUpdate) + err = b.UpdateTradablePairs(forceUpdate) if err != nil { log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", @@ -254,36 +276,55 @@ func (b *Binance) Run() { } // FetchTradablePairs returns a list of the exchanges tradable pairs -func (b *Binance) FetchTradablePairs(asset asset.Item) ([]string, error) { - var validCurrencyPairs []string - +func (b *Binance) FetchTradablePairs(a asset.Item) ([]string, error) { info, err := b.GetExchangeInfo() if err != nil { return nil, err } + format, err := b.GetPairFormat(a, false) + if err != nil { + return nil, err + } + + var pairs []string for x := range info.Symbols { if info.Symbols[x].Status == "TRADING" { - validCurrencyPairs = append(validCurrencyPairs, info.Symbols[x].BaseAsset+ - b.GetPairFormat(asset, false).Delimiter+ - info.Symbols[x].QuoteAsset) + pair := info.Symbols[x].BaseAsset + + format.Delimiter + + info.Symbols[x].QuoteAsset + if a == asset.Spot && info.Symbols[x].IsSpotTradingAllowed { + pairs = append(pairs, pair) + } + if a == asset.Margin && info.Symbols[x].IsMarginTradingAllowed { + pairs = append(pairs, pair) + } } } - return validCurrencyPairs, nil + return pairs, nil } // UpdateTradablePairs updates the exchanges available pairs and stores // them in the exchanges config func (b *Binance) UpdateTradablePairs(forceUpdate bool) error { - pairs, err := b.FetchTradablePairs(asset.Spot) - if err != nil { - return err - } + assetTypes := b.GetAssetTypes() + for i := range assetTypes { + p, err := b.FetchTradablePairs(assetTypes[i]) + if err != nil { + return err + } - return b.UpdatePairs(currency.NewPairsFromStrings(pairs), - asset.Spot, - false, - forceUpdate) + pairs, err := currency.NewPairsFromStrings(p) + if err != nil { + return err + } + + err = b.UpdatePairs(pairs, assetTypes[i], false, forceUpdate) + if err != nil { + return err + } + } + return nil } // UpdateTicker updates and returns the ticker for a currency pair @@ -292,28 +333,39 @@ func (b *Binance) UpdateTicker(p currency.Pair, assetType asset.Item) (*ticker.P if err != nil { return nil, err } - pairs := b.GetEnabledPairs(assetType) + + pairs, err := b.GetEnabledPairs(assetType) + if err != nil { + return nil, err + } + for i := range pairs { for y := range tick { - pairFmt := b.FormatExchangeCurrency(pairs[i], assetType).String() - if tick[y].Symbol != pairFmt { + pairFmt, err := b.FormatExchangeCurrency(pairs[i], assetType) + if err != nil { + return nil, err + } + + if tick[y].Symbol != pairFmt.String() { continue } - tickerPrice := &ticker.Price{ - Last: tick[y].LastPrice, - High: tick[y].HighPrice, - Low: tick[y].LowPrice, - Bid: tick[y].BidPrice, - Ask: tick[y].AskPrice, - Volume: tick[y].Volume, - QuoteVolume: tick[y].QuoteVolume, - Open: tick[y].OpenPrice, - Close: tick[y].PrevClosePrice, - Pair: pairs[i], - } - err = ticker.ProcessTicker(b.Name, tickerPrice, assetType) + + err = ticker.ProcessTicker(&ticker.Price{ + Last: tick[y].LastPrice, + High: tick[y].HighPrice, + Low: tick[y].LowPrice, + Bid: tick[y].BidPrice, + Ask: tick[y].AskPrice, + Volume: tick[y].Volume, + QuoteVolume: tick[y].QuoteVolume, + Open: tick[y].OpenPrice, + Close: tick[y].PrevClosePrice, + Pair: pairs[i], + ExchangeName: b.Name, + AssetType: assetType, + }) if err != nil { - log.Error(log.Ticker, err) + return nil, err } } } @@ -340,13 +392,19 @@ func (b *Binance) FetchOrderbook(p currency.Pair, assetType asset.Item) (*orderb // UpdateOrderbook updates and returns the orderbook for a currency pair func (b *Binance) UpdateOrderbook(p currency.Pair, assetType asset.Item) (*orderbook.Base, error) { - orderBook := new(orderbook.Base) - orderbookNew, err := b.GetOrderBook(OrderBookDataRequestParams{Symbol: b.FormatExchangeCurrency(p, - assetType).String(), Limit: 1000}) + fpair, err := b.FormatExchangeCurrency(p, assetType) if err != nil { - return orderBook, err + return nil, err } + orderbookNew, err := b.GetOrderBook(OrderBookDataRequestParams{ + Symbol: fpair.String(), + Limit: 1000}) + if err != nil { + return nil, err + } + + orderBook := new(orderbook.Base) for x := range orderbookNew.Bids { orderBook.Bids = append(orderBook.Bids, orderbook.Item{ @@ -499,8 +557,12 @@ func (b *Binance) CancelOrder(order *order.Cancel) error { return err } - _, err = b.CancelExistingOrder(b.FormatExchangeCurrency(order.Pair, - order.AssetType).String(), + fpair, err := b.FormatExchangeCurrency(order.Pair, order.AssetType) + if err != nil { + return err + } + + _, err = b.CancelExistingOrder(fpair.String(), orderIDInt, order.AccountID) return err @@ -567,11 +629,6 @@ func (b *Binance) WithdrawFiatFundsToInternationalBank(withdrawRequest *withdraw return nil, common.ErrFunctionNotSupported } -// GetWebsocket returns a pointer to the exchange websocket -func (b *Binance) GetWebsocket() (*wshandler.Websocket, error) { - return b.Websocket, nil -} - // GetFeeByType returns an estimate of fee based on type of transaction func (b *Binance) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { if (!b.AllowAuthenticatedRequest() || b.SkipAuthCheck) && // Todo check connection status @@ -589,8 +646,13 @@ func (b *Binance) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, var orders []order.Detail for x := range req.Pairs { - resp, err := b.OpenOrders(b.FormatExchangeCurrency(req.Pairs[x], - asset.Spot).String()) + fpair, err := b.FormatExchangeCurrency(req.Pairs[x], + asset.Spot) + if err != nil { + return nil, err + } + + resp, err := b.OpenOrders(fpair.String()) if err != nil { return nil, err } @@ -600,6 +662,11 @@ func (b *Binance) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, orderType := order.Type(strings.ToUpper(resp[i].Type)) orderDate := time.Unix(0, int64(resp[i].Time)*int64(time.Millisecond)) + pair, err := currency.NewPairFromString(resp[i].Symbol) + if err != nil { + return nil, err + } + orders = append(orders, order.Detail{ Amount: resp[i].OrigQty, Date: orderDate, @@ -609,7 +676,7 @@ func (b *Binance) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, Type: orderType, Price: resp[i].Price, Status: order.Status(resp[i].Status), - Pair: currency.NewPairFromString(resp[i].Symbol), + Pair: pair, }) } } @@ -629,8 +696,11 @@ func (b *Binance) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, var orders []order.Detail for x := range req.Pairs { - resp, err := b.AllOrders(b.FormatExchangeCurrency(req.Pairs[x], - asset.Spot).String(), + fpair, err := b.FormatExchangeCurrency(req.Pairs[x], asset.Spot) + if err != nil { + return nil, err + } + resp, err := b.AllOrders(fpair.String(), "", "1000") if err != nil { @@ -646,6 +716,11 @@ func (b *Binance) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, continue } + pair, err := currency.NewPairFromString(resp[i].Symbol) + if err != nil { + return nil, err + } + orders = append(orders, order.Detail{ Amount: resp[i].OrigQty, Date: orderDate, @@ -654,7 +729,7 @@ func (b *Binance) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, Side: orderSide, Type: orderType, Price: resp[i].Price, - Pair: currency.NewPairFromString(resp[i].Symbol), + Pair: pair, Status: order.Status(resp[i].Status), }) } @@ -666,28 +741,6 @@ func (b *Binance) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, return orders, nil } -// SubscribeToWebsocketChannels appends to ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle subscribing -func (b *Binance) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - return common.ErrFunctionNotSupported -} - -// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle unsubscribing -func (b *Binance) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - return common.ErrFunctionNotSupported -} - -// GetSubscriptions returns a copied list of subscriptions -func (b *Binance) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) { - return b.Websocket.GetSubscriptions(), nil -} - -// AuthenticateWebsocket sends an authentication message to the websocket -func (b *Binance) AuthenticateWebsocket() error { - return common.ErrFunctionNotSupported -} - // ValidateCredentials validates current credentials used for wrapper // functionality func (b *Binance) ValidateCredentials() error { @@ -718,9 +771,13 @@ func (b *Binance) GetHistoricCandles(pair currency.Pair, a asset.Item, start, en return kline.Item{}, errors.New(kline.ErrRequestExceedsExchangeLimits) } + fpair, err := b.FormatExchangeCurrency(pair, a) + if err != nil { + return kline.Item{}, err + } req := KlinesRequestParams{ Interval: b.FormatExchangeKlineInterval(interval), - Symbol: b.FormatExchangeCurrency(pair, a).String(), + Symbol: fpair.String(), StartTime: start.Unix() * 1000, EndTime: end.Unix() * 1000, Limit: int(b.Features.Enabled.Kline.ResultLimit), @@ -768,11 +825,16 @@ func (b *Binance) GetHistoricCandlesExtended(pair currency.Pair, a asset.Item, s Interval: interval, } + formattedPair, err := b.FormatExchangeCurrency(pair, a) + if err != nil { + return kline.Item{}, err + } + dates := kline.CalcDateRanges(start, end, interval, b.Features.Enabled.Kline.ResultLimit) for x := range dates { req := KlinesRequestParams{ Interval: b.FormatExchangeKlineInterval(interval), - Symbol: b.FormatExchangeCurrency(pair, a).String(), + Symbol: formattedPair.String(), StartTime: dates[x].Start.UTC().Unix() * 1000, EndTime: dates[x].End.UTC().Unix() * 1000, Limit: int(b.Features.Enabled.Kline.ResultLimit), diff --git a/exchanges/bitfinex/bitfinex.go b/exchanges/bitfinex/bitfinex.go index 82cef9d4..dc079db9 100644 --- a/exchanges/bitfinex/bitfinex.go +++ b/exchanges/bitfinex/bitfinex.go @@ -17,7 +17,6 @@ import ( exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/request" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" "github.com/thrasher-corp/gocryptotrader/log" "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) @@ -83,9 +82,7 @@ const ( // Bitfinex is the overarching type across the bitfinex package type Bitfinex struct { exchange.Base - WebsocketConn *wshandler.WebsocketConnection - AuthenticatedWebsocketConn *wshandler.WebsocketConnection - WebsocketSubdChannels map[int]WebsocketChanInfo + WebsocketSubdChannels map[int]WebsocketChanInfo } // GetPlatformStatus returns the Bifinex platform status @@ -778,7 +775,7 @@ func (b *Bitfinex) WithdrawFIAT(withdrawalType, walletType string, withdrawReque // Major Upgrade needed on this function to include all query params func (b *Bitfinex) NewOrder(currencyPair, orderType string, amount, price float64, buy, hidden bool) (Order, error) { if !common.StringDataCompare(AcceptedOrderType, orderType) { - return Order{}, errors.New("order type not accepted") + return Order{}, fmt.Errorf("order type %s not accepted", orderType) } response := Order{} diff --git a/exchanges/bitfinex/bitfinex_test.go b/exchanges/bitfinex/bitfinex_test.go index 0efd5788..139ebe08 100644 --- a/exchanges/bitfinex/bitfinex_test.go +++ b/exchanges/bitfinex/bitfinex_test.go @@ -16,7 +16,6 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/kline" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" "github.com/thrasher-corp/gocryptotrader/portfolio/banking" "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) @@ -42,6 +41,7 @@ func TestMain(m *testing.M) { if err != nil { log.Fatal("Bitfinex Setup() init error") } + b.Websocket = sharedtestvalues.NewTestWebsocket() err = b.Setup(bfxConfig) if err != nil { log.Fatal("Bitfinex setup error", err) @@ -58,11 +58,32 @@ func TestMain(m *testing.M) { b.API.AuthenticatedWebsocketSupport = true } b.WebsocketSubdChannels = make(map[int]WebsocketChanInfo) - b.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride() - b.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride() os.Exit(m.Run()) } +func TestAppendOptionalDelimiter(t *testing.T) { + t.Parallel() + curr1, err := currency.NewPairFromString("BTCUSD") + if err != nil { + t.Fatal(err) + } + + b.appendOptionalDelimiter(&curr1) + if curr1.Delimiter != "" { + t.Errorf("Expected no delimiter, received %v", curr1.Delimiter) + } + curr2, err := currency.NewPairFromString("DUSK:USD") + if err != nil { + t.Fatal(err) + } + + curr2.Delimiter = "" + b.appendOptionalDelimiter(&curr2) + if curr2.Delimiter != ":" { + t.Errorf("Expected \":\" as a delimiter, received %v", curr2.Delimiter) + } +} + func TestGetPlatformStatus(t *testing.T) { t.Parallel() result, err := b.GetPlatformStatus() @@ -320,7 +341,7 @@ func TestNewOrder(t *testing.T) { _, err := b.NewOrder("BTCUSD", order.Limit.Lower(), - 1, + -1, 2, false, true) @@ -330,27 +351,17 @@ func TestNewOrder(t *testing.T) { } func TestUpdateTicker(t *testing.T) { - _, err := b.UpdateTicker(currency.NewPairFromString("BTCUSD"), asset.Spot) + pair, err := currency.NewPairFromString("BTCUSD") + if err != nil { + t.Fatal(err) + } + + _, err = b.UpdateTicker(pair, asset.Spot) if err != nil { t.Error(err) } } -func TestAppendOptionalDelimiter(t *testing.T) { - t.Parallel() - curr1 := currency.NewPairFromString("BTCUSD") - b.appendOptionalDelimiter(&curr1) - if curr1.Delimiter != "" { - t.Errorf("Expected no delimiter, received %v", curr1.Delimiter) - } - curr2 := currency.NewPairFromString("DUSK:USD") - curr2.Delimiter = "" - b.appendOptionalDelimiter(&curr2) - if curr2.Delimiter != ":" { - t.Errorf("Expected \"-\" as a delimiter, received %v", curr2.Delimiter) - } -} - func TestNewOrderMulti(t *testing.T) { if !b.ValidateAPICredentials() { t.SkipNow() @@ -762,19 +773,20 @@ func TestSubmitOrder(t *testing.T) { var orderSubmission = &order.Submit{ Pair: currency.Pair{ Delimiter: "_", - Base: currency.BTC, + Base: currency.XRP, Quote: currency.USD, }, - Side: order.Buy, - Type: order.Limit, - Price: 1, - Amount: 1, - ClientID: "meowOrder", + AssetType: asset.Spot, + Side: order.Sell, + Type: order.Limit, + Price: 1000, + Amount: 20, + ClientID: "meowOrder", } response, err := b.SubmitOrder(orderSubmission) if areTestAPIKeysSet() && err != nil { - t.Errorf("Could not cancel orders: %v", err) + t.Errorf("Could not place order: %v", err) } if areTestAPIKeysSet() && !response.IsOrderPlaced { t.Error("Order not placed") @@ -944,21 +956,12 @@ func TestGetDepositAddress(t *testing.T) { } func setupWs() { - b.AuthenticatedWebsocketConn = &wshandler.WebsocketConnection{ - ExchangeName: b.Name, - URL: authenticatedBitfinexWebsocketEndpoint, - Verbose: b.Verbose, - ResponseMaxLimit: exchange.DefaultWebsocketResponseMaxLimit, - ResponseCheckTimeout: exchange.DefaultWebsocketResponseCheckTimeout, - } var dialer websocket.Dialer - err := b.AuthenticatedWebsocketConn.Dial(&dialer, http.Header{}) + err := b.Websocket.AuthConn.Dial(&dialer, http.Header{}) if err != nil { log.Fatal(err) } - b.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride() - b.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride() - go b.wsReadData(b.AuthenticatedWebsocketConn) + go b.wsReadData(b.Websocket.AuthConn) go b.WsDataHandler() } @@ -1001,12 +1004,13 @@ func TestWsPlaceOrder(t *testing.T) { if !wsAuthExecuted { runAuth(t) } + _, err := b.WsNewOrder(&WsNewOrderRequest{ - CustomID: 1337, - Type: order.Buy.String(), - Symbol: "tBTCUSD", - Amount: 10, - Price: -10, + GroupID: 1, + Type: "EXCHANGE LIMIT", + Symbol: "tXRPUSD", + Amount: -20, + Price: 1000, }) if err != nil { t.Error(err) @@ -1169,6 +1173,24 @@ func TestWsTickerResponse(t *testing.T) { if err != nil { t.Error(err) } + b.WebsocketSubdChannels[123412] = WebsocketChanInfo{Pair: "XAUTF0:USTF0", Channel: wsTicker} + pressXToJSON = `[123412,[61.304,2228.36155358,61.305,1323.2442970500003,0.395,0.0065,61.371,50973.3020771,62.5,57.421]]` + err = b.wsHandleData([]byte(pressXToJSON)) + if err != nil { + t.Error(err) + } + b.WebsocketSubdChannels[123413] = WebsocketChanInfo{Pair: "trade:1m:tXRPUSD", Channel: wsTicker} + pressXToJSON = `[123413,[61.304,2228.36155358,61.305,1323.2442970500003,0.395,0.0065,61.371,50973.3020771,62.5,57.421]]` + err = b.wsHandleData([]byte(pressXToJSON)) + if err != nil { + t.Error(err) + } + b.WebsocketSubdChannels[123414] = WebsocketChanInfo{Pair: "trade:1m:fZRX:p30", Channel: wsTicker} + pressXToJSON = `[123414,[61.304,2228.36155358,61.305,1323.2442970500003,0.395,0.0065,61.371,50973.3020771,62.5,57.421]]` + err = b.wsHandleData([]byte(pressXToJSON)) + if err != nil { + t.Error(err) + } } func TestWsCandleResponse(t *testing.T) { @@ -1186,6 +1208,7 @@ func TestWsCandleResponse(t *testing.T) { } func TestWsOrderSnapshot(t *testing.T) { + b.WsAddSubscriptionChannel(0, "account", "N/A") pressXToJSON := `[0,"os",[[34930659963,null,1574955083558,"tETHUSD",1574955083558,1574955083573,0.201104,0.201104,"EXCHANGE LIMIT",null,null,null,0,"ACTIVE",null,null,120,0,0,0,null,null,null,0,0,null,null,null,"BFX",null,null,null]]]` err := b.wsHandleData([]byte(pressXToJSON)) if err != nil { @@ -1213,9 +1236,12 @@ func TestWsNotifications(t *testing.T) { } func TestGetHistoricCandles(t *testing.T) { - currencyPair := currency.NewPairFromString("BTCUSD") + currencyPair, err := currency.NewPairFromString("BTCUSD") + if err != nil { + t.Fatal(err) + } startTime := time.Now().Add(-time.Hour * 24) - _, err := b.GetHistoricCandles(currencyPair, asset.Spot, startTime, time.Now(), kline.OneMin) + _, err = b.GetHistoricCandles(currencyPair, asset.Spot, startTime, time.Now(), kline.OneMin) if err != nil { t.Fatal(err) } @@ -1227,9 +1253,12 @@ func TestGetHistoricCandles(t *testing.T) { } func TestGetHistoricCandlesExtended(t *testing.T) { - currencyPair := currency.NewPairFromString("TBTCUSD") + currencyPair, err := currency.NewPairFromString("TBTCUSD") + if err != nil { + t.Fatal(err) + } startTime := time.Now().Add(-time.Hour * 24) - _, err := b.GetHistoricCandlesExtended(currencyPair, asset.Spot, startTime, time.Now(), kline.OneHour) + _, err = b.GetHistoricCandlesExtended(currencyPair, asset.Spot, startTime, time.Now(), kline.OneHour) if err != nil { t.Fatal(err) } @@ -1241,42 +1270,95 @@ func TestGetHistoricCandlesExtended(t *testing.T) { } func TestFixCasing(t *testing.T) { - ret := b.fixCasing(currency.NewPairFromString("BTCUSD"), asset.Spot) + pair, err := currency.NewPairFromString("BTCUSD") + if err != nil { + t.Fatal(err) + } + ret, err := b.fixCasing(pair, asset.Spot) + if err != nil { + t.Fatal(err) + } if ret != "tBTCUSD" { t.Errorf("unexpected result: %v", ret) } - - ret = b.fixCasing(currency.NewPairFromString("TBTCUSD"), asset.Spot) + pair, err = currency.NewPairFromString("TBTCUSD") + if err != nil { + t.Fatal(err) + } + ret, err = b.fixCasing(pair, asset.Spot) + if err != nil { + t.Fatal(err) + } if ret != "tBTCUSD" { t.Errorf("unexpected result: %v", ret) } - - ret = b.fixCasing(currency.NewPairFromString("tBTCUSD"), asset.Spot) + pair, err = currency.NewPairFromString("tBTCUSD") + if err != nil { + t.Fatal(err) + } + ret, err = b.fixCasing(pair, asset.Spot) + if err != nil { + t.Fatal(err) + } + if err != nil { + t.Fatal(err) + } if ret != "tBTCUSD" { t.Errorf("unexpected result: %v", ret) } - - ret = b.fixCasing(currency.NewPairFromString("BTCUSD"), asset.Margin) + pair, err = currency.NewPairFromString("BTCUSD") + if err != nil { + t.Fatal(err) + } + ret, err = b.fixCasing(pair, asset.Margin) + if err != nil { + t.Fatal(err) + } if ret != "fBTCUSD" { t.Errorf("unexpected result: %v", ret) } - - ret = b.fixCasing(currency.NewPairFromString("BTCUSD"), asset.Spot) + pair, err = currency.NewPairFromString("BTCUSD") + if err != nil { + t.Fatal(err) + } + ret, err = b.fixCasing(pair, asset.Spot) + if err != nil { + t.Fatal(err) + } if ret != "tBTCUSD" { t.Errorf("unexpected result: %v", ret) } - - ret = b.fixCasing(currency.NewPairFromString("FUNETH"), asset.Spot) + pair, err = currency.NewPairFromString("FUNETH") + if err != nil { + t.Fatal(err) + } + ret, err = b.fixCasing(pair, asset.Spot) + if err != nil { + t.Fatal(err) + } if ret != "tFUNETH" { t.Errorf("unexpected result: %v", ret) } - - ret = b.fixCasing(currency.NewPairFromString("TNBUSD"), asset.Spot) + pair, err = currency.NewPairFromString("TNBUSD") + if err != nil { + t.Fatal(err) + } + ret, err = b.fixCasing(pair, asset.Spot) + if err != nil { + t.Fatal(err) + } if ret != "tTNBUSD" { t.Errorf("unexpected result: %v", ret) } - ret = b.fixCasing(currency.NewPairFromString("tTNBUSD"), asset.Spot) + pair, err = currency.NewPairFromString("tTNBUSD") + if err != nil { + t.Fatal(err) + } + ret, err = b.fixCasing(pair, asset.Spot) + if err != nil { + t.Fatal(err) + } if ret != "tTNBUSD" { t.Errorf("unexpected result: %v", ret) } diff --git a/exchanges/bitfinex/bitfinex_types.go b/exchanges/bitfinex/bitfinex_types.go index 6752c973..e79a1f32 100644 --- a/exchanges/bitfinex/bitfinex_types.go +++ b/exchanges/bitfinex/bitfinex_types.go @@ -374,9 +374,11 @@ type WebsocketChanInfo struct { // WebsocketBook holds booking information type WebsocketBook struct { - Price float64 ID int64 + Price float64 Amount float64 + Rate float64 + Period int64 } // WebsocketTrade holds trade information diff --git a/exchanges/bitfinex/bitfinex_websocket.go b/exchanges/bitfinex/bitfinex_websocket.go index 13dd78c0..5a227627 100644 --- a/exchanges/bitfinex/bitfinex_websocket.go +++ b/exchanges/bitfinex/bitfinex_websocket.go @@ -10,69 +10,74 @@ import ( "time" "github.com/gorilla/websocket" + "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/crypto" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream/buffer" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook" "github.com/thrasher-corp/gocryptotrader/log" ) -var comms = make(chan wshandler.WebsocketResponse) +var comms = make(chan stream.Response) // WsConnect starts a new websocket connection func (b *Bitfinex) WsConnect() error { if !b.Websocket.IsEnabled() || !b.IsEnabled() { - return errors.New(wshandler.WebsocketNotEnabled) + return errors.New(stream.WebsocketNotEnabled) } var dialer websocket.Dialer - err := b.WebsocketConn.Dial(&dialer, http.Header{}) + err := b.Websocket.Conn.Dial(&dialer, http.Header{}) if err != nil { - return fmt.Errorf("%v unable to connect to Websocket. Error: %s", b.Name, err) + return fmt.Errorf("%v unable to connect to Websocket. Error: %s", + b.Name, + err) } - go b.wsReadData(b.WebsocketConn) + go b.wsReadData(b.Websocket.Conn) if b.Websocket.CanUseAuthenticatedEndpoints() { - err = b.AuthenticatedWebsocketConn.Dial(&dialer, http.Header{}) + err = b.Websocket.AuthConn.Dial(&dialer, http.Header{}) if err != nil { - log.Errorf(log.ExchangeSys, "%v unable to connect to authenticated Websocket. Error: %s", b.Name, err) + log.Errorf(log.ExchangeSys, + "%v unable to connect to authenticated Websocket. Error: %s", + b.Name, + err) b.Websocket.SetCanUseAuthenticatedEndpoints(false) } - go b.wsReadData(b.AuthenticatedWebsocketConn) + go b.wsReadData(b.Websocket.AuthConn) err = b.WsSendAuth() if err != nil { - log.Errorf(log.ExchangeSys, "%v - authentication failed: %v\n", b.Name, err) + log.Errorf(log.ExchangeSys, + "%v - authentication failed: %v\n", + b.Name, + err) b.Websocket.SetCanUseAuthenticatedEndpoints(false) } } - b.GenerateDefaultSubscriptions() + subs, err := b.GenerateDefaultSubscriptions() + if err != nil { + return err + } go b.WsDataHandler() - return nil + return b.Websocket.SubscribeToChannels(subs) } // wsReadData receives and passes on websocket messages for processing -func (b *Bitfinex) wsReadData(ws *wshandler.WebsocketConnection) { +func (b *Bitfinex) wsReadData(ws stream.Connection) { b.Websocket.Wg.Add(1) defer b.Websocket.Wg.Done() for { - select { - case <-b.Websocket.ShutdownC: + resp := ws.ReadMessage() + if resp.Raw == nil { return - default: - resp, err := ws.ReadMessage() - if err != nil { - b.Websocket.ReadMessageErrors <- err - return - } - b.Websocket.TrafficAlert <- struct{}{} - comms <- resp } + comms <- resp } } @@ -82,8 +87,6 @@ func (b *Bitfinex) WsDataHandler() { defer b.Websocket.Wg.Done() for { select { - case <-b.Websocket.ShutdownC: - return case resp := <-comms: if resp.Type == websocket.TextMessage { err := b.wsHandleData(resp.Raw) @@ -91,6 +94,8 @@ func (b *Bitfinex) WsDataHandler() { b.Websocket.DataHandler <- err } } + case <-b.Websocket.ShutdownC: + return } } } @@ -106,12 +111,21 @@ func (b *Bitfinex) wsHandleData(respRaw []byte) error { event := d["event"] switch event { case "subscribed": - if symbol, ok := d["pair"].(string); ok { + if symbol, ok := d["symbol"].(string); ok { b.WsAddSubscriptionChannel(int(d["chanId"].(float64)), d["channel"].(string), symbol, ) } else if key, ok := d["key"].(string); ok { + // Capture trading subscriptions + contents := strings.Split(d["key"].(string), ":") + if len(contents) > 3 { + // Edge case to parse margin strings. + // map[chanId:139136 channel:candles event:subscribed key:trade:1m:tXAUTF0:USTF0] + if contents[2][0] == 't' { + key = contents[2] + ":" + contents[3] + } + } b.WsAddSubscriptionChannel(int(d["chanId"].(float64)), d["channel"].(string), key, @@ -134,6 +148,7 @@ func (b *Bitfinex) wsHandleData(respRaw []byte) error { return nil } } + chanID := int(d[0].(float64)) chanInfo, ok := b.WebsocketSubdChannels[chanID] if !ok && chanID != 0 { @@ -141,67 +156,122 @@ func (b *Bitfinex) wsHandleData(respRaw []byte) error { chanID) } + var chanAsset = asset.Spot + var pair currency.Pair + pairInfo := strings.Split(chanInfo.Pair, ":") + switch { + case len(pairInfo) >= 3: + newPair := pairInfo[2] + if newPair[0] == 'f' { + chanAsset = asset.MarginFunding + } + + pair, err = currency.NewPairFromString(newPair[1:]) + if err != nil { + return err + } + case len(pairInfo) == 1: + newPair := pairInfo[0] + if newPair[0] == 'f' { + chanAsset = asset.MarginFunding + } + + pair, err = currency.NewPairFromString(newPair[1:]) + if err != nil { + return err + } + case chanInfo.Pair != "": + if strings.Contains(chanInfo.Pair, ":") { + chanAsset = asset.Margin + } + + pair, err = currency.NewPairFromString(chanInfo.Pair[1:]) + if err != nil { + return err + } + } + switch chanInfo.Channel { case wsBook: var newOrderbook []WebsocketBook - curr := currency.NewPairFromString(chanInfo.Pair) - if obSnapBundle, ok := d[1].([]interface{}); ok { - switch id := obSnapBundle[0].(type) { - case []interface{}: - for i := range obSnapBundle { - data := obSnapBundle[i].([]interface{}) + obSnapBundle, ok := d[1].([]interface{}) + if !ok { + return errors.New("orderbook interface cast failed") + } + + switch id := obSnapBundle[0].(type) { + case []interface{}: + for i := range obSnapBundle { + data := obSnapBundle[i].([]interface{}) + if len(data) == 4 { + newOrderbook = append(newOrderbook, WebsocketBook{ + ID: int64(data[0].(float64)), + Period: int64(data[1].(float64)), + Rate: data[2].(float64), + Amount: data[3].(float64)}) + } else { newOrderbook = append(newOrderbook, WebsocketBook{ ID: int64(data[0].(float64)), Price: data[1].(float64), Amount: data[2].(float64)}) } - err := b.WsInsertSnapshot(curr, - asset.Spot, - newOrderbook) - if err != nil { - return fmt.Errorf("bitfinex_websocket.go inserting snapshot error: %s", - err) - } - case float64: + } + err := b.WsInsertSnapshot(pair, chanAsset, newOrderbook) + if err != nil { + return fmt.Errorf("bitfinex_websocket.go inserting snapshot error: %s", + err) + } + case float64: + if len(obSnapBundle) == 4 { + newOrderbook = append(newOrderbook, WebsocketBook{ + ID: int64(id), + Period: int64(obSnapBundle[1].(float64)), + Rate: obSnapBundle[2].(float64), + Amount: obSnapBundle[3].(float64)}) + } else { newOrderbook = append(newOrderbook, WebsocketBook{ ID: int64(id), Price: obSnapBundle[1].(float64), Amount: obSnapBundle[2].(float64)}) - err := b.WsUpdateOrderbook(curr, - asset.Spot, - newOrderbook) - if err != nil { - return fmt.Errorf("bitfinex_websocket.go inserting snapshot error: %s", - err) - } + } + + err := b.WsUpdateOrderbook(pair, chanAsset, newOrderbook) + if err != nil { + return fmt.Errorf("bitfinex_websocket.go updating orderbook error: %s", + err) } } + return nil case wsCandles: - curr := currency.NewPairFromString(chanInfo.Pair) if candleBundle, ok := d[1].([]interface{}); ok { if len(candleBundle) == 0 { return nil } + switch candleData := candleBundle[0].(type) { case []interface{}: - b.Websocket.DataHandler <- wshandler.KlineData{ - Timestamp: time.Unix(0, int64(candleData[0].(float64))), - Exchange: b.Name, - AssetType: asset.Spot, - Pair: curr, - OpenPrice: candleData[1].(float64), - ClosePrice: candleData[2].(float64), - HighPrice: candleData[3].(float64), - LowPrice: candleData[4].(float64), - Volume: candleData[5].(float64), + for i := range candleBundle { + element := candleBundle[i].([]interface{}) + b.Websocket.DataHandler <- stream.KlineData{ + Timestamp: time.Unix(0, int64(element[0].(float64))*int64(time.Millisecond)), + Exchange: b.Name, + AssetType: chanAsset, + Pair: pair, + OpenPrice: element[1].(float64), + ClosePrice: element[2].(float64), + HighPrice: element[3].(float64), + LowPrice: element[4].(float64), + Volume: element[5].(float64), + } } + case float64: - b.Websocket.DataHandler <- wshandler.KlineData{ - Timestamp: time.Unix(0, int64(candleData)), + b.Websocket.DataHandler <- stream.KlineData{ + Timestamp: time.Unix(0, int64(candleData)*int64(time.Millisecond)), Exchange: b.Name, - AssetType: asset.Spot, - Pair: curr, + AssetType: chanAsset, + Pair: pair, OpenPrice: candleBundle[1].(float64), ClosePrice: candleBundle[2].(float64), HighPrice: candleBundle[3].(float64), @@ -221,8 +291,8 @@ func (b *Bitfinex) wsHandleData(respRaw []byte) error { Volume: tickerData[7].(float64), High: tickerData[8].(float64), Low: tickerData[9].(float64), - AssetType: asset.Spot, - Pair: currency.NewPairFromString(chanInfo.Pair), + AssetType: chanAsset, + Pair: pair, } return nil case wsTrades: @@ -233,22 +303,20 @@ func (b *Bitfinex) wsHandleData(respRaw []byte) error { for i := range snapshot { elem := snapshot[i].([]interface{}) if len(elem) == 5 { - trades = append(trades, - WebsocketTrade{ - ID: int64(elem[0].(float64)), - Timestamp: int64(elem[1].(float64)), - Amount: elem[2].(float64), - Rate: elem[3].(float64), - Period: int64(elem[4].(float64)), - }) + trades = append(trades, WebsocketTrade{ + ID: int64(elem[0].(float64)), + Timestamp: int64(elem[1].(float64)), + Amount: elem[2].(float64), + Rate: elem[3].(float64), + Period: int64(elem[4].(float64)), + }) } else { - trades = append(trades, - WebsocketTrade{ - ID: int64(elem[0].(float64)), - Timestamp: int64(elem[1].(float64)), - Amount: elem[2].(float64), - Price: elem[3].(float64), - }) + trades = append(trades, WebsocketTrade{ + ID: int64(elem[0].(float64)), + Timestamp: int64(elem[1].(float64)), + Amount: elem[2].(float64), + Price: elem[3].(float64), + }) } } case 3: @@ -260,11 +328,22 @@ func (b *Bitfinex) wsHandleData(respRaw []byte) error { return nil } data := d[2].([]interface{}) - trades = append(trades, WebsocketTrade{ - ID: int64(data[0].(float64)), - Timestamp: int64(data[1].(float64)), - Price: data[3].(float64), - Amount: data[2].(float64)}) + if len(data) == 5 { + trades = append(trades, WebsocketTrade{ + ID: int64(data[0].(float64)), + Timestamp: int64(data[1].(float64)), + Amount: data[2].(float64), + Rate: data[3].(float64), + Period: int64(data[4].(float64)), + }) + } else { + trades = append(trades, WebsocketTrade{ + ID: int64(data[0].(float64)), + Timestamp: int64(data[1].(float64)), + Amount: data[2].(float64), + Price: data[3].(float64), + }) + } } for i := range trades { @@ -276,29 +355,30 @@ func (b *Bitfinex) wsHandleData(respRaw []byte) error { } if trades[i].Rate > 0 { - b.Websocket.DataHandler <- wshandler.FundingData{ - CurrencyPair: currency.NewPairFromString(chanInfo.Pair), + b.Websocket.DataHandler <- stream.FundingData{ + CurrencyPair: pair, Timestamp: time.Unix(0, trades[i].Timestamp*int64(time.Millisecond)), Amount: newAmount, Exchange: b.Name, - AssetType: asset.Spot, + AssetType: chanAsset, Side: side, Rate: trades[i].Rate, Period: trades[i].Period, } - return nil + continue } - b.Websocket.DataHandler <- wshandler.TradeData{ - CurrencyPair: currency.NewPairFromString(chanInfo.Pair), + b.Websocket.DataHandler <- stream.TradeData{ + CurrencyPair: pair, Timestamp: time.Unix(0, trades[i].Timestamp*int64(time.Millisecond)), Price: trades[i].Price, Amount: newAmount, Exchange: b.Name, - AssetType: asset.Spot, + AssetType: chanAsset, Side: side, } } + return nil } if authResp, ok := d[1].(string); ok { @@ -315,8 +395,7 @@ func (b *Bitfinex) wsHandleData(respRaw []byte) error { strings.Contains(channelName, wsFundingOrderCancelRequest): if data[0] != nil && data[0].(float64) > 0 { id := int64(data[0].(float64)) - if b.WebsocketConn.IsIDWaitingForResponse(id) { - b.AuthenticatedWebsocketConn.SetResponseIDAndData(id, respRaw) + if b.Websocket.Match.IncomingWithData(id, respRaw) { return nil } b.wsHandleFundingOffer(data) @@ -326,19 +405,23 @@ func (b *Bitfinex) wsHandleData(respRaw []byte) error { strings.Contains(channelName, wsOrderCancelRequest): if data[2] != nil && data[2].(float64) > 0 { id := int64(data[2].(float64)) - if b.WebsocketConn.IsIDWaitingForResponse(id) { - b.AuthenticatedWebsocketConn.SetResponseIDAndData(id, respRaw) + if b.Websocket.Match.IncomingWithData(id, respRaw) { return nil } b.wsHandleOrder(data) } default: - return fmt.Errorf("%s - Unexpected data returned %s", b.Name, respRaw) + return fmt.Errorf("%s - Unexpected data returned %s", + b.Name, + respRaw) } } - if notification[5] != nil && strings.EqualFold(notification[5].(string), wsError) { - return fmt.Errorf("%s - Error %s", b.Name, notification[6].(string)) + if notification[5] != nil && + strings.EqualFold(notification[5].(string), wsError) { + return fmt.Errorf("%s - Error %s", + b.Name, + notification[6].(string)) } case wsOrderSnapshot: if snapBundle, ok := d[2].([]interface{}); ok && len(snapBundle) > 0 { @@ -634,7 +717,9 @@ func (b *Bitfinex) wsHandleData(respRaw []byte) error { } } default: - b.Websocket.DataHandler <- wshandler.UnhandledMessageWarning{Message: b.Name + wshandler.UnhandledMessage + string(respRaw)} + b.Websocket.DataHandler <- stream.UnhandledMessageWarning{ + Message: b.Name + stream.UnhandledMessage + string(respRaw), + } return nil } } @@ -781,20 +866,13 @@ func (b *Bitfinex) WsInsertSnapshot(p currency.Pair, assetType asset.Item, books newOrderBook.Pair = p newOrderBook.ExchangeName = b.Name - err := b.Websocket.Orderbook.LoadSnapshot(&newOrderBook) - if err != nil { - return fmt.Errorf("bitfinex.go error - %s", err) - } - b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{Pair: p, - Asset: assetType, - Exchange: b.Name} - return nil + return b.Websocket.Orderbook.LoadSnapshot(&newOrderBook) } // WsUpdateOrderbook updates the orderbook list, removing and adding to the // orderbook sides func (b *Bitfinex) WsUpdateOrderbook(p currency.Pair, assetType asset.Item, book []WebsocketBook) error { - orderbookUpdate := wsorderbook.WebsocketOrderbookUpdate{ + orderbookUpdate := buffer.Update{ Asset: assetType, Pair: p, } @@ -833,97 +911,115 @@ func (b *Bitfinex) WsUpdateOrderbook(p currency.Pair, assetType asset.Item, book } } } - err := b.Websocket.Orderbook.Update(&orderbookUpdate) - if err != nil { - return err - } - - b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{Pair: p, - Asset: assetType, - Exchange: b.Name} - - return nil + return b.Websocket.Orderbook.Update(&orderbookUpdate) } // GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() -func (b *Bitfinex) GenerateDefaultSubscriptions() { +func (b *Bitfinex) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, error) { var channels = []string{ wsBook, wsTrades, wsTicker, wsCandles, } - var subscriptions []wshandler.WebsocketChannelSubscription - for i := range channels { - enabledPairs := b.GetEnabledPairs(asset.Spot) - for j := range enabledPairs { - if strings.HasPrefix(enabledPairs[j].Base.String(), "f") { - log.Warnf(log.WebsocketMgr, - "%v - Websocket does not support funding currency %v, skipping", - b.Name, enabledPairs[j]) - continue - } - b.appendOptionalDelimiter(&enabledPairs[j]) - params := make(map[string]interface{}) - if channels[i] == wsBook { - params["prec"] = "R0" - params["len"] = "100" - } - subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{ - Channel: channels[i], - Currency: enabledPairs[j], - Params: params, - }) + var subscriptions []stream.ChannelSubscription + assets := b.GetAssetTypes() + for i := range assets { + enabledPairs, err := b.GetEnabledPairs(assets[i]) + if err != nil { + return nil, err + } + + for j := range channels { + for k := range enabledPairs { + params := make(map[string]interface{}) + if channels[j] == wsBook { + params["prec"] = "R0" + params["len"] = "100" + } + + if channels[j] == wsCandles { + // TODO: Add ability to select timescale && funding period + var fundingPeriod string + prefix := "t" + if assets[i] == asset.MarginFunding { + prefix = "f" + fundingPeriod = ":p30" + } + params["key"] = "trade:1m:" + prefix + enabledPairs[k].String() + fundingPeriod + } else { + params["symbol"] = enabledPairs[k].String() + } + + subscriptions = append(subscriptions, stream.ChannelSubscription{ + Channel: channels[j], + Currency: enabledPairs[k], + Params: params, + Asset: assets[i], + }) + } } } - b.Websocket.SubscribeToChannels(subscriptions) + + return subscriptions, nil } // Subscribe sends a websocket message to receive data from the channel -func (b *Bitfinex) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error { - req := make(map[string]interface{}) - req["event"] = "subscribe" - req["channel"] = channelToSubscribe.Channel +func (b *Bitfinex) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error { + var errs common.Errors + for i := range channelsToSubscribe { + req := make(map[string]interface{}) + req["event"] = "subscribe" + req["channel"] = channelsToSubscribe[i].Channel - if channelToSubscribe.Currency.String() != "" { - if channelToSubscribe.Channel == wsCandles { - // TODO: Add ability to select timescale - req["key"] = fmt.Sprintf("trade:1D:%v", - b.FormatExchangeCurrency(channelToSubscribe.Currency, asset.Spot).String()) - } else { - req["symbol"] = b.FormatExchangeCurrency(channelToSubscribe.Currency, - asset.Spot).String() - } - } - - if len(channelToSubscribe.Params) > 0 { - for k, v := range channelToSubscribe.Params { + for k, v := range channelsToSubscribe[i].Params { req[k] = v } - } - return b.WebsocketConn.SendJSONMessage(req) + err := b.Websocket.Conn.SendJSONMessage(req) + if err != nil { + errs = append(errs, err) + continue + } + b.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe[i]) + } + if errs != nil { + return errs + } + return nil } // Unsubscribe sends a websocket message to stop receiving data from the channel -func (b *Bitfinex) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error { - req := make(map[string]interface{}) - req["event"] = "unsubscribe" - req["channel"] = channelToSubscribe.Channel +func (b *Bitfinex) Unsubscribe(channelsToUnsubscribe []stream.ChannelSubscription) error { + var errs common.Errors + for i := range channelsToUnsubscribe { + req := make(map[string]interface{}) + req["event"] = "unsubscribe" + req["channel"] = channelsToUnsubscribe[i].Channel - if len(channelToSubscribe.Params) > 0 { - for k, v := range channelToSubscribe.Params { + for k, v := range channelsToUnsubscribe[i].Params { req[k] = v } + + err := b.Websocket.Conn.SendJSONMessage(req) + if err != nil { + errs = append(errs, err) + continue + } + b.Websocket.RemoveSuccessfulUnsubscriptions(channelsToUnsubscribe[i]) } - return b.WebsocketConn.SendJSONMessage(req) + if errs != nil { + return errs + } + return nil } // WsSendAuth sends a autheticated event payload func (b *Bitfinex) WsSendAuth() error { if !b.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) { - return fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", b.Name) + return fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", + b.Name) } nonce := strconv.FormatInt(time.Now().Unix(), 10) payload := "AUTH" + nonce @@ -931,15 +1027,13 @@ func (b *Bitfinex) WsSendAuth() error { Event: "auth", APIKey: b.API.Credentials.Key, AuthPayload: payload, - AuthSig: crypto.HexEncodeToString( - crypto.GetHMAC( - crypto.HashSHA512_384, - []byte(payload), - []byte(b.API.Credentials.Secret))), + AuthSig: crypto.HexEncodeToString(crypto.GetHMAC(crypto.HashSHA512_384, + []byte(payload), + []byte(b.API.Credentials.Secret))), AuthNonce: nonce, DeadManSwitch: 0, } - err := b.AuthenticatedWebsocketConn.SendJSONMessage(request) + err := b.Websocket.AuthConn.SendJSONMessage(request) if err != nil { b.Websocket.SetCanUseAuthenticatedEndpoints(false) return err @@ -954,7 +1048,8 @@ func (b *Bitfinex) WsAddSubscriptionChannel(chanID int, channel, pair string) { b.WebsocketSubdChannels[chanID] = chanInfo if b.Verbose { - log.Debugf(log.ExchangeSys, "%s Subscribed to Channel: %s Pair: %s ChannelID: %d\n", + log.Debugf(log.ExchangeSys, + "%s Subscribed to Channel: %s Pair: %s ChannelID: %d\n", b.Name, channel, pair, @@ -964,9 +1059,9 @@ func (b *Bitfinex) WsAddSubscriptionChannel(chanID int, channel, pair string) { // WsNewOrder authenticated new order request func (b *Bitfinex) WsNewOrder(data *WsNewOrderRequest) (string, error) { - data.CustomID = b.AuthenticatedWebsocketConn.GenerateMessageID(false) + data.CustomID = b.Websocket.AuthConn.GenerateMessageID(false) request := makeRequestInterface(wsOrderNew, data) - resp, err := b.AuthenticatedWebsocketConn.SendMessageReturnResponse(data.CustomID, request) + resp, err := b.Websocket.AuthConn.SendMessageReturnResponse(data.CustomID, request) if err != nil { return "", err } @@ -997,7 +1092,7 @@ func (b *Bitfinex) WsNewOrder(data *WsNewOrderRequest) (string, error) { // WsModifyOrder authenticated modify order request func (b *Bitfinex) WsModifyOrder(data *WsUpdateOrderRequest) error { request := makeRequestInterface(wsOrderUpdate, data) - resp, err := b.AuthenticatedWebsocketConn.SendMessageReturnResponse(data.OrderID, request) + resp, err := b.Websocket.AuthConn.SendMessageReturnResponse(data.OrderID, request) if err != nil { return err } @@ -1026,7 +1121,7 @@ func (b *Bitfinex) WsCancelMultiOrders(orderIDs []int64) error { OrderID: orderIDs, } request := makeRequestInterface(wsCancelMultipleOrders, cancel) - return b.AuthenticatedWebsocketConn.SendJSONMessage(request) + return b.Websocket.AuthConn.SendJSONMessage(request) } // WsCancelOrder authenticated cancel order request @@ -1035,7 +1130,7 @@ func (b *Bitfinex) WsCancelOrder(orderID int64) error { OrderID: orderID, } request := makeRequestInterface(wsOrderCancel, cancel) - resp, err := b.AuthenticatedWebsocketConn.SendMessageReturnResponse(orderID, request) + resp, err := b.Websocket.AuthConn.SendMessageReturnResponse(orderID, request) if err != nil { return err } @@ -1061,13 +1156,13 @@ func (b *Bitfinex) WsCancelOrder(orderID int64) error { func (b *Bitfinex) WsCancelAllOrders() error { cancelAll := WsCancelAllOrdersRequest{All: 1} request := makeRequestInterface(wsCancelMultipleOrders, cancelAll) - return b.AuthenticatedWebsocketConn.SendJSONMessage(request) + return b.Websocket.AuthConn.SendJSONMessage(request) } // WsNewOffer authenticated new offer request func (b *Bitfinex) WsNewOffer(data *WsNewOfferRequest) error { request := makeRequestInterface(wsFundingOrderNew, data) - return b.AuthenticatedWebsocketConn.SendJSONMessage(request) + return b.Websocket.AuthConn.SendJSONMessage(request) } // WsCancelOffer authenticated cancel offer request @@ -1076,7 +1171,7 @@ func (b *Bitfinex) WsCancelOffer(orderID int64) error { OrderID: orderID, } request := makeRequestInterface(wsFundingOrderCancel, cancel) - resp, err := b.AuthenticatedWebsocketConn.SendMessageReturnResponse(orderID, request) + resp, err := b.Websocket.AuthConn.SendMessageReturnResponse(orderID, request) if err != nil { return err } diff --git a/exchanges/bitfinex/bitfinex_wrapper.go b/exchanges/bitfinex/bitfinex_wrapper.go index 7b94995b..f31a94ec 100644 --- a/exchanges/bitfinex/bitfinex_wrapper.go +++ b/exchanges/bitfinex/bitfinex_wrapper.go @@ -19,8 +19,8 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" "github.com/thrasher-corp/gocryptotrader/log" "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) @@ -57,17 +57,27 @@ func (b *Bitfinex) SetDefaults() { b.API.CredentialsValidator.RequiresKey = true b.API.CredentialsValidator.RequiresSecret = true - b.CurrencyPairs = currency.PairsManager{ - AssetTypes: asset.Items{ - asset.Spot, - }, - UseGlobalFormat: true, - RequestFormat: ¤cy.PairFormat{ - Uppercase: true, - }, - ConfigFormat: ¤cy.PairFormat{ - Uppercase: true, - }, + fmt1 := currency.PairStore{ + RequestFormat: ¤cy.PairFormat{Uppercase: true}, + ConfigFormat: ¤cy.PairFormat{Uppercase: true}, + } + + fmt2 := currency.PairStore{ + RequestFormat: ¤cy.PairFormat{Uppercase: true}, + ConfigFormat: ¤cy.PairFormat{Uppercase: true, Delimiter: ":"}, + } + + err := b.StoreAssetPairFormat(asset.Spot, fmt1) + if err != nil { + log.Errorln(log.ExchangeSys, err) + } + err = b.StoreAssetPairFormat(asset.Margin, fmt2) + if err != nil { + log.Errorln(log.ExchangeSys, err) + } + err = b.StoreAssetPairFormat(asset.MarginFunding, fmt1) + if err != nil { + log.Errorln(log.ExchangeSys, err) } b.Features = exchange.Features{ @@ -111,7 +121,6 @@ func (b *Bitfinex) SetDefaults() { OrderbookFetching: true, AccountInfo: true, Subscribe: true, - Unsubscribe: true, AuthenticatedEndpoints: true, MessageCorrelation: true, DeadMansSwitch: true, @@ -155,7 +164,7 @@ func (b *Bitfinex) SetDefaults() { b.API.Endpoints.URLDefault = bitfinexAPIURLBase b.API.Endpoints.URL = b.API.Endpoints.URLDefault b.API.Endpoints.WebsocketURL = publicBitfinexWebsocketEndpoint - b.Websocket = wshandler.New() + b.Websocket = stream.New() b.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit b.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout b.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit @@ -173,49 +182,41 @@ func (b *Bitfinex) Setup(exch *config.ExchangeConfig) error { return err } - err = b.Websocket.Setup( - &wshandler.WebsocketSetup{ - Enabled: exch.Features.Enabled.Websocket, - Verbose: exch.Verbose, - AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, - WebsocketTimeout: exch.WebsocketTrafficTimeout, - DefaultURL: publicBitfinexWebsocketEndpoint, - ExchangeName: exch.Name, - RunningURL: exch.API.Endpoints.WebsocketURL, - Connector: b.WsConnect, - Subscriber: b.Subscribe, - UnSubscriber: b.Unsubscribe, - Features: &b.Features.Supports.WebsocketCapabilities, - }) + err = b.Websocket.Setup(&stream.WebsocketSetup{ + Enabled: exch.Features.Enabled.Websocket, + Verbose: exch.Verbose, + AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, + WebsocketTimeout: exch.WebsocketTrafficTimeout, + DefaultURL: publicBitfinexWebsocketEndpoint, + ExchangeName: exch.Name, + RunningURL: exch.API.Endpoints.WebsocketURL, + Connector: b.WsConnect, + Subscriber: b.Subscribe, + UnSubscriber: b.Unsubscribe, + GenerateSubscriptions: b.GenerateDefaultSubscriptions, + Features: &b.Features.Supports.WebsocketCapabilities, + OrderbookBufferLimit: exch.WebsocketOrderbookBufferLimit, + UpdateEntriesByID: true, + }) if err != nil { return err } - b.WebsocketConn = &wshandler.WebsocketConnection{ - ExchangeName: b.Name, - URL: b.Websocket.GetWebsocketURL(), - ProxyURL: b.Websocket.GetProxyAddress(), - Verbose: b.Verbose, - ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout, - ResponseMaxLimit: exch.WebsocketResponseMaxLimit, - } - b.AuthenticatedWebsocketConn = &wshandler.WebsocketConnection{ - ExchangeName: b.Name, - URL: authenticatedBitfinexWebsocketEndpoint, - ProxyURL: b.Websocket.GetProxyAddress(), - Verbose: b.Verbose, + err = b.Websocket.SetupNewConnection(stream.ConnectionSetup{ ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout, ResponseMaxLimit: exch.WebsocketResponseMaxLimit, + URL: publicBitfinexWebsocketEndpoint, + }) + if err != nil { + return err } - b.Websocket.Orderbook.Setup( - exch.WebsocketOrderbookBufferLimit, - false, - false, - false, - true, - exch.Name) - return nil + return b.Websocket.SetupNewConnection(stream.ConnectionSetup{ + ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout, + ResponseMaxLimit: exch.WebsocketResponseMaxLimit, + URL: authenticatedBitfinexWebsocketEndpoint, + Authenticated: true, + }) } // Start starts the Bitfinex go routine @@ -244,7 +245,9 @@ func (b *Bitfinex) Run() { err := b.UpdateTradablePairs(false) if err != nil { log.Errorf(log.ExchangeSys, - "%s failed to update tradable pairs. Err: %s", b.Name, err) + "%s failed to update tradable pairs. Err: %s", + b.Name, + err) } } @@ -265,6 +268,13 @@ func (b *Bitfinex) FetchTradablePairs(a asset.Item) ([]string, error) { symbols = append(symbols, k[1:]) } case asset.Margin: + for k := range items { + if !strings.Contains(k, ":") { + continue + } + symbols = append(symbols, k[1:]) + } + case asset.MarginFunding: for k := range items { if !strings.HasPrefix(k, "f") { continue @@ -281,15 +291,19 @@ func (b *Bitfinex) FetchTradablePairs(a asset.Item) ([]string, error) { // UpdateTradablePairs updates the exchanges available pairs and stores // them in the exchanges config func (b *Bitfinex) UpdateTradablePairs(forceUpdate bool) error { - for i := range b.CurrencyPairs.AssetTypes { - pairs, err := b.FetchTradablePairs(b.CurrencyPairs.AssetTypes[i]) + assets := b.CurrencyPairs.GetAssetTypes() + for i := range assets { + pairs, err := b.FetchTradablePairs(assets[i]) if err != nil { return err } - err = b.UpdatePairs(currency.NewPairsFromStrings(pairs), - b.CurrencyPairs.AssetTypes[i], - false, - forceUpdate) + + p, err := currency.NewPairsFromStrings(pairs) + if err != nil { + return err + } + + err = b.UpdatePairs(p, assets[i], false, forceUpdate) if err != nil { return err } @@ -299,34 +313,40 @@ func (b *Bitfinex) UpdateTradablePairs(forceUpdate bool) error { // UpdateTicker updates and returns the ticker for a currency pair func (b *Bitfinex) UpdateTicker(p currency.Pair, assetType asset.Item) (*ticker.Price, error) { - enabledPairs := b.GetEnabledPairs(assetType) + enabledPairs, err := b.GetEnabledPairs(assetType) + if err != nil { + return nil, err + } + tickerNew, err := b.GetTickerBatch() if err != nil { return nil, err } + for k, v := range tickerNew { - if strings.HasPrefix(k, "f") { - continue - } - pair := currency.NewPairFromString(k[1:]) // Remove prefix - if !enabledPairs.Contains(p, true) { - continue - } - tick := ticker.Price{ - Last: v.Last, - High: v.High, - Low: v.Low, - Bid: v.Bid, - Ask: v.Ask, - Volume: v.Volume, - Pair: pair, - } - err = ticker.ProcessTicker(b.Name, &tick, assetType) + pair, err := currency.NewPairFromString(k[1:]) // Remove prefix if err != nil { - log.Error(log.Ticker, err) + return nil, err + } + + if !enabledPairs.Contains(pair, true) { + continue + } + + err = ticker.ProcessTicker(&ticker.Price{ + Last: v.Last, + High: v.High, + Low: v.Low, + Bid: v.Bid, + Ask: v.Ask, + Volume: v.Volume, + Pair: pair, + AssetType: assetType, + ExchangeName: b.Name}) + if err != nil { + return nil, err } } - return ticker.GetTicker(b.Name, p, assetType) } @@ -354,7 +374,7 @@ func (b *Bitfinex) FetchOrderbook(p currency.Pair, assetType asset.Item) (*order func (b *Bitfinex) UpdateOrderbook(p currency.Pair, assetType asset.Item) (*orderbook.Base, error) { b.appendOptionalDelimiter(&p) var prefix = "t" - if assetType == asset.Margin { + if assetType == asset.MarginFunding { prefix = "f" } @@ -459,11 +479,17 @@ func (b *Bitfinex) SubmitOrder(o *order.Submit) (order.SubmitResponse, error) { if err != nil { return submitOrderResponse, err } + + fpair, err := b.FormatExchangeCurrency(o.Pair, o.AssetType) + if err != nil { + return submitOrderResponse, err + } + if b.Websocket.CanUseAuthenticatedWebsocketForWrapper() { submitOrderResponse.OrderID, err = b.WsNewOrder(&WsNewOrderRequest{ - CustomID: b.AuthenticatedWebsocketConn.GenerateMessageID(false), + CustomID: b.Websocket.AuthConn.GenerateMessageID(false), Type: o.Type.String(), - Symbol: b.FormatExchangeCurrency(o.Pair, asset.Spot).String(), + Symbol: fpair.String(), Amount: o.Amount, Price: o.Price, }) @@ -473,18 +499,22 @@ func (b *Bitfinex) SubmitOrder(o *order.Submit) (order.SubmitResponse, error) { } else { var response Order isBuying := o.Side == order.Buy - b.appendOptionalDelimiter(&o.Pair) - response, err = b.NewOrder(o.Pair.String(), - o.Type.String(), + b.appendOptionalDelimiter(&fpair) + orderType := o.Type.Lower() + if o.AssetType == asset.Spot { + orderType = "exchange " + orderType + } + response, err = b.NewOrder(fpair.String(), + orderType, o.Amount, o.Price, - false, - isBuying) + isBuying, + false) if err != nil { return submitOrderResponse, err } - if response.OrderID > 0 { - submitOrderResponse.OrderID = strconv.FormatInt(response.OrderID, 10) + if response.ID > 0 { + submitOrderResponse.OrderID = strconv.FormatInt(response.ID, 10) } if response.RemainingAmount == 0 { submitOrderResponse.FullyMatched = true @@ -615,11 +645,6 @@ func (b *Bitfinex) WithdrawFiatFundsToInternationalBank(withdrawRequest *withdra }, nil } -// GetWebsocket returns a pointer to the exchange websocket -func (b *Bitfinex) GetWebsocket() (*wshandler.Websocket, error) { - return b.Websocket, nil -} - // GetFeeByType returns an estimate of fee based on type of transaction func (b *Bitfinex) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { if !b.AllowAuthenticatedRequest() && // Todo check connection status @@ -639,23 +664,27 @@ func (b *Bitfinex) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, for i := range resp { orderSide := order.Side(strings.ToUpper(resp[i].Side)) - timestamp, err := strconv.ParseInt(resp[i].Timestamp, 10, 64) + timestamp, err := strconv.ParseFloat(resp[i].Timestamp, 64) if err != nil { log.Warnf(log.ExchangeSys, "Unable to convert timestamp '%s', leaving blank", resp[i].Timestamp) } - orderDate := time.Unix(timestamp, 0) + + pair, err := currency.NewPairFromString(resp[i].Symbol) + if err != nil { + return nil, err + } orderDetail := order.Detail{ Amount: resp[i].OriginalAmount, - Date: orderDate, + Date: time.Unix(int64(timestamp), 0), Exchange: b.Name, - ID: strconv.FormatInt(resp[i].OrderID, 10), + ID: strconv.FormatInt(resp[i].ID, 10), Side: orderSide, Price: resp[i].Price, RemainingAmount: resp[i].RemainingAmount, - Pair: currency.NewPairFromString(resp[i].Symbol), + Pair: pair, ExecutedAmount: resp[i].ExecutedAmount, } @@ -706,16 +735,21 @@ func (b *Bitfinex) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, } orderDate := time.Unix(timestamp, 0) + pair, err := currency.NewPairFromString(resp[i].Symbol) + if err != nil { + return nil, err + } + orderDetail := order.Detail{ Amount: resp[i].OriginalAmount, Date: orderDate, Exchange: b.Name, - ID: strconv.FormatInt(resp[i].OrderID, 10), + ID: strconv.FormatInt(resp[i].ID, 10), Side: orderSide, Price: resp[i].Price, RemainingAmount: resp[i].RemainingAmount, ExecutedAmount: resp[i].ExecutedAmount, - Pair: currency.NewPairFromString(resp[i].Symbol), + Pair: pair, } switch { @@ -751,31 +785,6 @@ func (b *Bitfinex) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, return orders, nil } -// SubscribeToWebsocketChannels appends to ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle subscribing -func (b *Bitfinex) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - for i := range channels { - b.appendOptionalDelimiter(&channels[i].Currency) - } - b.Websocket.SubscribeToChannels(channels) - return nil -} - -// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle unsubscribing -func (b *Bitfinex) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - for i := range channels { - b.appendOptionalDelimiter(&channels[i].Currency) - } - b.Websocket.RemoveSubscribedChannels(channels) - return nil -} - -// GetSubscriptions returns a copied list of subscriptions -func (b *Bitfinex) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) { - return b.Websocket.GetSubscriptions(), nil -} - // AuthenticateWebsocket sends an authentication message to the websocket func (b *Bitfinex) AuthenticateWebsocket() error { return b.WsSendAuth() @@ -822,7 +831,12 @@ func (b *Bitfinex) GetHistoricCandles(pair currency.Pair, a asset.Item, start, e return kline.Item{}, errors.New(kline.ErrRequestExceedsExchangeLimits) } - candles, err := b.GetCandles(b.fixCasing(pair, a), b.FormatExchangeKlineInterval(interval), + cf, err := b.fixCasing(pair, a) + if err != nil { + return kline.Item{}, err + } + + candles, err := b.GetCandles(cf, b.FormatExchangeKlineInterval(interval), start.Unix()*1000, end.Unix()*1000, b.Features.Enabled.Kline.ResultLimit, true) if err != nil { @@ -866,8 +880,13 @@ func (b *Bitfinex) GetHistoricCandlesExtended(pair currency.Pair, a asset.Item, } dates := kline.CalcDateRanges(start, end, interval, b.Features.Enabled.Kline.ResultLimit) + cf, err := b.fixCasing(pair, a) + if err != nil { + return kline.Item{}, err + } + for x := range dates { - candles, err := b.GetCandles(b.fixCasing(pair, a), b.FormatExchangeKlineInterval(interval), + candles, err := b.GetCandles(cf, b.FormatExchangeKlineInterval(interval), dates[x].Start.Unix()*1000, dates[x].End.Unix()*1000, b.Features.Enabled.Kline.ResultLimit, true) if err != nil { @@ -890,7 +909,7 @@ func (b *Bitfinex) GetHistoricCandlesExtended(pair currency.Pair, a asset.Item, return ret, nil } -func (b *Bitfinex) fixCasing(in currency.Pair, a asset.Item) string { +func (b *Bitfinex) fixCasing(in currency.Pair, a asset.Item) (string, error) { var checkString [2]byte if a == asset.Spot { checkString[0] = 't' @@ -900,13 +919,18 @@ func (b *Bitfinex) fixCasing(in currency.Pair, a asset.Item) string { checkString[1] = 'F' } + fmt, err := b.FormatExchangeCurrency(in, a) + if err != nil { + return "", err + } + y := in.Base.String() if (y[0] != checkString[0] && y[0] != checkString[1]) || (y[0] == checkString[1] && y[1] == checkString[1]) || in.Base == currency.TNB { - return string(checkString[0]) + b.FormatExchangeCurrency(in, a).Upper().String() + return string(checkString[0]) + fmt.Upper().String(), nil } - runes := []rune(b.FormatExchangeCurrency(in, a).Upper().String()) + runes := []rune(fmt.Upper().String()) runes[0] = unicode.ToLower(runes[0]) - return string(runes) + return string(runes), nil } diff --git a/exchanges/bitflyer/bitflyer_test.go b/exchanges/bitflyer/bitflyer_test.go index 01718bdd..bd735157 100644 --- a/exchanges/bitflyer/bitflyer_test.go +++ b/exchanges/bitflyer/bitflyer_test.go @@ -133,7 +133,10 @@ func TestGetExchangeStatus(t *testing.T) { func TestCheckFXString(t *testing.T) { t.Parallel() - p := currency.NewPairDelimiter("FXBTC_JPY", "_") + p, err := currency.NewPairDelimiter("FXBTC_JPY", "_") + if err != nil { + t.Fatal(err) + } p = b.CheckFXString(p) if p.Base.String() != "FX_BTC" { t.Error("Bitflyer - CheckFXString() error") @@ -144,15 +147,19 @@ func TestFetchTicker(t *testing.T) { t.Parallel() var p currency.Pair - currencies := b.GetAvailablePairs(asset.Spot) - for _, pair := range currencies { - if pair.String() == "FXBTC_JPY" { - p = pair + currencies, err := b.GetAvailablePairs(asset.Spot) + if err != nil { + t.Fatal(err) + } + + for i := range currencies { + if currencies[i].String() == "FXBTC_JPY" { + p = currencies[i] break } } - _, err := b.FetchTicker(p, asset.Spot) + _, err = b.FetchTicker(p, asset.Spot) if err != nil { t.Error("Bitflyer - FetchTicker() error", err) } diff --git a/exchanges/bitflyer/bitflyer_wrapper.go b/exchanges/bitflyer/bitflyer_wrapper.go index aff31ac4..038b42a0 100644 --- a/exchanges/bitflyer/bitflyer_wrapper.go +++ b/exchanges/bitflyer/bitflyer_wrapper.go @@ -17,7 +17,6 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" "github.com/thrasher-corp/gocryptotrader/log" "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) @@ -53,20 +52,20 @@ func (b *Bitflyer) SetDefaults() { b.API.CredentialsValidator.RequiresKey = true b.API.CredentialsValidator.RequiresSecret = true - b.CurrencyPairs = currency.PairsManager{ - AssetTypes: asset.Items{ - asset.Spot, - asset.Futures, - }, - UseGlobalFormat: true, - RequestFormat: ¤cy.PairFormat{ - Delimiter: "_", - Uppercase: true, - }, - ConfigFormat: ¤cy.PairFormat{ - Delimiter: "_", - Uppercase: true, - }, + requestFmt := ¤cy.PairFormat{ + Delimiter: currency.UnderscoreDelimiter, + Uppercase: true, + } + configFmt := ¤cy.PairFormat{ + Delimiter: currency.UnderscoreDelimiter, + Uppercase: true, + } + err := b.SetGlobalPairsManager(requestFmt, + configFmt, + asset.Spot, + asset.Futures) + if err != nil { + log.Errorln(log.ExchangeSys, err) } b.Features = exchange.Features{ @@ -105,7 +104,6 @@ func (b *Bitflyer) Setup(exch *config.ExchangeConfig) error { b.SetEnabled(false) return nil } - return b.SetupDefaults(exch) } @@ -141,6 +139,11 @@ func (b *Bitflyer) FetchTradablePairs(assetType asset.Item) ([]string, error) { return nil, err } + format, err := b.GetPairFormat(assetType, false) + if err != nil { + return nil, err + } + var products []string for i := range pairs { if pairs[i].Alias != "" && assetType == asset.Futures { @@ -148,7 +151,7 @@ func (b *Bitflyer) FetchTradablePairs(assetType asset.Item) ([]string, error) { } else if pairs[i].Alias == "" && assetType == asset.Spot && strings.Contains(pairs[i].ProductCode, - b.GetPairFormat(assetType, false).Delimiter) { + format.Delimiter) { products = append(products, pairs[i].ProductCode) } } @@ -158,17 +161,19 @@ func (b *Bitflyer) FetchTradablePairs(assetType asset.Item) ([]string, error) { // UpdateTradablePairs updates the exchanges available pairs and stores // them in the exchanges config func (b *Bitflyer) UpdateTradablePairs(forceUpdate bool) error { - for x := range b.CurrencyPairs.AssetTypes { - a := b.CurrencyPairs.AssetTypes[x] - pairs, err := b.FetchTradablePairs(a) + assets := b.CurrencyPairs.GetAssetTypes() + for x := range assets { + pairs, err := b.FetchTradablePairs(assets[x]) if err != nil { return err } - err = b.UpdatePairs(currency.NewPairsFromStrings(pairs), - a, - false, - forceUpdate) + p, err := currency.NewPairsFromStrings(pairs) + if err != nil { + return err + } + + err = b.UpdatePairs(p, assets[x], false, forceUpdate) if err != nil { return err } @@ -178,23 +183,21 @@ func (b *Bitflyer) UpdateTradablePairs(forceUpdate bool) error { // UpdateTicker updates and returns the ticker for a currency pair func (b *Bitflyer) UpdateTicker(p currency.Pair, assetType asset.Item) (*ticker.Price, error) { - tickerPrice := new(ticker.Price) - - p = b.CheckFXString(p) - - tickerNew, err := b.GetTicker(p.String()) + tickerNew, err := b.GetTicker(b.CheckFXString(p).String()) if err != nil { - return tickerPrice, err + return nil, err } - tickerPrice.Pair = p - tickerPrice.Ask = tickerNew.BestAsk - tickerPrice.Bid = tickerNew.BestBid - tickerPrice.Last = tickerNew.Last - tickerPrice.Volume = tickerNew.Volume - err = ticker.ProcessTicker(b.Name, tickerPrice, assetType) + err = ticker.ProcessTicker(&ticker.Price{ + Pair: p, + Ask: tickerNew.BestAsk, + Bid: tickerNew.BestBid, + Last: tickerNew.Last, + Volume: tickerNew.Volume, + ExchangeName: b.Name, + AssetType: assetType}) if err != nil { - return tickerPrice, err + return nil, err } return ticker.GetTicker(b.Name, p, assetType) @@ -231,9 +234,7 @@ func (b *Bitflyer) FetchOrderbook(p currency.Pair, assetType asset.Item) (*order func (b *Bitflyer) UpdateOrderbook(p currency.Pair, assetType asset.Item) (*orderbook.Base, error) { orderBook := new(orderbook.Base) - p = b.CheckFXString(p) - - orderbookNew, err := b.GetOrderBook(p.String()) + orderbookNew, err := b.GetOrderBook(b.CheckFXString(p).String()) if err != nil { return orderBook, err } @@ -337,11 +338,6 @@ func (b *Bitflyer) WithdrawFiatFundsToInternationalBank(withdrawRequest *withdra return nil, common.ErrNotYetImplemented } -// GetWebsocket returns a pointer to the exchange websocket -func (b *Bitflyer) GetWebsocket() (*wshandler.Websocket, error) { - return nil, common.ErrNotYetImplemented -} - // GetActiveOrders retrieves any orders that are active/open func (b *Bitflyer) GetActiveOrders(getOrdersRequest *order.GetOrdersRequest) ([]order.Detail, error) { return nil, common.ErrNotYetImplemented @@ -362,28 +358,6 @@ func (b *Bitflyer) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error return b.GetFee(feeBuilder) } -// SubscribeToWebsocketChannels appends to ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle subscribing -func (b *Bitflyer) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - return common.ErrFunctionNotSupported -} - -// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle unsubscribing -func (b *Bitflyer) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - return common.ErrFunctionNotSupported -} - -// GetSubscriptions returns a copied list of subscriptions -func (b *Bitflyer) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) { - return nil, common.ErrFunctionNotSupported -} - -// AuthenticateWebsocket sends an authentication message to the websocket -func (b *Bitflyer) AuthenticateWebsocket() error { - return common.ErrFunctionNotSupported -} - // ValidateCredentials validates current credentials used for wrapper // functionality func (b *Bitflyer) ValidateCredentials() error { diff --git a/exchanges/bithumb/bithumb_test.go b/exchanges/bithumb/bithumb_test.go index c27e3755..e763fc93 100644 --- a/exchanges/bithumb/bithumb_test.go +++ b/exchanges/bithumb/bithumb_test.go @@ -207,6 +207,20 @@ func TestMarketSellOrder(t *testing.T) { } } +func TestUpdateTicker(t *testing.T) { + cp := currency.NewPair(currency.QTUM, currency.KRW) + _, err := b.UpdateTicker(cp, asset.Spot) + if err != nil { + t.Fatal(err) + } + + cp = currency.NewPair(currency.DASH, currency.KRW) + _, err = b.UpdateTicker(cp, asset.Spot) + if err != nil { + t.Fatal(err) + } +} + func setFeeBuilder() *exchange.FeeBuilder { return &exchange.FeeBuilder{ Amount: 1, @@ -436,8 +450,11 @@ func TestGetAccountInfo(t *testing.T) { func TestModifyOrder(t *testing.T) { t.Parallel() - curr := currency.NewPairFromString("BTCUSD") - _, err := b.ModifyOrder(&order.Modify{ + curr, err := currency.NewPairFromString("BTCUSD") + if err != nil { + t.Fatal(err) + } + _, err = b.ModifyOrder(&order.Modify{ ID: "1337", Price: 100, Amount: 1000, @@ -535,18 +552,24 @@ func TestGetCandleStick(t *testing.T) { } func TestGetHistoricCandles(t *testing.T) { - currencyPair := currency.NewPairFromString("BTC_KRW") + currencyPair, err := currency.NewPairFromString("BTC_KRW") + if err != nil { + t.Fatal(err) + } startTime := time.Now().Add(-time.Hour * 24) - _, err := b.GetHistoricCandles(currencyPair, asset.Spot, startTime, time.Now(), kline.OneDay) + _, err = b.GetHistoricCandles(currencyPair, asset.Spot, startTime, time.Now(), kline.OneDay) if err != nil { t.Fatal(err) } } func TestGetHistoricCandlesExtended(t *testing.T) { - currencyPair := currency.NewPairFromString("BTC_KRW") + currencyPair, err := currency.NewPairFromString("BTC_KRW") + if err != nil { + t.Fatal(err) + } startTime := time.Now().Add(-time.Hour * 24) - _, err := b.GetHistoricCandlesExtended(currencyPair, asset.Spot, startTime, time.Now(), kline.OneDay) + _, err = b.GetHistoricCandlesExtended(currencyPair, asset.Spot, startTime, time.Now(), kline.OneDay) if err != nil { t.Fatal(err) } diff --git a/exchanges/bithumb/bithumb_wrapper.go b/exchanges/bithumb/bithumb_wrapper.go index 3baeab20..32701678 100644 --- a/exchanges/bithumb/bithumb_wrapper.go +++ b/exchanges/bithumb/bithumb_wrapper.go @@ -20,7 +20,6 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" "github.com/thrasher-corp/gocryptotrader/log" "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) @@ -56,20 +55,11 @@ func (b *Bithumb) SetDefaults() { b.API.CredentialsValidator.RequiresKey = true b.API.CredentialsValidator.RequiresSecret = true - b.CurrencyPairs = currency.PairsManager{ - AssetTypes: asset.Items{ - asset.Spot, - }, - - UseGlobalFormat: true, - RequestFormat: ¤cy.PairFormat{ - Uppercase: true, - Delimiter: "_", - }, - ConfigFormat: ¤cy.PairFormat{ - Uppercase: true, - Index: "KRW", - }, + requestFmt := ¤cy.PairFormat{Uppercase: true, Delimiter: currency.UnderscoreDelimiter} + configFmt := ¤cy.PairFormat{Uppercase: true, Index: "KRW"} + err := b.SetGlobalPairsManager(requestFmt, configFmt, asset.Spot) + if err != nil { + log.Errorln(log.ExchangeSys, err) } b.Features = exchange.Features{ @@ -135,7 +125,6 @@ func (b *Bithumb) Setup(exch *config.ExchangeConfig) error { b.SetEnabled(false) return nil } - return b.SetupDefaults(exch) } @@ -186,34 +175,46 @@ func (b *Bithumb) UpdateTradablePairs(forceUpdate bool) error { return err } - return b.UpdatePairs(currency.NewPairsFromStrings(pairs), asset.Spot, false, forceUpdate) + p, err := currency.NewPairsFromStrings(pairs) + if err != nil { + return err + } + + return b.UpdatePairs(p, asset.Spot, false, forceUpdate) } // UpdateTicker updates and returns the ticker for a currency pair func (b *Bithumb) UpdateTicker(p currency.Pair, assetType asset.Item) (*ticker.Price, error) { - tickerPrice := new(ticker.Price) tickers, err := b.GetAllTickers() if err != nil { - return tickerPrice, err + return nil, err } - pairs := b.GetEnabledPairs(assetType) + pairs, err := b.GetEnabledPairs(assetType) + if err != nil { + return nil, err + } + for i := range pairs { curr := pairs[i].Base.String() t, ok := tickers[curr] if !ok { - continue + return nil, + fmt.Errorf("enabled pair %s [%s] not found in returned ticker map %v", + pairs[i], pairs, tickers) } - tp := ticker.Price{ - High: t.MaxPrice, - Low: t.MinPrice, - Volume: t.UnitsTraded24Hr, - Open: t.OpeningPrice, - Close: t.ClosingPrice, - Pair: pairs[i], - } - err = ticker.ProcessTicker(b.Name, &tp, assetType) + + err = ticker.ProcessTicker(&ticker.Price{ + High: t.MaxPrice, + Low: t.MinPrice, + Volume: t.UnitsTraded24Hr, + Open: t.OpeningPrice, + Close: t.ClosingPrice, + Pair: pairs[i], + ExchangeName: b.Name, + AssetType: assetType, + }) if err != nil { - log.Error(log.Ticker, err) + return nil, err } } return ticker.GetTicker(b.Name, p, assetType) @@ -398,7 +399,11 @@ func (b *Bithumb) CancelAllOrders(orderCancellation *order.Cancel) (order.Cancel } var allOrders []OrderData - currs := b.GetEnabledPairs(asset.Spot) + currs, err := b.GetEnabledPairs(asset.Spot) + if err != nil { + return cancelAllOrdersResponse, err + } + for i := range currs { orders, err := b.GetOrders("", orderCancellation.Side.String(), @@ -484,11 +489,6 @@ func (b *Bithumb) WithdrawFiatFundsToInternationalBank(withdrawRequest *withdraw return nil, common.ErrFunctionNotSupported } -// GetWebsocket returns a pointer to the exchange websocket -func (b *Bithumb) GetWebsocket() (*wshandler.Websocket, error) { - return nil, common.ErrFunctionNotSupported -} - // GetFeeByType returns an estimate of fee based on type of transaction func (b *Bithumb) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { if !b.AllowAuthenticatedRequest() && // Todo check connection status @@ -506,6 +506,11 @@ func (b *Bithumb) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, return nil, err } + format, err := b.GetPairFormat(asset.Spot, false) + if err != nil { + return nil, err + } + for i := range resp.Data { if resp.Data[i].Status != "placed" { continue @@ -522,7 +527,7 @@ func (b *Bithumb) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, Status: order.Active, Pair: currency.NewPairWithDelimiter(resp.Data[i].OrderCurrency, resp.Data[i].PaymentCurrency, - b.GetPairFormat(asset.Spot, false).Delimiter), + format.Delimiter), } if resp.Data[i].Type == "bid" { @@ -549,6 +554,11 @@ func (b *Bithumb) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, return nil, err } + format, err := b.GetPairFormat(asset.Spot, false) + if err != nil { + return nil, err + } + for i := range resp.Data { if resp.Data[i].Status == "placed" { continue @@ -564,7 +574,7 @@ func (b *Bithumb) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, RemainingAmount: resp.Data[i].UnitsRemaining, Pair: currency.NewPairWithDelimiter(resp.Data[i].OrderCurrency, resp.Data[i].PaymentCurrency, - b.GetPairFormat(asset.Spot, false).Delimiter), + format.Delimiter), } if resp.Data[i].Type == "bid" { @@ -582,28 +592,6 @@ func (b *Bithumb) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, return orders, nil } -// SubscribeToWebsocketChannels appends to ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle subscribing -func (b *Bithumb) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - return common.ErrFunctionNotSupported -} - -// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle unsubscribing -func (b *Bithumb) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - return common.ErrFunctionNotSupported -} - -// GetSubscriptions returns a copied list of subscriptions -func (b *Bithumb) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) { - return nil, common.ErrFunctionNotSupported -} - -// AuthenticateWebsocket sends an authentication message to the websocket -func (b *Bithumb) AuthenticateWebsocket() error { - return common.ErrFunctionNotSupported -} - // ValidateCredentials validates current credentials used for wrapper // functionality func (b *Bithumb) ValidateCredentials() error { @@ -624,7 +612,13 @@ func (b *Bithumb) GetHistoricCandles(pair currency.Pair, a asset.Item, start, en } } - candle, err := b.GetCandleStick(b.FormatExchangeCurrency(pair, a).String(), b.FormatExchangeKlineInterval(interval)) + formattedPair, err := b.FormatExchangeCurrency(pair, a) + if err != nil { + return kline.Item{}, err + } + + candle, err := b.GetCandleStick(formattedPair.String(), + b.FormatExchangeKlineInterval(interval)) if err != nil { return kline.Item{}, err } diff --git a/exchanges/bitmex/bitmex.go b/exchanges/bitmex/bitmex.go index c2cdffbf..fe475b80 100644 --- a/exchanges/bitmex/bitmex.go +++ b/exchanges/bitmex/bitmex.go @@ -14,13 +14,11 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/request" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" ) // Bitmex is the overarching type across this package type Bitmex struct { exchange.Base - WebsocketConn *wshandler.WebsocketConnection } const ( diff --git a/exchanges/bitmex/bitmex_test.go b/exchanges/bitmex/bitmex_test.go index 82baab39..69fbb020 100644 --- a/exchanges/bitmex/bitmex_test.go +++ b/exchanges/bitmex/bitmex_test.go @@ -16,7 +16,7 @@ import ( exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) @@ -45,13 +45,11 @@ func TestMain(m *testing.M) { bitmexConfig.API.AuthenticatedWebsocketSupport = true bitmexConfig.API.Credentials.Key = apiKey bitmexConfig.API.Credentials.Secret = apiSecret - + b.Websocket = sharedtestvalues.NewTestWebsocket() err = b.Setup(bitmexConfig) if err != nil { log.Fatal("Bitmex setup error", err) } - b.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride() - b.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride() os.Exit(m.Run()) } @@ -678,17 +676,10 @@ func TestGetDepositAddress(t *testing.T) { // TestWsAuth dials websocket, sends login request. func TestWsAuth(t *testing.T) { if !b.Websocket.IsEnabled() && !b.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() { - t.Skip(wshandler.WebsocketNotEnabled) - } - b.WebsocketConn = &wshandler.WebsocketConnection{ - ExchangeName: b.Name, - URL: b.Websocket.GetWebsocketURL(), - Verbose: b.Verbose, - ResponseMaxLimit: exchange.DefaultWebsocketResponseMaxLimit, - ResponseCheckTimeout: exchange.DefaultWebsocketResponseCheckTimeout, + t.Skip(stream.WebsocketNotEnabled) } var dialer websocket.Dialer - err := b.WebsocketConn.Dial(&dialer, http.Header{}) + err := b.Websocket.Conn.Dial(&dialer, http.Header{}) if err != nil { t.Fatal(err) } @@ -710,6 +701,13 @@ func TestWsAuth(t *testing.T) { timer.Stop() } +func TestUpdateTradablePairs(t *testing.T) { + err := b.UpdateTradablePairs(true) + if err != nil { + t.Fatal(err) + } +} + func TestWsPositionUpdate(t *testing.T) { pressXToJSON := []byte(`{"table":"position", "action":"update", @@ -800,7 +798,6 @@ func TestWSPositionUpdateHandling(t *testing.T) { } func TestWSOrderbookHandling(t *testing.T) { - b.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride() pressXToJSON := []byte(`{ "table":"orderBookL2_25", "keys":["symbol","id","side"], @@ -871,7 +868,6 @@ func TestWSOrderbookHandling(t *testing.T) { } func TestWSDeleveragePositionUpdateHandling(t *testing.T) { - b.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride() pressXToJSON := []byte(`{"table":"position", "action":"update", "data":[{ diff --git a/exchanges/bitmex/bitmex_websocket.go b/exchanges/bitmex/bitmex_websocket.go index 0b3789a2..59e0ac17 100644 --- a/exchanges/bitmex/bitmex_websocket.go +++ b/exchanges/bitmex/bitmex_websocket.go @@ -16,8 +16,8 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream/buffer" "github.com/thrasher-corp/gocryptotrader/log" ) @@ -67,67 +67,72 @@ const ( // WsConnect initiates a new websocket connection func (b *Bitmex) WsConnect() error { if !b.Websocket.IsEnabled() || !b.IsEnabled() { - return errors.New(wshandler.WebsocketNotEnabled) + return errors.New(stream.WebsocketNotEnabled) } var dialer websocket.Dialer - err := b.WebsocketConn.Dial(&dialer, http.Header{}) + err := b.Websocket.Conn.Dial(&dialer, http.Header{}) if err != nil { return err } - p, err := b.WebsocketConn.ReadMessage() - if err != nil { - b.Websocket.ReadMessageErrors <- err - return err + resp := b.Websocket.Conn.ReadMessage() + if resp.Raw == nil { + return errors.New("connection closed") } - b.Websocket.TrafficAlert <- struct{}{} var welcomeResp WebsocketWelcome - err = json.Unmarshal(p.Raw, &welcomeResp) + err = json.Unmarshal(resp.Raw, &welcomeResp) if err != nil { return err } if b.Verbose { - log.Debugf(log.ExchangeSys, "Successfully connected to Bitmex %s at time: %s Limit: %d", + log.Debugf(log.ExchangeSys, + "Successfully connected to Bitmex %s at time: %s Limit: %d", welcomeResp.Info, welcomeResp.Timestamp, welcomeResp.Limit.Remaining) } go b.wsReadData() - b.GenerateDefaultSubscriptions() + subs, err := b.GenerateDefaultSubscriptions() + if err != nil { + return err + } + + err = b.Websocket.SubscribeToChannels(subs) + if err != nil { + return err + } + err = b.websocketSendAuth() if err != nil { - log.Errorf(log.ExchangeSys, "%v - authentication failed: %v\n", b.Name, err) + log.Errorf(log.ExchangeSys, + "%v - authentication failed: %v\n", + b.Name, + err) + } else { + authsubs, err := b.GenerateAuthenticatedSubscriptions() + if err != nil { + return err + } + return b.Websocket.SubscribeToChannels(authsubs) } - b.GenerateAuthenticatedSubscriptions() return nil } // wsReadData receives and passes on websocket messages for processing func (b *Bitmex) wsReadData() { b.Websocket.Wg.Add(1) - - defer func() { - b.Websocket.Wg.Done() - }() + defer b.Websocket.Wg.Done() for { - select { - case <-b.Websocket.ShutdownC: + resp := b.Websocket.Conn.ReadMessage() + if resp.Raw == nil { return - - default: - resp, err := b.WebsocketConn.ReadMessage() - if err != nil { - b.Websocket.DataHandler <- err - return - } - b.Websocket.TrafficAlert <- struct{}{} - err = b.wsHandleData(resp.Raw) - if err != nil { - b.Websocket.DataHandler <- err - } + } + err := b.wsHandleData(resp.Raw) + if err != nil { + b.Websocket.DataHandler <- err } } } @@ -188,7 +193,12 @@ func (b *Bitmex) wsHandleData(respRaw []byte) error { if len(orderbooks.Data) == 0 { return fmt.Errorf("%s - Empty orderbook data received: %s", b.Name, respRaw) } - p := currency.NewPairFromString(orderbooks.Data[0].Symbol) + var p currency.Pair + p, err = currency.NewPairFromString(orderbooks.Data[0].Symbol) + if err != nil { + return err + } + var a asset.Item a, err = b.GetPairAssetType(p) if err != nil { @@ -210,13 +220,14 @@ func (b *Bitmex) wsHandleData(respRaw []byte) error { return err } - if trades.Action == bitmexActionInitialData { - return nil - } - for i := range trades.Data { + var p currency.Pair + p, err = currency.NewPairFromString(trades.Data[i].Symbol) + if err != nil { + return err + } + var a asset.Item - p := currency.NewPairFromString(trades.Data[i].Symbol) a, err = b.GetPairAssetType(p) if err != nil { return err @@ -230,7 +241,7 @@ func (b *Bitmex) wsHandleData(respRaw []byte) error { } } - b.Websocket.DataHandler <- wshandler.TradeData{ + b.Websocket.DataHandler <- stream.TradeData{ Timestamp: trades.Data[i].Timestamp, Price: trades.Data[i].Price, Amount: float64(trades.Data[i].Size), @@ -271,7 +282,12 @@ func (b *Bitmex) wsHandleData(respRaw []byte) error { } for i := range response.Data { - p := currency.NewPairFromString(response.Data[i].Symbol) + var p currency.Pair + p, err = currency.NewPairFromString(response.Data[i].Symbol) + if err != nil { + return err + } + var a asset.Item a, err = b.GetPairAssetType(p) if err != nil { @@ -459,7 +475,7 @@ func (b *Bitmex) wsHandleData(respRaw []byte) error { } b.Websocket.DataHandler <- response default: - b.Websocket.DataHandler <- wshandler.UnhandledMessageWarning{Message: b.Name + wshandler.UnhandledMessage + string(respRaw)} + b.Websocket.DataHandler <- stream.UnhandledMessageWarning{Message: b.Name + stream.UnhandledMessage + string(respRaw)} return nil } } @@ -467,7 +483,7 @@ func (b *Bitmex) wsHandleData(respRaw []byte) error { } // ProcessOrderbook processes orderbook updates -func (b *Bitmex) processOrderbook(data []OrderBookL2, action string, currencyPair currency.Pair, assetType asset.Item) error { +func (b *Bitmex) processOrderbook(data []OrderBookL2, action string, p currency.Pair, a asset.Item) error { if len(data) < 1 { return errors.New("bitmex_websocket.go error - no orderbook data") } @@ -490,8 +506,8 @@ func (b *Bitmex) processOrderbook(data []OrderBookL2, action string, currencyPai ID: data[i].ID, }) } - newOrderBook.AssetType = assetType - newOrderBook.Pair = currencyPair + newOrderBook.AssetType = a + newOrderBook.Pair = p newOrderBook.ExchangeName = b.Name err := b.Websocket.Orderbook.LoadSnapshot(&newOrderBook) @@ -499,11 +515,6 @@ func (b *Bitmex) processOrderbook(data []OrderBookL2, action string, currencyPai return fmt.Errorf("bitmex_websocket.go process orderbook error - %s", err) } - b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{ - Pair: currencyPair, - Asset: assetType, - Exchange: b.Name, - } default: var asks, bids []orderbook.Item for i := range data { @@ -520,40 +531,42 @@ func (b *Bitmex) processOrderbook(data []OrderBookL2, action string, currencyPai }) } - err := b.Websocket.Orderbook.Update(&wsorderbook.WebsocketOrderbookUpdate{ + err := b.Websocket.Orderbook.Update(&buffer.Update{ Bids: bids, Asks: asks, - Pair: currencyPair, - Asset: assetType, + Pair: p, + Asset: a, Action: action, }) if err != nil { return err } - - b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{ - Pair: currencyPair, - Asset: assetType, - Exchange: b.Name, - } } return nil } // GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() -func (b *Bitmex) GenerateDefaultSubscriptions() { +func (b *Bitmex) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, error) { assets := b.GetAssetTypes() var allPairs currency.Pairs - + var associatedAssets []asset.Item for x := range assets { - contracts := b.GetEnabledPairs(assets[x]) + contracts, err := b.GetEnabledPairs(assets[x]) + if err != nil { + return nil, err + } for y := range contracts { allPairs = allPairs.Add(contracts[y]) + associatedAssets = append(associatedAssets, assets[x]) } } + if len(allPairs) != len(associatedAssets) { + return nil, fmt.Errorf("%s generate default subscriptions: pair and asset type len mismatch", b.Name) + } + channels := []string{bitmexWSOrderbookL2, bitmexWSTrade} - subscriptions := []wshandler.WebsocketChannelSubscription{ + subscriptions := []stream.ChannelSubscription{ { Channel: bitmexWSAnnouncement, }, @@ -561,25 +574,29 @@ func (b *Bitmex) GenerateDefaultSubscriptions() { for i := range channels { for j := range allPairs { - subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{ + subscriptions = append(subscriptions, stream.ChannelSubscription{ Channel: channels[i] + ":" + allPairs[j].String(), Currency: allPairs[j], + Asset: associatedAssets[j], }) } } - b.Websocket.SubscribeToChannels(subscriptions) + return subscriptions, nil } // GenerateAuthenticatedSubscriptions Adds authenticated subscriptions to websocket to be handled by ManageSubscriptions() -func (b *Bitmex) GenerateAuthenticatedSubscriptions() { +func (b *Bitmex) GenerateAuthenticatedSubscriptions() ([]stream.ChannelSubscription, error) { if !b.Websocket.CanUseAuthenticatedEndpoints() { - return + return nil, nil + } + contracts, err := b.GetEnabledPairs(asset.PerpetualContract) + if err != nil { + return nil, err } - contracts := b.GetEnabledPairs(asset.PerpetualContract) channels := []string{bitmexWSExecution, bitmexWSPosition, } - subscriptions := []wshandler.WebsocketChannelSubscription{ + subscriptions := []stream.ChannelSubscription{ { Channel: bitmexWSAffiliate, }, @@ -601,31 +618,48 @@ func (b *Bitmex) GenerateAuthenticatedSubscriptions() { } for i := range channels { for j := range contracts { - subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{ + subscriptions = append(subscriptions, stream.ChannelSubscription{ Channel: channels[i] + ":" + contracts[j].String(), Currency: contracts[j], + Asset: asset.PerpetualContract, }) } } - b.Websocket.SubscribeToChannels(subscriptions) + return subscriptions, nil } // Subscribe subscribes to a websocket channel -func (b *Bitmex) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error { +func (b *Bitmex) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error { var subscriber WebsocketRequest subscriber.Command = "subscribe" - subscriber.Arguments = append(subscriber.Arguments, channelToSubscribe.Channel) - return b.WebsocketConn.SendJSONMessage(subscriber) + + for i := range channelsToSubscribe { + subscriber.Arguments = append(subscriber.Arguments, + channelsToSubscribe[i].Channel) + } + err := b.Websocket.Conn.SendJSONMessage(subscriber) + if err != nil { + return err + } + b.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe...) + return nil } // Unsubscribe sends a websocket message to stop receiving data from the channel -func (b *Bitmex) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error { - var subscriber WebsocketRequest - subscriber.Command = "unsubscribe" - subscriber.Arguments = append(subscriber.Arguments, - channelToSubscribe.Params["args"], - channelToSubscribe.Channel+":"+channelToSubscribe.Currency.String()) - return b.WebsocketConn.SendJSONMessage(subscriber) +func (b *Bitmex) Unsubscribe(channelsToUnsubscribe []stream.ChannelSubscription) error { + var unsubscriber WebsocketRequest + unsubscriber.Command = "unsubscribe" + + for i := range channelsToUnsubscribe { + unsubscriber.Arguments = append(unsubscriber.Arguments, + channelsToUnsubscribe[i].Channel) + } + err := b.Websocket.Conn.SendJSONMessage(unsubscriber) + if err != nil { + return err + } + b.Websocket.RemoveSuccessfulUnsubscriptions(channelsToUnsubscribe...) + return nil } // WebsocketSendAuth sends an authenticated subscription @@ -645,7 +679,7 @@ func (b *Bitmex) websocketSendAuth() error { sendAuth.Command = "authKeyExpires" sendAuth.Arguments = append(sendAuth.Arguments, b.API.Credentials.Key, timestamp, signature) - err := b.WebsocketConn.SendJSONMessage(sendAuth) + err := b.Websocket.Conn.SendJSONMessage(sendAuth) if err != nil { b.Websocket.SetCanUseAuthenticatedEndpoints(false) return err diff --git a/exchanges/bitmex/bitmex_wrapper.go b/exchanges/bitmex/bitmex_wrapper.go index 68a38f93..b5c6ad6d 100644 --- a/exchanges/bitmex/bitmex_wrapper.go +++ b/exchanges/bitmex/bitmex_wrapper.go @@ -18,8 +18,8 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" "github.com/thrasher-corp/gocryptotrader/log" "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) @@ -55,41 +55,17 @@ func (b *Bitmex) SetDefaults() { b.API.CredentialsValidator.RequiresKey = true b.API.CredentialsValidator.RequiresSecret = true - b.CurrencyPairs = currency.PairsManager{ - AssetTypes: asset.Items{ - asset.PerpetualContract, - asset.Futures, - asset.DownsideProfitContract, - asset.UpsideProfitContract, - }, + requestFmt := ¤cy.PairFormat{Uppercase: true} + configFmt := ¤cy.PairFormat{Uppercase: true} + err := b.SetGlobalPairsManager(requestFmt, + configFmt, + asset.PerpetualContract, + asset.Futures, + asset.Index) + if err != nil { + log.Errorln(log.ExchangeSys, err) } - // Same format used for perpetual contracts and futures - fmt1 := currency.PairStore{ - RequestFormat: ¤cy.PairFormat{ - Uppercase: true, - }, - ConfigFormat: ¤cy.PairFormat{ - Uppercase: true, - }, - } - b.CurrencyPairs.Store(asset.PerpetualContract, fmt1) - b.CurrencyPairs.Store(asset.Futures, fmt1) - - // Upside and Downside profit contracts use the same format - fmt2 := currency.PairStore{ - RequestFormat: ¤cy.PairFormat{ - Delimiter: "_", - Uppercase: true, - }, - ConfigFormat: ¤cy.PairFormat{ - Delimiter: "_", - Uppercase: true, - }, - } - b.CurrencyPairs.Store(asset.DownsideProfitContract, fmt2) - b.CurrencyPairs.Store(asset.UpsideProfitContract, fmt2) - b.Features = exchange.Features{ Supports: exchange.FeaturesSupported{ REST: true, @@ -144,7 +120,7 @@ func (b *Bitmex) SetDefaults() { b.API.Endpoints.URLDefault = bitmexAPIURL b.API.Endpoints.URL = b.API.Endpoints.URLDefault b.API.Endpoints.WebsocketURL = bitmexWSURL - b.Websocket = wshandler.New() + b.Websocket = stream.New() b.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit b.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout b.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit @@ -162,41 +138,30 @@ func (b *Bitmex) Setup(exch *config.ExchangeConfig) error { return err } - err = b.Websocket.Setup( - &wshandler.WebsocketSetup{ - Enabled: exch.Features.Enabled.Websocket, - Verbose: exch.Verbose, - AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, - WebsocketTimeout: exch.WebsocketTrafficTimeout, - DefaultURL: bitmexWSURL, - ExchangeName: exch.Name, - RunningURL: exch.API.Endpoints.WebsocketURL, - Connector: b.WsConnect, - Subscriber: b.Subscribe, - UnSubscriber: b.Unsubscribe, - Features: &b.Features.Supports.WebsocketCapabilities, - }) + err = b.Websocket.Setup(&stream.WebsocketSetup{ + Enabled: exch.Features.Enabled.Websocket, + Verbose: exch.Verbose, + AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, + WebsocketTimeout: exch.WebsocketTrafficTimeout, + DefaultURL: bitmexWSURL, + ExchangeName: exch.Name, + RunningURL: exch.API.Endpoints.WebsocketURL, + Connector: b.WsConnect, + Subscriber: b.Subscribe, + UnSubscriber: b.Unsubscribe, + GenerateSubscriptions: b.GenerateDefaultSubscriptions, + Features: &b.Features.Supports.WebsocketCapabilities, + OrderbookBufferLimit: exch.WebsocketOrderbookBufferLimit, + UpdateEntriesByID: true, + }) if err != nil { return err } - b.WebsocketConn = &wshandler.WebsocketConnection{ - ExchangeName: b.Name, - URL: b.Websocket.GetWebsocketURL(), - ProxyURL: b.Websocket.GetProxyAddress(), - Verbose: b.Verbose, + return b.Websocket.SetupNewConnection(stream.ConnectionSetup{ ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout, ResponseMaxLimit: exch.WebsocketResponseMaxLimit, - } - - b.Websocket.Orderbook.Setup( - exch.WebsocketOrderbookBufferLimit, - false, - false, - false, - true, - exch.Name) - return nil + }) } // Start starts the Bitmex go routine @@ -211,7 +176,11 @@ func (b *Bitmex) Start(wg *sync.WaitGroup) { // Run implements the Bitmex wrapper func (b *Bitmex) Run() { if b.Verbose { - log.Debugf(log.ExchangeSys, "%s Websocket: %s. (url: %s).\n", b.Name, common.IsEnabled(b.Websocket.IsEnabled()), b.API.Endpoints.WebsocketURL) + log.Debugf(log.ExchangeSys, + "%s Websocket: %s. (url: %s).\n", + b.Name, + common.IsEnabled(b.Websocket.IsEnabled()), + b.API.Endpoints.WebsocketURL) b.PrintEnabledPairs() } @@ -226,8 +195,8 @@ func (b *Bitmex) Run() { } // FetchTradablePairs returns a list of the exchanges tradable pairs -func (b *Bitmex) FetchTradablePairs(_ asset.Item) ([]string, error) { - marketInfo, err := b.GetActiveInstruments(&GenericRequestParams{}) +func (b *Bitmex) FetchTradablePairs(asset asset.Item) ([]string, error) { + marketInfo, err := b.GetActiveAndIndexInstruments() if err != nil { return nil, err } @@ -248,72 +217,78 @@ func (b *Bitmex) UpdateTradablePairs(forceUpdate bool) error { return err } - var assetPairs []string - for x := range b.CurrencyPairs.AssetTypes { - switch b.CurrencyPairs.AssetTypes[x] { - case asset.PerpetualContract: - for y := range pairs { - if strings.Contains(pairs[y], "USD") { - assetPairs = append(assetPairs, pairs[y]) - } - } - case asset.Futures: - for y := range pairs { - if strings.Contains(pairs[y], "20") { - assetPairs = append(assetPairs, pairs[y]) - } - } - case asset.DownsideProfitContract: - for y := range pairs { - if strings.Contains(pairs[y], "_D") { - assetPairs = append(assetPairs, pairs[y]) - } - } - case asset.UpsideProfitContract: - for y := range pairs { - if strings.Contains(pairs[y], "_U") { - assetPairs = append(assetPairs, pairs[y]) - } - } + // Zerovalue current list which will remove old asset pairs when contract + // types expire or become obsolete + var assetPairs = map[asset.Item][]string{ + asset.Index: {}, + asset.PerpetualContract: {}, + asset.Futures: {}, + } + + for x := range pairs { + if strings.Contains(pairs[x], ".") { + assetPairs[asset.Index] = append(assetPairs[asset.Index], pairs[x]) + continue } - err = b.UpdatePairs(currency.NewPairsFromStrings(assetPairs), b.CurrencyPairs.AssetTypes[x], false, false) - if err != nil { - log.Warnf(log.ExchangeSys, "%s failed to update available pairs. Err: %v", b.Name, err) + if strings.Contains(pairs[x], "USD") { + assetPairs[asset.PerpetualContract] = append(assetPairs[asset.PerpetualContract], + pairs[x]) + continue } - assetPairs = nil + + assetPairs[asset.Futures] = append(assetPairs[asset.Futures], pairs[x]) } + + for a, values := range assetPairs { + p, err := currency.NewPairsFromStrings(values) + if err != nil { + return err + } + + err = b.UpdatePairs(p, a, false, false) + if err != nil { + log.Warnf(log.ExchangeSys, + "%s failed to update available pairs. Err: %v", + b.Name, + err) + } + } + return nil } // UpdateTicker updates and returns the ticker for a currency pair func (b *Bitmex) UpdateTicker(p currency.Pair, assetType asset.Item) (*ticker.Price, error) { - tickerPrice := new(ticker.Price) - tick, err := b.GetActiveInstruments(&GenericRequestParams{}) + tick, err := b.GetActiveAndIndexInstruments() if err != nil { - return tickerPrice, err + return nil, err } - pairs := b.GetEnabledPairs(assetType) - for i := range pairs { - for j := range tick { - if !pairs[i].Equal(tick[j].Symbol) { - continue - } - tickerPrice = &ticker.Price{ - Last: tick[j].LastPrice, - High: tick[j].HighPrice, - Low: tick[j].LowPrice, - Bid: tick[j].BidPrice, - Ask: tick[j].AskPrice, - Volume: tick[j].Volume24h, - Close: tick[j].PrevClosePrice, - Pair: tick[j].Symbol, - LastUpdated: tick[j].Timestamp, - } - err = ticker.ProcessTicker(b.Name, tickerPrice, assetType) - if err != nil { - log.Error(log.Ticker, err) - } + + pairs, err := b.GetEnabledPairs(assetType) + if err != nil { + return nil, err + } + + for j := range tick { + if !pairs.Contains(tick[j].Symbol, true) { + continue + } + + err = ticker.ProcessTicker(&ticker.Price{ + Last: tick[j].LastPrice, + High: tick[j].HighPrice, + Low: tick[j].LowPrice, + Bid: tick[j].BidPrice, + Ask: tick[j].AskPrice, + Volume: tick[j].Volume24h, + Close: tick[j].PrevClosePrice, + Pair: tick[j].Symbol, + LastUpdated: tick[j].Timestamp, + ExchangeName: b.Name, + AssetType: assetType}) + if err != nil { + return nil, err } } return ticker.GetTicker(b.Name, p, assetType) @@ -339,25 +314,34 @@ func (b *Bitmex) FetchOrderbook(p currency.Pair, assetType asset.Item) (*orderbo // UpdateOrderbook updates and returns the orderbook for a currency pair func (b *Bitmex) UpdateOrderbook(p currency.Pair, assetType asset.Item) (*orderbook.Base, error) { - orderBook := new(orderbook.Base) - - orderbookNew, err := b.GetOrderbook(OrderBookGetL2Params{ - Symbol: b.FormatExchangeCurrency(p, assetType).String(), - Depth: 500}) - if err != nil { - return orderBook, err + if assetType == asset.Index { + return nil, common.ErrFunctionNotSupported } - for _, ob := range orderbookNew { - if strings.EqualFold(ob.Side, order.Sell.String()) { - orderBook.Asks = append(orderBook.Asks, - orderbook.Item{Amount: float64(ob.Size), Price: ob.Price}) + fpair, err := b.FormatExchangeCurrency(p, assetType) + if err != nil { + return nil, err + } + + orderbookNew, err := b.GetOrderbook(OrderBookGetL2Params{ + Symbol: fpair.String(), + Depth: 500}) + if err != nil { + return nil, err + } + + orderBook := new(orderbook.Base) + for i := range orderbookNew { + if strings.EqualFold(orderbookNew[i].Side, order.Sell.String()) { + orderBook.Asks = append(orderBook.Asks, orderbook.Item{ + Amount: float64(orderbookNew[i].Size), + Price: orderbookNew[i].Price}) continue } - if strings.EqualFold(ob.Side, order.Buy.String()) { - orderBook.Bids = append(orderBook.Bids, - orderbook.Item{Amount: float64(ob.Size), Price: ob.Price}) - continue + if strings.EqualFold(orderbookNew[i].Side, order.Buy.String()) { + orderBook.Bids = append(orderBook.Bids, orderbook.Item{ + Amount: float64(orderbookNew[i].Size), + Price: orderbookNew[i].Price}) } } @@ -561,11 +545,6 @@ func (b *Bitmex) WithdrawFiatFundsToInternationalBank(withdrawRequest *withdraw. return nil, common.ErrFunctionNotSupported } -// GetWebsocket returns a pointer to the exchange websocket -func (b *Bitmex) GetWebsocket() (*wshandler.Websocket, error) { - return b.Websocket, nil -} - // GetFeeByType returns an estimate of fee based on type of transaction func (b *Bitmex) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { if !b.AllowAuthenticatedRequest() && // Todo check connection status @@ -587,6 +566,11 @@ func (b *Bitmex) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, e return nil, err } + format, err := b.GetPairFormat(asset.PerpetualContract, false) + if err != nil { + return nil, err + } + for i := range resp { orderSide := orderSideMap[resp[i].Side] orderType := orderTypeMap[resp[i].OrdType] @@ -604,7 +588,7 @@ func (b *Bitmex) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, e Status: order.Status(resp[i].OrdStatus), Pair: currency.NewPairWithDelimiter(resp[i].Symbol, resp[i].SettlCurrency, - b.GetPairFormat(asset.PerpetualContract, false).Delimiter), + format.Delimiter), } orders = append(orders, orderDetail) @@ -628,6 +612,11 @@ func (b *Bitmex) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, e return nil, err } + format, err := b.GetPairFormat(asset.PerpetualContract, false) + if err != nil { + return nil, err + } + for i := range resp { orderSide := orderSideMap[resp[i].Side] orderType := orderTypeMap[resp[i].OrdType] @@ -645,7 +634,7 @@ func (b *Bitmex) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, e Status: order.Status(resp[i].OrdStatus), Pair: currency.NewPairWithDelimiter(resp[i].Symbol, resp[i].SettlCurrency, - b.GetPairFormat(asset.PerpetualContract, false).Delimiter), + format.Delimiter), } orders = append(orders, orderDetail) @@ -658,25 +647,6 @@ func (b *Bitmex) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, e return orders, nil } -// SubscribeToWebsocketChannels appends to ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle subscribing -func (b *Bitmex) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - b.Websocket.SubscribeToChannels(channels) - return nil -} - -// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle unsubscribing -func (b *Bitmex) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - b.Websocket.RemoveSubscribedChannels(channels) - return nil -} - -// GetSubscriptions returns a copied list of subscriptions -func (b *Bitmex) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) { - return b.Websocket.GetSubscriptions(), nil -} - // AuthenticateWebsocket sends an authentication message to the websocket func (b *Bitmex) AuthenticateWebsocket() error { return b.websocketSendAuth() diff --git a/exchanges/bitstamp/bitstamp.go b/exchanges/bitstamp/bitstamp.go index 384a61f1..ca88a128 100644 --- a/exchanges/bitstamp/bitstamp.go +++ b/exchanges/bitstamp/bitstamp.go @@ -18,7 +18,6 @@ import ( exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/request" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" "github.com/thrasher-corp/gocryptotrader/log" ) @@ -64,7 +63,6 @@ const ( // Bitstamp is the overarching type across the bitstamp package type Bitstamp struct { exchange.Base - WebsocketConn *wshandler.WebsocketConnection } // GetFee returns an estimate of fee based on type of transaction diff --git a/exchanges/bitstamp/bitstamp_live_test.go b/exchanges/bitstamp/bitstamp_live_test.go index 5c6cebb0..96150d1e 100644 --- a/exchanges/bitstamp/bitstamp_live_test.go +++ b/exchanges/bitstamp/bitstamp_live_test.go @@ -30,12 +30,11 @@ func TestMain(m *testing.M) { bitstampConfig.API.Credentials.Secret = apiSecret bitstampConfig.API.Credentials.ClientID = customerID b.SetDefaults() + b.Websocket = sharedtestvalues.NewTestWebsocket() err = b.Setup(bitstampConfig) if err != nil { log.Fatal("Bitstamp setup error", err) } - b.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride() - b.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride() log.Printf(sharedtestvalues.LiveTesting, b.Name, b.API.Endpoints.URL) os.Exit(m.Run()) } diff --git a/exchanges/bitstamp/bitstamp_mock_test.go b/exchanges/bitstamp/bitstamp_mock_test.go index 136f8a85..eabbfc88 100644 --- a/exchanges/bitstamp/bitstamp_mock_test.go +++ b/exchanges/bitstamp/bitstamp_mock_test.go @@ -34,6 +34,7 @@ func TestMain(m *testing.M) { bitstampConfig.API.Credentials.Secret = apiSecret bitstampConfig.API.Credentials.ClientID = customerID b.SetDefaults() + b.Websocket = sharedtestvalues.NewTestWebsocket() err = b.Setup(bitstampConfig) if err != nil { log.Fatal("Bitstamp setup error", err) @@ -46,8 +47,6 @@ func TestMain(m *testing.M) { b.HTTPClient = newClient b.API.Endpoints.URL = serverDetails + "/api" - b.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride() - b.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride() log.Printf(sharedtestvalues.MockTesting, b.Name, b.API.Endpoints.URL) os.Exit(m.Run()) } diff --git a/exchanges/bitstamp/bitstamp_test.go b/exchanges/bitstamp/bitstamp_test.go index 7824c752..b8bfd573 100644 --- a/exchanges/bitstamp/bitstamp_test.go +++ b/exchanges/bitstamp/bitstamp_test.go @@ -678,21 +678,27 @@ func TestBitstamp_OHLC(t *testing.T) { } func TestBitstamp_GetHistoricCandles(t *testing.T) { - currencyPair := currency.NewPairFromString("btcusd") + currencyPair, err := currency.NewPairFromString("btcusd") + if err != nil { + t.Fatal(err) + } start := time.Unix(1546300800, 0) end := time.Unix(1577836799, 0) - _, err := b.GetHistoricCandles(currencyPair, asset.Spot, start, end, kline.OneDay) + _, err = b.GetHistoricCandles(currencyPair, asset.Spot, start, end, kline.OneDay) if err != nil { t.Fatal(err) } } func TestBitstamp_GetHistoricCandlesExtended(t *testing.T) { - currencyPair := currency.NewPairFromString("btcusd") + currencyPair, err := currency.NewPairFromString("btcusd") + if err != nil { + t.Fatal(err) + } start := time.Unix(1546300800, 0) end := time.Unix(1577836799, 0) - _, err := b.GetHistoricCandlesExtended(currencyPair, asset.Spot, start, end, kline.OneDay) + _, err = b.GetHistoricCandlesExtended(currencyPair, asset.Spot, start, end, kline.OneDay) if err != nil { t.Fatal(err) } diff --git a/exchanges/bitstamp/bitstamp_websocket.go b/exchanges/bitstamp/bitstamp_websocket.go index f91d5c88..7d8b72ce 100644 --- a/exchanges/bitstamp/bitstamp_websocket.go +++ b/exchanges/bitstamp/bitstamp_websocket.go @@ -9,11 +9,12 @@ import ( "time" "github.com/gorilla/websocket" + "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" "github.com/thrasher-corp/gocryptotrader/log" ) @@ -24,10 +25,10 @@ const ( // WsConnect connects to a websocket feed func (b *Bitstamp) WsConnect() error { if !b.Websocket.IsEnabled() || !b.IsEnabled() { - return errors.New(wshandler.WebsocketNotEnabled) + return errors.New(stream.WebsocketNotEnabled) } var dialer websocket.Dialer - err := b.WebsocketConn.Dial(&dialer, http.Header{}) + err := b.Websocket.Conn.Dial(&dialer, http.Header{}) if err != nil { return err } @@ -38,33 +39,27 @@ func (b *Bitstamp) WsConnect() error { if err != nil { b.Websocket.DataHandler <- err } - b.generateDefaultSubscriptions() + subs, err := b.generateDefaultSubscriptions() + if err != nil { + return err + } go b.wsReadData() - - return nil + return b.Websocket.SubscribeToChannels(subs) } // wsReadData receives and passes on websocket messages for processing func (b *Bitstamp) wsReadData() { b.Websocket.Wg.Add(1) - defer func() { - b.Websocket.Wg.Done() - }() + defer b.Websocket.Wg.Done() + for { - select { - case <-b.Websocket.ShutdownC: + resp := b.Websocket.Conn.ReadMessage() + if resp.Raw == nil { return - default: - resp, err := b.WebsocketConn.ReadMessage() - if err != nil { - b.Websocket.ReadMessageErrors <- err - return - } - b.Websocket.TrafficAlert <- struct{}{} - err = b.wsHandleData(resp.Raw) - if err != nil { - b.Websocket.DataHandler <- err - } + } + err := b.wsHandleData(resp.Raw) + if err != nil { + b.Websocket.DataHandler <- err } } } @@ -97,7 +92,11 @@ func (b *Bitstamp) wsHandleData(respRaw []byte) error { return err } currencyPair := strings.Split(wsResponse.Channel, "_") - p := currency.NewPairFromString(strings.ToUpper(currencyPair[2])) + p, err := currency.NewPairFromString(strings.ToUpper(currencyPair[2])) + if err != nil { + return err + } + err = b.wsUpdateOrderbook(wsOrderBookTemp.Data, p, asset.Spot) if err != nil { return err @@ -109,7 +108,11 @@ func (b *Bitstamp) wsHandleData(respRaw []byte) error { return err } currencyPair := strings.Split(wsResponse.Channel, "_") - p := currency.NewPairFromString(strings.ToUpper(currencyPair[2])) + p, err := currency.NewPairFromString(strings.ToUpper(currencyPair[2])) + if err != nil { + return err + } + side := order.Buy if wsTradeTemp.Data.Type == -1 { side = order.Sell @@ -119,7 +122,7 @@ func (b *Bitstamp) wsHandleData(respRaw []byte) error { if err != nil { return err } - b.Websocket.DataHandler <- wshandler.TradeData{ + b.Websocket.DataHandler <- stream.TradeData{ Timestamp: time.Unix(wsTradeTemp.Data.Timestamp, 0), CurrencyPair: p, AssetType: a, @@ -134,45 +137,73 @@ func (b *Bitstamp) wsHandleData(respRaw []byte) error { log.Debugf(log.ExchangeSys, "%v - Websocket order acknowledgement", b.Name) } default: - b.Websocket.DataHandler <- wshandler.UnhandledMessageWarning{Message: b.Name + wshandler.UnhandledMessage + string(respRaw)} + b.Websocket.DataHandler <- stream.UnhandledMessageWarning{Message: b.Name + stream.UnhandledMessage + string(respRaw)} } return nil } -func (b *Bitstamp) generateDefaultSubscriptions() { +func (b *Bitstamp) generateDefaultSubscriptions() ([]stream.ChannelSubscription, error) { var channels = []string{"live_trades_", "order_book_"} - enabledCurrencies := b.GetEnabledPairs(asset.Spot) - var subscriptions []wshandler.WebsocketChannelSubscription + enabledCurrencies, err := b.GetEnabledPairs(asset.Spot) + if err != nil { + return nil, err + } + var subscriptions []stream.ChannelSubscription for i := range channels { for j := range enabledCurrencies { - subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{ + subscriptions = append(subscriptions, stream.ChannelSubscription{ Channel: channels[i] + enabledCurrencies[j].Lower().String(), + Asset: asset.Spot, }) } } - b.Websocket.SubscribeToChannels(subscriptions) + return subscriptions, nil } // Subscribe sends a websocket message to receive data from the channel -func (b *Bitstamp) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error { - req := websocketEventRequest{ - Event: "bts:subscribe", - Data: websocketData{ - Channel: channelToSubscribe.Channel, - }, +func (b *Bitstamp) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error { + var errs common.Errors + for i := range channelsToSubscribe { + req := websocketEventRequest{ + Event: "bts:subscribe", + Data: websocketData{ + Channel: channelsToSubscribe[i].Channel, + }, + } + err := b.Websocket.Conn.SendJSONMessage(req) + if err != nil { + errs = append(errs, err) + continue + } + b.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe[i]) } - return b.WebsocketConn.SendJSONMessage(req) + if errs != nil { + return errs + } + return nil } // Unsubscribe sends a websocket message to stop receiving data from the channel -func (b *Bitstamp) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error { - req := websocketEventRequest{ - Event: "bts:unsubscribe", - Data: websocketData{ - Channel: channelToSubscribe.Channel, - }, +func (b *Bitstamp) Unsubscribe(channelsToUnsubscribe []stream.ChannelSubscription) error { + var errs common.Errors + for i := range channelsToUnsubscribe { + req := websocketEventRequest{ + Event: "bts:unsubscribe", + Data: websocketData{ + Channel: channelsToUnsubscribe[i].Channel, + }, + } + err := b.Websocket.Conn.SendJSONMessage(req) + if err != nil { + errs = append(errs, err) + continue + } + b.Websocket.RemoveSuccessfulUnsubscriptions(channelsToUnsubscribe[i]) } - return b.WebsocketConn.SendJSONMessage(req) + if errs != nil { + return errs + } + return nil } func (b *Bitstamp) wsUpdateOrderbook(update websocketOrderBook, p currency.Pair, assetType asset.Item) error { @@ -207,29 +238,22 @@ func (b *Bitstamp) wsUpdateOrderbook(update websocketOrderBook, p currency.Pair, bids = append(bids, orderbook.Item{Price: target, Amount: amount}) } - err := b.Websocket.Orderbook.LoadSnapshot(&orderbook.Base{ + return b.Websocket.Orderbook.LoadSnapshot(&orderbook.Base{ Bids: bids, Asks: asks, Pair: p, LastUpdated: time.Unix(update.Timestamp, 0), - AssetType: asset.Spot, + AssetType: assetType, ExchangeName: b.Name, }) +} + +func (b *Bitstamp) seedOrderBook() error { + p, err := b.GetEnabledPairs(asset.Spot) if err != nil { return err } - b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{ - Pair: p, - Asset: assetType, - Exchange: b.Name, - } - - return nil -} - -func (b *Bitstamp) seedOrderBook() error { - p := b.GetEnabledPairs(asset.Spot) for x := range p { orderbookSeed, err := b.GetOrderbook(p[x].String()) if err != nil { @@ -257,12 +281,6 @@ func (b *Bitstamp) seedOrderBook() error { if err != nil { return err } - - b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{ - Pair: p[x], - Asset: asset.Spot, - Exchange: b.Name, - } } return nil } diff --git a/exchanges/bitstamp/bitstamp_wrapper.go b/exchanges/bitstamp/bitstamp_wrapper.go index 79b2d1ca..1ab4624c 100644 --- a/exchanges/bitstamp/bitstamp_wrapper.go +++ b/exchanges/bitstamp/bitstamp_wrapper.go @@ -18,8 +18,8 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" "github.com/thrasher-corp/gocryptotrader/log" "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) @@ -56,17 +56,11 @@ func (b *Bitstamp) SetDefaults() { b.API.CredentialsValidator.RequiresSecret = true b.API.CredentialsValidator.RequiresClientID = true - b.CurrencyPairs = currency.PairsManager{ - AssetTypes: asset.Items{ - asset.Spot, - }, - UseGlobalFormat: true, - RequestFormat: ¤cy.PairFormat{ - Uppercase: true, - }, - ConfigFormat: ¤cy.PairFormat{ - Uppercase: true, - }, + requestFmt := ¤cy.PairFormat{Uppercase: true} + configFmt := ¤cy.PairFormat{Uppercase: true} + err := b.SetGlobalPairsManager(requestFmt, configFmt, asset.Spot) + if err != nil { + log.Errorln(log.ExchangeSys, err) } b.Features = exchange.Features{ @@ -137,7 +131,7 @@ func (b *Bitstamp) SetDefaults() { b.API.Endpoints.URLDefault = bitstampAPIURL b.API.Endpoints.URL = b.API.Endpoints.URLDefault b.API.Endpoints.WebsocketURL = bitstampWSURL - b.Websocket = wshandler.New() + b.Websocket = stream.New() b.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit b.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout b.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit @@ -155,34 +149,30 @@ func (b *Bitstamp) Setup(exch *config.ExchangeConfig) error { return err } - err = b.Websocket.Setup( - &wshandler.WebsocketSetup{ - Enabled: exch.Features.Enabled.Websocket, - Verbose: exch.Verbose, - AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, - WebsocketTimeout: exch.WebsocketTrafficTimeout, - DefaultURL: bitstampWSURL, - ExchangeName: exch.Name, - RunningURL: exch.API.Endpoints.WebsocketURL, - Connector: b.WsConnect, - Subscriber: b.Subscribe, - UnSubscriber: b.Unsubscribe, - Features: &b.Features.Supports.WebsocketCapabilities, - }) + err = b.Websocket.Setup(&stream.WebsocketSetup{ + Enabled: exch.Features.Enabled.Websocket, + Verbose: exch.Verbose, + AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, + WebsocketTimeout: exch.WebsocketTrafficTimeout, + DefaultURL: bitstampWSURL, + ExchangeName: exch.Name, + RunningURL: exch.API.Endpoints.WebsocketURL, + Connector: b.WsConnect, + Subscriber: b.Subscribe, + UnSubscriber: b.Unsubscribe, + GenerateSubscriptions: b.generateDefaultSubscriptions, + Features: &b.Features.Supports.WebsocketCapabilities, + OrderbookBufferLimit: exch.WebsocketOrderbookBufferLimit, + }) if err != nil { return err } - b.WebsocketConn = &wshandler.WebsocketConnection{ - ExchangeName: b.Name, + return b.Websocket.SetupNewConnection(stream.ConnectionSetup{ URL: b.Websocket.GetWebsocketURL(), - ProxyURL: b.Websocket.GetProxyAddress(), - Verbose: b.Verbose, ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout, ResponseMaxLimit: exch.WebsocketResponseMaxLimit, - } - - return nil + }) } // Start starts the Bitstamp go routine @@ -245,32 +235,35 @@ func (b *Bitstamp) UpdateTradablePairs(forceUpdate bool) error { return err } - return b.UpdatePairs(currency.NewPairsFromStrings(pairs), asset.Spot, false, forceUpdate) + p, err := currency.NewPairsFromStrings(pairs) + if err != nil { + return err + } + + return b.UpdatePairs(p, asset.Spot, false, forceUpdate) } // UpdateTicker updates and returns the ticker for a currency pair func (b *Bitstamp) UpdateTicker(p currency.Pair, assetType asset.Item) (*ticker.Price, error) { - tickerPrice := new(ticker.Price) tick, err := b.GetTicker(p.String(), false) if err != nil { - return tickerPrice, err + return nil, err } - tickerPrice = &ticker.Price{ - Last: tick.Last, - High: tick.High, - Low: tick.Low, - Bid: tick.Bid, - Ask: tick.Ask, - Volume: tick.Volume, - Open: tick.Open, - Pair: p, - LastUpdated: time.Unix(tick.Timestamp, 0), - } - - err = ticker.ProcessTicker(b.Name, tickerPrice, assetType) + err = ticker.ProcessTicker(&ticker.Price{ + Last: tick.Last, + High: tick.High, + Low: tick.Low, + Bid: tick.Bid, + Ask: tick.Ask, + Volume: tick.Volume, + Open: tick.Open, + Pair: p, + LastUpdated: time.Unix(tick.Timestamp, 0), + ExchangeName: b.Name, + AssetType: assetType}) if err != nil { - return tickerPrice, err + return nil, err } return ticker.GetTicker(b.Name, p, assetType) @@ -548,11 +541,6 @@ func (b *Bitstamp) WithdrawFiatFundsToInternationalBank(withdrawRequest *withdra }, nil } -// GetWebsocket returns a pointer to the exchange websocket -func (b *Bitstamp) GetWebsocket() (*wshandler.Websocket, error) { - return b.Websocket, nil -} - // GetActiveOrders retrieves any orders that are active/open func (b *Bitstamp) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, error) { var currPair string @@ -580,6 +568,11 @@ func (b *Bitstamp) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, "%s GetActiveOrders unable to parse time: %s\n", b.Name, err) } + pair, err := currency.NewPairFromString(resp[i].Currency) + if err != nil { + return nil, err + } + orders = append(orders, order.Detail{ Amount: resp[i].Amount, ID: strconv.FormatInt(resp[i].ID, 10), @@ -587,7 +580,7 @@ func (b *Bitstamp) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, Type: order.Limit, Side: orderSide, Date: tm, - Pair: currency.NewPairFromString(resp[i].Currency), + Pair: pair, Exchange: b.Name, }) } @@ -604,6 +597,12 @@ func (b *Bitstamp) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, if len(req.Pairs) == 1 { currPair = req.Pairs[0].String() } + + format, err := b.GetPairFormat(asset.Spot, false) + if err != nil { + return nil, err + } + resp, err := b.GetUserTransactions(currPair) if err != nil { return nil, err @@ -644,7 +643,7 @@ func (b *Bitstamp) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, if quoteCurrency.String() != "" && baseCurrency.String() != "" { currPair = currency.NewPairWithDelimiter(baseCurrency.String(), quoteCurrency.String(), - b.GetPairFormat(asset.Spot, false).Delimiter) + format.Delimiter) } tm, err := parseTime(resp[i].Date) @@ -666,30 +665,6 @@ func (b *Bitstamp) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, return orders, nil } -// SubscribeToWebsocketChannels appends to ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle subscribing -func (b *Bitstamp) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - b.Websocket.SubscribeToChannels(channels) - return nil -} - -// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle unsubscribing -func (b *Bitstamp) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - b.Websocket.RemoveSubscribedChannels(channels) - return nil -} - -// GetSubscriptions returns a copied list of subscriptions -func (b *Bitstamp) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) { - return b.Websocket.GetSubscriptions(), nil -} - -// AuthenticateWebsocket sends an authentication message to the websocket -func (b *Bitstamp) AuthenticateWebsocket() error { - return common.ErrFunctionNotSupported -} - // ValidateCredentials validates current credentials used for wrapper // functionality func (b *Bitstamp) ValidateCredentials() error { @@ -712,8 +687,13 @@ func (b *Bitstamp) GetHistoricCandles(pair currency.Pair, a asset.Item, start, e Interval: interval, } + formattedPair, err := b.FormatExchangeCurrency(pair, a) + if err != nil { + return kline.Item{}, err + } + candles, err := b.OHLC( - b.FormatExchangeCurrency(pair, a).Lower().String(), + formattedPair.Lower().String(), start, end, b.FormatExchangeKlineInterval(interval), @@ -759,9 +739,14 @@ func (b *Bitstamp) GetHistoricCandlesExtended(pair currency.Pair, a asset.Item, } dates := kline.CalcDateRanges(start, end, interval, b.Features.Enabled.Kline.ResultLimit) + formattedPair, err := b.FormatExchangeCurrency(pair, a) + if err != nil { + return kline.Item{}, err + } + for x := range dates { candles, err := b.OHLC( - b.FormatExchangeCurrency(pair, a).Lower().String(), + formattedPair.Lower().String(), dates[x].Start, dates[x].End, b.FormatExchangeKlineInterval(interval), diff --git a/exchanges/bittrex/bittrex_test.go b/exchanges/bittrex/bittrex_test.go index 0eea30a0..196db8d7 100644 --- a/exchanges/bittrex/bittrex_test.go +++ b/exchanges/bittrex/bittrex_test.go @@ -343,14 +343,19 @@ func TestFormatWithdrawPermissions(t *testing.T) { } func TestGetActiveOrders(t *testing.T) { + p, err := currency.NewPairFromString(currPair) + if err != nil { + t.Fatal(err) + } + var getOrdersRequest = order.GetOrdersRequest{ Type: order.AnyType, - Pairs: []currency.Pair{currency.NewPairFromString(currPair)}, + Pairs: []currency.Pair{p}, } getOrdersRequest.Pairs[0].Delimiter = "-" - _, err := b.GetActiveOrders(&getOrdersRequest) + _, err = b.GetActiveOrders(&getOrdersRequest) if areTestAPIKeysSet() && err != nil { t.Errorf("Could not get open orders: %s", err) } else if !areTestAPIKeysSet() && err == nil { diff --git a/exchanges/bittrex/bittrex_wrapper.go b/exchanges/bittrex/bittrex_wrapper.go index 032aa1f7..1cdf6fe3 100644 --- a/exchanges/bittrex/bittrex_wrapper.go +++ b/exchanges/bittrex/bittrex_wrapper.go @@ -18,7 +18,6 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" "github.com/thrasher-corp/gocryptotrader/log" "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) @@ -54,19 +53,11 @@ func (b *Bittrex) SetDefaults() { b.API.CredentialsValidator.RequiresKey = true b.API.CredentialsValidator.RequiresSecret = true - b.CurrencyPairs = currency.PairsManager{ - AssetTypes: asset.Items{ - asset.Spot, - }, - UseGlobalFormat: true, - RequestFormat: ¤cy.PairFormat{ - Delimiter: "-", - Uppercase: true, - }, - ConfigFormat: ¤cy.PairFormat{ - Delimiter: "-", - Uppercase: true, - }, + requestFmt := ¤cy.PairFormat{Delimiter: currency.DashDelimiter, Uppercase: true} + configFmt := ¤cy.PairFormat{Delimiter: currency.DashDelimiter, Uppercase: true} + err := b.SetGlobalPairsManager(requestFmt, configFmt, asset.Spot) + if err != nil { + log.Errorln(log.ExchangeSys, err) } b.Features = exchange.Features{ @@ -113,7 +104,6 @@ func (b *Bittrex) Setup(exch *config.ExchangeConfig) error { b.SetEnabled(false) return nil } - return b.SetupDefaults(exch) } @@ -133,24 +123,53 @@ func (b *Bittrex) Run() { } forceUpdate := false - delim := b.GetPairFormat(asset.Spot, false).Delimiter - if !common.StringDataContains(b.GetEnabledPairs(asset.Spot).Strings(), delim) || - !common.StringDataContains(b.GetAvailablePairs(asset.Spot).Strings(), delim) { + format, err := b.GetPairFormat(asset.Spot, false) + if err != nil { + log.Errorf(log.ExchangeSys, + "%s failed to update currencies. Err: %s\n", + b.Name, + err) + return + } + + pairs, err := b.GetEnabledPairs(asset.Spot) + if err != nil { + log.Errorf(log.ExchangeSys, + "%s failed to update currencies. Err: %s\n", + b.Name, + err) + return + } + + avail, err := b.GetAvailablePairs(asset.Spot) + if err != nil { + log.Errorf(log.ExchangeSys, + "%s failed to update currencies. Err: %s\n", + b.Name, + err) + return + } + + if !common.StringDataContains(pairs.Strings(), format.Delimiter) || + !common.StringDataContains(avail.Strings(), format.Delimiter) { forceUpdate = true log.Warn(log.ExchangeSys, "Available pairs for Bittrex reset due to config upgrade, please enable the ones you would like again") - - err := b.UpdatePairs(currency.NewPairsFromStrings( - []string{currency.USDT.String() + delim + currency.BTC.String()}, - ), - asset.Spot, - true, - true, - ) + pairs, err = currency.NewPairsFromStrings([]string{currency.USDT.String() + + format.Delimiter + + currency.BTC.String()}) if err != nil { log.Errorf(log.ExchangeSys, "%s failed to update currencies. Err: %s\n", b.Name, err) + } else { + err = b.UpdatePairs(pairs, asset.Spot, true, true) + if err != nil { + log.Errorf(log.ExchangeSys, + "%s failed to update currencies. Err: %s\n", + b.Name, + err) + } } } @@ -158,7 +177,7 @@ func (b *Bittrex) Run() { return } - err := b.UpdateTradablePairs(forceUpdate) + err = b.UpdateTradablePairs(forceUpdate) if err != nil { log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", @@ -192,8 +211,12 @@ func (b *Bittrex) UpdateTradablePairs(forceUpdate bool) error { if err != nil { return err } + p, err := currency.NewPairsFromStrings(pairs) + if err != nil { + return err + } - return b.UpdatePairs(currency.NewPairsFromStrings(pairs), asset.Spot, false, forceUpdate) + return b.UpdatePairs(p, asset.Spot, false, forceUpdate) } // UpdateAccountInfo Retrieves balances for all enabled currencies for the @@ -239,41 +262,46 @@ func (b *Bittrex) FetchAccountInfo() (account.Holdings, error) { // UpdateTicker updates and returns the ticker for a currency pair func (b *Bittrex) UpdateTicker(p currency.Pair, assetType asset.Item) (*ticker.Price, error) { - tickerPrice := new(ticker.Price) ticks, err := b.GetMarketSummaries() if err != nil { - return tickerPrice, err - } - pairs := b.GetEnabledPairs(assetType) - for i := range pairs { - for j := range ticks.Result { - if !strings.EqualFold(ticks.Result[j].MarketName, pairs[i].String()) { - continue - } - tickerTime, err := parseTime(ticks.Result[j].TimeStamp) - if err != nil { - log.Errorf(log.ExchangeSys, - "%s UpdateTicker unable to parse time: %s\n", b.Name, err) - } - tickerPrice = &ticker.Price{ - Last: ticks.Result[j].Last, - High: ticks.Result[j].High, - Low: ticks.Result[j].Low, - Bid: ticks.Result[j].Bid, - Ask: ticks.Result[j].Ask, - Volume: ticks.Result[j].BaseVolume, - QuoteVolume: ticks.Result[j].Volume, - Close: ticks.Result[j].PrevDay, - Pair: pairs[i], - LastUpdated: tickerTime, - } - err = ticker.ProcessTicker(b.Name, tickerPrice, assetType) - if err != nil { - log.Error(log.Ticker, err) - } - } + return nil, err } + pairs, err := b.GetEnabledPairs(assetType) + if err != nil { + return nil, err + } + + for j := range ticks.Result { + cp, err := currency.NewPairFromString(ticks.Result[j].MarketName) + if err != nil { + return nil, err + } + if !pairs.Contains(cp, true) { + continue + } + tickerTime, err := parseTime(ticks.Result[j].TimeStamp) + if err != nil { + return nil, err + } + + err = ticker.ProcessTicker(&ticker.Price{ + Last: ticks.Result[j].Last, + High: ticks.Result[j].High, + Low: ticks.Result[j].Low, + Bid: ticks.Result[j].Bid, + Ask: ticks.Result[j].Ask, + Volume: ticks.Result[j].BaseVolume, + QuoteVolume: ticks.Result[j].Volume, + Close: ticks.Result[j].PrevDay, + Pair: cp, + LastUpdated: tickerTime, + ExchangeName: b.Name, + AssetType: assetType}) + if err != nil { + return nil, err + } + } return ticker.GetTicker(b.Name, p, assetType) } @@ -297,12 +325,17 @@ func (b *Bittrex) FetchOrderbook(p currency.Pair, assetType asset.Item) (*orderb // UpdateOrderbook updates and returns the orderbook for a currency pair func (b *Bittrex) UpdateOrderbook(p currency.Pair, assetType asset.Item) (*orderbook.Base, error) { - orderBook := new(orderbook.Base) - orderbookNew, err := b.GetOrderbook(b.FormatExchangeCurrency(p, assetType).String()) + fpair, err := b.FormatExchangeCurrency(p, assetType) if err != nil { - return orderBook, err + return nil, err } + orderbookNew, err := b.GetOrderbook(fpair.String()) + if err != nil { + return nil, err + } + + orderBook := new(orderbook.Base) for x := range orderbookNew.Result.Buy { orderBook.Bids = append(orderBook.Bids, orderbook.Item{ @@ -453,11 +486,6 @@ func (b *Bittrex) WithdrawFiatFundsToInternationalBank(withdrawRequest *withdraw return nil, common.ErrFunctionNotSupported } -// GetWebsocket returns a pointer to the exchange websocket -func (b *Bittrex) GetWebsocket() (*wshandler.Websocket, error) { - return nil, common.ErrNotYetImplemented -} - // GetFeeByType returns an estimate of fee based on type of transaction func (b *Bittrex) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { if !b.AllowAuthenticatedRequest() && // Todo check connection status @@ -474,6 +502,11 @@ func (b *Bittrex) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, currPair = req.Pairs[0].String() } + format, err := b.GetPairFormat(asset.Spot, false) + if err != nil { + return nil, err + } + resp, err := b.GetOpenOrders(currPair) if err != nil { return nil, err @@ -491,8 +524,16 @@ func (b *Bittrex) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, resp.Result[i].Opened) } - pair := currency.NewPairDelimiter(resp.Result[i].Exchange, - b.GetPairFormat(asset.Spot, false).Delimiter) + pair, err := currency.NewPairDelimiter(resp.Result[i].Exchange, + format.Delimiter) + if err != nil { + log.Errorf(log.ExchangeSys, + "Exchange %v Func %v Order %v Could not parse currency pair %v", + b.Name, + "GetActiveOrders", + resp.Result[i].OrderUUID, + err) + } orderType := order.Type(strings.ToUpper(resp.Result[i].Type)) orders = append(orders, order.Detail{ @@ -521,6 +562,11 @@ func (b *Bittrex) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, currPair = req.Pairs[0].String() } + format, err := b.GetPairFormat(asset.Spot, false) + if err != nil { + return nil, err + } + resp, err := b.GetOrderHistoryForCurrency(currPair) if err != nil { return nil, err @@ -533,13 +579,21 @@ func (b *Bittrex) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, log.Errorf(log.ExchangeSys, "Exchange %v Func %v Order %v Could not parse date to unix with value of %v", b.Name, - "GetActiveOrders", + "GetOrderHistory", resp.Result[i].OrderUUID, resp.Result[i].Opened) } - pair := currency.NewPairDelimiter(resp.Result[i].Exchange, - b.GetPairFormat(asset.Spot, false).Delimiter) + pair, err := currency.NewPairDelimiter(resp.Result[i].Exchange, + format.Delimiter) + if err != nil { + log.Errorf(log.ExchangeSys, + "Exchange %v Func %v Order %v Could not parse currency pair %v", + b.Name, + "GetOrderHistory", + resp.Result[i].OrderUUID, + err) + } orderType := order.Type(strings.ToUpper(resp.Result[i].Type)) orders = append(orders, order.Detail{ @@ -561,28 +615,6 @@ func (b *Bittrex) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, return orders, nil } -// SubscribeToWebsocketChannels appends to ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle subscribing -func (b *Bittrex) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - return common.ErrFunctionNotSupported -} - -// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle unsubscribing -func (b *Bittrex) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - return common.ErrFunctionNotSupported -} - -// GetSubscriptions returns a copied list of subscriptions -func (b *Bittrex) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) { - return nil, common.ErrFunctionNotSupported -} - -// AuthenticateWebsocket sends an authentication message to the websocket -func (b *Bittrex) AuthenticateWebsocket() error { - return common.ErrFunctionNotSupported -} - // ValidateCredentials validates current credentials used for wrapper // functionality func (b *Bittrex) ValidateCredentials() error { diff --git a/exchanges/btcmarkets/btcmarkets.go b/exchanges/btcmarkets/btcmarkets.go index 870dc56c..02988dff 100644 --- a/exchanges/btcmarkets/btcmarkets.go +++ b/exchanges/btcmarkets/btcmarkets.go @@ -18,7 +18,6 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/request" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" ) const ( @@ -72,13 +71,12 @@ const ( heartbeat = "heartbeat" tick = "tick" wsOB = "orderbookUpdate" - tradeEndPoint = "tradeEndPoint" + tradeEndPoint = "trade" ) // BTCMarkets is the overarching type across the BTCMarkets package type BTCMarkets struct { exchange.Base - WebsocketConn *wshandler.WebsocketConnection } // GetMarkets returns the BTCMarkets instruments @@ -191,7 +189,7 @@ func (b *BTCMarkets) GetMarketCandles(marketID, timeWindow string, from, to time } // GetTickers gets multiple tickers -func (b *BTCMarkets) GetTickers(marketIDs []currency.Pair) ([]Ticker, error) { +func (b *BTCMarkets) GetTickers(marketIDs currency.Pairs) ([]Ticker, error) { var tickers []Ticker params := url.Values{} for x := range marketIDs { @@ -761,7 +759,11 @@ func (b *BTCMarkets) GetFee(feeBuilder *exchange.FeeBuilder) (float64, error) { return fee, err } for x := range temp.FeeByMarkets { - if currency.NewPairFromString(temp.FeeByMarkets[x].MarketID) == feeBuilder.Pair { + p, err := currency.NewPairFromString(temp.FeeByMarkets[x].MarketID) + if err != nil { + return 0, err + } + if p == feeBuilder.Pair { fee = temp.FeeByMarkets[x].MakerFeeRate if !feeBuilder.IsMaker { fee = temp.FeeByMarkets[x].TakerFeeRate diff --git a/exchanges/btcmarkets/btcmarkets_test.go b/exchanges/btcmarkets/btcmarkets_test.go index b0df0e52..46acc76d 100644 --- a/exchanges/btcmarkets/btcmarkets_test.go +++ b/exchanges/btcmarkets/btcmarkets_test.go @@ -44,21 +44,17 @@ func TestMain(m *testing.M) { bConfig.API.Credentials.Key = apiKey bConfig.API.Credentials.Secret = apiSecret bConfig.API.AuthenticatedSupport = true - + b.Websocket = sharedtestvalues.NewTestWebsocket() err = b.Setup(bConfig) if err != nil { log.Fatal(err) } - b.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride() - b.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride() - err = b.ValidateCredentials() if err != nil { fmt.Println("API credentials are invalid:", err) b.API.AuthenticatedSupport = false b.API.AuthenticatedWebsocketSupport = false } - os.Exit(m.Run()) } @@ -108,8 +104,11 @@ func TestGetMarketCandles(t *testing.T) { func TestGetTickers(t *testing.T) { t.Parallel() - temp := currency.NewPairsFromStrings([]string{LTCAUD, BTCAUD}) - _, err := b.GetTickers(temp) + temp, err := currency.NewPairsFromStrings([]string{LTCAUD, BTCAUD}) + if err != nil { + t.Fatal(err) + } + _, err = b.GetTickers(temp) if err != nil { t.Error(err) } @@ -720,8 +719,11 @@ func TestWsOrders(t *testing.T) { } func TestBTCMarkets_GetHistoricCandles(t *testing.T) { - p := currency.NewPairFromString(BTCAUD) - _, err := b.GetHistoricCandles(p, asset.Spot, time.Now().Add(-time.Hour*24).UTC(), time.Now().UTC(), kline.OneHour) + p, err := currency.NewPairFromString(BTCAUD) + if err != nil { + t.Fatal(err) + } + _, err = b.GetHistoricCandles(p, asset.Spot, time.Now().Add(-time.Hour*24).UTC(), time.Now().UTC(), kline.OneHour) if err != nil { t.Fatal(err) } @@ -736,8 +738,11 @@ func TestBTCMarkets_GetHistoricCandles(t *testing.T) { func TestBTCMarkets_GetHistoricCandlesExtended(t *testing.T) { start := time.Now().AddDate(0, 0, -1001) end := time.Now() - p := currency.NewPairFromString(BTCAUD) - _, err := b.GetHistoricCandlesExtended(p, asset.Spot, start, end, kline.OneDay) + p, err := currency.NewPairFromString(BTCAUD) + if err != nil { + t.Fatal(err) + } + _, err = b.GetHistoricCandlesExtended(p, asset.Spot, start, end, kline.OneDay) if err != nil { t.Fatal(err) } diff --git a/exchanges/btcmarkets/btcmarkets_types.go b/exchanges/btcmarkets/btcmarkets_types.go index 0592296f..8c9c77a6 100644 --- a/exchanges/btcmarkets/btcmarkets_types.go +++ b/exchanges/btcmarkets/btcmarkets_types.go @@ -337,21 +337,14 @@ type TradingFeeResponse struct { FeeByMarkets []TradingFeeData `json:"FeeByMarkets"` } -// WsSubscribe message sent via ws to subscribe +// WsSubscribe defines a subscription message used in the Subscribe function type WsSubscribe struct { MarketIDs []string `json:"marketIds,omitempty"` - Channels []string `json:"channels"` - MessageType string `json:"messageType"` -} - -// WsAuthSubscribe message sent via login to subscribe -type WsAuthSubscribe struct { - MarketIDs []string `json:"marketIds,omitempty"` - Channels []string `json:"channels"` - Key string `json:"key"` - Signature string `json:"signature"` - Timestamp string `json:"timestamp"` - MessageType string `json:"messageType"` + Channels []string `json:"channels,omitempty"` + Key string `json:"key,omitempty"` + Signature string `json:"signature,omitempty"` + Timestamp string `json:"timestamp,omitempty"` + MessageType string `json:"messageType,omitempty"` } // WsMessageType message sent via ws to determine type @@ -380,6 +373,7 @@ type WsTrade struct { TradeID int64 `json:"tradeId"` Price float64 `json:"price,string"` Volume float64 `json:"volume,string"` + Side string `json:"side"` MessageType string `json:"messageType"` } diff --git a/exchanges/btcmarkets/btcmarkets_websocket.go b/exchanges/btcmarkets/btcmarkets_websocket.go index a5f187a5..7fe37656 100644 --- a/exchanges/btcmarkets/btcmarkets_websocket.go +++ b/exchanges/btcmarkets/btcmarkets_websocket.go @@ -12,13 +12,12 @@ import ( "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/crypto" "github.com/thrasher-corp/gocryptotrader/currency" - exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream/buffer" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook" "github.com/thrasher-corp/gocryptotrader/log" ) @@ -29,10 +28,10 @@ const ( // WsConnect connects to a websocket feed func (b *BTCMarkets) WsConnect() error { if !b.Websocket.IsEnabled() || !b.IsEnabled() { - return errors.New(wshandler.WebsocketNotEnabled) + return errors.New(stream.WebsocketNotEnabled) } var dialer websocket.Dialer - err := b.WebsocketConn.Dial(&dialer, http.Header{}) + err := b.Websocket.Conn.Dial(&dialer, http.Header{}) if err != nil { return err } @@ -40,35 +39,26 @@ func (b *BTCMarkets) WsConnect() error { log.Debugf(log.ExchangeSys, "%s Connected to Websocket.\n", b.Name) } go b.wsReadData() - if b.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) { - b.createChannels() + subs, err := b.generateDefaultSubscriptions() + if err != nil { + return err } - b.generateDefaultSubscriptions() - return nil + return b.Websocket.SubscribeToChannels(subs) } // wsReadData receives and passes on websocket messages for processing func (b *BTCMarkets) wsReadData() { b.Websocket.Wg.Add(1) - defer func() { - b.Websocket.Wg.Done() - }() + defer b.Websocket.Wg.Done() for { - select { - case <-b.Websocket.ShutdownC: + resp := b.Websocket.Conn.ReadMessage() + if resp.Raw == nil { return - default: - resp, err := b.WebsocketConn.ReadMessage() - if err != nil { - b.Websocket.ReadMessageErrors <- err - return - } - b.Websocket.TrafficAlert <- struct{}{} - err = b.wsHandleData(resp.Raw) - if err != nil { - b.Websocket.DataHandler <- err - } + } + err := b.wsHandleData(resp.Raw) + if err != nil { + b.Websocket.DataHandler <- err } } } @@ -91,7 +81,11 @@ func (b *BTCMarkets) wsHandleData(respRaw []byte) error { return err } - p := currency.NewPairFromString(ob.Currency) + p, err := currency.NewPairFromString(ob.Currency) + if err != nil { + return err + } + var bids, asks []orderbook.Item for x := range ob.Bids { var price, amount float64 @@ -135,7 +129,7 @@ func (b *BTCMarkets) wsHandleData(respRaw []byte) error { ExchangeName: b.Name, }) } else { - err = b.Websocket.Orderbook.Update(&wsorderbook.WebsocketOrderbookUpdate{ + err = b.Websocket.Orderbook.Update(&buffer.Update{ UpdateTime: ob.Timestamp, Asset: asset.Spot, Bids: bids, @@ -147,26 +141,31 @@ func (b *BTCMarkets) wsHandleData(respRaw []byte) error { if err != nil { return err } - b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{ - Pair: p, - Asset: asset.Spot, - Exchange: b.Name, - } case tradeEndPoint: var trade WsTrade err := json.Unmarshal(respRaw, &trade) if err != nil { return err } - p := currency.NewPairFromString(trade.Currency) - b.Websocket.DataHandler <- wshandler.TradeData{ + + p, err := currency.NewPairFromString(trade.Currency) + if err != nil { + return err + } + + side := order.Buy + if trade.Side == "Ask" { + side = order.Sell + } + + b.Websocket.DataHandler <- stream.TradeData{ Timestamp: trade.Timestamp, CurrencyPair: p, AssetType: asset.Spot, Exchange: b.Name, Price: trade.Price, Amount: trade.Volume, - Side: order.UnknownSide, + Side: side, EventType: order.UnknownType, } case tick: @@ -176,7 +175,10 @@ func (b *BTCMarkets) wsHandleData(respRaw []byte) error { return err } - p := currency.NewPairFromString(tick.Currency) + p, err := currency.NewPairFromString(tick.Currency) + if err != nil { + return err + } b.Websocket.DataHandler <- &ticker.Price{ ExchangeName: b.Name, @@ -247,12 +249,16 @@ func (b *BTCMarkets) wsHandleData(respRaw []byte) error { Err: err, } } - p := currency.NewPairFromString(orderData.MarketID) - var a asset.Item - a, err = b.GetPairAssetType(p) + + p, err := currency.NewPairFromString(orderData.MarketID) if err != nil { - return err + b.Websocket.DataHandler <- order.ClassificationError{ + Exchange: b.Name, + OrderID: orderID, + Err: err, + } } + b.Websocket.DataHandler <- &order.Detail{ Price: price, Amount: originalAmount, @@ -263,7 +269,7 @@ func (b *BTCMarkets) wsHandleData(respRaw []byte) error { Type: oType, Side: oSide, Status: oStatus, - AssetType: a, + AssetType: asset.Spot, Date: orderData.Timestamp, Trades: trades, Pair: p, @@ -276,91 +282,80 @@ func (b *BTCMarkets) wsHandleData(respRaw []byte) error { } return fmt.Errorf("%v websocket error. Code: %v Message: %v", b.Name, wsErr.Code, wsErr.Message) default: - b.Websocket.DataHandler <- wshandler.UnhandledMessageWarning{Message: b.Name + wshandler.UnhandledMessage + string(respRaw)} + b.Websocket.DataHandler <- stream.UnhandledMessageWarning{Message: b.Name + stream.UnhandledMessage + string(respRaw)} return nil } return nil } -func (b *BTCMarkets) generateDefaultSubscriptions() { - var channels = []string{tick, tradeEndPoint, wsOB} - enabledCurrencies := b.GetEnabledPairs(asset.Spot) - var subscriptions []wshandler.WebsocketChannelSubscription +func (b *BTCMarkets) generateDefaultSubscriptions() ([]stream.ChannelSubscription, error) { + var channels = []string{wsOB, tick, tradeEndPoint} + enabledCurrencies, err := b.GetEnabledPairs(asset.Spot) + if err != nil { + return nil, err + } + var subscriptions []stream.ChannelSubscription for i := range channels { for j := range enabledCurrencies { - subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{ + subscriptions = append(subscriptions, stream.ChannelSubscription{ Channel: channels[i], Currency: enabledCurrencies[j], + Asset: asset.Spot, }) } } - b.Websocket.SubscribeToChannels(subscriptions) + + var authChannels = []string{fundChange, heartbeat, orderChange} + if b.Websocket.CanUseAuthenticatedEndpoints() { + for i := range authChannels { + subscriptions = append(subscriptions, stream.ChannelSubscription{ + Channel: authChannels[i], + }) + } + } + return subscriptions, nil } // Subscribe sends a websocket message to receive data from the channel -func (b *BTCMarkets) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error { - unauthChannels := []string{tick, tradeEndPoint, wsOB} - authChannels := []string{fundChange, heartbeat, orderChange} - switch { - case common.StringDataCompare(unauthChannels, channelToSubscribe.Channel): - req := WsSubscribe{ - MarketIDs: []string{b.FormatExchangeCurrency(channelToSubscribe.Currency, asset.Spot).String()}, - Channels: []string{channelToSubscribe.Channel}, - MessageType: subscribe, - } - err := b.WebsocketConn.SendJSONMessage(req) - if err != nil { - return err - } - case common.StringDataCompare(authChannels, channelToSubscribe.Channel): - message, ok := channelToSubscribe.Params["AuthSub"].(WsAuthSubscribe) - if !ok { - return errors.New("invalid params data") - } - tempAuthData := b.generateAuthSubscriptions() - message.Channels = append(message.Channels, channelToSubscribe.Channel, heartbeat) - message.Key = tempAuthData.Key - message.Signature = tempAuthData.Signature - message.Timestamp = tempAuthData.Timestamp - err := b.WebsocketConn.SendJSONMessage(message) - if err != nil { - return err +func (b *BTCMarkets) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error { + var authChannels = []string{fundChange, heartbeat, orderChange} + + var payload WsSubscribe + payload.MessageType = subscribe + + for i := range channelsToSubscribe { + payload.Channels = append(payload.Channels, + channelsToSubscribe[i].Channel) + + if channelsToSubscribe[i].Currency.String() != "" { + if !common.StringDataCompare(payload.MarketIDs, + channelsToSubscribe[i].Currency.String()) { + payload.MarketIDs = append(payload.MarketIDs, + channelsToSubscribe[i].Currency.String()) + } } } + + for i := range authChannels { + if !common.StringDataCompare(payload.Channels, authChannels[i]) { + continue + } + signTime := strconv.FormatInt(time.Now().UTC().UnixNano()/1000000, 10) + strToSign := "/users/self/subscribe" + "\n" + signTime + tempSign := crypto.GetHMAC(crypto.HashSHA512, + []byte(strToSign), + []byte(b.API.Credentials.Secret)) + sign := crypto.Base64Encode(tempSign) + payload.Key = b.API.Credentials.Key + payload.Signature = sign + payload.Timestamp = signTime + break + } + + err := b.Websocket.Conn.SendJSONMessage(payload) + if err != nil { + return err + } + b.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe...) return nil } - -// Login logs in allowing private ws events -func (b *BTCMarkets) generateAuthSubscriptions() WsAuthSubscribe { - var authSubInfo WsAuthSubscribe - signTime := strconv.FormatInt(time.Now().UTC().UnixNano()/1000000, 10) - strToSign := "/users/self/subscribe" + "\n" + signTime - tempSign := crypto.GetHMAC(crypto.HashSHA512, - []byte(strToSign), - []byte(b.API.Credentials.Secret)) - sign := crypto.Base64Encode(tempSign) - authSubInfo.Key = b.API.Credentials.Key - authSubInfo.Signature = sign - authSubInfo.Timestamp = signTime - return authSubInfo -} - -// createChannels creates channels that need to be -func (b *BTCMarkets) createChannels() { - tempChannels := []string{orderChange, fundChange} - var channels []wshandler.WebsocketChannelSubscription - pairArray := b.GetEnabledPairs(asset.Spot) - for y := range tempChannels { - for x := range pairArray { - var authSub WsAuthSubscribe - var channel wshandler.WebsocketChannelSubscription - channel.Params = make(map[string]interface{}) - channel.Channel = tempChannels[y] - authSub.MarketIDs = append(authSub.MarketIDs, b.FormatExchangeCurrency(pairArray[x], asset.Spot).String()) - authSub.MessageType = subscribe - channel.Params["AuthSub"] = authSub - channels = append(channels, channel) - } - } - b.Websocket.SubscribeToChannels(channels) -} diff --git a/exchanges/btcmarkets/btcmarkets_wrapper.go b/exchanges/btcmarkets/btcmarkets_wrapper.go index 20abdf82..d37372a6 100644 --- a/exchanges/btcmarkets/btcmarkets_wrapper.go +++ b/exchanges/btcmarkets/btcmarkets_wrapper.go @@ -19,8 +19,8 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" "github.com/thrasher-corp/gocryptotrader/log" "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) @@ -59,19 +59,11 @@ func (b *BTCMarkets) SetDefaults() { b.API.Endpoints.URLDefault = btcMarketsAPIURL b.API.Endpoints.URL = b.API.Endpoints.URLDefault - b.CurrencyPairs = currency.PairsManager{ - AssetTypes: asset.Items{ - asset.Spot, - }, - UseGlobalFormat: true, - RequestFormat: ¤cy.PairFormat{ - Delimiter: "-", - Uppercase: true, - }, - ConfigFormat: ¤cy.PairFormat{ - Delimiter: "-", - Uppercase: true, - }, + requestFmt := ¤cy.PairFormat{Delimiter: currency.DashDelimiter, Uppercase: true} + configFmt := ¤cy.PairFormat{Delimiter: currency.DashDelimiter, Uppercase: true} + err := b.SetGlobalPairsManager(requestFmt, configFmt, asset.Spot) + if err != nil { + log.Errorln(log.ExchangeSys, err) } b.Features = exchange.Features{ @@ -131,7 +123,7 @@ func (b *BTCMarkets) SetDefaults() { request.WithLimiter(SetRateLimit())) b.API.Endpoints.WebsocketURL = btcMarketsWSURL - b.Websocket = wshandler.New() + b.Websocket = stream.New() b.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit b.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout b.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit @@ -149,41 +141,30 @@ func (b *BTCMarkets) Setup(exch *config.ExchangeConfig) error { return err } - err = b.Websocket.Setup( - &wshandler.WebsocketSetup{ - Enabled: exch.Features.Enabled.Websocket, - Verbose: exch.Verbose, - AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, - WebsocketTimeout: exch.WebsocketTrafficTimeout, - DefaultURL: btcMarketsWSURL, - ExchangeName: exch.Name, - RunningURL: exch.API.Endpoints.WebsocketURL, - Connector: b.WsConnect, - Subscriber: b.Subscribe, - Features: &b.Features.Supports.WebsocketCapabilities, - }) + err = b.Websocket.Setup(&stream.WebsocketSetup{ + Enabled: exch.Features.Enabled.Websocket, + Verbose: exch.Verbose, + AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, + WebsocketTimeout: exch.WebsocketTrafficTimeout, + DefaultURL: btcMarketsWSURL, + ExchangeName: exch.Name, + RunningURL: exch.API.Endpoints.WebsocketURL, + Connector: b.WsConnect, + Subscriber: b.Subscribe, + GenerateSubscriptions: b.generateDefaultSubscriptions, + Features: &b.Features.Supports.WebsocketCapabilities, + OrderbookBufferLimit: exch.WebsocketOrderbookBufferLimit, + BufferEnabled: true, + SortBuffer: true, + }) if err != nil { return err } - b.WebsocketConn = &wshandler.WebsocketConnection{ - ExchangeName: b.Name, - URL: b.Websocket.GetWebsocketURL(), - ProxyURL: b.Websocket.GetProxyAddress(), - Verbose: b.Verbose, + return b.Websocket.SetupNewConnection(stream.ConnectionSetup{ ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout, ResponseMaxLimit: exch.WebsocketResponseMaxLimit, - } - - b.Websocket.Orderbook.Setup( - exch.WebsocketOrderbookBufferLimit, - true, - true, - false, - false, - exch.Name) - - return nil + }) } // Start starts the BTC Markets go routine @@ -205,10 +186,34 @@ func (b *BTCMarkets) Run() { btcMarketsWSURL) b.PrintEnabledPairs() } + forceUpdate := false - delim := b.GetPairFormat(asset.Spot, false).Delimiter - if !common.StringDataContains(b.GetEnabledPairs(asset.Spot).Strings(), delim) || - !common.StringDataContains(b.GetAvailablePairs(asset.Spot).Strings(), delim) { + pairs, err := b.GetEnabledPairs(asset.Spot) + if err != nil { + log.Errorf(log.ExchangeSys, + "%s Failed to update enabled currencies Err:%s\n", + b.Name, + err) + return + } + format, err := b.GetPairFormat(asset.Spot, false) + if err != nil { + log.Errorf(log.ExchangeSys, + "%s Failed to update enabled currencies.\n", + b.Name) + return + } + + avail, err := b.GetAvailablePairs(asset.Spot) + if err != nil { + log.Errorf(log.ExchangeSys, + "%s Failed to update enabled currencies.\n", + b.Name) + return + } + + if !common.StringDataContains(pairs.Strings(), format.Delimiter) || + !common.StringDataContains(avail.Strings(), format.Delimiter) { log.Warnln(log.ExchangeSys, "Available pairs for BTC Markets reset due to config upgrade, please enable the pairs you would like again.") forceUpdate = true } @@ -216,10 +221,10 @@ func (b *BTCMarkets) Run() { enabledPairs := currency.Pairs{currency.Pair{ Base: currency.BTC.Lower(), Quote: currency.AUD.Lower(), - Delimiter: delim, + Delimiter: format.Delimiter, }, } - err := b.UpdatePairs(enabledPairs, asset.Spot, true, true) + err = b.UpdatePairs(enabledPairs, asset.Spot, true, true) if err != nil { log.Errorf(log.ExchangeSys, "%s Failed to update enabled currencies.\n", @@ -231,7 +236,7 @@ func (b *BTCMarkets) Run() { return } - err := b.UpdateTradablePairs(forceUpdate) + err = b.UpdateTradablePairs(forceUpdate) if err != nil { log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", @@ -264,28 +269,49 @@ func (b *BTCMarkets) UpdateTradablePairs(forceUpdate bool) error { if err != nil { return err } + p, err := currency.NewPairsFromStrings(pairs) + if err != nil { + return err + } - return b.UpdatePairs(currency.NewPairsFromStrings(pairs), asset.Spot, false, forceUpdate) + return b.UpdatePairs(p, asset.Spot, false, forceUpdate) } // UpdateTicker updates and returns the ticker for a currency pair func (b *BTCMarkets) UpdateTicker(p currency.Pair, assetType asset.Item) (*ticker.Price, error) { - allPairs := b.GetEnabledPairs(assetType) - tickers, err := b.GetTickers(allPairs.Slice()) + allPairs, err := b.GetEnabledPairs(assetType) if err != nil { return nil, err } + + tickers, err := b.GetTickers(allPairs) + if err != nil { + return nil, err + } + + if len(allPairs) != len(tickers) { + return nil, errors.New("enabled pairs differ from returned tickers") + } + for x := range tickers { - var resp ticker.Price - resp.Pair = currency.NewPairFromString(tickers[x].MarketID) - resp.Last = tickers[x].LastPrice - resp.High = tickers[x].High24h - resp.Low = tickers[x].Low24h - resp.Bid = tickers[x].BestBID - resp.Ask = tickers[x].BestAsk - resp.Volume = tickers[x].Volume - resp.LastUpdated = time.Now() - err = ticker.ProcessTicker(b.Name, &resp, assetType) + var newP currency.Pair + newP, err = currency.NewPairFromString(tickers[x].MarketID) + if err != nil { + return nil, err + } + + err = ticker.ProcessTicker(&ticker.Price{ + Pair: newP, + Last: tickers[x].LastPrice, + High: tickers[x].High24h, + Low: tickers[x].Low24h, + Bid: tickers[x].BestBID, + Ask: tickers[x].BestAsk, + Volume: tickers[x].Volume, + LastUpdated: time.Now(), + ExchangeName: b.Name, + AssetType: assetType, + }) if err != nil { return nil, err } @@ -313,11 +339,17 @@ func (b *BTCMarkets) FetchOrderbook(p currency.Pair, assetType asset.Item) (*ord // UpdateOrderbook updates and returns the orderbook for a currency pair func (b *BTCMarkets) UpdateOrderbook(p currency.Pair, assetType asset.Item) (*orderbook.Base, error) { - orderBook := new(orderbook.Base) - tempResp, err := b.GetOrderbook(b.FormatExchangeCurrency(p, assetType).String(), 2) + fpair, err := b.FormatExchangeCurrency(p, assetType) if err != nil { - return orderBook, err + return nil, err } + + tempResp, err := b.GetOrderbook(fpair.String(), 2) + if err != nil { + return nil, err + } + + orderBook := new(orderbook.Base) for x := range tempResp.Bids { orderBook.Bids = append(orderBook.Bids, orderbook.Item{ Amount: tempResp.Bids[x].Volume, @@ -401,7 +433,12 @@ func (b *BTCMarkets) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) s.Side = order.Bid } - tempResp, err := b.NewOrder(b.FormatExchangeCurrency(s.Pair, asset.Spot).String(), + fpair, err := b.FormatExchangeCurrency(s.Pair, asset.Spot) + if err != nil { + return resp, err + } + + tempResp, err := b.NewOrder(fpair.String(), s.Price, s.Amount, s.Type.String(), @@ -468,9 +505,15 @@ func (b *BTCMarkets) GetOrderInfo(orderID string) (order.Detail, error) { if err != nil { return resp, err } + + p, err := currency.NewPairFromString(o.MarketID) + if err != nil { + return order.Detail{}, err + } + resp.Exchange = b.Name resp.ID = orderID - resp.Pair = currency.NewPairFromString(o.MarketID) + resp.Pair = p resp.Price = o.Price resp.Date = o.CreationTime resp.ExecutedAmount = o.Amount - o.OpenAmount @@ -569,11 +612,6 @@ func (b *BTCMarkets) WithdrawFiatFundsToInternationalBank(withdrawRequest *withd return nil, common.ErrFunctionNotSupported } -// GetWebsocket returns a pointer to the exchange websocket -func (b *BTCMarkets) GetWebsocket() (*wshandler.Websocket, error) { - return b.Websocket, nil -} - // GetFeeByType returns an estimate of fee based on type of transaction func (b *BTCMarkets) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { if !b.AllowAuthenticatedRequest() && // Todo check connection status @@ -586,7 +624,10 @@ func (b *BTCMarkets) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, err // GetActiveOrders retrieves any orders that are active/open func (b *BTCMarkets) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, error) { if len(req.Pairs) == 0 { - allPairs := b.GetEnabledPairs(asset.Spot) + allPairs, err := b.GetEnabledPairs(asset.Spot) + if err != nil { + return nil, err + } for a := range allPairs { req.Pairs = append(req.Pairs, allPairs[a]) @@ -595,7 +636,11 @@ func (b *BTCMarkets) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detai var resp []order.Detail for x := range req.Pairs { - tempData, err := b.GetOrders(b.FormatExchangeCurrency(req.Pairs[x], asset.Spot).String(), -1, -1, -1, true) + fpair, err := b.FormatExchangeCurrency(req.Pairs[x], asset.Spot) + if err != nil { + return nil, err + } + tempData, err := b.GetOrders(fpair.String(), -1, -1, -1, true) if err != nil { return resp, err } @@ -666,7 +711,12 @@ func (b *BTCMarkets) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detai } } for y := range req.Pairs { - orders, err := b.GetOrders(b.FormatExchangeCurrency(req.Pairs[y], asset.Spot).String(), -1, -1, -1, false) + fpair, err := b.FormatExchangeCurrency(req.Pairs[y], asset.Spot) + if err != nil { + return nil, err + } + + orders, err := b.GetOrders(fpair.String(), -1, -1, -1, false) if err != nil { return resp, err } @@ -697,8 +747,14 @@ func (b *BTCMarkets) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detai case orderAccepted: continue } + + p, err := currency.NewPairFromString(tempData.Orders[c].MarketID) + if err != nil { + return nil, err + } + tempResp.Exchange = b.Name - tempResp.Pair = currency.NewPairFromString(tempData.Orders[c].MarketID) + tempResp.Pair = p tempResp.Side = order.Bid if tempData.Orders[c].Side == ask { tempResp.Side = order.Ask @@ -713,28 +769,6 @@ func (b *BTCMarkets) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detai return resp, nil } -// SubscribeToWebsocketChannels appends to ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle subscribing -func (b *BTCMarkets) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - return common.ErrFunctionNotSupported -} - -// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle unsubscribing -func (b *BTCMarkets) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - return common.ErrFunctionNotSupported -} - -// GetSubscriptions returns a copied list of subscriptions -func (b *BTCMarkets) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) { - return nil, common.ErrFunctionNotSupported -} - -// AuthenticateWebsocket sends an authentication message to the websocket -func (b *BTCMarkets) AuthenticateWebsocket() error { - return common.ErrFunctionNotSupported -} - // ValidateCredentials validates current credentials used for wrapper // functionality func (b *BTCMarkets) ValidateCredentials() error { @@ -776,7 +810,12 @@ func (b *BTCMarkets) GetHistoricCandles(pair currency.Pair, a asset.Item, start, return kline.Item{}, errors.New(kline.ErrRequestExceedsExchangeLimits) } - candles, err := b.GetMarketCandles(b.FormatExchangeCurrency(pair, a).String(), + formattedPair, err := b.FormatExchangeCurrency(pair, a) + if err != nil { + return kline.Item{}, err + } + + candles, err := b.GetMarketCandles(formattedPair.String(), b.FormatExchangeKlineInterval(interval), start, end, @@ -789,7 +828,7 @@ func (b *BTCMarkets) GetHistoricCandles(pair currency.Pair, a asset.Item, start, } ret := kline.Item{ Exchange: b.Name, - Pair: b.FormatExchangeCurrency(pair, a), + Pair: formattedPair, Asset: asset.Spot, Interval: interval, } diff --git a/exchanges/btse/btse.go b/exchanges/btse/btse.go index 24914e4c..5c93d016 100644 --- a/exchanges/btse/btse.go +++ b/exchanges/btse/btse.go @@ -15,19 +15,18 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/request" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" "github.com/thrasher-corp/gocryptotrader/log" ) // BTSE is the overarching type across this package type BTSE struct { exchange.Base - WebsocketConn *wshandler.WebsocketConnection } const ( - btseAPIURL = "https://api.btse.com" - btseAPIPath = "/spot/v2/" + btseAPIURL = "https://api.btse.com" + btseSPOTAPIPath = "/spot/v2/" + btseFuturesAPIPath = "/futures/api/v2.1/" // Public endpoints btseMarketOverview = "market_summary" @@ -50,34 +49,40 @@ const ( // GetMarketsSummary stores market summary data func (b *BTSE) GetMarketsSummary() (*HighLevelMarketData, error) { var m HighLevelMarketData - return &m, b.SendHTTPRequest(http.MethodGet, btseMarketOverview, &m) + return &m, b.SendHTTPRequest(http.MethodGet, btseMarketOverview, &m, true) } -// GetMarkets returns a list of markets available on BTSE -func (b *BTSE) GetMarkets() ([]Market, error) { - var m []Market - return m, b.SendHTTPRequest(http.MethodGet, btseMarkets, &m) +// GetSpotMarkets returns a list of spot markets available on BTSE +func (b *BTSE) GetSpotMarkets() ([]SpotMarket, error) { + var m []SpotMarket + return m, b.SendHTTPRequest(http.MethodGet, btseMarkets, &m, true) +} + +// GetFuturesMarkets returns a list of futures markets available on BTSE +func (b *BTSE) GetFuturesMarkets() ([]FuturesMarket, error) { + var m []FuturesMarket + return m, b.SendHTTPRequest(http.MethodGet, btseMarketOverview, &m, false) } // FetchOrderBook gets orderbook data for a given pair func (b *BTSE) FetchOrderBook(symbol string) (*Orderbook, error) { var o Orderbook endpoint := fmt.Sprintf("%s/%s", btseOrderbook, symbol) - return &o, b.SendHTTPRequest(http.MethodGet, endpoint, &o) + return &o, b.SendHTTPRequest(http.MethodGet, endpoint, &o, true) } // GetTrades returns a list of trades for the specified symbol func (b *BTSE) GetTrades(symbol string) ([]Trade, error) { var t []Trade endpoint := fmt.Sprintf("%s/%s", btseTrades, symbol) - return t, b.SendHTTPRequest(http.MethodGet, endpoint, &t) + return t, b.SendHTTPRequest(http.MethodGet, endpoint, &t, true) } // GetTicker returns the ticker for a specified symbol func (b *BTSE) GetTicker(symbol string) (*Ticker, error) { var t Ticker endpoint := fmt.Sprintf("%s/%s", btseTicker, symbol) - err := b.SendHTTPRequest(http.MethodGet, endpoint, &t) + err := b.SendHTTPRequest(http.MethodGet, endpoint, &t, true) if err != nil { return nil, err } @@ -88,19 +93,19 @@ func (b *BTSE) GetTicker(symbol string) (*Ticker, error) { func (b *BTSE) GetMarketStatistics(symbol string) (*MarketStatistics, error) { var m MarketStatistics endpoint := fmt.Sprintf("%s/%s", btseStats, symbol) - return &m, b.SendHTTPRequest(http.MethodGet, endpoint, &m) + return &m, b.SendHTTPRequest(http.MethodGet, endpoint, &m, true) } // GetServerTime returns the exchanges server time func (b *BTSE) GetServerTime() (*ServerTime, error) { var s ServerTime - return &s, b.SendHTTPRequest(http.MethodGet, btseTime, &s) + return &s, b.SendHTTPRequest(http.MethodGet, btseTime, &s, true) } // GetAccountBalance returns the users account balance func (b *BTSE) GetAccountBalance() ([]CurrencyBalance, error) { var a []CurrencyBalance - return a, b.SendAuthenticatedHTTPRequest(http.MethodGet, btseAccount, nil, &a) + return a, b.SendAuthenticatedHTTPRequest(http.MethodGet, btseAccount, nil, &a, true) } // CreateOrder creates an order @@ -129,7 +134,7 @@ func (b *BTSE) CreateOrder(amount, price float64, side, orderType, symbol, timeI } var r orderResp - return &r.ID, b.SendAuthenticatedHTTPRequest(http.MethodPost, btseOrder, req, &r) + return &r.ID, b.SendAuthenticatedHTTPRequest(http.MethodPost, btseOrder, req, &r, true) } // GetOrders returns all pending orders @@ -139,7 +144,7 @@ func (b *BTSE) GetOrders(symbol string) ([]OpenOrder, error) { req["symbol"] = symbol } var o []OpenOrder - return o, b.SendAuthenticatedHTTPRequest(http.MethodGet, btsePendingOrders, req, &o) + return o, b.SendAuthenticatedHTTPRequest(http.MethodGet, btsePendingOrders, req, &o, true) } // CancelExistingOrder cancels an order @@ -148,7 +153,7 @@ func (b *BTSE) CancelExistingOrder(orderID, symbol string) (*CancelOrder, error) req := make(map[string]interface{}) req["order_id"] = orderID req["symbol"] = symbol - return &c, b.SendAuthenticatedHTTPRequest(http.MethodPost, btseDeleteOrder, req, &c) + return &c, b.SendAuthenticatedHTTPRequest(http.MethodPost, btseDeleteOrder, req, &c, true) } // GetFills gets all filled orders @@ -184,14 +189,18 @@ func (b *BTSE) GetFills(orderID, symbol, before, after, limit, username string) } var o []FilledOrder - return o, b.SendAuthenticatedHTTPRequest(http.MethodPost, btseFills, req, &o) + return o, b.SendAuthenticatedHTTPRequest(http.MethodPost, btseFills, req, &o, true) } // SendHTTPRequest sends an HTTP request to the desired endpoint -func (b *BTSE) SendHTTPRequest(method, endpoint string, result interface{}) error { +func (b *BTSE) SendHTTPRequest(method, endpoint string, result interface{}, spotEndpoint bool) error { + p := btseSPOTAPIPath + if !spotEndpoint { + p = btseFuturesAPIPath + } return b.SendPayload(context.Background(), &request.Item{ Method: method, - Path: b.API.Endpoints.URL + btseAPIPath + endpoint, + Path: b.API.Endpoints.URL + p + endpoint, Result: result, Verbose: b.Verbose, HTTPDebugging: b.HTTPDebugging, @@ -200,12 +209,18 @@ func (b *BTSE) SendHTTPRequest(method, endpoint string, result interface{}) erro } // SendAuthenticatedHTTPRequest sends an authenticated HTTP request to the desired endpoint -func (b *BTSE) SendAuthenticatedHTTPRequest(method, endpoint string, req map[string]interface{}, result interface{}) error { +func (b *BTSE) SendAuthenticatedHTTPRequest(method, endpoint string, req map[string]interface{}, result interface{}, spotEndpoint bool) error { if !b.AllowAuthenticatedRequest() { return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, b.Name) } - path := btseAPIPath + endpoint + + p := btseSPOTAPIPath + if !spotEndpoint { + p = btseFuturesAPIPath + } + + path := p + endpoint headers := make(map[string]string) headers["btse-api"] = b.API.Credentials.Key nonce := strconv.FormatInt(time.Now().UnixNano()/int64(time.Millisecond), 10) diff --git a/exchanges/btse/btse_test.go b/exchanges/btse/btse_test.go index ab190711..6582fd38 100644 --- a/exchanges/btse/btse_test.go +++ b/exchanges/btse/btse_test.go @@ -39,13 +39,11 @@ func TestMain(m *testing.M) { btseConfig.API.AuthenticatedSupport = true btseConfig.API.Credentials.Key = apiKey btseConfig.API.Credentials.Secret = apiSecret - + b.Websocket = sharedtestvalues.NewTestWebsocket() err = b.Setup(btseConfig) if err != nil { log.Fatal(err) } - b.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride() - b.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride() os.Exit(m.Run()) } @@ -61,9 +59,17 @@ func TestGetMarketsSummary(t *testing.T) { } } -func TestGetMarkets(t *testing.T) { +func TestGetSpotMarkets(t *testing.T) { t.Parallel() - _, err := b.GetMarkets() + _, err := b.GetSpotMarkets() + if err != nil { + t.Error(err) + } +} + +func TestGetFuturesMarkets(t *testing.T) { + t.Parallel() + _, err := b.GetFuturesMarkets() if err != nil { t.Error(err) } @@ -411,3 +417,16 @@ func TestStatusToStandardStatus(t *testing.T) { } } } + +func TestFetchTradablePairs(t *testing.T) { + assets := b.GetAssetTypes() + for i := range assets { + data, err := b.FetchTradablePairs(assets[i]) + if err != nil { + t.Fatal(err) + } + if len(data) == 0 { + t.Fatal("data cannot be zero") + } + } +} diff --git a/exchanges/btse/btse_types.go b/exchanges/btse/btse_types.go index e6d9b935..133fcc4b 100644 --- a/exchanges/btse/btse_types.go +++ b/exchanges/btse/btse_types.go @@ -21,8 +21,8 @@ type OverviewData struct { // HighLevelMarketData stores market overview data type HighLevelMarketData map[string]OverviewData -// Market stores market data -type Market struct { +// SpotMarket stores market data +type SpotMarket struct { Symbol string `json:"symbol"` ID string `json:"id"` BaseCurrency string `json:"base_currency"` @@ -35,6 +35,41 @@ type Market struct { Status string `json:"status"` } +// FuturesMarket stores market data +type FuturesMarket struct { + Symbol string `json:"symbol"` + Last float64 `json:"last"` + LowestAsk float64 `json:"lowestAsk"` + HighestBid float64 `json:"highestBid"` + OpenInterest float64 `json:"openInterest"` + OpenInterestUSD float64 `json:"openInterestUSD"` + PercentageChange float64 `json:"percentageChange"` + Volume float64 `json:"volume"` + High24Hr float64 `json:"high24Hr"` + Low24Hr float64 `json:"low24Hr"` + Base string `json:"base"` + Quote string `json:"quote"` + ContractStart int64 `json:"contractStart"` + ContractEnd int64 `json:"contractEnd"` + Active bool `json:"active"` + TimeBasedContract bool `json:"timeBasedContract"` + OpenTime int64 `json:"openTime"` + CloseTime int64 `json:"closeTime"` + StartMatching int64 `json:"startMatching"` + InactiveTime int64 `json:"inactiveTime"` + FundingRate float64 `json:"fundingRate"` + ContractSize float64 `json:"contractSize"` + MaxPosition int64 `json:"maxPosition"` + MinValidPrice float64 `json:"minValidPrice"` + MinPriceIncrement float64 `json:"minPriceIncrement"` + MinOrderSize int32 `json:"minOrderSize"` + MaxOrderSize int32 `json:"maxOrderSize"` + MinRiskLimit int32 `json:"minRiskLimit"` + MaxRiskLimit int32 `json:"maxRiskLimit"` + MinSizeIncrement float64 `json:"minSizeIncrement"` + AvailableSettlement []string `json:"availableSettlement"` +} + // Trade stores trade data type Trade struct { SerialID string `json:"serial_id"` diff --git a/exchanges/btse/btse_websocket.go b/exchanges/btse/btse_websocket.go index 5402dd79..743b70e8 100644 --- a/exchanges/btse/btse_websocket.go +++ b/exchanges/btse/btse_websocket.go @@ -16,25 +16,25 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" ) const ( btseWebsocket = "wss://ws.btse.com/spotWS" - btseWebsocketTimer = 57 * time.Second + btseWebsocketTimer = time.Second * 57 ) // WsConnect connects the websocket client func (b *BTSE) WsConnect() error { if !b.Websocket.IsEnabled() || !b.IsEnabled() { - return errors.New(wshandler.WebsocketNotEnabled) + return errors.New(stream.WebsocketNotEnabled) } var dialer websocket.Dialer - err := b.WebsocketConn.Dial(&dialer, http.Header{}) + err := b.Websocket.Conn.Dial(&dialer, http.Header{}) if err != nil { return err } - b.WebsocketConn.SetupPingHandler(wshandler.WebsocketPingHandler{ + b.Websocket.Conn.SetupPingHandler(stream.PingHandler{ MessageType: websocket.PingMessage, Delay: btseWebsocketTimer, }) @@ -48,17 +48,19 @@ func (b *BTSE) WsConnect() error { } } - b.GenerateDefaultSubscriptions() - return nil + subs, err := b.GenerateDefaultSubscriptions() + if err != nil { + return err + } + return b.Websocket.SubscribeToChannels(subs) } // WsAuthenticate Send an authentication message to receive auth data func (b *BTSE) WsAuthenticate() error { nonce := strconv.FormatInt(time.Now().UnixNano()/int64(time.Millisecond), 10) path := "/spotWS" + nonce - hmac := crypto.GetHMAC( - crypto.HashSHA512_384, - []byte((path + nonce)), + hmac := crypto.GetHMAC(crypto.HashSHA512_384, + []byte((path)), []byte(b.API.Credentials.Secret), ) sign := crypto.HexEncodeToString(hmac) @@ -66,7 +68,7 @@ func (b *BTSE) WsAuthenticate() error { Operation: "authKeyExpires", Arguments: []string{b.API.Credentials.Key, nonce, sign}, } - return b.WebsocketConn.SendJSONMessage(req) + return b.Websocket.Conn.SendJSONMessage(req) } func stringToOrderStatus(status string) (order.Status, error) { @@ -93,27 +95,16 @@ func stringToOrderStatus(status string) (order.Status, error) { // wsReadData receives and passes on websocket messages for processing func (b *BTSE) wsReadData() { b.Websocket.Wg.Add(1) - - defer func() { - b.Websocket.Wg.Done() - }() + defer b.Websocket.Wg.Done() for { - select { - case <-b.Websocket.ShutdownC: + resp := b.Websocket.Conn.ReadMessage() + if resp.Raw == nil { return - - default: - resp, err := b.WebsocketConn.ReadMessage() - if err != nil { - b.Websocket.ReadMessageErrors <- err - return - } - b.Websocket.TrafficAlert <- struct{}{} - err = b.wsHandleData(resp.Raw) - if err != nil { - b.Websocket.DataHandler <- err - } + } + err := b.wsHandleData(resp.Raw) + if err != nil { + b.Websocket.DataHandler <- err } } } @@ -123,6 +114,13 @@ func (b *BTSE) wsHandleData(respRaw []byte) error { var result Result err := json.Unmarshal(respRaw, &result) if err != nil { + if strings.Contains(string(respRaw), "UNLOGIN_USER connect success") || + strings.Contains(string(respRaw), "authenticated successfully") { + return nil + } else if strings.Contains(string(respRaw), "AUTHENTICATE ERROR") { + b.Websocket.SetCanUseAuthenticatedEndpoints(false) + return errors.New("authentication failure") + } return err } switch { @@ -160,12 +158,19 @@ func (b *BTSE) wsHandleData(respRaw []byte) error { Err: err, } } - p := currency.NewPairFromString(notification.Data[i].Symbol) + + var p currency.Pair + p, err = currency.NewPairFromString(notification.Data[i].Symbol) + if err != nil { + return err + } + var a asset.Item a, err = b.GetPairAssetType(p) if err != nil { return err } + b.Websocket.DataHandler <- &order.Detail{ Price: notification.Data[i].Price, Amount: notification.Data[i].Size, @@ -192,13 +197,21 @@ func (b *BTSE) wsHandleData(respRaw []byte) error { if tradeHistory.Data[x].Gain == -1 { side = order.Sell } - p := currency.NewPairFromString(strings.Replace(tradeHistory.Topic, "tradeHistory:", "", 1)) + + var p currency.Pair + p, err = currency.NewPairFromString(strings.Replace(tradeHistory.Topic, + "tradeHistory:", + "", + 1)) + if err != nil { + return err + } var a asset.Item a, err = b.GetPairAssetType(p) if err != nil { return err } - b.Websocket.DataHandler <- wshandler.TradeData{ + b.Websocket.DataHandler <- stream.TradeData{ Timestamp: time.Unix(0, tradeHistory.Data[x].TransactionTime*int64(time.Millisecond)), CurrencyPair: p, AssetType: a, @@ -248,7 +261,10 @@ func (b *BTSE) wsHandleData(respRaw []byte) error { Amount: amount, }) } - p := currency.NewPairFromString(t.Topic[strings.Index(t.Topic, ":")+1 : strings.Index(t.Topic, "_")]) + p, err := currency.NewPairFromString(t.Topic[strings.Index(t.Topic, ":")+1 : strings.Index(t.Topic, "_")]) + if err != nil { + return err + } var a asset.Item a, err = b.GetPairAssetType(p) if err != nil { @@ -261,50 +277,65 @@ func (b *BTSE) wsHandleData(respRaw []byte) error { if err != nil { return err } - b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{Pair: newOB.Pair, - Asset: a, - Exchange: b.Name} default: - b.Websocket.DataHandler <- wshandler.UnhandledMessageWarning{Message: b.Name + wshandler.UnhandledMessage + string(respRaw)} + b.Websocket.DataHandler <- stream.UnhandledMessageWarning{Message: b.Name + stream.UnhandledMessage + string(respRaw)} return nil } return nil } // GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() -func (b *BTSE) GenerateDefaultSubscriptions() { +func (b *BTSE) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, error) { var channels = []string{"orderBookApi:%s_0", "tradeHistory:%s"} - pairs := b.GetEnabledPairs(asset.Spot) - var subscriptions []wshandler.WebsocketChannelSubscription + pairs, err := b.GetEnabledPairs(asset.Spot) + if err != nil { + return nil, err + } + var subscriptions []stream.ChannelSubscription if b.Websocket.CanUseAuthenticatedEndpoints() { - subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{ + subscriptions = append(subscriptions, stream.ChannelSubscription{ Channel: "notificationApi", }) } for i := range channels { for j := range pairs { - subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{ + subscriptions = append(subscriptions, stream.ChannelSubscription{ Channel: fmt.Sprintf(channels[i], pairs[j]), Currency: pairs[j], + Asset: asset.Spot, }) } } - b.Websocket.SubscribeToChannels(subscriptions) + return subscriptions, nil } // Subscribe sends a websocket message to receive data from the channel -func (b *BTSE) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error { +func (b *BTSE) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error { var sub wsSub sub.Operation = "subscribe" - sub.Arguments = []string{channelToSubscribe.Channel} - - return b.WebsocketConn.SendJSONMessage(sub) + for i := range channelsToSubscribe { + sub.Arguments = append(sub.Arguments, channelsToSubscribe[i].Channel) + } + err := b.Websocket.Conn.SendJSONMessage(sub) + if err != nil { + return err + } + b.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe...) + return nil } // Unsubscribe sends a websocket message to stop receiving data from the channel -func (b *BTSE) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error { +func (b *BTSE) Unsubscribe(channelsToUnsubscribe []stream.ChannelSubscription) error { var unSub wsSub unSub.Operation = "unsubscribe" - unSub.Arguments = []string{channelToSubscribe.Channel} - return b.WebsocketConn.SendJSONMessage(unSub) + for i := range channelsToUnsubscribe { + unSub.Arguments = append(unSub.Arguments, + channelsToUnsubscribe[i].Channel) + } + err := b.Websocket.Conn.SendJSONMessage(unSub) + if err != nil { + return err + } + b.Websocket.RemoveSuccessfulUnsubscriptions(channelsToUnsubscribe...) + return nil } diff --git a/exchanges/btse/btse_wrapper.go b/exchanges/btse/btse_wrapper.go index 9efd9ca3..a9ed0c92 100644 --- a/exchanges/btse/btse_wrapper.go +++ b/exchanges/btse/btse_wrapper.go @@ -19,8 +19,8 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" "github.com/thrasher-corp/gocryptotrader/log" "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) @@ -56,11 +56,7 @@ func (b *BTSE) SetDefaults() { b.API.CredentialsValidator.RequiresKey = true b.API.CredentialsValidator.RequiresSecret = true - b.CurrencyPairs = currency.PairsManager{ - AssetTypes: asset.Items{ - asset.Spot, - }, - UseGlobalFormat: true, + fmt1 := currency.PairStore{ RequestFormat: ¤cy.PairFormat{ Uppercase: true, Delimiter: "-", @@ -70,6 +66,23 @@ func (b *BTSE) SetDefaults() { Delimiter: "-", }, } + err := b.StoreAssetPairFormat(asset.Spot, fmt1) + if err != nil { + log.Errorln(log.ExchangeSys, err) + } + + fmt2 := currency.PairStore{ + RequestFormat: ¤cy.PairFormat{ + Uppercase: true, + }, + ConfigFormat: ¤cy.PairFormat{ + Uppercase: true, + }, + } + err = b.StoreAssetPairFormat(asset.Futures, fmt2) + if err != nil { + log.Errorln(log.ExchangeSys, err) + } b.Features = exchange.Features{ Supports: exchange.FeaturesSupported{ @@ -112,7 +125,7 @@ func (b *BTSE) SetDefaults() { b.API.Endpoints.URLDefault = btseAPIURL b.API.Endpoints.URL = b.API.Endpoints.URLDefault - b.Websocket = wshandler.New() + b.Websocket = stream.New() b.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit b.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout b.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit @@ -130,41 +143,29 @@ func (b *BTSE) Setup(exch *config.ExchangeConfig) error { return err } - err = b.Websocket.Setup( - &wshandler.WebsocketSetup{ - Enabled: exch.Features.Enabled.Websocket, - Verbose: exch.Verbose, - AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, - WebsocketTimeout: exch.WebsocketTrafficTimeout, - DefaultURL: btseWebsocket, - ExchangeName: exch.Name, - RunningURL: exch.API.Endpoints.WebsocketURL, - Connector: b.WsConnect, - Subscriber: b.Subscribe, - UnSubscriber: b.Unsubscribe, - Features: &b.Features.Supports.WebsocketCapabilities, - }) + err = b.Websocket.Setup(&stream.WebsocketSetup{ + Enabled: exch.Features.Enabled.Websocket, + Verbose: exch.Verbose, + AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, + WebsocketTimeout: exch.WebsocketTrafficTimeout, + DefaultURL: btseWebsocket, + ExchangeName: exch.Name, + RunningURL: exch.API.Endpoints.WebsocketURL, + Connector: b.WsConnect, + Subscriber: b.Subscribe, + UnSubscriber: b.Unsubscribe, + GenerateSubscriptions: b.GenerateDefaultSubscriptions, + Features: &b.Features.Supports.WebsocketCapabilities, + OrderbookBufferLimit: exch.WebsocketOrderbookBufferLimit, + }) if err != nil { return err } - b.WebsocketConn = &wshandler.WebsocketConnection{ - ExchangeName: b.Name, - URL: b.Websocket.GetWebsocketURL(), - ProxyURL: b.Websocket.GetProxyAddress(), - Verbose: b.Verbose, + return b.Websocket.SetupNewConnection(stream.ConnectionSetup{ ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout, ResponseMaxLimit: exch.WebsocketResponseMaxLimit, - } - - b.Websocket.Orderbook.Setup( - exch.WebsocketOrderbookBufferLimit, - false, - false, - false, - false, - exch.Name) - return nil + }) } // Start starts the BTSE go routine @@ -194,61 +195,96 @@ func (b *BTSE) Run() { } // FetchTradablePairs returns a list of the exchanges tradable pairs -func (b *BTSE) FetchTradablePairs(asset asset.Item) ([]string, error) { - m, err := b.GetMarkets() - if err != nil { - return nil, err +func (b *BTSE) FetchTradablePairs(a asset.Item) ([]string, error) { + var currencies []string + if a == asset.Spot { + m, err := b.GetSpotMarkets() + if err != nil { + return nil, err + } + + for x := range m { + if m[x].Status != "active" { + continue + } + currencies = append(currencies, m[x].Symbol) + } + } else if a == asset.Futures { + m, err := b.GetFuturesMarkets() + if err != nil { + return nil, err + } + + for x := range m { + if !m[x].Active { + continue + } + currencies = append(currencies, m[x].Symbol) + } } - var currencies []string - for x := range m { - if m[x].Status != "active" { - continue - } - currencies = append(currencies, m[x].Symbol) - } return currencies, nil } // UpdateTradablePairs updates the exchanges available pairs and stores // them in the exchanges config func (b *BTSE) UpdateTradablePairs(forceUpdate bool) error { - pairs, err := b.FetchTradablePairs(asset.Spot) - if err != nil { - return err - } + a := b.GetAssetTypes() + for i := range a { + pairs, err := b.FetchTradablePairs(a[i]) + if err != nil { + return err + } - return b.UpdatePairs(currency.NewPairsFromStrings(pairs), asset.Spot, false, forceUpdate) + p, err := currency.NewPairsFromStrings(pairs) + if err != nil { + return err + } + + err = b.UpdatePairs(p, a[i], false, forceUpdate) + if err != nil { + return err + } + } + return nil } // UpdateTicker updates and returns the ticker for a currency pair func (b *BTSE) UpdateTicker(p currency.Pair, assetType asset.Item) (*ticker.Price, error) { - tickerPrice := new(ticker.Price) - - t, err := b.GetTicker(b.FormatExchangeCurrency(p, - assetType).String()) - if err != nil { - return tickerPrice, err + if assetType == asset.Futures { + // Futures REST implementation needs to be done before this can be + // removed + return nil, common.ErrNotYetImplemented } - s, err := b.GetMarketStatistics(b.FormatExchangeCurrency(p, - assetType).String()) + fpair, err := b.FormatExchangeCurrency(p, assetType) if err != nil { - return tickerPrice, err + return nil, err } - tickerPrice.Pair = p - tickerPrice.Ask = t.Ask - tickerPrice.Bid = t.Bid - tickerPrice.Low = s.Low - tickerPrice.Last = t.Price - tickerPrice.Volume = s.Volume - tickerPrice.High = s.High - tickerPrice.LastUpdated = s.Time - - err = ticker.ProcessTicker(b.Name, tickerPrice, assetType) + t, err := b.GetTicker(fpair.String()) if err != nil { - return tickerPrice, err + return nil, err + } + + s, err := b.GetMarketStatistics(fpair.String()) + if err != nil { + return nil, err + } + + err = ticker.ProcessTicker(&ticker.Price{ + Pair: p, + Ask: t.Ask, + Bid: t.Bid, + Low: s.Low, + Last: t.Price, + Volume: s.Volume, + High: s.High, + LastUpdated: s.Time, + ExchangeName: b.Name, + AssetType: assetType}) + if err != nil { + return nil, err } return ticker.GetTicker(b.Name, p, assetType) } @@ -273,11 +309,22 @@ func (b *BTSE) FetchOrderbook(p currency.Pair, assetType asset.Item) (*orderbook // UpdateOrderbook updates and returns the orderbook for a currency pair func (b *BTSE) UpdateOrderbook(p currency.Pair, assetType asset.Item) (*orderbook.Base, error) { - orderBook := new(orderbook.Base) - a, err := b.FetchOrderBook(b.FormatExchangeCurrency(p, assetType).String()) - if err != nil { - return orderBook, err + if assetType == asset.Futures { + // Futures REST implementation needs to be done before this can be + // removed + return nil, common.ErrNotYetImplemented } + + fpair, err := b.FormatExchangeCurrency(p, assetType) + if err != nil { + return nil, err + } + a, err := b.FetchOrderBook(fpair.String()) + if err != nil { + return nil, err + } + + orderBook := new(orderbook.Base) for x := range a.BuyQuote { orderBook.Bids = append(orderBook.Bids, orderbook.Item{ Price: a.BuyQuote[x].Price, @@ -360,11 +407,16 @@ func (b *BTSE) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { return resp, err } + fpair, err := b.FormatExchangeCurrency(s.Pair, s.AssetType) + if err != nil { + return resp, err + } + r, err := b.CreateOrder(s.Amount, s.Price, s.Side.String(), s.Type.String(), - b.FormatExchangeCurrency(s.Pair, asset.Spot).String(), + fpair.String(), goodTillCancel, s.ClientID) if err != nil { @@ -389,9 +441,13 @@ func (b *BTSE) ModifyOrder(action *order.Modify) (string, error) { // CancelOrder cancels an order by its corresponding ID number func (b *BTSE) CancelOrder(order *order.Cancel) error { - r, err := b.CancelExistingOrder(order.ID, - b.FormatExchangeCurrency(order.Pair, - asset.Spot).String()) + fpair, err := b.FormatExchangeCurrency(order.Pair, + order.AssetType) + if err != nil { + return err + } + + r, err := b.CancelExistingOrder(order.ID, fpair.String()) if err != nil { return err } @@ -411,19 +467,28 @@ func (b *BTSE) CancelOrder(order *order.Cancel) error { // If not specified, all orders of all markets will be cancelled func (b *BTSE) CancelAllOrders(orderCancellation *order.Cancel) (order.CancelAllResponse, error) { var resp order.CancelAllResponse - markets, err := b.GetMarkets() + markets, err := b.GetSpotMarkets() + if err != nil { + return resp, err + } + + format, err := b.GetPairFormat(orderCancellation.AssetType, false) if err != nil { return resp, err } resp.Status = make(map[string]string) for x := range markets { - strPair := b.FormatExchangeCurrency(orderCancellation.Pair, - orderCancellation.AssetType).String() + fair, err := b.FormatExchangeCurrency(orderCancellation.Pair, + orderCancellation.AssetType) + if err != nil { + return resp, err + } + checkPair := currency.NewPairWithDelimiter(markets[x].BaseCurrency, markets[x].QuoteCurrency, - b.GetPairFormat(asset.Spot, false).Delimiter).String() - if strPair != "" && strPair != checkPair { + format.Delimiter).String() + if fair.String() != checkPair { continue } else { orders, err := b.GetOrders(checkPair) @@ -455,6 +520,11 @@ func (b *BTSE) GetOrderInfo(orderID string) (order.Detail, error) { return od, errors.New("no orders found") } + format, err := b.GetPairFormat(asset.Spot, false) + if err != nil { + return order.Detail{}, err + } + for i := range o { if o[i].ID != orderID { continue @@ -465,8 +535,14 @@ func (b *BTSE) GetOrderInfo(orderID string) (order.Detail, error) { side = order.Sell } - od.Pair = currency.NewPairDelimiter(o[i].Symbol, - b.GetPairFormat(asset.Spot, false).Delimiter) + od.Pair, err = currency.NewPairDelimiter(o[i].Symbol, + format.Delimiter) + if err != nil { + log.Errorf(log.ExchangeSys, + "%s GetOrderInfo unable to parse currency pair: %s\n", + b.Name, + err) + } od.Exchange = b.Name od.Amount = o[i].Amount od.ID = o[i].ID @@ -530,11 +606,6 @@ func (b *BTSE) WithdrawFiatFundsToInternationalBank(withdrawRequest *withdraw.Re return nil, common.ErrFunctionNotSupported } -// GetWebsocket returns a pointer to the exchange websocket -func (b *BTSE) GetWebsocket() (*wshandler.Websocket, error) { - return b.Websocket, nil -} - // GetActiveOrders retrieves any orders that are active/open func (b *BTSE) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, error) { resp, err := b.GetOrders("") @@ -542,6 +613,11 @@ func (b *BTSE) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, err return nil, err } + format, err := b.GetPairFormat(asset.Spot, false) + if err != nil { + return nil, err + } + var orders []order.Detail for i := range resp { var side = order.Buy @@ -557,9 +633,17 @@ func (b *BTSE) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, err err) } + p, err := currency.NewPairDelimiter(resp[i].Symbol, + format.Delimiter) + if err != nil { + log.Errorf(log.ExchangeSys, + "%s GetActiveOrders unable to parse currency pair: %s\n", + b.Name, + err) + } + openOrder := order.Detail{ - Pair: currency.NewPairDelimiter(resp[i].Symbol, - b.GetPairFormat(asset.Spot, false).Delimiter), + Pair: p, Exchange: b.Name, Amount: resp[i].Amount, ID: resp[i].ID, @@ -621,30 +705,6 @@ func (b *BTSE) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { return b.GetFee(feeBuilder) } -// SubscribeToWebsocketChannels appends to ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle subscribing -func (b *BTSE) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - b.Websocket.SubscribeToChannels(channels) - return nil -} - -// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle unsubscribing -func (b *BTSE) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - b.Websocket.RemoveSubscribedChannels(channels) - return nil -} - -// GetSubscriptions returns a copied list of subscriptions -func (b *BTSE) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) { - return b.Websocket.GetSubscriptions(), nil -} - -// AuthenticateWebsocket sends an authentication message to the websocket -func (b *BTSE) AuthenticateWebsocket() error { - return common.ErrFunctionNotSupported -} - // ValidateCredentials validates current credentials used for wrapper // functionality func (b *BTSE) ValidateCredentials() error { diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index 63d08cdf..11a6effb 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -18,7 +18,6 @@ import ( exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/request" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" "github.com/thrasher-corp/gocryptotrader/log" ) @@ -57,7 +56,6 @@ const ( // CoinbasePro is the overarching type across the coinbasepro package type CoinbasePro struct { exchange.Base - WebsocketConn *wshandler.WebsocketConnection } // GetProducts returns supported currency pairs on the exchange with specific diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index 0237027f..d3dd077b 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -17,7 +17,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/kline" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" "github.com/thrasher-corp/gocryptotrader/portfolio/banking" "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) @@ -49,12 +49,11 @@ func TestMain(m *testing.M) { gdxConfig.API.Credentials.ClientID = clientID gdxConfig.API.AuthenticatedSupport = true gdxConfig.API.AuthenticatedWebsocketSupport = true + c.Websocket = sharedtestvalues.NewTestWebsocket() err = c.Setup(gdxConfig) if err != nil { log.Fatal("CoinbasePro setup error", err) } - c.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride() - c.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride() os.Exit(m.Run()) } @@ -593,26 +592,24 @@ func TestGetDepositAddress(t *testing.T) { // TestWsAuth dials websocket, sends login request. func TestWsAuth(t *testing.T) { if !c.Websocket.IsEnabled() && !c.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() { - t.Skip(wshandler.WebsocketNotEnabled) - } - c.WebsocketConn = &wshandler.WebsocketConnection{ - ExchangeName: c.Name, - URL: c.Websocket.GetWebsocketURL(), - Verbose: c.Verbose, - ResponseMaxLimit: exchange.DefaultWebsocketResponseMaxLimit, - ResponseCheckTimeout: exchange.DefaultWebsocketResponseCheckTimeout, + t.Skip(stream.WebsocketNotEnabled) } var dialer websocket.Dialer - err := c.WebsocketConn.Dial(&dialer, http.Header{}) + err := c.Websocket.Conn.Dial(&dialer, http.Header{}) if err != nil { t.Fatal(err) } - c.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride() - c.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride() go c.wsReadData() - err = c.Subscribe(wshandler.WebsocketChannelSubscription{ - Channel: "user", - Currency: currency.NewPairFromString(testPair), + + p, err := currency.NewPairFromString(testPair) + if err != nil { + t.Fatal(err) + } + err = c.Subscribe([]stream.ChannelSubscription{ + { + Channel: "user", + Currency: p, + }, }) if err != nil { t.Error(err) @@ -947,3 +944,64 @@ func TestParseTime(t *testing.T) { t.Error("unexpected result") } } + +func TestCheckInterval(t *testing.T) { + interval := time.Minute + i, err := checkInterval(interval) + if err != nil { + t.Fatal(err) + } + if i != 60 { + t.Fatal("incorrect return") + } + interval = time.Minute * 5 + i, err = checkInterval(interval) + if err != nil { + t.Fatal(err) + } + if i != 300 { + t.Fatal("incorrect return") + } + + interval = time.Minute * 15 + i, err = checkInterval(interval) + if err != nil { + t.Fatal(err) + } + if i != 900 { + t.Fatal("incorrect return") + } + + interval = time.Hour + i, err = checkInterval(interval) + if err != nil { + t.Fatal(err) + } + if i != 3600 { + t.Fatal("incorrect return") + } + + interval = time.Hour * 6 + i, err = checkInterval(interval) + if err != nil { + t.Fatal(err) + } + if i != 21600 { + t.Fatal("incorrect return") + } + + interval = time.Hour * 24 + i, err = checkInterval(interval) + if err != nil { + t.Fatal(err) + } + if i != 86400 { + t.Fatal("incorrect return") + } + + interval = time.Hour * 1337 + _, err = checkInterval(interval) + if err == nil { + t.Fatal("error cannot be nil") + } +} diff --git a/exchanges/coinbasepro/coinbasepro_types.go b/exchanges/coinbasepro/coinbasepro_types.go index b8cb052f..e02f0556 100644 --- a/exchanges/coinbasepro/coinbasepro_types.go +++ b/exchanges/coinbasepro/coinbasepro_types.go @@ -353,7 +353,7 @@ type FillResponse struct { // WebsocketSubscribe takes in subscription information type WebsocketSubscribe struct { Type string `json:"type"` - ProductID string `json:"product_id,omitempty"` + ProductIDs []string `json:"product_ids,omitempty"` Channels []WsChannels `json:"channels,omitempty"` Signature string `json:"signature,omitempty"` Key string `json:"key,omitempty"` @@ -364,7 +364,7 @@ type WebsocketSubscribe struct { // WsChannels defines outgoing channels for subscription purposes type WsChannels struct { Name string `json:"name"` - ProductIDs []string `json:"product_ids"` + ProductIDs []string `json:"product_ids,omitempty"` } // wsOrderReceived holds websocket received values diff --git a/exchanges/coinbasepro/coinbasepro_websocket.go b/exchanges/coinbasepro/coinbasepro_websocket.go index c785cf4a..6dad3340 100644 --- a/exchanges/coinbasepro/coinbasepro_websocket.go +++ b/exchanges/coinbasepro/coinbasepro_websocket.go @@ -9,6 +9,7 @@ import ( "time" "github.com/gorilla/websocket" + "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/convert" "github.com/thrasher-corp/gocryptotrader/common/crypto" "github.com/thrasher-corp/gocryptotrader/currency" @@ -16,9 +17,9 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream/buffer" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook" ) const ( @@ -28,18 +29,20 @@ const ( // WsConnect initiates a websocket connection func (c *CoinbasePro) WsConnect() error { if !c.Websocket.IsEnabled() || !c.IsEnabled() { - return errors.New(wshandler.WebsocketNotEnabled) + return errors.New(stream.WebsocketNotEnabled) } var dialer websocket.Dialer - err := c.WebsocketConn.Dial(&dialer, http.Header{}) + err := c.Websocket.Conn.Dial(&dialer, http.Header{}) if err != nil { return err } - c.GenerateDefaultSubscriptions() + subs, err := c.GenerateDefaultSubscriptions() + if err != nil { + return err + } go c.wsReadData() - - return nil + return c.Websocket.SubscribeToChannels(subs) } // wsReadData receives and passes on websocket messages for processing @@ -51,20 +54,13 @@ func (c *CoinbasePro) wsReadData() { }() for { - select { - case <-c.Websocket.ShutdownC: + resp := c.Websocket.Conn.ReadMessage() + if resp.Raw == nil { return - default: - resp, err := c.WebsocketConn.ReadMessage() - if err != nil { - c.Websocket.ReadMessageErrors <- err - return - } - c.Websocket.TrafficAlert <- struct{}{} - err = c.wsHandleData(resp.Raw) - if err != nil { - c.Websocket.DataHandler <- err - } + } + err := c.wsHandleData(resp.Raw) + if err != nil { + c.Websocket.DataHandler <- err } } } @@ -233,7 +229,7 @@ func (c *CoinbasePro) wsHandleData(respRaw []byte) error { }, } default: - c.Websocket.DataHandler <- wshandler.UnhandledMessageWarning{Message: c.Name + wshandler.UnhandledMessage + string(respRaw)} + c.Websocket.DataHandler <- stream.UnhandledMessageWarning{Message: c.Name + stream.UnhandledMessage + string(respRaw)} return nil } return nil @@ -289,23 +285,16 @@ func (c *CoinbasePro) ProcessSnapshot(snapshot *WebsocketOrderbookSnapshot) erro orderbook.Item{Price: price, Amount: amount}) } - pair := currency.NewPairFromString(snapshot.ProductID) - base.AssetType = asset.Spot - base.Pair = pair - base.ExchangeName = c.Name - - err := c.Websocket.Orderbook.LoadSnapshot(&base) + pair, err := currency.NewPairFromString(snapshot.ProductID) if err != nil { return err } - c.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{ - Pair: pair, - Asset: asset.Spot, - Exchange: c.Name, - } + base.AssetType = asset.Spot + base.Pair = pair + base.ExchangeName = c.Name - return nil + return c.Websocket.Orderbook.LoadSnapshot(&base) } // ProcessUpdate updates the orderbook local cache @@ -313,8 +302,14 @@ func (c *CoinbasePro) ProcessUpdate(update WebsocketL2Update) error { var asks, bids []orderbook.Item for i := range update.Changes { - price, _ := strconv.ParseFloat(update.Changes[i][1].(string), 64) - volume, _ := strconv.ParseFloat(update.Changes[i][2].(string), 64) + price, err := strconv.ParseFloat(update.Changes[i][1].(string), 64) + if err != nil { + return err + } + volume, err := strconv.ParseFloat(update.Changes[i][2].(string), 64) + if err != nil { + return err + } if update.Changes[i][0].(string) == order.Buy.Lower() { bids = append(bids, orderbook.Item{Price: price, Amount: volume}) @@ -327,91 +322,123 @@ func (c *CoinbasePro) ProcessUpdate(update WebsocketL2Update) error { return errors.New("coinbasepro_websocket.go error - no data in websocket update") } - p := currency.NewPairFromString(update.ProductID) + p, err := currency.NewPairFromString(update.ProductID) + if err != nil { + return err + } + timestamp, err := time.Parse(time.RFC3339, update.Time) if err != nil { return err } - err = c.Websocket.Orderbook.Update(&wsorderbook.WebsocketOrderbookUpdate{ + return c.Websocket.Orderbook.Update(&buffer.Update{ Bids: bids, Asks: asks, Pair: p, UpdateTime: timestamp, Asset: asset.Spot, }) - if err != nil { - return err - } - - c.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{ - Pair: p, - Asset: asset.Spot, - Exchange: c.Name, - } - - return nil } // GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() -func (c *CoinbasePro) GenerateDefaultSubscriptions() { +func (c *CoinbasePro) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, error) { var channels = []string{"heartbeat", "level2", "ticker", "user"} - enabledCurrencies := c.GetEnabledPairs(asset.Spot) - var subscriptions []wshandler.WebsocketChannelSubscription + enabledCurrencies, err := c.GetEnabledPairs(asset.Spot) + if err != nil { + return nil, err + } + var subscriptions []stream.ChannelSubscription for i := range channels { - if (channels[i] == "user" || channels[i] == "full") && !c.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) { + if (channels[i] == "user" || channels[i] == "full") && + !c.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) { continue } for j := range enabledCurrencies { - subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{ - Channel: channels[i], - Currency: c.FormatExchangeCurrency(enabledCurrencies[j], - asset.Spot), + fpair, err := c.FormatExchangeCurrency(enabledCurrencies[j], + asset.Spot) + if err != nil { + return nil, err + } + subscriptions = append(subscriptions, stream.ChannelSubscription{ + Channel: channels[i], + Currency: fpair, + Asset: asset.Spot, }) } } - c.Websocket.SubscribeToChannels(subscriptions) + return subscriptions, nil } // Subscribe sends a websocket message to receive data from the channel -func (c *CoinbasePro) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error { +func (c *CoinbasePro) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error { subscribe := WebsocketSubscribe{ Type: "subscribe", - Channels: []WsChannels{ - { - Name: channelToSubscribe.Channel, - ProductIDs: []string{ - c.FormatExchangeCurrency(channelToSubscribe.Currency, - asset.Spot).String(), - }, - }, - }, } - if channelToSubscribe.Channel == "user" || channelToSubscribe.Channel == "full" { - n := strconv.FormatInt(time.Now().Unix(), 10) - message := n + http.MethodGet + "/users/self/verify" - hmac := crypto.GetHMAC(crypto.HashSHA256, []byte(message), - []byte(c.API.Credentials.Secret)) - subscribe.Signature = crypto.Base64Encode(hmac) - subscribe.Key = c.API.Credentials.Key - subscribe.Passphrase = c.API.Credentials.ClientID - subscribe.Timestamp = n + +subscriptions: + for i := range channelsToSubscribe { + p := channelsToSubscribe[i].Currency.String() + if !common.StringDataCompare(subscribe.ProductIDs, p) && p != "" { + subscribe.ProductIDs = append(subscribe.ProductIDs, p) + } + + for j := range subscribe.Channels { + if subscribe.Channels[j].Name == channelsToSubscribe[i].Channel { + continue subscriptions + } + } + + subscribe.Channels = append(subscribe.Channels, WsChannels{ + Name: channelsToSubscribe[i].Channel, + }) + + if channelsToSubscribe[i].Channel == "user" || + channelsToSubscribe[i].Channel == "full" { + n := strconv.FormatInt(time.Now().Unix(), 10) + message := n + http.MethodGet + "/users/self/verify" + hmac := crypto.GetHMAC(crypto.HashSHA256, []byte(message), + []byte(c.API.Credentials.Secret)) + subscribe.Signature = crypto.Base64Encode(hmac) + subscribe.Key = c.API.Credentials.Key + subscribe.Passphrase = c.API.Credentials.ClientID + subscribe.Timestamp = n + } } - return c.WebsocketConn.SendJSONMessage(subscribe) + err := c.Websocket.Conn.SendJSONMessage(subscribe) + if err != nil { + return err + } + c.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe...) + return nil } // Unsubscribe sends a websocket message to stop receiving data from the channel -func (c *CoinbasePro) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error { - subscribe := WebsocketSubscribe{ +func (c *CoinbasePro) Unsubscribe(channelsToUnsubscribe []stream.ChannelSubscription) error { + unsubscribe := WebsocketSubscribe{ Type: "unsubscribe", - Channels: []WsChannels{ - { - Name: channelToSubscribe.Channel, - ProductIDs: []string{ - c.FormatExchangeCurrency(channelToSubscribe.Currency, - asset.Spot).String(), - }, - }, - }, } - return c.WebsocketConn.SendJSONMessage(subscribe) + +unsubscriptions: + for i := range channelsToUnsubscribe { + p := channelsToUnsubscribe[i].Currency.String() + if !common.StringDataCompare(unsubscribe.ProductIDs, p) && p != "" { + unsubscribe.ProductIDs = append(unsubscribe.ProductIDs, p) + } + + for j := range unsubscribe.Channels { + if unsubscribe.Channels[j].Name == channelsToUnsubscribe[i].Channel { + continue unsubscriptions + } + } + + unsubscribe.Channels = append(unsubscribe.Channels, WsChannels{ + Name: channelsToUnsubscribe[i].Channel, + }) + } + err := c.Websocket.Conn.SendJSONMessage(unsubscribe) + if err != nil { + return err + } + c.Websocket.RemoveSuccessfulUnsubscriptions(channelsToUnsubscribe...) + return nil } diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index 868cd5ea..b707fb98 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -19,8 +19,8 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" "github.com/thrasher-corp/gocryptotrader/log" "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) @@ -58,19 +58,11 @@ func (c *CoinbasePro) SetDefaults() { c.API.CredentialsValidator.RequiresClientID = true c.API.CredentialsValidator.RequiresBase64DecodeSecret = true - c.CurrencyPairs = currency.PairsManager{ - AssetTypes: asset.Items{ - asset.Spot, - }, - UseGlobalFormat: true, - RequestFormat: ¤cy.PairFormat{ - Delimiter: "-", - Uppercase: true, - }, - ConfigFormat: ¤cy.PairFormat{ - Delimiter: "-", - Uppercase: true, - }, + requestFmt := ¤cy.PairFormat{Delimiter: currency.DashDelimiter, Uppercase: true} + configFmt := ¤cy.PairFormat{Delimiter: currency.DashDelimiter, Uppercase: true} + err := c.SetGlobalPairsManager(requestFmt, configFmt, asset.Spot) + if err != nil { + log.Errorln(log.ExchangeSys, err) } c.Features = exchange.Features{ @@ -141,7 +133,7 @@ func (c *CoinbasePro) SetDefaults() { c.API.Endpoints.URLDefault = coinbaseproAPIURL c.API.Endpoints.URL = c.API.Endpoints.URLDefault c.API.Endpoints.WebsocketURL = coinbaseproWebsocketURL - c.Websocket = wshandler.New() + c.Websocket = stream.New() c.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit c.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout c.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit @@ -159,41 +151,31 @@ func (c *CoinbasePro) Setup(exch *config.ExchangeConfig) error { return err } - err = c.Websocket.Setup( - &wshandler.WebsocketSetup{ - Enabled: exch.Features.Enabled.Websocket, - Verbose: exch.Verbose, - AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, - WebsocketTimeout: exch.WebsocketTrafficTimeout, - DefaultURL: coinbaseproWebsocketURL, - ExchangeName: exch.Name, - RunningURL: exch.API.Endpoints.WebsocketURL, - Connector: c.WsConnect, - Subscriber: c.Subscribe, - UnSubscriber: c.Unsubscribe, - Features: &c.Features.Supports.WebsocketCapabilities, - }) + err = c.Websocket.Setup(&stream.WebsocketSetup{ + Enabled: exch.Features.Enabled.Websocket, + Verbose: exch.Verbose, + AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, + WebsocketTimeout: exch.WebsocketTrafficTimeout, + DefaultURL: coinbaseproWebsocketURL, + ExchangeName: exch.Name, + RunningURL: exch.API.Endpoints.WebsocketURL, + Connector: c.WsConnect, + Subscriber: c.Subscribe, + UnSubscriber: c.Unsubscribe, + GenerateSubscriptions: c.GenerateDefaultSubscriptions, + Features: &c.Features.Supports.WebsocketCapabilities, + OrderbookBufferLimit: exch.WebsocketOrderbookBufferLimit, + BufferEnabled: true, + SortBuffer: true, + }) if err != nil { return err } - c.WebsocketConn = &wshandler.WebsocketConnection{ - ExchangeName: c.Name, - URL: c.Websocket.GetWebsocketURL(), - ProxyURL: c.Websocket.GetProxyAddress(), - Verbose: c.Verbose, + return c.Websocket.SetupNewConnection(stream.ConnectionSetup{ ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout, ResponseMaxLimit: exch.WebsocketResponseMaxLimit, - } - - c.Websocket.Orderbook.Setup( - exch.WebsocketOrderbookBufferLimit, - true, - true, - false, - false, - exch.Name) - return nil + }) } // Start starts the coinbasepro go routine @@ -217,21 +199,55 @@ func (c *CoinbasePro) Run() { } forceUpdate := false - delim := c.GetPairFormat(asset.Spot, false).Delimiter - if !common.StringDataContains(c.CurrencyPairs.GetPairs(asset.Spot, - true).Strings(), delim) || - !common.StringDataContains(c.CurrencyPairs.GetPairs(asset.Spot, - false).Strings(), delim) { - enabledPairs := currency.NewPairsFromStrings( - []string{currency.BTC.String() + delim + currency.USD.String()}, - ) - log.Warn(log.ExchangeSys, - "Enabled pairs for CoinbasePro reset due to config upgrade, please enable the ones you would like to use again") - forceUpdate = true + format, err := c.GetPairFormat(asset.Spot, false) + if err != nil { + log.Errorf(log.ExchangeSys, + "%s failed to update currencies. Err: %s\n", + c.Name, + err) + return + } + enabled, err := c.CurrencyPairs.GetPairs(asset.Spot, true) + if err != nil { + log.Errorf(log.ExchangeSys, + "%s failed to update currencies. Err: %s\n", + c.Name, + err) + return + } - err := c.UpdatePairs(enabledPairs, asset.Spot, true, true) + avail, err := c.CurrencyPairs.GetPairs(asset.Spot, false) + if err != nil { + log.Errorf(log.ExchangeSys, + "%s failed to update currencies. Err: %s\n", + c.Name, + err) + return + } + + if !common.StringDataContains(enabled.Strings(), format.Delimiter) || + !common.StringDataContains(avail.Strings(), format.Delimiter) { + var p currency.Pairs + p, err = currency.NewPairsFromStrings([]string{currency.BTC.String() + + format.Delimiter + + currency.USD.String()}) if err != nil { - log.Errorf(log.ExchangeSys, "%s failed to update currencies. Err: %s\n", c.Name, err) + log.Errorf(log.ExchangeSys, + "%s failed to update currencies. Err: %s\n", + c.Name, + err) + } else { + log.Warn(log.ExchangeSys, + "Enabled pairs for CoinbasePro reset due to config upgrade, please enable the ones you would like to use again") + forceUpdate = true + + err = c.UpdatePairs(p, asset.Spot, true, true) + if err != nil { + log.Errorf(log.ExchangeSys, + "%s failed to update currencies. Err: %s\n", + c.Name, + err) + } } } @@ -239,7 +255,7 @@ func (c *CoinbasePro) Run() { return } - err := c.UpdateTradablePairs(forceUpdate) + err = c.UpdateTradablePairs(forceUpdate) if err != nil { log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", c.Name, err) } @@ -252,10 +268,15 @@ func (c *CoinbasePro) FetchTradablePairs(asset asset.Item) ([]string, error) { return nil, err } + format, err := c.GetPairFormat(asset, false) + if err != nil { + return nil, err + } + var products []string for x := range pairs { products = append(products, pairs[x].BaseCurrency+ - c.GetPairFormat(asset, false).Delimiter+ + format.Delimiter+ pairs[x].QuoteCurrency) } @@ -270,7 +291,12 @@ func (c *CoinbasePro) UpdateTradablePairs(forceUpdate bool) error { return err } - return c.UpdatePairs(currency.NewPairsFromStrings(pairs), asset.Spot, false, forceUpdate) + p, err := currency.NewPairsFromStrings(pairs) + if err != nil { + return err + } + + return c.UpdatePairs(p, asset.Spot, false, forceUpdate) } // UpdateAccountInfo retrieves balances for all enabled currencies for the @@ -317,28 +343,34 @@ func (c *CoinbasePro) FetchAccountInfo() (account.Holdings, error) { // UpdateTicker updates and returns the ticker for a currency pair func (c *CoinbasePro) UpdateTicker(p currency.Pair, assetType asset.Item) (*ticker.Price, error) { - tick, err := c.GetTicker(c.FormatExchangeCurrency(p, assetType).String()) + fpair, err := c.FormatExchangeCurrency(p, assetType) if err != nil { return nil, err } - stats, err := c.GetStats(c.FormatExchangeCurrency(p, assetType).String()) + + tick, err := c.GetTicker(fpair.String()) + if err != nil { + return nil, err + } + stats, err := c.GetStats(fpair.String()) if err != nil { return nil, err } tickerPrice := &ticker.Price{ - Last: stats.Last, - High: stats.High, - Low: stats.Low, - Bid: tick.Bid, - Ask: tick.Ask, - Volume: tick.Volume, - Open: stats.Open, - Pair: p, - LastUpdated: tick.Time, - } + Last: stats.Last, + High: stats.High, + Low: stats.Low, + Bid: tick.Bid, + Ask: tick.Ask, + Volume: tick.Volume, + Open: stats.Open, + Pair: p, + LastUpdated: tick.Time, + ExchangeName: c.Name, + AssetType: assetType} - err = ticker.ProcessTicker(c.Name, tickerPrice, assetType) + err = ticker.ProcessTicker(tickerPrice) if err != nil { return tickerPrice, err } @@ -366,15 +398,18 @@ func (c *CoinbasePro) FetchOrderbook(p currency.Pair, assetType asset.Item) (*or // UpdateOrderbook updates and returns the orderbook for a currency pair func (c *CoinbasePro) UpdateOrderbook(p currency.Pair, assetType asset.Item) (*orderbook.Base, error) { - orderBook := new(orderbook.Base) - orderbookNew, err := c.GetOrderbook(c.FormatExchangeCurrency(p, - assetType).String(), 2) + fpair, err := c.FormatExchangeCurrency(p, assetType) if err != nil { - return orderBook, err + return nil, err + } + + orderbookNew, err := c.GetOrderbook(fpair.String(), 2) + if err != nil { + return nil, err } obNew := orderbookNew.(OrderbookL1L2) - + orderBook := new(orderbook.Base) for x := range obNew.Bids { orderBook.Bids = append(orderBook.Bids, orderbook.Item{Amount: obNew.Bids[x].Amount, Price: obNew.Bids[x].Price}) } @@ -413,15 +448,19 @@ func (c *CoinbasePro) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) return submitOrderResponse, err } + fpair, err := c.FormatExchangeCurrency(s.Pair, asset.Spot) + if err != nil { + return submitOrderResponse, err + } + var response string - var err error switch s.Type { case order.Market: response, err = c.PlaceMarketOrder("", s.Amount, s.Amount, s.Side.Lower(), - c.FormatExchangeCurrency(s.Pair, asset.Spot).String(), + fpair.String(), "") case order.Limit: response, err = c.PlaceLimitOrder("", @@ -430,7 +469,7 @@ func (c *CoinbasePro) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) s.Side.Lower(), "", "", - c.FormatExchangeCurrency(s.Pair, asset.Spot).String(), + fpair.String(), "", false) default: @@ -491,10 +530,15 @@ func (c *CoinbasePro) GetOrderInfo(orderID string) (order.Detail, error) { if errOss != nil { return order.Detail{}, fmt.Errorf("error parsing order side: %s", errOss) } + p, errP := currency.NewPairDelimiter(genOrderDetail.ProductID, "-") + if errP != nil { + return order.Detail{}, fmt.Errorf("error parsing order side: %s", errP) + } + response := order.Detail{ Exchange: c.GetName(), ID: genOrderDetail.ID, - Pair: currency.NewPairDelimiter(genOrderDetail.ProductID, "-"), + Pair: p, Side: ss, Type: tt, Date: od, @@ -587,11 +631,6 @@ func (c *CoinbasePro) WithdrawFiatFundsToInternationalBank(withdrawRequest *with }, nil } -// GetWebsocket returns a pointer to the exchange websocket -func (c *CoinbasePro) GetWebsocket() (*wshandler.Websocket, error) { - return c.Websocket, nil -} - // GetFeeByType returns an estimate of fee based on type of transaction func (c *CoinbasePro) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { if !c.AllowAuthenticatedRequest() && // Todo check connection status @@ -605,18 +644,32 @@ func (c *CoinbasePro) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, er func (c *CoinbasePro) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, error) { var respOrders []GeneralizedOrderResponse for i := range req.Pairs { + fpair, err := c.FormatExchangeCurrency(req.Pairs[i], asset.Spot) + if err != nil { + return nil, err + } + resp, err := c.GetOrders([]string{"open", "pending", "active"}, - c.FormatExchangeCurrency(req.Pairs[i], asset.Spot).String()) + fpair.String()) if err != nil { return nil, err } respOrders = append(respOrders, resp...) } + format, err := c.GetPairFormat(asset.Spot, false) + if err != nil { + return nil, err + } + var orders []order.Detail for i := range respOrders { - curr := currency.NewPairDelimiter(respOrders[i].ProductID, - c.GetPairFormat(asset.Spot, false).Delimiter) + var curr currency.Pair + curr, err = currency.NewPairDelimiter(respOrders[i].ProductID, + format.Delimiter) + if err != nil { + return nil, err + } orderSide := order.Side(strings.ToUpper(respOrders[i].Side)) orderType := order.Type(strings.ToUpper(respOrders[i].Type)) orders = append(orders, order.Detail{ @@ -642,18 +695,31 @@ func (c *CoinbasePro) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Deta func (c *CoinbasePro) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, error) { var respOrders []GeneralizedOrderResponse for i := range req.Pairs { + fpair, err := c.FormatExchangeCurrency(req.Pairs[i], asset.Spot) + if err != nil { + return nil, err + } resp, err := c.GetOrders([]string{"done", "settled"}, - c.FormatExchangeCurrency(req.Pairs[i], asset.Spot).String()) + fpair.String()) if err != nil { return nil, err } respOrders = append(respOrders, resp...) } + format, err := c.GetPairFormat(asset.Spot, false) + if err != nil { + return nil, err + } + var orders []order.Detail for i := range respOrders { - curr := currency.NewPairDelimiter(respOrders[i].ProductID, - c.GetPairFormat(asset.Spot, false).Delimiter) + var curr currency.Pair + curr, err = currency.NewPairDelimiter(respOrders[i].ProductID, + format.Delimiter) + if err != nil { + return nil, err + } orderSide := order.Side(strings.ToUpper(respOrders[i].Side)) orderType := order.Type(strings.ToUpper(respOrders[i].Type)) orders = append(orders, order.Detail{ @@ -674,28 +740,23 @@ func (c *CoinbasePro) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Deta return orders, nil } -// SubscribeToWebsocketChannels appends to ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle subscribing -func (c *CoinbasePro) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - c.Websocket.SubscribeToChannels(channels) - return nil -} - -// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle unsubscribing -func (c *CoinbasePro) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - c.Websocket.RemoveSubscribedChannels(channels) - return nil -} - -// GetSubscriptions returns a copied list of subscriptions -func (c *CoinbasePro) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) { - return c.Websocket.GetSubscriptions(), nil -} - -// AuthenticateWebsocket sends an authentication message to the websocket -func (c *CoinbasePro) AuthenticateWebsocket() error { - return common.ErrFunctionNotSupported +// checkInterval checks allowable interval +func checkInterval(i time.Duration) (int64, error) { + switch i.Seconds() { + case 60: + return 60, nil + case 300: + return 300, nil + case 900: + return 900, nil + case 3600: + return 3600, nil + case 21600: + return 21600, nil + case 86400: + return 86400, nil + } + return 0, fmt.Errorf("interval not allowed %v", i.Seconds()) } // GetHistoricCandles returns a set of candle between two time periods for a @@ -722,7 +783,13 @@ func (c *CoinbasePro) GetHistoricCandles(p currency.Pair, a asset.Item, start, e if err != nil { return kline.Item{}, err } - history, err := c.GetHistoricRates(c.FormatExchangeCurrency(p, a).String(), + + formatP, err := c.FormatExchangeCurrency(p, a) + if err != nil { + return kline.Item{}, err + } + + history, err := c.GetHistoricRates(formatP.String(), start.Format(time.RFC3339), end.Format(time.RFC3339), gran) @@ -765,8 +832,14 @@ func (c *CoinbasePro) GetHistoricCandlesExtended(p currency.Pair, a asset.Item, return kline.Item{}, err } dates := kline.CalcDateRanges(start, end, interval, c.Features.Enabled.Kline.ResultLimit) + + formattedPair, err := c.FormatExchangeCurrency(p, a) + if err != nil { + return kline.Item{}, err + } + for x := range dates { - history, err := c.GetHistoricRates(c.FormatExchangeCurrency(p, a).String(), + history, err := c.GetHistoricRates(formattedPair.String(), dates[x].Start.Format(time.RFC3339), dates[x].End.Format(time.RFC3339), gran) diff --git a/exchanges/coinbene/coinbene.go b/exchanges/coinbene/coinbene.go index ffc4d028..9080d838 100644 --- a/exchanges/coinbene/coinbene.go +++ b/exchanges/coinbene/coinbene.go @@ -18,13 +18,11 @@ import ( exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/request" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" ) // Coinbene is the overarching type across this package type Coinbene struct { exchange.Base - WebsocketConn *wshandler.WebsocketConnection } const ( @@ -35,10 +33,11 @@ const ( coinbeneAPIVersion = "v2" // Public endpoints - coinbeneGetTicker = "/market/ticker/one" - coinbeneGetTickers = "/market/tickers" - coinbeneGetOrderBook = "/market/orderBook" - coinbeneGetKlines = "/market/klines" + coinbeneGetTicker = "/market/ticker/one" + coinbeneGetTickersSpot = "/market/ticker/list" + coinbeneGetTickers = "/market/tickers" + coinbeneGetOrderBook = "/market/orderBook" + coinbeneGetKlines = "/market/klines" // TODO: Implement function --- coinbeneSpotKlines = "/market/instruments/candles" coinbeneSpotExchangeRate = "/market/rate/list" @@ -65,12 +64,15 @@ const ( coinbeneListSwapPositions = "/position/list" coinbenePositionFeeRate = "/position/feeRate" - limitOrder = "1" - marketOrder = "2" - buyDirection = "1" - openLong = "openLong" - openShort = "openShort" - sellDirection = "2" + limitOrder = "1" + marketOrder = "2" + postOnlyOrder = "8" + fillOrKillOrder = "9" + iosOrder = "10" + buyDirection = "1" + openLong = "openLong" + openShort = "openShort" + sellDirection = "2" ) // GetAllPairs gets all pairs on the exchange @@ -162,7 +164,7 @@ func (c *Coinbene) GetTickers() ([]TickerData, error) { TickerData []TickerData `json:"data"` }{} - path := c.API.Endpoints.URL + coinbeneAPIVersion + coinbeneGetTicker + path := c.API.Endpoints.URL + coinbeneAPIVersion + coinbeneGetTickersSpot return resp.TickerData, c.SendHTTPRequest(path, spotTickerList, &resp) } @@ -266,9 +268,15 @@ func (c *Coinbene) PlaceSpotOrder(price, quantity float64, symbol, direction, params.Set("orderType", limitOrder) case order.Market.Lower(): params.Set("orderType", marketOrder) + case order.PostOnly.Lower(): + params.Set("orderType", postOnlyOrder) + case order.FillOrKill.Lower(): + params.Set("orderType", fillOrKillOrder) + case order.IOS.Lower(): + params.Set("orderType", iosOrder) default: return resp, - errors.New("invalid order type, must be either 'limit' or 'market'") + errors.New("invalid order type, must be either 'limit', 'market', 'postOnly', 'fillOrKill', 'ios'") } params.Set("symbol", symbol) diff --git a/exchanges/coinbene/coinbene_test.go b/exchanges/coinbene/coinbene_test.go index d2ffb463..01baac97 100644 --- a/exchanges/coinbene/coinbene_test.go +++ b/exchanges/coinbene/coinbene_test.go @@ -40,13 +40,11 @@ func TestMain(m *testing.M) { coinbeneConfig.API.AuthenticatedSupport = true coinbeneConfig.API.Credentials.Secret = testAPISecret coinbeneConfig.API.Credentials.Key = testAPIKey - + c.Websocket = sharedtestvalues.NewTestWebsocket() err = c.Setup(coinbeneConfig) if err != nil { log.Fatal(err) } - c.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride() - c.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride() os.Exit(m.Run()) } @@ -288,7 +286,11 @@ func TestGetSwapOrderbook(t *testing.T) { func TestGetKlines(t *testing.T) { t.Parallel() - _, err := c.GetKlines(currency.NewPairFromString(spotTestPair).String(), + p, err := currency.NewPairFromString(spotTestPair) + if err != nil { + t.Fatal(err) + } + _, err = c.GetKlines(p.String(), time.Now().Add(-time.Hour*1), time.Now(), "1") if err != nil { t.Fatal(err) @@ -297,7 +299,11 @@ func TestGetKlines(t *testing.T) { func TestGetSwapKlines(t *testing.T) { t.Parallel() - _, err := c.GetSwapKlines(currency.NewPairFromString(swapTestPair).String(), + p, err := currency.NewPairFromString(swapTestPair) + if err != nil { + t.Fatal(err) + } + _, err = c.GetSwapKlines(p.String(), time.Now().Add(-time.Hour*1), time.Now(), "1") if err != nil { t.Error(err) @@ -480,8 +486,8 @@ func TestWsUnsubscribe(t *testing.T) { func TestWsLogin(t *testing.T) { pressXToJSON := []byte(`{"event":"login","success":true}`) err := c.wsHandleData(pressXToJSON) - if err != nil { - t.Error(err) + if err == nil { + t.Error("error cannot be nil as this will initiate an auth subscription") } pressXToJSON = []byte(`{"event":"login","success":false}`) @@ -692,14 +698,20 @@ func TestWsUserOrder(t *testing.T) { } func TestGetHistoricCandles(t *testing.T) { - currencyPair := currency.NewPairFromString(spotTestPair) + currencyPair, err := currency.NewPairFromString(spotTestPair) + if err != nil { + t.Fatal(err) + } startTime := time.Now().Add(-time.Hour * 24) - _, err := c.GetHistoricCandles(currencyPair, asset.Spot, startTime, time.Now(), kline.OneHour) + _, err = c.GetHistoricCandles(currencyPair, asset.Spot, startTime, time.Now(), kline.OneHour) if err != nil { t.Fatal(err) } - currencyPairSwap := currency.NewPairFromString(swapTestPair) + currencyPairSwap, err := currency.NewPairFromString(swapTestPair) + if err != nil { + t.Fatal(err) + } _, err = c.GetHistoricCandles(currencyPairSwap, asset.PerpetualSwap, startTime, time.Now(), kline.OneHour) if err != nil { t.Fatal(err) @@ -707,9 +719,12 @@ func TestGetHistoricCandles(t *testing.T) { } func TestGetHistoricCandlesExtended(t *testing.T) { - currencyPair := currency.NewPairFromString(spotTestPair) + currencyPair, err := currency.NewPairFromString(spotTestPair) + if err != nil { + t.Fatal(err) + } startTime := time.Now().Add(-time.Hour * 24) - _, err := c.GetHistoricCandlesExtended(currencyPair, asset.Spot, startTime, time.Now(), kline.OneHour) + _, err = c.GetHistoricCandlesExtended(currencyPair, asset.Spot, startTime, time.Now(), kline.OneHour) if err != nil { t.Fatal(err) } diff --git a/exchanges/coinbene/coinbene_websocket.go b/exchanges/coinbene/coinbene_websocket.go index 26e9359c..bc364ce2 100644 --- a/exchanges/coinbene/coinbene_websocket.go +++ b/exchanges/coinbene/coinbene_websocket.go @@ -16,9 +16,9 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream/buffer" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook" ) const ( @@ -27,15 +27,13 @@ const ( topic = "topic" ) -var comms = make(chan wshandler.WebsocketResponse) - // WsConnect connects to websocket func (c *Coinbene) WsConnect() error { if !c.Websocket.IsEnabled() || !c.IsEnabled() { - return errors.New(wshandler.WebsocketNotEnabled) + return errors.New(stream.WebsocketNotEnabled) } var dialer websocket.Dialer - err := c.WebsocketConn.Dial(&dialer, http.Header{}) + err := c.Websocket.Conn.Dial(&dialer, http.Header{}) if err != nil { return err } @@ -48,38 +46,44 @@ func (c *Coinbene) WsConnect() error { c.Websocket.SetCanUseAuthenticatedEndpoints(false) } } - c.GenerateDefaultSubscriptions() - - return nil + subs, err := c.GenerateDefaultSubscriptions() + if err != nil { + return err + } + return c.Websocket.SubscribeToChannels(subs) } // GenerateDefaultSubscriptions generates stuff -func (c *Coinbene) GenerateDefaultSubscriptions() { +func (c *Coinbene) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, error) { var channels = []string{"orderBook.%s.100", "tradeList.%s", "ticker.%s", "kline.%s"} - var subscriptions []wshandler.WebsocketChannelSubscription - pairs := c.GetEnabledPairs(asset.PerpetualSwap) + var subscriptions []stream.ChannelSubscription + pairs, err := c.GetEnabledPairs(asset.PerpetualSwap) + if err != nil { + return nil, err + } for x := range channels { for y := range pairs { pairs[y].Delimiter = "" - subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{ + subscriptions = append(subscriptions, stream.ChannelSubscription{ Channel: fmt.Sprintf(channels[x], pairs[y]), Currency: pairs[y], + Asset: asset.PerpetualSwap, }) } } - c.Websocket.SubscribeToChannels(subscriptions) + return subscriptions, nil } // GenerateAuthSubs generates auth subs -func (c *Coinbene) GenerateAuthSubs() { - var subscriptions []wshandler.WebsocketChannelSubscription - var sub wshandler.WebsocketChannelSubscription +func (c *Coinbene) GenerateAuthSubs() ([]stream.ChannelSubscription, error) { + var subscriptions []stream.ChannelSubscription + var sub stream.ChannelSubscription var userChannels = []string{"user.account", "user.position", "user.order"} for z := range userChannels { sub.Channel = userChannels[z] subscriptions = append(subscriptions, sub) } - c.Websocket.SubscribeToChannels(subscriptions) + return subscriptions, nil } // wsReadData receives and passes on websocket messages for processing @@ -87,27 +91,20 @@ func (c *Coinbene) wsReadData() { c.Websocket.Wg.Add(1) defer c.Websocket.Wg.Done() for { - select { - case <-c.Websocket.ShutdownC: + resp := c.Websocket.Conn.ReadMessage() + if resp.Raw == nil { return - default: - resp, err := c.WebsocketConn.ReadMessage() - if err != nil { - c.Websocket.ReadMessageErrors <- err - return - } - err = c.wsHandleData(resp.Raw) - if err != nil { - c.Websocket.DataHandler <- err - } + } + err := c.wsHandleData(resp.Raw) + if err != nil { + c.Websocket.DataHandler <- err } } } func (c *Coinbene) wsHandleData(respRaw []byte) error { - c.Websocket.TrafficAlert <- struct{}{} - if string(respRaw) == wshandler.Ping { - err := c.WebsocketConn.SendRawMessage(websocket.TextMessage, []byte(wshandler.Pong)) + if string(respRaw) == stream.Ping { + err := c.Websocket.Conn.SendRawMessage(websocket.TextMessage, []byte(stream.Pong)) if err != nil { return err } @@ -128,8 +125,12 @@ func (c *Coinbene) wsHandleData(respRaw []byte) error { if ok && strings.Contains(result[event].(string), "login") { if result["success"].(bool) { c.Websocket.SetCanUseAuthenticatedEndpoints(true) - c.GenerateAuthSubs() - return nil + var authsubs []stream.ChannelSubscription + authsubs, err = c.GenerateAuthSubs() + if err != nil { + return err + } + return c.Websocket.SubscribeToChannels(authsubs) } c.Websocket.SetCanUseAuthenticatedEndpoints(false) return fmt.Errorf("message: %s. code: %v", result["message"], result["code"]) @@ -141,17 +142,35 @@ func (c *Coinbene) wsHandleData(respRaw []byte) error { if err != nil { return err } + + var format currency.PairFormat + format, err = c.GetPairFormat(asset.PerpetualSwap, true) + if err != nil { + return err + } + + var pairs currency.Pairs + pairs, err = c.GetEnabledPairs(asset.PerpetualSwap) + if err != nil { + return err + } + for x := range wsTicker.Data { + var p currency.Pair + p, err = currency.NewPairFromFormattedPairs(wsTicker.Data[x].Symbol, + pairs, + format) + if err != nil { + return err + } c.Websocket.DataHandler <- &ticker.Price{ - Volume: wsTicker.Data[x].Volume24h, - Last: wsTicker.Data[x].LastPrice, - High: wsTicker.Data[x].High24h, - Low: wsTicker.Data[x].Low24h, - Bid: wsTicker.Data[x].BestBidPrice, - Ask: wsTicker.Data[x].BestAskPrice, - Pair: currency.NewPairFromFormattedPairs(wsTicker.Data[x].Symbol, - c.GetEnabledPairs(asset.PerpetualSwap), - c.GetPairFormat(asset.PerpetualSwap, true)), + Volume: wsTicker.Data[x].Volume24h, + Last: wsTicker.Data[x].LastPrice, + High: wsTicker.Data[x].High24h, + Low: wsTicker.Data[x].Low24h, + Bid: wsTicker.Data[x].BestBidPrice, + Ask: wsTicker.Data[x].BestAskPrice, + Pair: p, ExchangeName: c.Name, AssetType: asset.PerpetualSwap, LastUpdated: wsTicker.Data[x].Timestamp, @@ -182,16 +201,33 @@ func (c *Coinbene) wsHandleData(respRaw []byte) error { if tradeList.Data[0][1] == "s" { tSide = order.Sell } - c.Websocket.DataHandler <- wshandler.TradeData{ - CurrencyPair: currency.NewPairFromFormattedPairs(p, - c.GetEnabledPairs(asset.PerpetualSwap), - c.GetPairFormat(asset.PerpetualSwap, true)), - Timestamp: t, - Price: price, - Amount: amount, - Exchange: c.Name, - AssetType: asset.PerpetualSwap, - Side: tSide, + + var format currency.PairFormat + format, err = c.GetPairFormat(asset.PerpetualSwap, true) + if err != nil { + return err + } + + var pairs currency.Pairs + pairs, err = c.GetEnabledPairs(asset.PerpetualSwap) + if err != nil { + return err + } + + var newP currency.Pair + newP, err = currency.NewPairFromFormattedPairs(p, pairs, format) + if err != nil { + return err + } + + c.Websocket.DataHandler <- stream.TradeData{ + CurrencyPair: newP, + Timestamp: t, + Price: price, + Amount: amount, + Exchange: c.Name, + AssetType: asset.PerpetualSwap, + Side: tSide, } case strings.Contains(result[topic].(string), "orderBook"): orderBook := struct { @@ -209,9 +245,25 @@ func (c *Coinbene) wsHandleData(respRaw []byte) error { return err } p := strings.Replace(orderBook.Topic, "orderBook.", "", 1) - cp := currency.NewPairFromFormattedPairs(p, - c.GetEnabledPairs(asset.PerpetualSwap), - c.GetPairFormat(asset.PerpetualSwap, true)) + + var format currency.PairFormat + format, err = c.GetPairFormat(asset.PerpetualSwap, true) + if err != nil { + return err + } + + var pairs currency.Pairs + pairs, err = c.GetEnabledPairs(asset.PerpetualSwap) + if err != nil { + return err + } + + var newP currency.Pair + newP, err = currency.NewPairFromFormattedPairs(p, pairs, format) + if err != nil { + return err + } + var amount, price float64 var asks, bids []orderbook.Item for i := range orderBook.Data[0].Asks { @@ -247,23 +299,19 @@ func (c *Coinbene) wsHandleData(respRaw []byte) error { newOB.Asks = asks newOB.Bids = bids newOB.AssetType = asset.PerpetualSwap - newOB.Pair = cp + newOB.Pair = newP newOB.ExchangeName = c.Name newOB.LastUpdated = orderBook.Data[0].Timestamp err = c.Websocket.Orderbook.LoadSnapshot(&newOB) if err != nil { return err } - c.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{Pair: newOB.Pair, - Asset: asset.PerpetualSwap, - Exchange: c.Name, - } } else if orderBook.Action == "update" { - newOB := wsorderbook.WebsocketOrderbookUpdate{ + newOB := buffer.Update{ Asks: asks, Bids: bids, Asset: asset.PerpetualSwap, - Pair: cp, + Pair: newP, UpdateID: orderBook.Data[0].Version, UpdateTime: orderBook.Data[0].Timestamp, } @@ -271,10 +319,6 @@ func (c *Coinbene) wsHandleData(respRaw []byte) error { if err != nil { return err } - c.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{Pair: newOB.Pair, - Asset: asset.PerpetualSwap, - Exchange: c.Name, - } } case strings.Contains(result[topic].(string), "kline"): var kline WsKline @@ -291,15 +335,33 @@ func (c *Coinbene) wsHandleData(respRaw []byte) error { } tempKline = append(tempKline, tempFloat) } - p := currency.NewPairFromFormattedPairs(kline.Data[0][0].(string), - c.GetEnabledPairs(asset.PerpetualSwap), - c.GetPairFormat(asset.PerpetualSwap, true)) + + var format currency.PairFormat + format, err = c.GetPairFormat(asset.PerpetualSwap, true) + if err != nil { + return err + } + + var pairs currency.Pairs + pairs, err = c.GetEnabledPairs(asset.PerpetualSwap) + if err != nil { + return err + } + + var newP currency.Pair + newP, err = currency.NewPairFromFormattedPairs(kline.Data[0][0].(string), + pairs, + format) + if err != nil { + return err + } + if tempKline == nil && len(tempKline) < 5 { return errors.New(c.Name + " - received bad data ") } - c.Websocket.DataHandler <- wshandler.KlineData{ + c.Websocket.DataHandler <- stream.KlineData{ Timestamp: time.Unix(int64(kline.Data[0][1].(float64)), 0), - Pair: p, + Pair: newP, AssetType: asset.PerpetualSwap, Exchange: c.Name, OpenPrice: tempKline[0], @@ -328,6 +390,17 @@ func (c *Coinbene) wsHandleData(respRaw []byte) error { if err != nil { return err } + + format, err := c.GetPairFormat(asset.PerpetualSwap, true) + if err != nil { + return err + } + + pairs, err := c.GetEnabledPairs(asset.PerpetualSwap) + if err != nil { + return err + } + for i := range orders.Data { oType, err := order.StringToOrderType(orders.Data[i].OrderType) if err != nil { @@ -345,6 +418,14 @@ func (c *Coinbene) wsHandleData(respRaw []byte) error { Err: err, } } + + newP, err := currency.NewPairFromFormattedPairs(orders.Data[i].Symbol, + pairs, + format) + if err != nil { + return err + } + c.Websocket.DataHandler <- &order.Detail{ Price: orders.Data[i].OrderPrice, Amount: orders.Data[i].Quantity, @@ -358,32 +439,46 @@ func (c *Coinbene) wsHandleData(respRaw []byte) error { AssetType: asset.PerpetualSwap, Date: orders.Data[i].OrderTime, Leverage: strconv.FormatInt(orders.Data[i].Leverage, 10), - Pair: currency.NewPairFromFormattedPairs(orders.Data[i].Symbol, - c.GetEnabledPairs(asset.PerpetualSwap), - c.GetPairFormat(asset.PerpetualSwap, true)), + Pair: newP, } } default: - c.Websocket.DataHandler <- wshandler.UnhandledMessageWarning{Message: c.Name + wshandler.UnhandledMessage + string(respRaw)} + c.Websocket.DataHandler <- stream.UnhandledMessageWarning{ + Message: c.Name + stream.UnhandledMessage + string(respRaw), + } return nil } return nil } // Subscribe sends a websocket message to receive data from the channel -func (c *Coinbene) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error { +func (c *Coinbene) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error { var sub WsSub sub.Operation = "subscribe" - sub.Arguments = []string{channelToSubscribe.Channel} - return c.WebsocketConn.SendJSONMessage(sub) + for i := range channelsToSubscribe { + sub.Arguments = append(sub.Arguments, channelsToSubscribe[i].Channel) + } + err := c.Websocket.Conn.SendJSONMessage(sub) + if err != nil { + return err + } + c.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe...) + return nil } // Unsubscribe sends a websocket message to receive data from the channel -func (c *Coinbene) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error { - var sub WsSub - sub.Operation = "unsubscribe" - sub.Arguments = []string{channelToSubscribe.Channel} - return c.WebsocketConn.SendJSONMessage(sub) +func (c *Coinbene) Unsubscribe(channelToUnsubscribe []stream.ChannelSubscription) error { + var unsub WsSub + unsub.Operation = "unsubscribe" + for i := range channelToUnsubscribe { + unsub.Arguments = append(unsub.Arguments, channelToUnsubscribe[i].Channel) + } + err := c.Websocket.Conn.SendJSONMessage(unsub) + if err != nil { + return err + } + c.Websocket.RemoveSuccessfulUnsubscriptions(channelToUnsubscribe...) + return nil } // Login logs in @@ -397,5 +492,5 @@ func (c *Coinbene) Login() error { sign := crypto.HexEncodeToString(tempSign) sub.Operation = "login" sub.Arguments = []string{c.API.Credentials.Key, expTime, sign} - return c.WebsocketConn.SendJSONMessage(sub) + return c.Websocket.Conn.SendJSONMessage(sub) } diff --git a/exchanges/coinbene/coinbene_wrapper.go b/exchanges/coinbene/coinbene_wrapper.go index 9e1d0fa0..d4a931bd 100644 --- a/exchanges/coinbene/coinbene_wrapper.go +++ b/exchanges/coinbene/coinbene_wrapper.go @@ -19,8 +19,8 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" "github.com/thrasher-corp/gocryptotrader/log" "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) @@ -56,33 +56,32 @@ func (c *Coinbene) SetDefaults() { c.API.CredentialsValidator.RequiresKey = true c.API.CredentialsValidator.RequiresSecret = true - c.CurrencyPairs = currency.PairsManager{ - AssetTypes: asset.Items{ - asset.Spot, - asset.PerpetualSwap, + err := c.StoreAssetPairFormat(asset.Spot, currency.PairStore{ + RequestFormat: ¤cy.PairFormat{ + Uppercase: true, + Delimiter: currency.ForwardSlashDelimiter, }, + ConfigFormat: ¤cy.PairFormat{ + Uppercase: true, + Delimiter: currency.ForwardSlashDelimiter, + }, + }) + if err != nil { + log.Errorln(log.ExchangeSys, err) } - c.CurrencyPairs.Store(asset.Spot, currency.PairStore{ - RequestFormat: ¤cy.PairFormat{ - Uppercase: true, - Delimiter: "/", - }, - ConfigFormat: ¤cy.PairFormat{ - Uppercase: true, - Delimiter: "/", - }, - }) - - c.CurrencyPairs.Store(asset.PerpetualSwap, currency.PairStore{ + err = c.StoreAssetPairFormat(asset.PerpetualSwap, currency.PairStore{ RequestFormat: ¤cy.PairFormat{ Uppercase: true, }, ConfigFormat: ¤cy.PairFormat{ Uppercase: true, - Delimiter: "/", + Delimiter: currency.ForwardSlashDelimiter, }, }) + if err != nil { + log.Errorln(log.ExchangeSys, err) + } c.Features = exchange.Features{ Supports: exchange.FeaturesSupported{ @@ -149,7 +148,7 @@ func (c *Coinbene) SetDefaults() { c.API.Endpoints.URLDefault = coinbeneAPIURL c.API.Endpoints.URL = c.API.Endpoints.URLDefault c.API.Endpoints.WebsocketURL = wsContractURL - c.Websocket = wshandler.New() + c.Websocket = stream.New() c.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit c.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout c.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit @@ -167,42 +166,31 @@ func (c *Coinbene) Setup(exch *config.ExchangeConfig) error { return err } - err = c.Websocket.Setup( - &wshandler.WebsocketSetup{ - Enabled: exch.Features.Enabled.Websocket, - Verbose: exch.Verbose, - AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, - WebsocketTimeout: exch.WebsocketTrafficTimeout, - DefaultURL: wsContractURL, - ExchangeName: exch.Name, - RunningURL: exch.API.Endpoints.WebsocketURL, - Connector: c.WsConnect, - Subscriber: c.Subscribe, - UnSubscriber: c.Unsubscribe, - Features: &c.Features.Supports.WebsocketCapabilities, - }) + err = c.Websocket.Setup(&stream.WebsocketSetup{ + Enabled: exch.Features.Enabled.Websocket, + Verbose: exch.Verbose, + AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, + WebsocketTimeout: exch.WebsocketTrafficTimeout, + DefaultURL: wsContractURL, + ExchangeName: exch.Name, + RunningURL: exch.API.Endpoints.WebsocketURL, + Connector: c.WsConnect, + Subscriber: c.Subscribe, + UnSubscriber: c.Unsubscribe, + GenerateSubscriptions: c.GenerateDefaultSubscriptions, + Features: &c.Features.Supports.WebsocketCapabilities, + OrderbookBufferLimit: exch.WebsocketOrderbookBufferLimit, + BufferEnabled: true, + SortBuffer: true, + }) if err != nil { return err } - c.WebsocketConn = &wshandler.WebsocketConnection{ - ExchangeName: c.Name, - URL: c.Websocket.GetWebsocketURL(), - ProxyURL: c.Websocket.GetProxyAddress(), - Verbose: c.Verbose, + return c.Websocket.SetupNewConnection(stream.ConnectionSetup{ ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout, ResponseMaxLimit: exch.WebsocketResponseMaxLimit, - } - - c.Websocket.Orderbook.Setup( - exch.WebsocketOrderbookBufferLimit, - true, - true, - false, - false, - exch.Name) - - return nil + }) } // Start starts the Coinbene go routine @@ -257,6 +245,11 @@ func (c *Coinbene) FetchTradablePairs(a asset.Item) ([]string, error) { currencies = append(currencies, pairs[x].Symbol) } case asset.PerpetualSwap: + format, err := c.GetPairFormat(a, false) + if err != nil { + return nil, err + } + tickers, err := c.GetSwapTickers() if err != nil { return nil, err @@ -264,10 +257,11 @@ func (c *Coinbene) FetchTradablePairs(a asset.Item) ([]string, error) { for t := range tickers { idx := strings.Index(t, currency.USDT.String()) if idx == 0 { - return nil, fmt.Errorf("%s SWAP currency does not contain USDT", c.Name) + return nil, + fmt.Errorf("%s SWAP currency does not contain USDT", c.Name) } currencies = append(currencies, - t[0:idx]+c.GetPairFormat(a, false).Delimiter+t[idx:]) + t[0:idx]+format.Delimiter+t[idx:]) } } return currencies, nil @@ -282,8 +276,13 @@ func (c *Coinbene) UpdateTradablePairs(forceUpdate bool) error { if err != nil { return err } - err = c.UpdatePairs(currency.NewPairsFromStrings(pairs), - assets[x], false, forceUpdate) + + p, err := currency.NewPairsFromStrings(pairs) + if err != nil { + return err + } + + err = c.UpdatePairs(p, assets[x], false, forceUpdate) if err != nil { return err } @@ -293,30 +292,44 @@ func (c *Coinbene) UpdateTradablePairs(forceUpdate bool) error { // UpdateTicker updates and returns the ticker for a currency pair func (c *Coinbene) UpdateTicker(p currency.Pair, assetType asset.Item) (*ticker.Price, error) { - resp := new(ticker.Price) if !c.SupportsAsset(assetType) { return nil, fmt.Errorf("%s does not support asset type %s", c.Name, assetType) } + allPairs, err := c.GetEnabledPairs(assetType) + if err != nil { + return nil, err + } + switch assetType { case asset.Spot: - allPairs := c.GetEnabledPairs(assetType) - for x := range allPairs { - tempResp, err := c.GetTicker(c.FormatExchangeCurrency(allPairs[x], - assetType).String()) + tickers, err := c.GetTickers() + if err != nil { + return nil, err + } + + for i := range tickers { + var newP currency.Pair + newP, err = currency.NewPairFromString(tickers[i].Symbol) if err != nil { return nil, err } - resp.Pair = allPairs[x] - resp.Last = tempResp.LatestPrice - resp.High = tempResp.DailyHigh - resp.Low = tempResp.DailyLow - resp.Bid = tempResp.BestBid - resp.Ask = tempResp.BestAsk - resp.Volume = tempResp.DailyVolume - resp.LastUpdated = time.Now() - err = ticker.ProcessTicker(c.Name, resp, assetType) + + if !allPairs.Contains(newP, true) { + continue + } + + err = ticker.ProcessTicker(&ticker.Price{ + Pair: newP, + Last: tickers[i].LatestPrice, + High: tickers[i].DailyHigh, + Low: tickers[i].DailyLow, + Bid: tickers[i].BestBid, + Ask: tickers[i].BestAsk, + Volume: tickers[i].DailyVolume, + ExchangeName: c.Name, + AssetType: assetType}) if err != nil { return nil, err } @@ -327,24 +340,31 @@ func (c *Coinbene) UpdateTicker(p currency.Pair, assetType asset.Item) (*ticker. return nil, err } - allPairs := c.GetEnabledPairs(assetType) for x := range allPairs { - tick, ok := tickers[c.FormatExchangeCurrency(allPairs[x], - assetType).String()] + fpair, err := c.FormatExchangeCurrency(allPairs[x], assetType) + if err != nil { + return nil, err + } + + tick, ok := tickers[fpair.String()] if !ok { log.Warnf(log.ExchangeSys, - "%s SWAP ticker item was not found", c.Name) + "%s SWAP ticker item was not found", + c.Name) continue } - resp.Pair = allPairs[x] - resp.Last = tick.LastPrice - resp.High = tick.High24Hour - resp.Low = tick.Low24Hour - resp.Bid = tick.BestBidPrice - resp.Ask = tick.BestAskPrice - resp.Volume = tick.Volume24Hour - resp.LastUpdated = tick.Timestamp - err = ticker.ProcessTicker(c.Name, resp, assetType) + + err = ticker.ProcessTicker(&ticker.Price{ + Pair: allPairs[x], + Last: tick.LastPrice, + High: tick.High24Hour, + Low: tick.Low24Hour, + Bid: tick.BestBidPrice, + Ask: tick.BestAskPrice, + Volume: tick.Volume24Hour, + LastUpdated: tick.Timestamp, + ExchangeName: c.Name, + AssetType: assetType}) if err != nil { return nil, err } @@ -368,15 +388,15 @@ func (c *Coinbene) FetchTicker(p currency.Pair, assetType asset.Item) (*ticker.P } // FetchOrderbook returns orderbook base on the currency pair -func (c *Coinbene) FetchOrderbook(currency currency.Pair, assetType asset.Item) (*orderbook.Base, error) { +func (c *Coinbene) FetchOrderbook(p currency.Pair, assetType asset.Item) (*orderbook.Base, error) { if !c.SupportsAsset(assetType) { return nil, fmt.Errorf("%s does not support asset type %s", c.Name, assetType) } - ob, err := orderbook.Get(c.Name, currency, assetType) + ob, err := orderbook.Get(c.Name, p, assetType) if err != nil { - return c.UpdateOrderbook(currency, assetType) + return c.UpdateOrderbook(p, assetType) } return ob, nil } @@ -389,18 +409,19 @@ func (c *Coinbene) UpdateOrderbook(p currency.Pair, assetType asset.Item) (*orde fmt.Errorf("%s does not support asset type %s", c.Name, assetType) } - var tempResp Orderbook - var err error + fpair, err := c.FormatExchangeCurrency(p, assetType) + if err != nil { + return nil, err + } + var tempResp Orderbook switch assetType { case asset.Spot: - tempResp, err = c.GetOrderbook( - c.FormatExchangeCurrency(p, assetType).String(), + tempResp, err = c.GetOrderbook(fpair.String(), 100, // TO-DO: Update this once we support configurable orderbook depth ) case asset.PerpetualSwap: - tempResp, err = c.GetSwapOrderbook( - c.FormatExchangeCurrency(p, assetType).String(), + tempResp, err = c.GetSwapOrderbook(fpair.String(), 100, // TO-DO: Update this once we support configurable orderbook depth ) } @@ -501,13 +522,15 @@ func (c *Coinbene) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { fmt.Errorf("%s orderside is not supported by this exchange", s.Side) } - if s.Type != order.Limit { - return resp, fmt.Errorf("only limit order is supported by this exchange") + + fpair, err := c.FormatExchangeCurrency(s.Pair, asset.Spot) + if err != nil { + return resp, err } tempResp, err := c.PlaceSpotOrder(s.Price, s.Amount, - c.FormatExchangeCurrency(s.Pair, asset.Spot).String(), + fpair.String(), s.Side.String(), s.Type.String(), s.ClientID, @@ -535,13 +558,17 @@ func (c *Coinbene) CancelOrder(order *order.Cancel) error { // CancelAllOrders cancels all orders associated with a currency pair func (c *Coinbene) CancelAllOrders(orderCancellation *order.Cancel) (order.CancelAllResponse, error) { var resp order.CancelAllResponse - orders, err := c.FetchOpenSpotOrders( - c.FormatExchangeCurrency(orderCancellation.Pair, - asset.Spot).String(), - ) + fpair, err := c.FormatExchangeCurrency(orderCancellation.Pair, + orderCancellation.AssetType) if err != nil { return resp, err } + + orders, err := c.FetchOpenSpotOrders(fpair.String()) + if err != nil { + return resp, err + } + tempMap := make(map[string]string) for x := range orders { _, err := c.CancelSpotOrder(orders[x].OrderID) @@ -597,11 +624,6 @@ func (c *Coinbene) WithdrawFiatFundsToInternationalBank(withdrawRequest *withdra return nil, common.ErrFunctionNotSupported } -// GetWebsocket returns a pointer to the exchange websocket -func (c *Coinbene) GetWebsocket() (*wshandler.Websocket, error) { - return c.Websocket, nil -} - // GetActiveOrders retrieves any orders that are active/open func (c *Coinbene) GetActiveOrders(getOrdersRequest *order.GetOrdersRequest) ([]order.Detail, error) { if len(getOrdersRequest.Pairs) == 0 { @@ -610,21 +632,24 @@ func (c *Coinbene) GetActiveOrders(getOrdersRequest *order.GetOrdersRequest) ([] return nil, err } for a := range allPairs { - getOrdersRequest.Pairs = append(getOrdersRequest.Pairs, - currency.NewPairFromString(allPairs[a].Symbol)) + p, err := currency.NewPairFromString(allPairs[a].Symbol) + if err != nil { + return nil, err + } + getOrdersRequest.Pairs = append(getOrdersRequest.Pairs, p) } } - var err error var resp []order.Detail - for x := range getOrdersRequest.Pairs { + fpair, err := c.FormatExchangeCurrency(getOrdersRequest.Pairs[x], + asset.Spot) + if err != nil { + return nil, err + } + var tempData OrdersInfo - tempData, err = c.FetchOpenSpotOrders( - c.FormatExchangeCurrency( - getOrdersRequest.Pairs[x], - asset.Spot).String(), - ) + tempData, err = c.FetchOpenSpotOrders(fpair.String()) if err != nil { return nil, err } @@ -658,23 +683,26 @@ func (c *Coinbene) GetOrderHistory(getOrdersRequest *order.GetOrdersRequest) ([] if err != nil { return nil, err } + for a := range allPairs { - getOrdersRequest.Pairs = append(getOrdersRequest.Pairs, - currency.NewPairFromString(allPairs[a].Symbol)) + p, err := currency.NewPairFromString(allPairs[a].Symbol) + if err != nil { + return nil, err + } + getOrdersRequest.Pairs = append(getOrdersRequest.Pairs, p) } } var resp []order.Detail var tempData OrdersInfo - var err error - for x := range getOrdersRequest.Pairs { - tempData, err = c.FetchClosedOrders( - c.FormatExchangeCurrency( - getOrdersRequest.Pairs[x], - asset.Spot).String(), - "", - ) + fpair, err := c.FormatExchangeCurrency(getOrdersRequest.Pairs[x], + asset.Spot) + if err != nil { + return nil, err + } + + tempData, err = c.FetchClosedOrders(fpair.String(), "") if err != nil { return nil, err } @@ -702,40 +730,20 @@ func (c *Coinbene) GetOrderHistory(getOrdersRequest *order.GetOrdersRequest) ([] // GetFeeByType returns an estimate of fee based on the type of transaction func (c *Coinbene) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { - var fee float64 - tempData, err := c.GetPairInfo( - c.FormatExchangeCurrency( - feeBuilder.Pair, asset.Spot).String(), - ) + fpair, err := c.FormatExchangeCurrency(feeBuilder.Pair, asset.Spot) if err != nil { - return fee, err + return 0, err } - switch feeBuilder.IsMaker { - case true: - fee = feeBuilder.PurchasePrice * feeBuilder.Amount * tempData.MakerFeeRate - case false: - fee = feeBuilder.PurchasePrice * feeBuilder.Amount * tempData.TakerFeeRate + + tempData, err := c.GetPairInfo(fpair.String()) + if err != nil { + return 0, err } - return fee, nil -} -// SubscribeToWebsocketChannels appends to ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle subscribing -func (c *Coinbene) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - c.Websocket.SubscribeToChannels(channels) - return nil -} - -// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle unsubscribing -func (c *Coinbene) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - c.Websocket.RemoveSubscribedChannels(channels) - return nil -} - -// GetSubscriptions returns a copied list of subscriptions -func (c *Coinbene) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) { - return c.Websocket.GetSubscriptions(), nil + if feeBuilder.IsMaker { + return feeBuilder.PurchasePrice * feeBuilder.Amount * tempData.MakerFeeRate, nil + } + return feeBuilder.PurchasePrice * feeBuilder.Amount * tempData.TakerFeeRate, nil } // AuthenticateWebsocket sends an authentication message to the websocket @@ -774,14 +782,18 @@ func (c *Coinbene) GetHistoricCandles(pair currency.Pair, a asset.Item, start, e } } + formattedPair, err := c.FormatExchangeCurrency(pair, asset.PerpetualSwap) + if err != nil { + return kline.Item{}, err + } + var candles CandleResponse - var err error if a == asset.PerpetualSwap { - candles, err = c.GetSwapKlines(c.FormatExchangeCurrency(pair, asset.PerpetualSwap).String(), + candles, err = c.GetSwapKlines(formattedPair.String(), start, end, c.FormatExchangeKlineInterval(interval)) } else { - candles, err = c.GetKlines(c.FormatExchangeCurrency(pair, asset.Spot).String(), + candles, err = c.GetKlines(formattedPair.String(), start, end, c.FormatExchangeKlineInterval(interval)) } diff --git a/exchanges/coinut/coinut.go b/exchanges/coinut/coinut.go index 9c77dde1..c4e6916f 100644 --- a/exchanges/coinut/coinut.go +++ b/exchanges/coinut/coinut.go @@ -17,7 +17,6 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/request" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" "github.com/thrasher-corp/gocryptotrader/log" ) @@ -42,6 +41,8 @@ const ( coinutStatusOK = "OK" coinutMaxNonce = 16777215 // See https://github.com/coinut/api/wiki/Websocket-API#nonce + + wsRateLimitInMilliseconds = 33 ) var ( @@ -52,7 +53,6 @@ var ( // COINUT is the overarching type across the coinut package type COINUT struct { exchange.Base - WebsocketConn *wshandler.WebsocketConnection instrumentMap instrumentMap } diff --git a/exchanges/coinut/coinut_test.go b/exchanges/coinut/coinut_test.go index 6e95e983..b1f31fb0 100644 --- a/exchanges/coinut/coinut_test.go +++ b/exchanges/coinut/coinut_test.go @@ -14,7 +14,7 @@ import ( exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) @@ -43,6 +43,7 @@ func TestMain(m *testing.M) { coinutCfg.API.AuthenticatedWebsocketSupport = true coinutCfg.API.Credentials.Key = apiKey coinutCfg.API.Credentials.ClientID = clientID + c.Websocket = sharedtestvalues.NewTestWebsocket() err = c.Setup(coinutCfg) if err != nil { log.Fatal("Coinut setup error", err) @@ -51,8 +52,6 @@ func TestMain(m *testing.M) { if err != nil { log.Fatal("Coinut setup error ", err) } - c.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride() - c.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride() os.Exit(m.Run()) } @@ -62,27 +61,17 @@ func setupWSTestAuth(t *testing.T) { } if !c.Websocket.IsEnabled() && !c.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() { - t.Skip(wshandler.WebsocketNotEnabled) + t.Skip(stream.WebsocketNotEnabled) } if areTestAPIKeysSet() { c.Websocket.SetCanUseAuthenticatedEndpoints(true) } - c.WebsocketConn = &wshandler.WebsocketConnection{ - ExchangeName: c.Name, - URL: coinutWebsocketURL, - Verbose: c.Verbose, - RateLimit: coinutWebsocketRateLimit, - ResponseMaxLimit: exchange.DefaultWebsocketResponseMaxLimit, - ResponseCheckTimeout: exchange.DefaultWebsocketResponseCheckTimeout, - } var dialer websocket.Dialer - err := c.WebsocketConn.Dial(&dialer, http.Header{}) + err := c.Websocket.Conn.Dial(&dialer, http.Header{}) if err != nil { t.Fatal(err) } - c.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride() - c.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride() go c.wsReadData() err = c.wsAuthenticate() if err != nil { diff --git a/exchanges/coinut/coinut_websocket.go b/exchanges/coinut/coinut_websocket.go index bd271d12..f7bc0740 100644 --- a/exchanges/coinut/coinut_websocket.go +++ b/exchanges/coinut/coinut_websocket.go @@ -10,15 +10,16 @@ import ( "time" "github.com/gorilla/websocket" + "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/crypto" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream/buffer" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook" "github.com/thrasher-corp/gocryptotrader/log" ) @@ -39,10 +40,10 @@ var ( // WsConnect intiates a websocket connection func (c *COINUT) WsConnect() error { if !c.Websocket.IsEnabled() || !c.IsEnabled() { - return errors.New(wshandler.WebsocketNotEnabled) + return errors.New(stream.WebsocketNotEnabled) } var dialer websocket.Dialer - err := c.WebsocketConn.Dial(&dialer, http.Header{}) + err := c.Websocket.Conn.Dial(&dialer, http.Header{}) if err != nil { return err } @@ -59,7 +60,15 @@ func (c *COINUT) WsConnect() error { c.Websocket.SetCanUseAuthenticatedEndpoints(false) log.Error(log.WebsocketMgr, err) } - c.GenerateDefaultSubscriptions() + subs, err := c.GenerateDefaultSubscriptions() + if err != nil { + return err + } + + err = c.Websocket.SubscribeToChannels(subs) + if err != nil { + return err + } // define bi-directional communication channels = make(map[string]chan []byte) @@ -71,61 +80,49 @@ func (c *COINUT) WsConnect() error { // wsReadData receives and passes on websocket messages for processing func (c *COINUT) wsReadData() { c.Websocket.Wg.Add(1) - - defer func() { - c.Websocket.Wg.Done() - }() + defer c.Websocket.Wg.Done() for { - select { - case <-c.Websocket.ShutdownC: + resp := c.Websocket.Conn.ReadMessage() + if resp.Raw == nil { return + } - default: - resp, err := c.WebsocketConn.ReadMessage() + if strings.HasPrefix(string(resp.Raw), "[") { + var incoming []wsResponse + err := json.Unmarshal(resp.Raw, &incoming) if err != nil { - c.Websocket.ReadMessageErrors <- err - return + c.Websocket.DataHandler <- err + continue } - c.Websocket.TrafficAlert <- struct{}{} - - if strings.HasPrefix(string(resp.Raw), "[") { - var incoming []wsResponse - err = json.Unmarshal(resp.Raw, &incoming) + for i := range incoming { + if incoming[i].Nonce > 0 { + if c.Websocket.Match.IncomingWithData(incoming[i].Nonce, resp.Raw) { + break + } + } + var individualJSON []byte + individualJSON, err = json.Marshal(incoming[i]) if err != nil { c.Websocket.DataHandler <- err continue } - for i := range incoming { - if incoming[i].Nonce > 0 { - if c.WebsocketConn.IsIDWaitingForResponse(incoming[i].Nonce) { - c.WebsocketConn.SetResponseIDAndData(incoming[i].Nonce, resp.Raw) - break - } - } - var individualJSON []byte - individualJSON, err = json.Marshal(incoming[i]) - if err != nil { - c.Websocket.DataHandler <- err - continue - } - err = c.wsHandleData(individualJSON) - if err != nil { - c.Websocket.DataHandler <- err - } - } - } else { - var incoming wsResponse - err = json.Unmarshal(resp.Raw, &incoming) - if err != nil { - c.Websocket.DataHandler <- err - continue - } - err = c.wsHandleData(resp.Raw) + err = c.wsHandleData(individualJSON) if err != nil { c.Websocket.DataHandler <- err } } + } else { + var incoming wsResponse + err := json.Unmarshal(resp.Raw, &incoming) + if err != nil { + c.Websocket.DataHandler <- err + continue + } + err = c.wsHandleData(resp.Raw) + if err != nil { + c.Websocket.DataHandler <- err + } } } } @@ -153,11 +150,16 @@ func (c *COINUT) wsHandleData(respRaw []byte) error { return err } if strings.Contains(string(respRaw), "client_ord_id") { - if c.WebsocketConn.IsIDWaitingForResponse(incoming.Nonce) { - c.WebsocketConn.SetResponseIDAndData(incoming.Nonce, respRaw) + if c.Websocket.Match.IncomingWithData(incoming.Nonce, respRaw) { return nil } } + + format, err := c.GetPairFormat(asset.Spot, true) + if err != nil { + return err + } + switch incoming.Reply { case "hb": channels["hb"] <- respRaw @@ -167,9 +169,16 @@ func (c *COINUT) wsHandleData(respRaw []byte) error { if err != nil { return err } + + var endpointFailure []byte if login.APIKey != c.API.Credentials.Key { - c.API.AuthenticatedWebsocketSupport = false + endpointFailure = []byte("failed to authenticate") } + + if c.Websocket.Match.IncomingWithData(login.Nonce, endpointFailure) { + return nil + } + case "user_balance": var userBalance WsUserBalanceResponse err := json.Unmarshal(respRaw, &userBalance) @@ -231,7 +240,18 @@ func (c *COINUT) wsHandleData(respRaw []byte) error { if err != nil { return err } + pairs, err := c.GetEnabledPairs(asset.Spot) + if err != nil { + return err + } currencyPair := c.instrumentMap.LookupInstrument(wsTicker.InstID) + p, err := currency.NewPairFromFormattedPairs(currencyPair, + pairs, + format) + if err != nil { + return err + } + c.Websocket.DataHandler <- &ticker.Price{ ExchangeName: c.Name, Volume: wsTicker.Volume24, @@ -243,9 +263,7 @@ func (c *COINUT) wsHandleData(respRaw []byte) error { Last: wsTicker.Last, LastUpdated: time.Unix(0, wsTicker.Timestamp), AssetType: asset.Spot, - Pair: currency.NewPairFromFormattedPairs(currencyPair, - c.GetEnabledPairs(asset.Spot), - c.GetPairFormat(asset.Spot, true)), + Pair: p, } case "inst_order_book": var orderbookSnapshot WsOrderbookSnapshot @@ -257,14 +275,6 @@ func (c *COINUT) wsHandleData(respRaw []byte) error { if err != nil { return err } - currencyPair := c.instrumentMap.LookupInstrument(orderbookSnapshot.InstID) - c.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{ - Exchange: c.Name, - Asset: asset.Spot, - Pair: currency.NewPairFromFormattedPairs(currencyPair, - c.GetEnabledPairs(asset.Spot), - c.GetPairFormat(asset.Spot, true)), - } case "inst_order_book_update": var orderbookUpdate WsOrderbookUpdate err := json.Unmarshal(respRaw, &orderbookUpdate) @@ -275,14 +285,6 @@ func (c *COINUT) wsHandleData(respRaw []byte) error { if err != nil { return err } - currencyPair := c.instrumentMap.LookupInstrument(orderbookUpdate.InstID) - c.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{ - Exchange: c.Name, - Asset: asset.Spot, - Pair: currency.NewPairFromFormattedPairs(currencyPair, - c.GetEnabledPairs(asset.Spot), - c.GetPairFormat(asset.Spot, true)), - } case "inst_trade": var tradeSnap WsTradeSnapshot err := json.Unmarshal(respRaw, &tradeSnap) @@ -296,7 +298,19 @@ func (c *COINUT) wsHandleData(respRaw []byte) error { if err != nil { return err } + + pairs, err := c.GetEnabledPairs(asset.Spot) + if err != nil { + return err + } currencyPair := c.instrumentMap.LookupInstrument(tradeUpdate.InstID) + p, err := currency.NewPairFromFormattedPairs(currencyPair, + pairs, + format) + if err != nil { + return err + } + tSide, err := order.StringToOrderSide(tradeUpdate.Side) if err != nil { c.Websocket.DataHandler <- order.ClassificationError{ @@ -304,15 +318,14 @@ func (c *COINUT) wsHandleData(respRaw []byte) error { Err: err, } } - c.Websocket.DataHandler <- wshandler.TradeData{ - Timestamp: time.Unix(tradeUpdate.Timestamp, 0), - CurrencyPair: currency.NewPairFromFormattedPairs(currencyPair, - c.GetEnabledPairs(asset.Spot), - c.GetPairFormat(asset.Spot, true)), - AssetType: asset.Spot, - Exchange: c.Name, - Price: tradeUpdate.Price, - Side: tSide, + + c.Websocket.DataHandler <- stream.TradeData{ + Timestamp: time.Unix(tradeUpdate.Timestamp, 0), + CurrencyPair: p, + AssetType: asset.Spot, + Exchange: c.Name, + Price: tradeUpdate.Price, + Side: tSide, } case "order_filled", "order_rejected", "order_accepted": var orderContainer wsOrderContainer @@ -326,7 +339,7 @@ func (c *COINUT) wsHandleData(respRaw []byte) error { } c.Websocket.DataHandler <- o default: - c.Websocket.DataHandler <- wshandler.UnhandledMessageWarning{Message: c.Name + wshandler.UnhandledMessage + string(respRaw)} + c.Websocket.DataHandler <- stream.UnhandledMessageWarning{Message: c.Name + stream.UnhandledMessage + string(respRaw)} return nil } return nil @@ -444,7 +457,7 @@ func (c *COINUT) WsGetInstruments() (Instruments, error) { SecurityType: strings.ToUpper(asset.Spot.String()), Nonce: getNonce(), } - resp, err := c.WebsocketConn.SendMessageReturnResponse(request.Nonce, request) + resp, err := c.Websocket.Conn.SendMessageReturnResponse(request.Nonce, request) if err != nil { return list, err } @@ -482,11 +495,25 @@ func (c *COINUT) WsProcessOrderbookSnapshot(ob *WsOrderbookSnapshot) error { var newOrderBook orderbook.Base newOrderBook.Asks = asks newOrderBook.Bids = bids - newOrderBook.Pair = currency.NewPairFromFormattedPairs( + + pairs, err := c.GetEnabledPairs(asset.Spot) + if err != nil { + return err + } + + format, err := c.GetPairFormat(asset.Spot, true) + if err != nil { + return err + } + + newOrderBook.Pair, err = currency.NewPairFromFormattedPairs( c.instrumentMap.LookupInstrument(ob.InstID), - c.GetEnabledPairs(asset.Spot), - c.GetPairFormat(asset.Spot, true), - ) + pairs, + format) + if err != nil { + return err + } + newOrderBook.AssetType = asset.Spot newOrderBook.ExchangeName = c.Name @@ -495,12 +522,25 @@ func (c *COINUT) WsProcessOrderbookSnapshot(ob *WsOrderbookSnapshot) error { // WsProcessOrderbookUpdate process an orderbook update func (c *COINUT) WsProcessOrderbookUpdate(update *WsOrderbookUpdate) error { - p := currency.NewPairFromFormattedPairs( + pairs, err := c.GetEnabledPairs(asset.Spot) + if err != nil { + return err + } + + format, err := c.GetPairFormat(asset.Spot, true) + if err != nil { + return err + } + + p, err := currency.NewPairFromFormattedPairs( c.instrumentMap.LookupInstrument(update.InstID), - c.GetEnabledPairs(asset.Spot), - c.GetPairFormat(asset.Spot, true), - ) - bufferUpdate := &wsorderbook.WebsocketOrderbookUpdate{ + pairs, + format) + if err != nil { + return err + } + + bufferUpdate := &buffer.Update{ Pair: p, UpdateID: update.TransID, Asset: asset.Spot, @@ -514,67 +554,109 @@ func (c *COINUT) WsProcessOrderbookUpdate(update *WsOrderbookUpdate) error { } // GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() -func (c *COINUT) GenerateDefaultSubscriptions() { +func (c *COINUT) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, error) { var channels = []string{"inst_tick", "inst_order_book"} - var subscriptions []wshandler.WebsocketChannelSubscription - enabledCurrencies := c.GetEnabledPairs(asset.Spot) + var subscriptions []stream.ChannelSubscription + enabledCurrencies, err := c.GetEnabledPairs(asset.Spot) + if err != nil { + return nil, err + } for i := range channels { for j := range enabledCurrencies { - subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{ + subscriptions = append(subscriptions, stream.ChannelSubscription{ Channel: channels[i], Currency: enabledCurrencies[j], + Asset: asset.Spot, }) } } - c.Websocket.SubscribeToChannels(subscriptions) + return subscriptions, nil } // Subscribe sends a websocket message to receive data from the channel -func (c *COINUT) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error { - subscribe := wsRequest{ - Request: channelToSubscribe.Channel, - InstrumentID: c.instrumentMap.LookupID(c.FormatExchangeCurrency(channelToSubscribe.Currency, - asset.Spot).String()), - Subscribe: true, - Nonce: getNonce(), +func (c *COINUT) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error { + var errs common.Errors + for i := range channelsToSubscribe { + fpair, err := c.FormatExchangeCurrency(channelsToSubscribe[i].Currency, asset.Spot) + if err != nil { + errs = append(errs, err) + continue + } + + subscribe := wsRequest{ + Request: channelsToSubscribe[i].Channel, + InstrumentID: c.instrumentMap.LookupID(fpair.String()), + Subscribe: true, + Nonce: getNonce(), + } + err = c.Websocket.Conn.SendJSONMessage(subscribe) + if err != nil { + errs = append(errs, err) + continue + } + c.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe[i]) } - return c.WebsocketConn.SendJSONMessage(subscribe) + if errs != nil { + return errs + } + return nil } // Unsubscribe sends a websocket message to stop receiving data from the channel -func (c *COINUT) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error { - subscribe := wsRequest{ - Request: channelToSubscribe.Channel, - InstrumentID: c.instrumentMap.LookupID(c.FormatExchangeCurrency(channelToSubscribe.Currency, - asset.Spot).String()), - Subscribe: false, - Nonce: getNonce(), +func (c *COINUT) Unsubscribe(channelToUnsubscribe []stream.ChannelSubscription) error { + var errs common.Errors + for i := range channelToUnsubscribe { + fpair, err := c.FormatExchangeCurrency(channelToUnsubscribe[i].Currency, asset.Spot) + if err != nil { + errs = append(errs, err) + continue + } + + subscribe := wsRequest{ + Request: channelToUnsubscribe[i].Channel, + InstrumentID: c.instrumentMap.LookupID(fpair.String()), + Subscribe: false, + Nonce: getNonce(), + } + resp, err := c.Websocket.Conn.SendMessageReturnResponse(subscribe.Nonce, + subscribe) + if err != nil { + errs = append(errs, err) + continue + } + var response map[string]interface{} + err = json.Unmarshal(resp, &response) + if err != nil { + errs = append(errs, err) + continue + } + if response["status"].([]interface{})[0] != "OK" { + errs = append(errs, fmt.Errorf("%v unsubscribe failed for channel %v", + c.Name, + channelToUnsubscribe[i].Channel)) + continue + } + c.Websocket.RemoveSuccessfulUnsubscriptions(channelToUnsubscribe[i]) } - resp, err := c.WebsocketConn.SendMessageReturnResponse(subscribe.Nonce, subscribe) - if err != nil { - return err - } - var response map[string]interface{} - err = json.Unmarshal(resp, &response) - if err != nil { - return err - } - if response["status"].([]interface{})[0] != "OK" { - return fmt.Errorf("%v unsubscribe failed for channel %v", c.Name, channelToSubscribe.Channel) + if errs != nil { + return errs } return nil } func (c *COINUT) wsAuthenticate() error { if !c.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) { - return fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", c.Name) + return fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", + c.Name) } timestamp := time.Now().Unix() nonce := getNonce() payload := c.API.Credentials.ClientID + "|" + strconv.FormatInt(timestamp, 10) + "|" + strconv.FormatInt(nonce, 10) - hmac := crypto.GetHMAC(crypto.HashSHA256, []byte(payload), []byte(c.API.Credentials.Key)) + hmac := crypto.GetHMAC(crypto.HashSHA256, + []byte(payload), + []byte(c.API.Credentials.Key)) loginRequest := struct { Request string `json:"request"` Username string `json:"username"` @@ -589,18 +671,14 @@ func (c *COINUT) wsAuthenticate() error { Timestamp: timestamp, } - resp, err := c.WebsocketConn.SendMessageReturnResponse(loginRequest.Nonce, loginRequest) + resp, err := c.Websocket.Conn.SendMessageReturnResponse(loginRequest.Nonce, + loginRequest) if err != nil { return err } - var response map[string]interface{} - err = json.Unmarshal(resp, &response) - if err != nil { - return err - } - if response["status"].([]interface{})[0] != "OK" { + if resp != nil { c.Websocket.SetCanUseAuthenticatedEndpoints(false) - return fmt.Errorf("%v failed to authenticate", c.Name) + return fmt.Errorf("%v %s", c.Name, resp) } c.Websocket.SetCanUseAuthenticatedEndpoints(true) return nil @@ -614,7 +692,8 @@ func (c *COINUT) wsGetAccountBalance() (*UserBalance, error) { Request: "user_balance", Nonce: getNonce(), } - resp, err := c.WebsocketConn.SendMessageReturnResponse(accBalance.Nonce, accBalance) + resp, err := c.Websocket.Conn.SendMessageReturnResponse(accBalance.Nonce, + accBalance) if err != nil { return nil, err } @@ -633,11 +712,16 @@ func (c *COINUT) wsSubmitOrder(o *WsSubmitOrderParameters) (*order.Detail, error if !c.Websocket.CanUseAuthenticatedEndpoints() { return nil, fmt.Errorf("%v not authorised to submit order", c.Name) } - curr := c.FormatExchangeCurrency(o.Currency, asset.Spot).String() + + curr, err := c.FormatExchangeCurrency(o.Currency, asset.Spot) + if err != nil { + return nil, err + } + var orderSubmissionRequest WsSubmitOrderRequest orderSubmissionRequest.Request = "new_order" orderSubmissionRequest.Nonce = getNonce() - orderSubmissionRequest.InstrumentID = c.instrumentMap.LookupID(curr) + orderSubmissionRequest.InstrumentID = c.instrumentMap.LookupID(curr.String()) orderSubmissionRequest.Quantity = o.Amount orderSubmissionRequest.Price = o.Price orderSubmissionRequest.Side = string(o.Side) @@ -645,7 +729,8 @@ func (c *COINUT) wsSubmitOrder(o *WsSubmitOrderParameters) (*order.Detail, error if o.OrderID > 0 { orderSubmissionRequest.OrderID = o.OrderID } - resp, err := c.WebsocketConn.SendMessageReturnResponse(orderSubmissionRequest.Nonce, orderSubmissionRequest) + resp, err := c.Websocket.Conn.SendMessageReturnResponse(orderSubmissionRequest.Nonce, + orderSubmissionRequest) if err != nil { return nil, err } @@ -666,25 +751,31 @@ func (c *COINUT) wsSubmitOrders(orders []WsSubmitOrderParameters) ([]order.Detai var errs []error var ordersResponse []order.Detail if !c.Websocket.CanUseAuthenticatedEndpoints() { - errs = append(errs, fmt.Errorf("%v not authorised to submit orders", c.Name)) + errs = append(errs, fmt.Errorf("%v not authorised to submit orders", + c.Name)) return nil, errs } orderRequest := WsSubmitOrdersRequest{} for i := range orders { - curr := c.FormatExchangeCurrency(orders[i].Currency, asset.Spot).String() + curr, err := c.FormatExchangeCurrency(orders[i].Currency, asset.Spot) + if err != nil { + return nil, []error{err} + } + orderRequest.Orders = append(orderRequest.Orders, WsSubmitOrdersRequestData{ Quantity: orders[i].Amount, Price: orders[i].Price, Side: string(orders[i].Side), - InstrumentID: c.instrumentMap.LookupID(curr), + InstrumentID: c.instrumentMap.LookupID(curr.String()), ClientOrderID: i + 1, }) } orderRequest.Nonce = getNonce() orderRequest.Request = "new_orders" - resp, err := c.WebsocketConn.SendMessageReturnResponse(orderRequest.Nonce, orderRequest) + resp, err := c.Websocket.Conn.SendMessageReturnResponse(orderRequest.Nonce, + orderRequest) if err != nil { errs = append(errs, err) return nil, errs @@ -710,14 +801,16 @@ func (c *COINUT) wsSubmitOrders(orders []WsSubmitOrderParameters) ([]order.Detai func (c *COINUT) wsGetOpenOrders(curr string) (*WsUserOpenOrdersResponse, error) { var response *WsUserOpenOrdersResponse if !c.Websocket.CanUseAuthenticatedEndpoints() { - return response, fmt.Errorf("%v not authorised to get open orders", c.Name) + return response, fmt.Errorf("%v not authorised to get open orders", + c.Name) } var openOrdersRequest WsGetOpenOrdersRequest openOrdersRequest.Request = "user_open_orders" openOrdersRequest.Nonce = getNonce() openOrdersRequest.InstrumentID = c.instrumentMap.LookupID(curr) - resp, err := c.WebsocketConn.SendMessageReturnResponse(openOrdersRequest.Nonce, openOrdersRequest) + resp, err := c.Websocket.Conn.SendMessageReturnResponse(openOrdersRequest.Nonce, + openOrdersRequest) if err != nil { return response, err } @@ -738,14 +831,20 @@ func (c *COINUT) wsCancelOrder(cancellation *WsCancelOrderParameters) (*CancelOr if !c.Websocket.CanUseAuthenticatedEndpoints() { return response, fmt.Errorf("%v not authorised to cancel order", c.Name) } - curr := c.FormatExchangeCurrency(cancellation.Currency, asset.Spot).String() + + curr, err := c.FormatExchangeCurrency(cancellation.Currency, asset.Spot) + if err != nil { + return nil, err + } + var cancellationRequest WsCancelOrderRequest cancellationRequest.Request = "cancel_order" - cancellationRequest.InstrumentID = c.instrumentMap.LookupID(curr) + cancellationRequest.InstrumentID = c.instrumentMap.LookupID(curr.String()) cancellationRequest.OrderID = cancellation.OrderID cancellationRequest.Nonce = getNonce() - resp, err := c.WebsocketConn.SendMessageReturnResponse(cancellationRequest.Nonce, cancellationRequest) + resp, err := c.Websocket.Conn.SendMessageReturnResponse(cancellationRequest.Nonce, + cancellationRequest) if err != nil { return response, err } @@ -771,16 +870,23 @@ func (c *COINUT) wsCancelOrders(cancellations []WsCancelOrderParameters) (*Cance } var cancelOrderRequest WsCancelOrdersRequest for i := range cancellations { - curr := c.FormatExchangeCurrency(cancellations[i].Currency, asset.Spot).String() - cancelOrderRequest.Entries = append(cancelOrderRequest.Entries, WsCancelOrdersRequestEntry{ - InstID: c.instrumentMap.LookupID(curr), - OrderID: cancellations[i].OrderID, - }) + var curr currency.Pair + curr, err = c.FormatExchangeCurrency(cancellations[i].Currency, + asset.Spot) + if err != nil { + return nil, err + } + cancelOrderRequest.Entries = append(cancelOrderRequest.Entries, + WsCancelOrdersRequestEntry{ + InstID: c.instrumentMap.LookupID(curr.String()), + OrderID: cancellations[i].OrderID, + }) } cancelOrderRequest.Request = "cancel_orders" cancelOrderRequest.Nonce = getNonce() - resp, err := c.WebsocketConn.SendMessageReturnResponse(cancelOrderRequest.Nonce, cancelOrderRequest) + resp, err := c.Websocket.Conn.SendMessageReturnResponse(cancelOrderRequest.Nonce, + cancelOrderRequest) if err != nil { return response, err } @@ -794,17 +900,24 @@ func (c *COINUT) wsCancelOrders(cancellations []WsCancelOrderParameters) (*Cance func (c *COINUT) wsGetTradeHistory(p currency.Pair, start, limit int64) (*WsTradeHistoryResponse, error) { var response *WsTradeHistoryResponse if !c.Websocket.CanUseAuthenticatedEndpoints() { - return response, fmt.Errorf("%v not authorised to get trade history", c.Name) + return response, fmt.Errorf("%v not authorised to get trade history", + c.Name) } - curr := c.FormatExchangeCurrency(p, asset.Spot).String() + + curr, err := c.FormatExchangeCurrency(p, asset.Spot) + if err != nil { + return nil, err + } + var request WsTradeHistoryRequest request.Request = "trade_history" - request.InstID = c.instrumentMap.LookupID(curr) + request.InstID = c.instrumentMap.LookupID(curr.String()) request.Nonce = getNonce() request.Start = start request.Limit = limit - resp, err := c.WebsocketConn.SendMessageReturnResponse(request.Nonce, request) + resp, err := c.Websocket.Conn.SendMessageReturnResponse(request.Nonce, + request) if err != nil { return response, err } diff --git a/exchanges/coinut/coinut_wrapper.go b/exchanges/coinut/coinut_wrapper.go index fed59a71..51b3b59a 100644 --- a/exchanges/coinut/coinut_wrapper.go +++ b/exchanges/coinut/coinut_wrapper.go @@ -20,8 +20,8 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" "github.com/thrasher-corp/gocryptotrader/log" "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) @@ -57,18 +57,11 @@ func (c *COINUT) SetDefaults() { c.API.CredentialsValidator.RequiresKey = true c.API.CredentialsValidator.RequiresClientID = true - c.CurrencyPairs = currency.PairsManager{ - AssetTypes: asset.Items{ - asset.Spot, - }, - UseGlobalFormat: true, - RequestFormat: ¤cy.PairFormat{ - Uppercase: true, - }, - ConfigFormat: ¤cy.PairFormat{ - Uppercase: true, - Delimiter: "-", - }, + requestFmt := ¤cy.PairFormat{Uppercase: true} + configFmt := ¤cy.PairFormat{Uppercase: true, Delimiter: currency.DashDelimiter} + err := c.SetGlobalPairsManager(requestFmt, configFmt, asset.Spot) + if err != nil { + log.Errorln(log.ExchangeSys, err) } c.Features = exchange.Features{ @@ -122,7 +115,7 @@ func (c *COINUT) SetDefaults() { c.API.Endpoints.URLDefault = coinutAPIURL c.API.Endpoints.URL = c.API.Endpoints.URLDefault c.API.Endpoints.WebsocketURL = coinutWebsocketURL - c.Websocket = wshandler.New() + c.Websocket = stream.New() c.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit c.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout c.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit @@ -141,41 +134,33 @@ func (c *COINUT) Setup(exch *config.ExchangeConfig) error { return err } - err = c.Websocket.Setup( - &wshandler.WebsocketSetup{ - Enabled: exch.Features.Enabled.Websocket, - Verbose: exch.Verbose, - AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, - WebsocketTimeout: exch.WebsocketTrafficTimeout, - DefaultURL: coinutWebsocketURL, - ExchangeName: exch.Name, - RunningURL: exch.API.Endpoints.WebsocketURL, - Connector: c.WsConnect, - Subscriber: c.Subscribe, - UnSubscriber: c.Unsubscribe, - Features: &c.Features.Supports.WebsocketCapabilities, - }) + err = c.Websocket.Setup(&stream.WebsocketSetup{ + Enabled: exch.Features.Enabled.Websocket, + Verbose: exch.Verbose, + AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, + WebsocketTimeout: exch.WebsocketTrafficTimeout, + DefaultURL: coinutWebsocketURL, + ExchangeName: exch.Name, + RunningURL: exch.API.Endpoints.WebsocketURL, + Connector: c.WsConnect, + Subscriber: c.Subscribe, + UnSubscriber: c.Unsubscribe, + GenerateSubscriptions: c.GenerateDefaultSubscriptions, + Features: &c.Features.Supports.WebsocketCapabilities, + OrderbookBufferLimit: exch.WebsocketOrderbookBufferLimit, + BufferEnabled: true, + SortBuffer: true, + SortBufferByUpdateIDs: true, + }) if err != nil { return err } - c.WebsocketConn = &wshandler.WebsocketConnection{ - ExchangeName: c.Name, - URL: c.Websocket.GetWebsocketURL(), - ProxyURL: c.Websocket.GetProxyAddress(), - Verbose: c.Verbose, + return c.Websocket.SetupNewConnection(stream.ConnectionSetup{ ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout, ResponseMaxLimit: exch.WebsocketResponseMaxLimit, - } - - c.Websocket.Orderbook.Setup( - exch.WebsocketOrderbookBufferLimit, - true, - true, - true, - false, - exch.Name) - return nil + RateLimit: wsRateLimitInMilliseconds, + }) } // Start starts the COINUT go routine @@ -195,21 +180,55 @@ func (c *COINUT) Run() { } forceUpdate := false - delim := c.GetPairFormat(asset.Spot, false).Delimiter - if !common.StringDataContains(c.CurrencyPairs.GetPairs(asset.Spot, - true).Strings(), delim) || - !common.StringDataContains(c.CurrencyPairs.GetPairs(asset.Spot, - false).Strings(), delim) { - enabledPairs := currency.NewPairsFromStrings( - []string{currency.LTC.String() + delim + currency.USDT.String()}, - ) - log.Warn(log.ExchangeSys, - "Enabled pairs for Coinut reset due to config upgrade, please enable the ones you would like to use again") - forceUpdate = true + format, err := c.GetPairFormat(asset.Spot, false) + if err != nil { + log.Errorf(log.ExchangeSys, + "%s failed to update currencies. Err: %s\n", + c.Name, + err) + return + } - err := c.UpdatePairs(enabledPairs, asset.Spot, true, true) + enabled, err := c.CurrencyPairs.GetPairs(asset.Spot, true) + if err != nil { + log.Errorf(log.ExchangeSys, + "%s failed to update currencies. Err: %s\n", + c.Name, + err) + return + } + avail, err := c.CurrencyPairs.GetPairs(asset.Spot, false) + if err != nil { + log.Errorf(log.ExchangeSys, + "%s failed to update currencies. Err: %s\n", + c.Name, + err) + return + } + + if !common.StringDataContains(enabled.Strings(), format.Delimiter) || + !common.StringDataContains(avail.Strings(), format.Delimiter) { + var p currency.Pairs + p, err = currency.NewPairsFromStrings([]string{currency.LTC.String() + + format.Delimiter + + currency.USDT.String()}) if err != nil { - log.Errorf(log.ExchangeSys, "%s failed to update currencies. Err: %s\n", c.Name, err) + log.Errorf(log.ExchangeSys, + "%s failed to update currencies. Err: %s\n", + c.Name, + err) + } else { + log.Warn(log.ExchangeSys, + "Enabled pairs for Coinut reset due to config upgrade, please enable the ones you would like to use again") + forceUpdate = true + + err = c.UpdatePairs(p, asset.Spot, true, true) + if err != nil { + log.Errorf(log.ExchangeSys, + "%s failed to update currencies. Err: %s\n", + c.Name, + err) + } } } @@ -217,7 +236,7 @@ func (c *COINUT) Run() { return } - err := c.UpdateTradablePairs(forceUpdate) + err = c.UpdateTradablePairs(forceUpdate) if err != nil { log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", c.Name, err) } @@ -239,11 +258,17 @@ func (c *COINUT) FetchTradablePairs(asset asset.Item) ([]string, error) { return nil, err } } + + format, err := c.GetPairFormat(asset, false) + if err != nil { + return nil, err + } + instruments = resp.Instruments var pairs []string for i := range instruments { c.instrumentMap.Seed(instruments[i][0].Base+instruments[i][0].Quote, instruments[i][0].InstrumentID) - p := instruments[i][0].Base + c.GetPairFormat(asset, false).Delimiter + instruments[i][0].Quote + p := instruments[i][0].Base + format.Delimiter + instruments[i][0].Quote pairs = append(pairs, p) } @@ -258,8 +283,11 @@ func (c *COINUT) UpdateTradablePairs(forceUpdate bool) error { return err } - return c.UpdatePairs(currency.NewPairsFromStrings(pairs), - asset.Spot, false, forceUpdate) + p, err := currency.NewPairsFromStrings(pairs) + if err != nil { + return err + } + return c.UpdatePairs(p, asset.Spot, false, forceUpdate) } // UpdateAccountInfo retrieves balances for all enabled currencies for the @@ -365,35 +393,39 @@ func (c *COINUT) FetchAccountInfo() (account.Holdings, error) { // UpdateTicker updates and returns the ticker for a currency pair func (c *COINUT) UpdateTicker(p currency.Pair, assetType asset.Item) (*ticker.Price, error) { - tickerPrice := new(ticker.Price) err := c.loadInstrumentsIfNotLoaded() if err != nil { - return tickerPrice, err + return nil, err } - instID := c.instrumentMap.LookupID(c.FormatExchangeCurrency(p, - assetType).String()) + fpair, err := c.FormatExchangeCurrency(p, assetType) + if err != nil { + return nil, err + } + + instID := c.instrumentMap.LookupID(fpair.String()) if instID == 0 { - return tickerPrice, errors.New("unable to lookup instrument ID") + return nil, errors.New("unable to lookup instrument ID") } var tick Ticker tick, err = c.GetInstrumentTicker(instID) if err != nil { - return tickerPrice, err + return nil, err } - tickerPrice = &ticker.Price{ - Last: tick.Last, - High: tick.High24, - Low: tick.Low24, - Bid: tick.HighestBuy, - Ask: tick.LowestSell, - Volume: tick.Volume24, - Pair: p, - LastUpdated: time.Unix(0, tick.Timestamp), - } - err = ticker.ProcessTicker(c.Name, tickerPrice, assetType) + + err = ticker.ProcessTicker(&ticker.Price{ + Last: tick.Last, + High: tick.High24, + Low: tick.Low24, + Bid: tick.HighestBuy, + Ask: tick.LowestSell, + Volume: tick.Volume24, + Pair: p, + LastUpdated: time.Unix(0, tick.Timestamp), + ExchangeName: c.Name, + AssetType: assetType}) if err != nil { - return tickerPrice, err + return nil, err } return ticker.GetTicker(c.Name, p, assetType) @@ -425,8 +457,12 @@ func (c *COINUT) UpdateOrderbook(p currency.Pair, assetType asset.Item) (*orderb return orderBook, err } - instID := c.instrumentMap.LookupID(c.FormatExchangeCurrency(p, - assetType).String()) + fpair, err := c.FormatExchangeCurrency(p, assetType) + if err != nil { + return nil, err + } + + instID := c.instrumentMap.LookupID(fpair.String()) if instID == 0 { return orderBook, errLookupInstrumentID } @@ -499,8 +535,12 @@ func (c *COINUT) SubmitOrder(o *order.Submit) (order.SubmitResponse, error) { return submitOrderResponse, err } - currencyID := c.instrumentMap.LookupID(c.FormatExchangeCurrency(o.Pair, - asset.Spot).String()) + fpair, err := c.FormatExchangeCurrency(o.Pair, asset.Spot) + if err != nil { + return submitOrderResponse, err + } + + currencyID := c.instrumentMap.LookupID(fpair.String()) if currencyID == 0 { return submitOrderResponse, errLookupInstrumentID } @@ -555,10 +595,13 @@ func (c *COINUT) CancelOrder(o *order.Cancel) error { return err } - currencyID := c.instrumentMap.LookupID(c.FormatExchangeCurrency( - o.Pair, - asset.Spot).String(), - ) + fpair, err := c.FormatExchangeCurrency(o.Pair, asset.Spot) + if err != nil { + return err + } + + currencyID := c.instrumentMap.LookupID(fpair.String()) + if c.Websocket.CanUseAuthenticatedWebsocketForWrapper() { var resp *CancelOrdersResponse resp, err = c.wsCancelOrder(&WsCancelOrderParameters{ @@ -599,7 +642,12 @@ func (c *COINUT) CancelAllOrders(details *order.Cancel) (order.CancelAllResponse } var ordersToCancel []WsCancelOrderParameters for i := range openOrders.Orders { - if openOrders.Orders[i].InstrumentID == c.instrumentMap.LookupID(c.FormatExchangeCurrency(details.Pair, asset.Spot).String()) { + var fpair currency.Pair + fpair, err = c.FormatExchangeCurrency(details.Pair, asset.Spot) + if err != nil { + return cancelAllOrdersResponse, err + } + if openOrders.Orders[i].InstrumentID == c.instrumentMap.LookupID(fpair.String()) { ordersToCancel = append(ordersToCancel, WsCancelOrderParameters{ Currency: details.Pair, OrderID: openOrders.Orders[i].OrderID, @@ -619,7 +667,11 @@ func (c *COINUT) CancelAllOrders(details *order.Cancel) (order.CancelAllResponse var allTheOrders []OrderResponse ids := c.instrumentMap.GetInstrumentIDs() for x := range ids { - if ids[x] == c.instrumentMap.LookupID(c.FormatExchangeCurrency(details.Pair, asset.Spot).String()) { + fpair, err := c.FormatExchangeCurrency(details.Pair, asset.Spot) + if err != nil { + return cancelAllOrdersResponse, err + } + if ids[x] == c.instrumentMap.LookupID(fpair.String()) { openOrders, err := c.GetOpenOrders(ids[x]) if err != nil { return cancelAllOrdersResponse, err @@ -682,11 +734,6 @@ func (c *COINUT) WithdrawFiatFundsToInternationalBank(withdrawRequest *withdraw. return nil, common.ErrFunctionNotSupported } -// GetWebsocket returns a pointer to the exchange websocket -func (c *COINUT) GetWebsocket() (*wshandler.Websocket, error) { - return c.Websocket, nil -} - // GetFeeByType returns an estimate of fee based on type of transaction func (c *COINUT) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { if !c.AllowAuthenticatedRequest() && // Todo check connection status @@ -706,7 +753,11 @@ func (c *COINUT) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, e var currenciesToCheck []string if len(req.Pairs) == 0 { for i := range req.Pairs { - currenciesToCheck = append(currenciesToCheck, c.FormatExchangeCurrency(req.Pairs[i], asset.Spot).String()) + fpair, err := c.FormatExchangeCurrency(req.Pairs[i], asset.Spot) + if err != nil { + return nil, err + } + currenciesToCheck = append(currenciesToCheck, fpair.String()) } } else { for k := range c.instrumentMap.Instruments { @@ -720,10 +771,20 @@ func (c *COINUT) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, e return nil, err } for i := range openOrders.Orders { + p, err := currency.NewPairFromString(currenciesToCheck[x]) + if err != nil { + return nil, err + } + + fpair, err := c.FormatExchangeCurrency(p, asset.Spot) + if err != nil { + return nil, err + } + orders = append(orders, order.Detail{ Exchange: c.Name, ID: strconv.FormatInt(openOrders.Orders[i].OrderID, 10), - Pair: c.FormatExchangeCurrency(currency.NewPairFromString(currenciesToCheck[x]), asset.Spot), + Pair: fpair, Side: order.Side(openOrders.Orders[i].Side), Date: time.Unix(0, openOrders.Orders[i].Timestamp), Status: order.Active, @@ -737,15 +798,28 @@ func (c *COINUT) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, e } else { var instrumentsToUse []int64 for x := range req.Pairs { - curr := c.FormatExchangeCurrency(req.Pairs[x], - asset.Spot).String() + curr, err := c.FormatExchangeCurrency(req.Pairs[x], + asset.Spot) + if err != nil { + return nil, err + } instrumentsToUse = append(instrumentsToUse, - c.instrumentMap.LookupID(curr)) + c.instrumentMap.LookupID(curr.String())) } if len(instrumentsToUse) == 0 { instrumentsToUse = c.instrumentMap.GetInstrumentIDs() } + pairs, err := c.GetEnabledPairs(asset.Spot) + if err != nil { + return nil, err + } + + format, err := c.GetPairFormat(asset.Spot, true) + if err != nil { + return nil, err + } + for x := range instrumentsToUse { openOrders, err := c.GetOpenOrders(instrumentsToUse[x]) if err != nil { @@ -753,9 +827,13 @@ func (c *COINUT) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, e } for y := range openOrders.Orders { curr := c.instrumentMap.LookupInstrument(instrumentsToUse[x]) - p := currency.NewPairFromFormattedPairs(curr, - c.GetEnabledPairs(asset.Spot), - c.GetPairFormat(asset.Spot, true)) + p, err := currency.NewPairFromFormattedPairs(curr, + pairs, + format) + if err != nil { + return nil, err + } + orderSide := order.Side(strings.ToUpper(openOrders.Orders[y].Side)) orderDate := time.Unix(openOrders.Orders[y].Timestamp, 0) orders = append(orders, order.Detail{ @@ -793,10 +871,15 @@ func (c *COINUT) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, e } for x := range trades.Trades { curr := c.instrumentMap.LookupInstrument(trades.Trades[x].InstrumentID) + p, err := currency.NewPairFromString(curr) + if err != nil { + return nil, err + } + allOrders = append(allOrders, order.Detail{ Exchange: c.Name, ID: strconv.FormatInt(trades.Trades[x].OrderID, 10), - Pair: currency.NewPairFromString(curr), + Pair: p, Side: order.Side(trades.Trades[x].Side), Date: time.Unix(0, trades.Trades[x].Timestamp), Status: order.Filled, @@ -814,9 +897,13 @@ func (c *COINUT) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, e } else { var instrumentsToUse []int64 for x := range req.Pairs { - curr := c.FormatExchangeCurrency(req.Pairs[x], - asset.Spot).String() - instrumentID := c.instrumentMap.LookupID(curr) + curr, err := c.FormatExchangeCurrency(req.Pairs[x], + asset.Spot) + if err != nil { + return nil, err + } + + instrumentID := c.instrumentMap.LookupID(curr.String()) if instrumentID > 0 { instrumentsToUse = append(instrumentsToUse, instrumentID) } @@ -824,6 +911,17 @@ func (c *COINUT) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, e if len(instrumentsToUse) == 0 { instrumentsToUse = c.instrumentMap.GetInstrumentIDs() } + + pairs, err := c.GetEnabledPairs(asset.Spot) + if err != nil { + return nil, err + } + + format, err := c.GetPairFormat(asset.Spot, true) + if err != nil { + return nil, err + } + for x := range instrumentsToUse { orders, err := c.GetTradeHistory(instrumentsToUse[x], -1, -1) if err != nil { @@ -831,9 +929,13 @@ func (c *COINUT) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, e } for y := range orders.Trades { curr := c.instrumentMap.LookupInstrument(instrumentsToUse[x]) - p := currency.NewPairFromFormattedPairs(curr, - c.GetEnabledPairs(asset.Spot), - c.GetPairFormat(asset.Spot, true)) + p, err := currency.NewPairFromFormattedPairs(curr, + pairs, + format) + if err != nil { + return nil, err + } + orderSide := order.Side(strings.ToUpper(orders.Trades[y].Order.Side)) orderDate := time.Unix(orders.Trades[y].Order.Timestamp, 0) allOrders = append(allOrders, order.Detail{ @@ -854,25 +956,6 @@ func (c *COINUT) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, e return allOrders, nil } -// SubscribeToWebsocketChannels appends to ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle subscribing -func (c *COINUT) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - c.Websocket.SubscribeToChannels(channels) - return nil -} - -// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle unsubscribing -func (c *COINUT) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - c.Websocket.RemoveSubscribedChannels(channels) - return nil -} - -// GetSubscriptions returns a copied list of subscriptions -func (c *COINUT) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) { - return c.Websocket.GetSubscriptions(), nil -} - // AuthenticateWebsocket sends an authentication message to the websocket func (c *COINUT) AuthenticateWebsocket() error { return c.wsAuthenticate() diff --git a/exchanges/exchange.go b/exchanges/exchange.go index e22de1b2..b1f1cef7 100644 --- a/exchanges/exchange.go +++ b/exchanges/exchange.go @@ -10,6 +10,7 @@ import ( "strings" "time" + "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/crypto" "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" @@ -17,6 +18,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/kline" "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" "github.com/thrasher-corp/gocryptotrader/log" "github.com/thrasher-corp/gocryptotrader/portfolio/banking" ) @@ -197,19 +199,9 @@ func (e *Base) GetLastPairsUpdateTime() int64 { return e.CurrencyPairs.LastUpdated } -// SetAssetTypes checks the exchange asset types (whether it supports SPOT, -// Binary or Futures) and sets it to a default setting if it doesn't exist -func (e *Base) SetAssetTypes() { - if e.Config.CurrencyPairs.AssetTypes.JoinToString(",") == "" { - e.Config.CurrencyPairs.AssetTypes = e.CurrencyPairs.AssetTypes - } else if e.Config.CurrencyPairs.AssetTypes.JoinToString(",") != e.CurrencyPairs.AssetTypes.JoinToString(",") { - e.Config.CurrencyPairs.AssetTypes = e.CurrencyPairs.AssetTypes - } -} - // GetAssetTypes returns the available asset types for an individual exchange func (e *Base) GetAssetTypes() asset.Items { - return e.CurrencyPairs.AssetTypes + return e.CurrencyPairs.GetAssetTypes() } // GetPairAssetType returns the associated asset type for the currency pair @@ -218,7 +210,11 @@ func (e *Base) GetAssetTypes() asset.Items { func (e *Base) GetPairAssetType(c currency.Pair) (asset.Item, error) { assetTypes := e.GetAssetTypes() for i := range assetTypes { - if e.GetAvailablePairs(assetTypes[i]).Contains(c, true) { + avail, err := e.GetAvailablePairs(assetTypes[i]) + if err != nil { + return "", err + } + if avail.Contains(c, true) { return assetTypes[i], nil } } @@ -262,35 +258,53 @@ func (e *Base) SetCurrencyPairFormat() { assetTypes := e.GetAssetTypes() for x := range assetTypes { - if e.Config.CurrencyPairs.Get(assetTypes[x]) == nil { - r := e.CurrencyPairs.Get(assetTypes[x]) - if r == nil { + if _, err := e.Config.CurrencyPairs.Get(assetTypes[x]); err != nil { + ps, err := e.CurrencyPairs.Get(assetTypes[x]) + if err != nil { continue } - e.Config.CurrencyPairs.Store(assetTypes[x], *e.CurrencyPairs.Get(assetTypes[x])) + e.Config.CurrencyPairs.Store(assetTypes[x], *ps) } } } // SetConfigPairs sets the exchanges currency pairs to the pairs set in the config -func (e *Base) SetConfigPairs() { - assetTypes := e.GetAssetTypes() +func (e *Base) SetConfigPairs() error { + assetTypes := e.Config.CurrencyPairs.GetAssetTypes() + exchangeAssets := e.CurrencyPairs.GetAssetTypes() for x := range assetTypes { - cfgPS := e.Config.CurrencyPairs.Get(assetTypes[x]) - if cfgPS == nil { - continue + if !exchangeAssets.Contains(assetTypes[x]) { + log.Warnf(log.ExchangeSys, + "%s exchange asset type %s unsupported, please manually remove from configuration", + e.Name, + assetTypes[x]) } + cfgPS, err := e.Config.CurrencyPairs.Get(assetTypes[x]) + if err != nil { + return err + } + + var enabledAsset bool + if e.Config.CurrencyPairs.IsAssetEnabled(assetTypes[x]) == nil { + enabledAsset = true + } + e.CurrencyPairs.SetAssetEnabled(assetTypes[x], enabledAsset) + if e.Config.CurrencyPairs.UseGlobalFormat { e.CurrencyPairs.StorePairs(assetTypes[x], cfgPS.Available, false) e.CurrencyPairs.StorePairs(assetTypes[x], cfgPS.Enabled, true) continue } - exchPS := e.CurrencyPairs.Get(assetTypes[x]) + exchPS, err := e.CurrencyPairs.Get(assetTypes[x]) + if err != nil { + return err + } cfgPS.ConfigFormat = exchPS.ConfigFormat cfgPS.RequestFormat = exchPS.RequestFormat e.CurrencyPairs.StorePairs(assetTypes[x], cfgPS.Available, false) e.CurrencyPairs.StorePairs(assetTypes[x], cfgPS.Enabled, true) } + return nil } // GetAuthenticatedAPISupport returns whether the exchange supports @@ -322,26 +336,63 @@ func (e *Base) GetSupportedFeatures() FeaturesSupported { // GetPairFormat returns the pair format based on the exchange and // asset type -func (e *Base) GetPairFormat(assetType asset.Item, requestFormat bool) currency.PairFormat { +func (e *Base) GetPairFormat(assetType asset.Item, requestFormat bool) (currency.PairFormat, error) { if e.CurrencyPairs.UseGlobalFormat { if requestFormat { - return *e.CurrencyPairs.RequestFormat + if e.CurrencyPairs.RequestFormat == nil { + return currency.PairFormat{}, + errors.New("global request format is nil") + } + return *e.CurrencyPairs.RequestFormat, nil } - return *e.CurrencyPairs.ConfigFormat + + if e.CurrencyPairs.ConfigFormat == nil { + return currency.PairFormat{}, + errors.New("global config format is nil") + } + return *e.CurrencyPairs.ConfigFormat, nil + } + + ps, err := e.CurrencyPairs.Get(assetType) + if err != nil { + return currency.PairFormat{}, err } if requestFormat { - return *e.CurrencyPairs.Get(assetType).RequestFormat + if ps.RequestFormat == nil { + return currency.PairFormat{}, + errors.New("asset type request format is nil") + } + return *ps.RequestFormat, nil } - return *e.CurrencyPairs.Get(assetType).ConfigFormat + + if ps.ConfigFormat == nil { + return currency.PairFormat{}, + errors.New("asset type config format is nil") + } + return *ps.ConfigFormat, nil } // GetEnabledPairs is a method that returns the enabled currency pairs of -// the exchange by asset type -func (e *Base) GetEnabledPairs(assetType asset.Item) currency.Pairs { - format := e.GetPairFormat(assetType, false) - pairs := e.CurrencyPairs.GetPairs(assetType, true) - return pairs.Format(format.Delimiter, format.Index, format.Uppercase) +// the exchange by asset type, if the asset type is disabled this will return no +// enabled pairs +func (e *Base) GetEnabledPairs(a asset.Item) (currency.Pairs, error) { + err := e.CurrencyPairs.IsAssetEnabled(a) + if err != nil { + return nil, nil + } + format, err := e.GetPairFormat(a, false) + if err != nil { + return nil, err + } + enabledpairs, err := e.CurrencyPairs.GetPairs(a, true) + if err != nil { + return nil, err + } + return enabledpairs.Format(format.Delimiter, + format.Index, + format.Uppercase), + nil } // GetRequestFormattedPairAndAssetType is a method that returns the enabled currency pair of @@ -350,8 +401,16 @@ func (e *Base) GetRequestFormattedPairAndAssetType(p string) (currency.Pair, ass assetTypes := e.GetAssetTypes() var response currency.Pair for i := range assetTypes { - format := e.GetPairFormat(assetTypes[i], true) - pairs := e.CurrencyPairs.GetPairs(assetTypes[i], true) + format, err := e.GetPairFormat(assetTypes[i], true) + if err != nil { + return response, assetTypes[i], err + } + + pairs, err := e.CurrencyPairs.GetPairs(assetTypes[i], true) + if err != nil { + return response, assetTypes[i], err + } + for j := range pairs { formattedPair := pairs[j].Format(format.Delimiter, format.Uppercase) if strings.EqualFold(formattedPair.String(), p) { @@ -364,29 +423,57 @@ func (e *Base) GetRequestFormattedPairAndAssetType(p string) (currency.Pair, ass // GetAvailablePairs is a method that returns the available currency pairs // of the exchange by asset type -func (e *Base) GetAvailablePairs(assetType asset.Item) currency.Pairs { - format := e.GetPairFormat(assetType, false) - pairs := e.CurrencyPairs.GetPairs(assetType, false) - return pairs.Format(format.Delimiter, format.Index, format.Uppercase) +func (e *Base) GetAvailablePairs(assetType asset.Item) (currency.Pairs, error) { + format, err := e.GetPairFormat(assetType, false) + if err != nil { + return nil, err + } + pairs, err := e.CurrencyPairs.GetPairs(assetType, false) + if err != nil { + return nil, err + } + return pairs.Format(format.Delimiter, format.Index, format.Uppercase), nil } // SupportsPair returns true or not whether a currency pair exists in the // exchange available currencies or not -func (e *Base) SupportsPair(p currency.Pair, enabledPairs bool, assetType asset.Item) bool { +func (e *Base) SupportsPair(p currency.Pair, enabledPairs bool, assetType asset.Item) error { if enabledPairs { - return e.GetEnabledPairs(assetType).Contains(p, false) + pairs, err := e.GetEnabledPairs(assetType) + if err != nil { + return err + } + if pairs.Contains(p, false) { + return nil + } + return errors.New("pair not supported") } - return e.GetAvailablePairs(assetType).Contains(p, false) + + avail, err := e.GetAvailablePairs(assetType) + if err != nil { + return err + } + if avail.Contains(p, false) { + return nil + } + return errors.New("pair not supported") } // FormatExchangeCurrencies returns a string containing // the exchanges formatted currency pairs func (e *Base) FormatExchangeCurrencies(pairs []currency.Pair, assetType asset.Item) (string, error) { var currencyItems strings.Builder - pairFmt := e.GetPairFormat(assetType, true) + pairFmt, err := e.GetPairFormat(assetType, true) + if err != nil { + return "", err + } for x := range pairs { - currencyItems.WriteString(e.FormatExchangeCurrency(pairs[x], assetType).String()) + format, err := e.FormatExchangeCurrency(pairs[x], assetType) + if err != nil { + return "", err + } + currencyItems.WriteString(format.String()) if x == len(pairs)-1 { continue } @@ -401,9 +488,12 @@ func (e *Base) FormatExchangeCurrencies(pairs []currency.Pair, assetType asset.I // FormatExchangeCurrency is a method that formats and returns a currency pair // based on the user currency display preferences -func (e *Base) FormatExchangeCurrency(p currency.Pair, assetType asset.Item) currency.Pair { - pairFmt := e.GetPairFormat(assetType, true) - return p.Format(pairFmt.Delimiter, pairFmt.Uppercase) +func (e *Base) FormatExchangeCurrency(p currency.Pair, assetType asset.Item) (currency.Pair, error) { + pairFmt, err := e.GetPairFormat(assetType, true) + if err != nil { + return currency.Pair{}, err + } + return p.Format(pairFmt.Delimiter, pairFmt.Uppercase), nil } // SetEnabled is a method that sets if the exchange is enabled @@ -464,18 +554,26 @@ func (e *Base) SetupDefaults(exch *config.ExchangeConfig) error { e.HTTPDebugging = exch.HTTPDebugging e.SetHTTPClientUserAgent(exch.HTTPUserAgent) - e.SetAssetTypes() e.SetCurrencyPairFormat() - e.SetConfigPairs() - e.SetFeatureDefaults() - e.SetAPIURL() - e.SetAPICredentialDefaults() - e.SetClientProxyAddress(exch.ProxyAddress) - e.BaseCurrencies = exch.BaseCurrencies - if e.Features.Supports.Websocket { - return e.Websocket.Initialise() + err := e.SetConfigPairs() + if err != nil { + return err } + + e.SetFeatureDefaults() + err = e.SetAPIURL() + if err != nil { + return err + } + + e.SetAPICredentialDefaults() + + err = e.SetClientProxyAddress(exch.ProxyAddress) + if err != nil { + return err + } + e.BaseCurrencies = exch.BaseCurrencies return nil } @@ -564,7 +662,11 @@ func (e *Base) SetPairs(pairs currency.Pairs, assetType asset.Item, enabled bool return fmt.Errorf("%s SetPairs error - pairs is empty", e.Name) } - pairFmt := e.GetPairFormat(assetType, false) + pairFmt, err := e.GetPairFormat(assetType, false) + if err != nil { + return err + } + var newPairs currency.Pairs for x := range pairs { newPairs = append(newPairs, pairs[x].Format(pairFmt.Delimiter, @@ -579,10 +681,6 @@ func (e *Base) SetPairs(pairs currency.Pairs, assetType asset.Item, enabled bool // UpdatePairs updates the exchange currency pairs for either enabledPairs or // availablePairs func (e *Base) UpdatePairs(exchangeProducts currency.Pairs, assetType asset.Item, enabled, force bool) error { - if len(exchangeProducts) == 0 { - return fmt.Errorf("%s UpdatePairs error - exchangeProducts is empty", e.Name) - } - exchangeProducts = exchangeProducts.Upper() var products currency.Pairs for x := range exchangeProducts { @@ -592,37 +690,71 @@ func (e *Base) UpdatePairs(exchangeProducts currency.Pairs, assetType asset.Item products = append(products, exchangeProducts[x]) } - var newPairs, removedPairs currency.Pairs var updateType string - targetPairs := e.CurrencyPairs.GetPairs(assetType, enabled) + targetPairs, err := e.CurrencyPairs.GetPairs(assetType, enabled) + if err != nil { + return err + } if enabled { - newPairs, removedPairs = targetPairs.FindDifferences(products) updateType = "enabled" } else { - newPairs, removedPairs = targetPairs.FindDifferences(products) updateType = "available" } + newPairs, removedPairs := targetPairs.FindDifferences(products) if force || len(newPairs) > 0 || len(removedPairs) > 0 { if force { log.Debugf(log.ExchangeSys, - "%s forced update of %s [%v] pairs.", e.Name, updateType, + "%s forced update of %s [%v] pairs.", + e.Name, + updateType, strings.ToUpper(assetType.String())) } else { if len(newPairs) > 0 { log.Debugf(log.ExchangeSys, - "%s Updating pairs [%v] - New: %s.\n", e.Name, - strings.ToUpper(assetType.String()), newPairs) + "%s Updating %s pairs [%v] - Added: %s.\n", + e.Name, + updateType, + strings.ToUpper(assetType.String()), + newPairs) } if len(removedPairs) > 0 { log.Debugf(log.ExchangeSys, - "%s Updating pairs [%v] - Removed: %s.\n", e.Name, - strings.ToUpper(assetType.String()), removedPairs) + "%s Updating %s pairs [%v] - Removed: %s.\n", + e.Name, + updateType, + strings.ToUpper(assetType.String()), + removedPairs) } } + e.Config.CurrencyPairs.StorePairs(assetType, products, enabled) e.CurrencyPairs.StorePairs(assetType, products, enabled) + + if !enabled { + // If available pairs are changed we will remove currency pair items + // that are still included in the enabled pairs list. + enabledPairs, err := e.CurrencyPairs.GetPairs(assetType, true) + if err == nil { + return nil + } + _, remove := enabledPairs.FindDifferences(products) + for i := range remove { + enabledPairs = enabledPairs.Remove(remove[i]) + } + + if len(remove) > 0 { + log.Debugf(log.ExchangeSys, + "%s Checked and updated enabled pairs [%v] - Removed: %s.\n", + e.Name, + strings.ToUpper(assetType.String()), + remove) + + e.Config.CurrencyPairs.StorePairs(assetType, enabledPairs, true) + e.CurrencyPairs.StorePairs(assetType, enabledPairs, true) + } + } } return nil } @@ -675,27 +807,12 @@ func (e *Base) GetAPIURLSecondaryDefault() string { return e.API.Endpoints.URLSecondaryDefault } -// SupportsWebsocket returns whether or not the exchange supports -// websocket -func (e *Base) SupportsWebsocket() bool { - return e.Features.Supports.Websocket -} - // SupportsREST returns whether or not the exchange supports // REST func (e *Base) SupportsREST() bool { return e.Features.Supports.REST } -// IsWebsocketEnabled returns whether or not the exchange has its -// websocket client enabled -func (e *Base) IsWebsocketEnabled() bool { - if e.Websocket != nil { - return e.Websocket.IsEnabled() - } - return false -} - // GetWithdrawPermissions passes through the exchange's withdraw permissions func (e *Base) GetWithdrawPermissions() uint32 { return e.Features.Supports.WithdrawPermissions @@ -767,7 +884,8 @@ func (e *Base) FormatWithdrawPermissions() string { // SupportsAsset whether or not the supplied asset is supported // by the exchange func (e *Base) SupportsAsset(a asset.Item) bool { - return e.CurrencyPairs.AssetTypes.Contains(a) + _, ok := e.CurrencyPairs.Pairs[a] + return ok } // PrintEnabledPairs prints the exchanges enabled asset pairs @@ -804,6 +922,135 @@ func (e *Base) EnableRateLimiter() error { return e.Requester.EnableRateLimiter() } +// StoreAssetPairFormat initialises and stores a defined asset format +func (e *Base) StoreAssetPairFormat(a asset.Item, f currency.PairStore) error { + if a.String() == "" { + return fmt.Errorf("%s cannot add to pairs manager, no asset provided", + e.Name) + } + + if f.RequestFormat == nil { + return fmt.Errorf("%s cannot add to pairs manager, request pair format not provided", + e.Name) + } + + if f.ConfigFormat == nil { + return fmt.Errorf("%s cannot add to pairs manager, config pair format not provided", + e.Name) + } + + if e.CurrencyPairs.Pairs == nil { + e.CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore) + } + + e.CurrencyPairs.Pairs[a] = &f + return nil +} + +// SetGlobalPairsManager sets defined asset and pairs management system with +// with global formatting +func (e *Base) SetGlobalPairsManager(request, config *currency.PairFormat, assets ...asset.Item) error { + if request == nil { + return fmt.Errorf("%s cannot set pairs manager, request pair format not provided", + e.Name) + } + + if config == nil { + return fmt.Errorf("%s cannot set pairs manager, config pair format not provided", + e.Name) + } + + if len(assets) == 0 { + return fmt.Errorf("%s cannot set pairs manager, no assets provided", + e.Name) + } + + e.CurrencyPairs.UseGlobalFormat = true + e.CurrencyPairs.RequestFormat = request + e.CurrencyPairs.ConfigFormat = config + + if e.CurrencyPairs.Pairs != nil { + return fmt.Errorf("%s cannot set pairs manager, pairs already set", + e.Name) + } + + e.CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore) + + for i := range assets { + if assets[i].String() == "" { + e.CurrencyPairs.Pairs = nil + return fmt.Errorf("%s cannot set pairs manager, asset is empty string", + e.Name) + } + e.CurrencyPairs.Pairs[assets[i]] = new(currency.PairStore) + } + + return nil +} + +// GetWebsocket returns a pointer to the exchange websocket +func (e *Base) GetWebsocket() (*stream.Websocket, error) { + if e.Websocket == nil { + return nil, common.ErrFunctionNotSupported + } + return e.Websocket, nil +} + +// SupportsWebsocket returns whether or not the exchange supports +// websocket +func (e *Base) SupportsWebsocket() bool { + return e.Features.Supports.Websocket +} + +// IsWebsocketEnabled returns whether or not the exchange has its +// websocket client enabled +func (e *Base) IsWebsocketEnabled() bool { + if e.Websocket == nil { + return false + } + return e.Websocket.IsEnabled() +} + +// FlushWebsocketChannels refreshes websocket channel subscriptions based on +// websocket features. Used in the event of a pair/asset or subscription change. +func (e *Base) FlushWebsocketChannels() error { + if e.Websocket == nil { + return nil + } + return e.Websocket.FlushChannels() +} + +// SubscribeToWebsocketChannels appends to ChannelsToSubscribe +// which lets websocket.manageSubscriptions handle subscribing +func (e *Base) SubscribeToWebsocketChannels(channels []stream.ChannelSubscription) error { + if e.Websocket == nil { + return common.ErrFunctionNotSupported + } + return e.Websocket.SubscribeToChannels(channels) +} + +// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe +// which lets websocket.manageSubscriptions handle unsubscribing +func (e *Base) UnsubscribeToWebsocketChannels(channels []stream.ChannelSubscription) error { + if e.Websocket == nil { + return common.ErrFunctionNotSupported + } + return e.Websocket.UnsubscribeChannels(channels) +} + +// GetSubscriptions returns a copied list of subscriptions +func (e *Base) GetSubscriptions() ([]stream.ChannelSubscription, error) { + if e.Websocket == nil { + return nil, common.ErrFunctionNotSupported + } + return e.Websocket.GetSubscriptions(), nil +} + +// AuthenticateWebsocket sends an authentication message to the websocket +func (e *Base) AuthenticateWebsocket() error { + return common.ErrFunctionNotSupported +} + // KlineIntervalEnabled returns if requested interval is enabled on exchange func (e *Base) KlineIntervalEnabled(in kline.Interval) bool { return e.Features.Enabled.Kline.Intervals[in.Word()] diff --git a/exchanges/exchange_test.go b/exchanges/exchange_test.go index e61a4d28..29b03388 100644 --- a/exchanges/exchange_test.go +++ b/exchanges/exchange_test.go @@ -2,17 +2,20 @@ package exchange import ( "net/http" + "os" "strings" "testing" "time" + "github.com/thrasher-corp/gocryptotrader/common/convert" "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/kline" "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" + "github.com/thrasher-corp/gocryptotrader/log" "github.com/thrasher-corp/gocryptotrader/portfolio/banking" ) @@ -21,6 +24,13 @@ const ( defaultTestCurrencyPair = "BTC-USD" ) +func TestMain(m *testing.M) { + c := log.GenDefaultSettings() + log.GlobalLogConfig = &c + log.SetupGlobalLogger() + os.Exit(m.Run()) +} + func TestSupportsRESTTickerBatchUpdates(t *testing.T) { t.Parallel() @@ -98,7 +108,7 @@ func TestSetClientProxyAddress(t *testing.T) { Name: "rawr", Requester: requester} - newBase.Websocket = wshandler.New() + newBase.Websocket = stream.New() err := newBase.SetClientProxyAddress(":invalid") if err == nil { t.Error("SetClientProxyAddress parsed invalid URL") @@ -108,18 +118,18 @@ func TestSetClientProxyAddress(t *testing.T) { t.Error("SetClientProxyAddress error", err) } - err = newBase.SetClientProxyAddress("www.valid.com") + err = newBase.SetClientProxyAddress("http://www.valid.com") if err != nil { t.Error("SetClientProxyAddress error", err) } // calling this again will cause the ws check to fail - err = newBase.SetClientProxyAddress("www.valid.com") + err = newBase.SetClientProxyAddress("http://www.valid.com") if err == nil { t.Error("trying to set the same proxy addr should thrown an err for ws") } - if newBase.Websocket.GetProxyAddress() != "www.valid.com" { + if newBase.Websocket.GetProxyAddress() != "http://www.valid.com" { t.Error("SetClientProxyAddress error", err) } } @@ -253,46 +263,15 @@ func TestGetLastPairsUpdateTime(t *testing.T) { } } -func TestSetAssetTypes(t *testing.T) { - t.Parallel() - - b := Base{ - Config: &config.ExchangeConfig{ - CurrencyPairs: ¤cy.PairsManager{}, - }, - CurrencyPairs: currency.PairsManager{ - AssetTypes: asset.Items{ - asset.Spot, - asset.Binary, - asset.Futures, - }, - }, - } - b.SetAssetTypes() - if len(b.GetAssetTypes()) != 3 { - t.Error("incorrect assets len") - } - - b.CurrencyPairs.AssetTypes = append(b.CurrencyPairs.AssetTypes, - asset.PerpetualSwap) - b.Config.CurrencyPairs.AssetTypes = asset.Items{ - asset.Index, - } - b.SetAssetTypes() - if len(b.GetAssetTypes()) != 4 { - t.Error("incorrect assets len") - } -} - func TestGetAssetTypes(t *testing.T) { t.Parallel() testExchange := Base{ CurrencyPairs: currency.PairsManager{ - AssetTypes: asset.Items{ - asset.Spot, - asset.Binary, - asset.Futures, + Pairs: map[asset.Item]*currency.PairStore{ + asset.Spot: new(currency.PairStore), + asset.Binary: new(currency.PairStore), + asset.Futures: new(currency.PairStore), }, }, } @@ -347,14 +326,17 @@ func TestSetCurrencyPairFormat(t *testing.T) { b.CurrencyPairs.RequestFormat = pFmt b.CurrencyPairs.ConfigFormat = pFmt b.SetCurrencyPairFormat() - if b.GetPairFormat(asset.Spot, true).Delimiter != "#" { + spot, err := b.GetPairFormat(asset.Spot, true) + if err != nil { + t.Fatal(err) + } + + if spot.Delimiter != "#" { t.Error("incorrect pair format delimiter") } // Test individual asset type formatting logic b.CurrencyPairs.UseGlobalFormat = false - // This will generate a nil pair store - b.CurrencyPairs.AssetTypes = asset.Items{asset.Index} // Store non-nil pair stores b.CurrencyPairs.Store(asset.Spot, currency.PairStore{ ConfigFormat: ¤cy.PairFormat{ @@ -367,10 +349,18 @@ func TestSetCurrencyPairFormat(t *testing.T) { }, }) b.SetCurrencyPairFormat() - if b.GetPairFormat(asset.Spot, false).Delimiter != "~" { + spot, err = b.GetPairFormat(asset.Spot, false) + if err != nil { + t.Fatal(err) + } + if spot.Delimiter != "~" { t.Error("incorrect pair format delimiter") } - if b.GetPairFormat(asset.Futures, false).Delimiter != ":)" { + futures, err := b.GetPairFormat(asset.Futures, false) + if err != nil { + t.Fatal(err) + } + if futures.Delimiter != ":)" { t.Error("incorrect pair format delimiter") } } @@ -386,7 +376,6 @@ func TestLoadConfigPairs(t *testing.T) { b := Base{ CurrencyPairs: currency.PairsManager{ UseGlobalFormat: true, - AssetTypes: asset.Items{asset.Spot}, RequestFormat: ¤cy.PairFormat{ Delimiter: ">", Uppercase: false, @@ -408,7 +397,10 @@ func TestLoadConfigPairs(t *testing.T) { } // Test a nil PairsManager - b.SetConfigPairs() + err := b.SetConfigPairs() + if err != nil { + t.Fatal(err) + } // Now setup a proper PairsManager b.Config.CurrencyPairs = ¤cy.PairsManager{ @@ -421,33 +413,51 @@ func TestLoadConfigPairs(t *testing.T) { Delimiter: "!", Uppercase: true, }, - AssetTypes: asset.Items{asset.Spot}, Pairs: map[asset.Item]*currency.PairStore{ asset.Spot: { - Enabled: pairs, - Available: pairs, - RequestFormat: ¤cy.PairFormat{}, - ConfigFormat: ¤cy.PairFormat{}, + AssetEnabled: convert.BoolPtr(true), + Enabled: pairs, + Available: pairs, }, }, } // Test UseGlobalFormat setting of pairs b.SetCurrencyPairFormat() - b.SetConfigPairs() + err = b.SetConfigPairs() + if err != nil { + t.Fatal(err) + } // Test four things: // 1) Config pairs are set // 2) pair format is set for RequestFormat // 3) pair format is set for ConfigFormat // 4) Config global format delimiter is updated based off exchange.Base - pFmt := b.GetPairFormat(asset.Spot, false) - p := b.GetEnabledPairs(asset.Spot)[0].Format(pFmt.Delimiter, - pFmt.Uppercase).String() + pFmt, err := b.GetPairFormat(asset.Spot, false) + if err != nil { + t.Fatal(err) + } + pairs, err = b.GetEnabledPairs(asset.Spot) + if err != nil { + t.Fatal(err) + } + + p := pairs[0].Format(pFmt.Delimiter, pFmt.Uppercase).String() if p != "BTC^USD" { t.Errorf("incorrect value, expected BTC^USD") } - p = b.FormatExchangeCurrency(b.GetAvailablePairs(asset.Spot)[0], - asset.Spot).String() + + avail, err := b.GetAvailablePairs(asset.Spot) + if err != nil { + t.Fatal(err) + } + + format, err := b.FormatExchangeCurrency(avail[0], asset.Spot) + if err != nil { + t.Fatal(err) + } + + p = format.String() if p != "btc>usd" { t.Error("incorrect value, expected btc>usd") } @@ -459,7 +469,10 @@ func TestLoadConfigPairs(t *testing.T) { } // Test !UseGlobalFormat setting of pairs - exchPS := b.CurrencyPairs.Get(asset.Spot) + exchPS, err := b.CurrencyPairs.Get(asset.Spot) + if err != nil { + t.Fatal(err) + } exchPS.RequestFormat.Delimiter = "~" exchPS.RequestFormat.Uppercase = false exchPS.ConfigFormat.Delimiter = "/" @@ -476,18 +489,36 @@ func TestLoadConfigPairs(t *testing.T) { // 2) pair format is set for RequestFormat // 3) pair format is set for ConfigFormat // 4) Config pair store formats are the same as the exchanges - pFmt = b.GetPairFormat(asset.Spot, false) - p = b.GetEnabledPairs(asset.Spot)[2].Format(pFmt.Delimiter, - pFmt.Uppercase).String() + pFmt, err = b.GetPairFormat(asset.Spot, false) + if err != nil { + t.Fatal(err) + } + pairs, err = b.GetEnabledPairs(asset.Spot) + if err != nil { + t.Fatal(err) + } + p = pairs[2].Format(pFmt.Delimiter, pFmt.Uppercase).String() if p != "xrp/usd" { t.Error("incorrect value, expected xrp/usd") } - p = b.FormatExchangeCurrency(b.GetAvailablePairs(asset.Spot)[2], - asset.Spot).String() + + avail, err = b.GetAvailablePairs(asset.Spot) + if err != nil { + t.Fatal(err) + } + + format, err = b.FormatExchangeCurrency(avail[2], asset.Spot) + if err != nil { + t.Fatal(err) + } + p = format.String() if p != "xrp~usd" { t.Error("incorrect value, expected xrp~usd") } - ps := b.Config.CurrencyPairs.Get(asset.Spot) + ps, err := b.Config.CurrencyPairs.Get(asset.Spot) + if err != nil { + t.Fatal(err) + } if ps.RequestFormat.Delimiter != "~" || ps.RequestFormat.Uppercase || ps.ConfigFormat.Delimiter != "/" || @@ -570,11 +601,17 @@ func TestGetPairFormat(t *testing.T) { b.CurrencyPairs.RequestFormat = ¤cy.PairFormat{ Delimiter: "~", } - pFmt := b.GetPairFormat(asset.Spot, true) + pFmt, err := b.GetPairFormat(asset.Spot, true) + if err != nil { + t.Fatal(err) + } if pFmt.Delimiter != "~" && !pFmt.Uppercase { t.Error("incorrect pair format values") } - pFmt = b.GetPairFormat(asset.Spot, false) + pFmt, err = b.GetPairFormat(asset.Spot, false) + if err != nil { + t.Fatal(err) + } if pFmt.Delimiter != "" && pFmt.Uppercase { t.Error("incorrect pair format values") } @@ -588,11 +625,17 @@ func TestGetPairFormat(t *testing.T) { Uppercase: true, }, }) - pFmt = b.GetPairFormat(asset.Spot, false) + pFmt, err = b.GetPairFormat(asset.Spot, false) + if err != nil { + t.Fatal(err) + } if pFmt.Delimiter != "" && pFmt.Uppercase { t.Error("incorrect pair format values") } - pFmt = b.GetPairFormat(asset.Spot, true) + pFmt, err = b.GetPairFormat(asset.Spot, true) + if err != nil { + t.Fatal(err) + } if pFmt.Delimiter != "~" && !pFmt.Uppercase { t.Error("incorrect pair format values") } @@ -605,70 +648,116 @@ func TestGetEnabledPairs(t *testing.T) { Name: "TESTNAME", } - b.CurrencyPairs.StorePairs(asset.Spot, - currency.NewPairsFromStrings([]string{defaultTestCurrencyPair}), true) + defaultPairs, err := currency.NewPairsFromStrings([]string{defaultTestCurrencyPair}) + if err != nil { + t.Fatal(err) + } + + b.CurrencyPairs.StorePairs(asset.Spot, defaultPairs, true) + b.CurrencyPairs.StorePairs(asset.Spot, defaultPairs, false) format := currency.PairFormat{ Delimiter: "-", Index: "", Uppercase: true, } - assetType := asset.Spot + err = b.CurrencyPairs.SetAssetEnabled(asset.Spot, true) + if err != nil { + t.Fatal(err) + } + b.CurrencyPairs.UseGlobalFormat = true b.CurrencyPairs.RequestFormat = &format b.CurrencyPairs.ConfigFormat = &format - c := b.GetEnabledPairs(assetType) + c, err := b.GetEnabledPairs(asset.Spot) + if err != nil { + t.Fatal(err) + } + if c[0].String() != defaultTestCurrencyPair { t.Error("Exchange GetAvailablePairs() incorrect string") } format.Delimiter = "~" b.CurrencyPairs.RequestFormat = &format - c = b.GetEnabledPairs(assetType) + c, err = b.GetEnabledPairs(asset.Spot) + if err != nil { + t.Fatal(err) + } if c[0].String() != "BTC~USD" { t.Error("Exchange GetAvailablePairs() incorrect string") } format.Delimiter = "" b.CurrencyPairs.ConfigFormat = &format - c = b.GetEnabledPairs(assetType) + c, err = b.GetEnabledPairs(asset.Spot) + if err != nil { + t.Fatal(err) + } if c[0].String() != "BTCUSD" { t.Error("Exchange GetAvailablePairs() incorrect string") } - b.CurrencyPairs.StorePairs(asset.Spot, - currency.NewPairsFromStrings([]string{"BTCDOGE"}), true) + btcdoge, err := currency.NewPairsFromStrings([]string{"BTCDOGE"}) + if err != nil { + t.Fatal(err) + } + + b.CurrencyPairs.StorePairs(asset.Spot, btcdoge, true) + b.CurrencyPairs.StorePairs(asset.Spot, btcdoge, false) format.Index = currency.BTC.String() b.CurrencyPairs.ConfigFormat = &format - c = b.GetEnabledPairs(assetType) + c, err = b.GetEnabledPairs(asset.Spot) + if err != nil { + t.Fatal(err) + } if c[0].Base != currency.BTC && c[0].Quote != currency.DOGE { t.Error("Exchange GetAvailablePairs() incorrect string") } - b.CurrencyPairs.StorePairs(asset.Spot, - currency.NewPairsFromStrings([]string{"BTC_USD"}), true) + btcusdUnderscore, err := currency.NewPairsFromStrings([]string{"BTC_USD"}) + if err != nil { + t.Fatal(err) + } + + b.CurrencyPairs.StorePairs(asset.Spot, btcusdUnderscore, true) + b.CurrencyPairs.StorePairs(asset.Spot, btcusdUnderscore, false) b.CurrencyPairs.RequestFormat.Delimiter = "" b.CurrencyPairs.ConfigFormat.Delimiter = "_" - c = b.GetEnabledPairs(assetType) + c, err = b.GetEnabledPairs(asset.Spot) + if err != nil { + t.Fatal(err) + } if c[0].Base != currency.BTC && c[0].Quote != currency.USD { t.Error("Exchange GetAvailablePairs() incorrect string") } - b.CurrencyPairs.StorePairs(asset.Spot, - currency.NewPairsFromStrings([]string{"BTCDOGE"}), true) + b.CurrencyPairs.StorePairs(asset.Spot, btcdoge, true) + b.CurrencyPairs.StorePairs(asset.Spot, btcdoge, false) b.CurrencyPairs.RequestFormat.Delimiter = "" b.CurrencyPairs.ConfigFormat.Delimiter = "" b.CurrencyPairs.ConfigFormat.Index = currency.BTC.String() - c = b.GetEnabledPairs(assetType) + c, err = b.GetEnabledPairs(asset.Spot) + if err != nil { + t.Fatal(err) + } if c[0].Base != currency.BTC && c[0].Quote != currency.DOGE { t.Error("Exchange GetAvailablePairs() incorrect string") } - b.CurrencyPairs.StorePairs(asset.Spot, - currency.NewPairsFromStrings([]string{"BTCUSD"}), true) + btcusd, err := currency.NewPairsFromStrings([]string{"BTCUSD"}) + if err != nil { + t.Fatal(err) + } + + b.CurrencyPairs.StorePairs(asset.Spot, btcusd, true) + b.CurrencyPairs.StorePairs(asset.Spot, btcusd, false) b.CurrencyPairs.ConfigFormat.Index = "" - c = b.GetEnabledPairs(assetType) + c, err = b.GetEnabledPairs(asset.Spot) + if err != nil { + t.Fatal(err) + } if c[0].Base != currency.BTC && c[0].Quote != currency.USD { t.Error("Exchange GetAvailablePairs() incorrect string") } @@ -681,8 +770,12 @@ func TestGetAvailablePairs(t *testing.T) { Name: "TESTNAME", } - b.CurrencyPairs.StorePairs(asset.Spot, - currency.NewPairsFromStrings([]string{defaultTestCurrencyPair}), false) + defaultPairs, err := currency.NewPairsFromStrings([]string{defaultTestCurrencyPair}) + if err != nil { + t.Fatal(err) + } + + b.CurrencyPairs.StorePairs(asset.Spot, defaultPairs, false) format := currency.PairFormat{ Delimiter: "-", Index: "", @@ -694,57 +787,96 @@ func TestGetAvailablePairs(t *testing.T) { b.CurrencyPairs.RequestFormat = &format b.CurrencyPairs.ConfigFormat = &format - c := b.GetAvailablePairs(assetType) + c, err := b.GetAvailablePairs(assetType) + if err != nil { + t.Fatal(err) + } + if c[0].String() != defaultTestCurrencyPair { t.Error("Exchange GetAvailablePairs() incorrect string") } format.Delimiter = "~" b.CurrencyPairs.RequestFormat = &format - c = b.GetAvailablePairs(assetType) + c, err = b.GetAvailablePairs(assetType) + if err != nil { + t.Fatal(err) + } + if c[0].String() != "BTC~USD" { t.Error("Exchange GetAvailablePairs() incorrect string") } format.Delimiter = "" b.CurrencyPairs.ConfigFormat = &format - c = b.GetAvailablePairs(assetType) + c, err = b.GetAvailablePairs(assetType) + if err != nil { + t.Fatal(err) + } + if c[0].String() != "BTCUSD" { t.Error("Exchange GetAvailablePairs() incorrect string") } - b.CurrencyPairs.StorePairs(asset.Spot, - currency.NewPairsFromStrings([]string{"BTCDOGE"}), false) + dogePairs, err := currency.NewPairsFromStrings([]string{"BTCDOGE"}) + if err != nil { + t.Fatal(err) + } + + b.CurrencyPairs.StorePairs(asset.Spot, dogePairs, false) format.Index = currency.BTC.String() b.CurrencyPairs.ConfigFormat = &format - c = b.GetAvailablePairs(assetType) + c, err = b.GetAvailablePairs(assetType) + if err != nil { + t.Fatal(err) + } + if c[0].Base != currency.BTC && c[0].Quote != currency.DOGE { t.Error("Exchange GetAvailablePairs() incorrect string") } - b.CurrencyPairs.StorePairs(asset.Spot, - currency.NewPairsFromStrings([]string{"BTC_USD"}), false) + btcusdUnderscore, err := currency.NewPairsFromStrings([]string{"BTC_USD"}) + if err != nil { + t.Fatal(err) + } + + b.CurrencyPairs.StorePairs(asset.Spot, btcusdUnderscore, false) b.CurrencyPairs.RequestFormat.Delimiter = "" b.CurrencyPairs.ConfigFormat.Delimiter = "_" - c = b.GetAvailablePairs(assetType) + c, err = b.GetAvailablePairs(assetType) + if err != nil { + t.Fatal(err) + } + if c[0].Base != currency.BTC && c[0].Quote != currency.USD { t.Error("Exchange GetAvailablePairs() incorrect string") } - b.CurrencyPairs.StorePairs(asset.Spot, - currency.NewPairsFromStrings([]string{"BTCDOGE"}), false) + b.CurrencyPairs.StorePairs(asset.Spot, dogePairs, false) b.CurrencyPairs.RequestFormat.Delimiter = "" b.CurrencyPairs.ConfigFormat.Delimiter = "_" b.CurrencyPairs.ConfigFormat.Index = currency.BTC.String() - c = b.GetAvailablePairs(assetType) + c, err = b.GetAvailablePairs(assetType) + if err != nil { + t.Fatal(err) + } + if c[0].Base != currency.BTC && c[0].Quote != currency.DOGE { t.Error("Exchange GetAvailablePairs() incorrect string") } - b.CurrencyPairs.StorePairs(asset.Spot, - currency.NewPairsFromStrings([]string{"BTCUSD"}), false) + btcusd, err := currency.NewPairsFromStrings([]string{"BTCUSD"}) + if err != nil { + t.Fatal(err) + } + + b.CurrencyPairs.StorePairs(asset.Spot, btcusd, false) b.CurrencyPairs.ConfigFormat.Index = "" - c = b.GetAvailablePairs(assetType) + c, err = b.GetAvailablePairs(assetType) + if err != nil { + t.Fatal(err) + } + if c[0].Base != currency.BTC && c[0].Quote != currency.USD { t.Error("Exchange GetAvailablePairs() incorrect string") } @@ -755,13 +887,29 @@ func TestSupportsPair(t *testing.T) { b := Base{ Name: "TESTNAME", + CurrencyPairs: currency.PairsManager{ + Pairs: map[asset.Item]*currency.PairStore{ + asset.Spot: { + AssetEnabled: convert.BoolPtr(true), + }, + }, + }, } - b.CurrencyPairs.StorePairs(asset.Spot, - currency.NewPairsFromStrings([]string{ - defaultTestCurrencyPair, "ETH-USD"}), false) - b.CurrencyPairs.StorePairs(asset.Spot, - currency.NewPairsFromStrings([]string{defaultTestCurrencyPair}), true) + pairs, err := currency.NewPairsFromStrings([]string{defaultTestCurrencyPair, + "ETH-USD"}) + if err != nil { + t.Fatal(err) + } + + b.CurrencyPairs.StorePairs(asset.Spot, pairs, false) + + defaultpairs, err := currency.NewPairsFromStrings([]string{defaultTestCurrencyPair}) + if err != nil { + t.Fatal(err) + } + + b.CurrencyPairs.StorePairs(asset.Spot, defaultpairs, true) format := ¤cy.PairFormat{ Delimiter: "-", @@ -773,15 +921,20 @@ func TestSupportsPair(t *testing.T) { b.CurrencyPairs.ConfigFormat = format assetType := asset.Spot - if !b.SupportsPair(currency.NewPair(currency.BTC, currency.USD), true, assetType) { + if b.SupportsPair(currency.NewPair(currency.BTC, currency.USD), true, assetType) != nil { t.Error("Exchange SupportsPair() incorrect value") } - if !b.SupportsPair(currency.NewPair(currency.ETH, currency.USD), false, assetType) { + if b.SupportsPair(currency.NewPair(currency.ETH, currency.USD), false, assetType) != nil { t.Error("Exchange SupportsPair() incorrect value") } - if b.SupportsPair(currency.NewPairFromStrings("ASD", "ASDF"), true, assetType) { + asdasdf, err := currency.NewPairFromStrings("ASD", "ASDF") + if err != nil { + t.Fatal(err) + } + + if b.SupportsPair(asdasdf, true, assetType) == nil { t.Error("Exchange SupportsPair() incorrect value") } } @@ -805,10 +958,17 @@ func TestFormatExchangeCurrencies(t *testing.T) { }, }, } - + p1, err := currency.NewPairDelimiter("BTC_USD", "_") + if err != nil { + t.Fatal(err) + } + p2, err := currency.NewPairDelimiter("LTC_BTC", "_") + if err != nil { + t.Fatal(err) + } var pairs = []currency.Pair{ - currency.NewPairDelimiter("BTC_USD", "_"), - currency.NewPairDelimiter("LTC_BTC", "_"), + p1, + p2, } actual, err := e.FormatExchangeCurrencies(pairs, asset.Spot) @@ -839,7 +999,10 @@ func TestFormatExchangeCurrency(t *testing.T) { p := currency.NewPair(currency.BTC, currency.USD) expected := defaultTestCurrencyPair - actual := b.FormatExchangeCurrency(p, asset.Spot) + actual, err := b.FormatExchangeCurrency(p, asset.Spot) + if err != nil { + t.Fatal(err) + } if actual.String() != expected { t.Errorf("Exchange TestFormatExchangeCurrency %s != %s", @@ -919,24 +1082,23 @@ func TestSetupDefaults(t *testing.T) { AuthenticatedSupport: true, }, } - if err := b.SetupDefaults(&cfg); err != nil { - t.Error(err) - } + b.SetupDefaults(&cfg) if cfg.HTTPTimeout.String() != "15s" { t.Error("HTTP timeout should be set to 15s") } // Test custom HTTP timeout is set cfg.HTTPTimeout = time.Second * 30 - if err := b.SetupDefaults(&cfg); err != nil { - t.Error(err) - } + b.SetupDefaults(&cfg) if cfg.HTTPTimeout.String() != "30s" { t.Error("HTTP timeout should be set to 30s") } // Test asset types - p := currency.NewPairDelimiter(defaultTestCurrencyPair, "-") + p, err := currency.NewPairDelimiter(defaultTestCurrencyPair, "-") + if err != nil { + t.Fatal(err) + } b.CurrencyPairs.Store(asset.Spot, currency.PairStore{ Enabled: currency.Pairs{ @@ -944,23 +1106,35 @@ func TestSetupDefaults(t *testing.T) { }, }, ) - if err := b.SetupDefaults(&cfg); err != nil { - t.Error(err) + b.SetupDefaults(&cfg) + ps, err := cfg.CurrencyPairs.Get(asset.Spot) + if err != nil { + t.Fatal(err) } - ps := cfg.CurrencyPairs.Get(asset.Spot) if !ps.Enabled.Contains(p, true) { t.Error("default pair should be stored in the configs pair store") } // Test websocket support - b.Websocket = wshandler.New() + b.Websocket = stream.New() b.Features.Supports.Websocket = true - if err := b.SetupDefaults(&cfg); err != nil { - t.Error(err) - } - b.Websocket.Setup(&wshandler.WebsocketSetup{ - Enabled: true, + b.SetupDefaults(&cfg) + err = b.Websocket.Setup(&stream.WebsocketSetup{ + Enabled: false, + WebsocketTimeout: time.Second * 30, + Features: &protocol.Features{}, + DefaultURL: "ws://something.com", + RunningURL: "ws://something.com", + ExchangeName: "test", + Connector: func() error { return nil }, }) + if err != nil { + t.Fatal(err) + } + err = b.Websocket.Enable() + if err != nil { + t.Fatal(err) + } if !b.IsWebsocketEnabled() { t.Error("websocket should be enabled") } @@ -1081,7 +1255,11 @@ func TestSetPairs(t *testing.T) { ConfigFormat: ¤cy.PairFormat{ Uppercase: true, }, - Pairs: map[asset.Item]*currency.PairStore{}, + Pairs: map[asset.Item]*currency.PairStore{ + asset.Spot: { + AssetEnabled: convert.BoolPtr(true), + }, + }, }, }, } @@ -1098,7 +1276,22 @@ func TestSetPairs(t *testing.T) { t.Error(err) } - if p := b.GetEnabledPairs(asset.Spot); len(p) != 1 { + err = b.SetPairs(pairs, asset.Spot, false) + if err != nil { + t.Error(err) + } + + err = b.SetConfigPairs() + if err != nil { + t.Fatal(err) + } + + p, err := b.GetEnabledPairs(asset.Spot) + if err != nil { + t.Fatal(err) + } + + if len(p) != 1 { t.Error("pairs shouldn't be nil") } } @@ -1115,14 +1308,34 @@ func TestUpdatePairs(t *testing.T) { t.Fatal("TestUpdatePairs failed to load config") } - UAC := Base{Name: defaultTestExchange} + UAC := Base{ + Name: defaultTestExchange, + CurrencyPairs: currency.PairsManager{ + Pairs: map[asset.Item]*currency.PairStore{ + asset.Spot: { + AssetEnabled: convert.BoolPtr(true), + }, + }, + }, + } UAC.Config = exchCfg - exchangeProducts := currency.NewPairsFromStrings([]string{"ltc", "btc", "usd", "aud", ""}) + exchangeProducts, err := currency.NewPairsFromStrings([]string{"ltcusd", + "btcusd", + "usdbtc", + "audusd"}) + if err != nil { + t.Fatal(err) + } err = UAC.UpdatePairs(exchangeProducts, asset.Spot, true, false) if err != nil { t.Errorf("TestUpdatePairs error: %s", err) } + err = UAC.UpdatePairs(exchangeProducts, asset.Spot, false, false) + if err != nil { + t.Errorf("TestUpdatePairs error: %s", err) + } + // Test updating the same new products, diff should be 0 err = UAC.UpdatePairs(exchangeProducts, asset.Spot, true, false) if err != nil { @@ -1130,14 +1343,24 @@ func TestUpdatePairs(t *testing.T) { } // Test force updating to only one product - exchangeProducts = currency.NewPairsFromStrings([]string{"btc"}) + exchangeProducts, err = currency.NewPairsFromStrings([]string{"btcusd"}) + if err != nil { + t.Fatal(err) + } + err = UAC.UpdatePairs(exchangeProducts, asset.Spot, true, true) if err != nil { t.Errorf("TestUpdatePairs error: %s", err) } // Test updating exchange products - exchangeProducts = currency.NewPairsFromStrings([]string{"ltc", "btc", "usd", "aud"}) + exchangeProducts, err = currency.NewPairsFromStrings([]string{"ltcusd", + "btcusd", + "usdbtc", + "audbtc"}) + if err != nil { + t.Fatal(err) + } UAC.Name = defaultTestExchange err = UAC.UpdatePairs(exchangeProducts, asset.Spot, false, false) if err != nil { @@ -1151,28 +1374,30 @@ func TestUpdatePairs(t *testing.T) { } // Test force updating to only one product - exchangeProducts = currency.NewPairsFromStrings([]string{"btc"}) + exchangeProducts, err = currency.NewPairsFromStrings([]string{"btcusd"}) + if err != nil { + t.Fatal(err) + } err = UAC.UpdatePairs(exchangeProducts, asset.Spot, false, true) if err != nil { t.Errorf("Forced Exchange UpdatePairs() error: %s", err) } // Test update currency pairs with btc excluded - exchangeProducts = currency.NewPairsFromStrings([]string{"ltc", "eth"}) + exchangeProducts, err = currency.NewPairsFromStrings([]string{"ltcusd", "ethusd"}) + if err != nil { + t.Fatal(err) + } err = UAC.UpdatePairs(exchangeProducts, asset.Spot, false, false) if err != nil { t.Errorf("Forced Exchange UpdatePairs() error: %s", err) } - // Test that empty exchange products should return an error - exchangeProducts = nil - err = UAC.UpdatePairs(exchangeProducts, asset.Spot, false, false) - if err == nil { - t.Errorf("empty available pairs should return an error") - } - // Test empty pair - p := currency.NewPairDelimiter(defaultTestCurrencyPair, "-") + p, err := currency.NewPairDelimiter(defaultTestCurrencyPair, "-") + if err != nil { + t.Fatal(err) + } pairs := currency.Pairs{ currency.Pair{}, p, @@ -1181,11 +1406,20 @@ func TestUpdatePairs(t *testing.T) { if err != nil { t.Errorf("Forced Exchange UpdatePairs() error: %s", err) } + err = UAC.UpdatePairs(pairs, asset.Spot, false, true) + if err != nil { + t.Errorf("Forced Exchange UpdatePairs() error: %s", err) + } UAC.CurrencyPairs.UseGlobalFormat = true UAC.CurrencyPairs.ConfigFormat = ¤cy.PairFormat{ Delimiter: "-", } - if !UAC.GetEnabledPairs(asset.Spot).Contains(p, true) { + + uacPairs, err := UAC.GetEnabledPairs(asset.Spot) + if err != nil { + t.Fatal(err) + } + if !uacPairs.Contains(p, true) { t.Fatal("expected currency pair not found") } } @@ -1298,8 +1532,16 @@ func TestIsWebsocketEnabled(t *testing.T) { t.Error("exchange doesn't support websocket") } - b.Websocket = wshandler.New() - err := b.Websocket.Setup(&wshandler.WebsocketSetup{Enabled: true}) + b.Websocket = stream.New() + err := b.Websocket.Setup(&stream.WebsocketSetup{ + Enabled: true, + WebsocketTimeout: time.Second * 30, + Features: &protocol.Features{}, + DefaultURL: "ws://something.com", + RunningURL: "ws://something.com", + ExchangeName: "test", + Connector: func() error { return nil }, + }) if err != nil { t.Error(err) } @@ -1380,8 +1622,8 @@ func TestFormatWithdrawPermissions(t *testing.T) { func TestSupportsAsset(t *testing.T) { t.Parallel() var b Base - b.CurrencyPairs.AssetTypes = asset.Items{ - asset.Spot, + b.CurrencyPairs.Pairs = map[asset.Item]*currency.PairStore{ + asset.Spot: {}, } if !b.SupportsAsset(asset.Spot) { t.Error("spot should be supported") @@ -1426,9 +1668,12 @@ func TestGetAssetType(t *testing.T) { if err == nil { t.Fatal("error cannot be nil") } - b.CurrencyPairs.AssetTypes = asset.Items{asset.Spot} b.CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore) b.CurrencyPairs.Pairs[asset.Spot] = ¤cy.PairStore{ + AssetEnabled: convert.BoolPtr(true), + Enabled: currency.Pairs{ + currency.NewPair(currency.BTC, currency.USD), + }, Available: currency.Pairs{ currency.NewPair(currency.BTC, currency.USD), }, @@ -1460,11 +1705,14 @@ func TestGetFormattedPairAndAssetType(t *testing.T) { b.CurrencyPairs.ConfigFormat = pFmt b.CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore) b.CurrencyPairs.Pairs[asset.Spot] = ¤cy.PairStore{ + AssetEnabled: convert.BoolPtr(true), Enabled: currency.Pairs{ currency.NewPair(currency.BTC, currency.USD), }, + Available: currency.Pairs{ + currency.NewPair(currency.BTC, currency.USD), + }, } - b.CurrencyPairs.AssetTypes = asset.Items{asset.Spot} p, a, err := b.GetRequestFormattedPairAndAssetType("btc#usd") if err != nil { t.Error(err) @@ -1481,6 +1729,85 @@ func TestGetFormattedPairAndAssetType(t *testing.T) { } } +func TestStoreAssetPairFormat(t *testing.T) { + b := Base{ + Config: &config.ExchangeConfig{Name: "kitties"}, + } + + err := b.StoreAssetPairFormat(asset.Item(""), currency.PairStore{}) + if err == nil { + t.Error("error cannot be nil") + } + + err = b.StoreAssetPairFormat(asset.Spot, currency.PairStore{}) + if err == nil { + t.Error("error cannot be nil") + } + + err = b.StoreAssetPairFormat(asset.Spot, currency.PairStore{ + RequestFormat: ¤cy.PairFormat{Uppercase: true}}) + if err == nil { + t.Error("error cannot be nil") + } + + err = b.StoreAssetPairFormat(asset.Spot, currency.PairStore{ + RequestFormat: ¤cy.PairFormat{Uppercase: true}, + ConfigFormat: ¤cy.PairFormat{Uppercase: true}}) + if err != nil { + t.Error(err) + } + + err = b.StoreAssetPairFormat(asset.Futures, currency.PairStore{ + RequestFormat: ¤cy.PairFormat{Uppercase: true}, + ConfigFormat: ¤cy.PairFormat{Uppercase: true}}) + if err != nil { + t.Error(err) + } +} + +func TestSetGlobalPairsManager(t *testing.T) { + b := Base{ + Config: &config.ExchangeConfig{Name: "kitties"}, + } + + err := b.SetGlobalPairsManager(nil, nil, "") + if err == nil { + t.Error("error cannot be nil") + } + + err = b.SetGlobalPairsManager(¤cy.PairFormat{Uppercase: true}, nil, "") + if err == nil { + t.Error("error cannot be nil") + } + + err = b.SetGlobalPairsManager(¤cy.PairFormat{Uppercase: true}, + ¤cy.PairFormat{Uppercase: true}) + if err == nil { + t.Error("error cannot be nil") + } + + err = b.SetGlobalPairsManager(¤cy.PairFormat{Uppercase: true}, + ¤cy.PairFormat{Uppercase: true}, "") + if err == nil { + t.Error("error cannot be nil") + } + + err = b.SetGlobalPairsManager(¤cy.PairFormat{Uppercase: true}, + ¤cy.PairFormat{Uppercase: true}, asset.Spot, asset.Binary) + if err != nil { + t.Error(err) + } + + if !b.SupportsAsset(asset.Binary) || !b.SupportsAsset(asset.Spot) { + t.Fatal("global pairs manager not set correctly") + } + + err = b.SetGlobalPairsManager(¤cy.PairFormat{Uppercase: true}, + ¤cy.PairFormat{Uppercase: true}, asset.Spot, asset.Binary) + if err == nil { + t.Error("error cannot be nil") + } +} func Test_FormatExchangeKlineInterval(t *testing.T) { testCases := []struct { name string diff --git a/exchanges/exchange_types.go b/exchanges/exchange_types.go index 15f5d253..38c9cbe2 100644 --- a/exchanges/exchange_types.go +++ b/exchanges/exchange_types.go @@ -8,7 +8,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/kline" "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" ) // Endpoint authentication types @@ -212,7 +212,7 @@ type Base struct { WebsocketResponseCheckTimeout time.Duration WebsocketResponseMaxLimit time.Duration WebsocketOrderbookBufferLimit int64 - Websocket *wshandler.Websocket + Websocket *stream.Websocket *request.Requester Config *config.ExchangeConfig } diff --git a/exchanges/exmo/exmo_wrapper.go b/exchanges/exmo/exmo_wrapper.go index 16d9f474..23c4858b 100644 --- a/exchanges/exmo/exmo_wrapper.go +++ b/exchanges/exmo/exmo_wrapper.go @@ -20,7 +20,6 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" "github.com/thrasher-corp/gocryptotrader/log" "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) @@ -56,20 +55,18 @@ func (e *EXMO) SetDefaults() { e.API.CredentialsValidator.RequiresKey = true e.API.CredentialsValidator.RequiresSecret = true - e.CurrencyPairs = currency.PairsManager{ - AssetTypes: asset.Items{ - asset.Spot, - }, - UseGlobalFormat: true, - RequestFormat: ¤cy.PairFormat{ - Delimiter: "_", - Uppercase: true, - Separator: ",", - }, - ConfigFormat: ¤cy.PairFormat{ - Delimiter: "_", - Uppercase: true, - }, + requestFmt := ¤cy.PairFormat{ + Delimiter: currency.UnderscoreDelimiter, + Uppercase: true, + Separator: ",", + } + configFmt := ¤cy.PairFormat{ + Delimiter: currency.UnderscoreDelimiter, + Uppercase: true, + } + err := e.SetGlobalPairsManager(requestFmt, configFmt, asset.Spot) + if err != nil { + log.Errorln(log.ExchangeSys, err) } e.Features = exchange.Features{ @@ -120,7 +117,6 @@ func (e *EXMO) Setup(exch *config.ExchangeConfig) error { e.SetEnabled(false) return nil } - return e.SetupDefaults(exch) } @@ -172,37 +168,45 @@ func (e *EXMO) UpdateTradablePairs(forceUpdate bool) error { return err } - return e.UpdatePairs(currency.NewPairsFromStrings(pairs), asset.Spot, false, forceUpdate) + p, err := currency.NewPairsFromStrings(pairs) + if err != nil { + return err + } + + return e.UpdatePairs(p, asset.Spot, false, forceUpdate) } // UpdateTicker updates and returns the ticker for a currency pair func (e *EXMO) UpdateTicker(p currency.Pair, assetType asset.Item) (*ticker.Price, error) { - tickerPrice := new(ticker.Price) result, err := e.GetTicker() if err != nil { - return tickerPrice, err + return nil, err } if _, ok := result[p.String()]; !ok { - return tickerPrice, err + return nil, err + } + pairs, err := e.GetEnabledPairs(assetType) + if err != nil { + return nil, err } - pairs := e.GetEnabledPairs(assetType) for i := range pairs { for j := range result { if !strings.EqualFold(pairs[i].String(), j) { continue } - tickerPrice = &ticker.Price{ - Pair: pairs[i], - Last: result[j].Last, - Ask: result[j].Sell, - High: result[j].High, - Bid: result[j].Buy, - Low: result[j].Low, - Volume: result[j].Volume, - } - err = ticker.ProcessTicker(e.Name, tickerPrice, assetType) + + err = ticker.ProcessTicker(&ticker.Price{ + Pair: pairs[i], + Last: result[j].Last, + Ask: result[j].Sell, + High: result[j].High, + Bid: result[j].Buy, + Low: result[j].Low, + Volume: result[j].Volume, + ExchangeName: e.Name, + AssetType: assetType}) if err != nil { - log.Error(log.Ticker, err) + return nil, err } } } @@ -229,7 +233,11 @@ func (e *EXMO) FetchOrderbook(p currency.Pair, assetType asset.Item) (*orderbook // UpdateOrderbook updates and returns the orderbook for a currency pair func (e *EXMO) UpdateOrderbook(p currency.Pair, assetType asset.Item) (*orderbook.Base, error) { - enabledPairs := e.GetEnabledPairs(assetType) + enabledPairs, err := e.GetEnabledPairs(assetType) + if err != nil { + return nil, err + } + pairsCollated, err := e.FormatExchangeCurrencies(enabledPairs, assetType) if err != nil { return nil, err @@ -241,7 +249,12 @@ func (e *EXMO) UpdateOrderbook(p currency.Pair, assetType asset.Item) (*orderboo } for i := range enabledPairs { - data, ok := result[e.FormatExchangeCurrency(enabledPairs[i], assetType).String()] + curr, err := e.FormatExchangeCurrency(enabledPairs[i], assetType) + if err != nil { + return nil, err + } + + data, ok := result[curr.String()] if !ok { continue } @@ -473,11 +486,6 @@ func (e *EXMO) WithdrawFiatFundsToInternationalBank(withdrawRequest *withdraw.Re return nil, common.ErrFunctionNotSupported } -// GetWebsocket returns a pointer to the exchange websocket -func (e *EXMO) GetWebsocket() (*wshandler.Websocket, error) { - return nil, common.ErrFunctionNotSupported -} - // GetFeeByType returns an estimate of fee based on type of transaction func (e *EXMO) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { if !e.AllowAuthenticatedRequest() && // Todo check connection status @@ -496,7 +504,11 @@ func (e *EXMO) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, err var orders []order.Detail for i := range resp { - symbol := currency.NewPairDelimiter(resp[i].Pair, "_") + var symbol currency.Pair + symbol, err = currency.NewPairDelimiter(resp[i].Pair, "_") + if err != nil { + return nil, err + } orderDate := time.Unix(resp[i].Created, 0) orderSide := order.Side(strings.ToUpper(resp[i].Type)) orders = append(orders, order.Detail{ @@ -524,7 +536,12 @@ func (e *EXMO) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, err var allTrades []UserTrades for i := range req.Pairs { - resp, err := e.GetUserTrades(e.FormatExchangeCurrency(req.Pairs[i], asset.Spot).String(), "", "10000") + fpair, err := e.FormatExchangeCurrency(req.Pairs[i], asset.Spot) + if err != nil { + return nil, err + } + + resp, err := e.GetUserTrades(fpair.String(), "", "10000") if err != nil { return nil, err } @@ -535,7 +552,10 @@ func (e *EXMO) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, err var orders []order.Detail for i := range allTrades { - symbol := currency.NewPairDelimiter(allTrades[i].Pair, "_") + symbol, err := currency.NewPairDelimiter(allTrades[i].Pair, "_") + if err != nil { + return nil, err + } orderDate := time.Unix(allTrades[i].Date, 0) orderSide := order.Side(strings.ToUpper(allTrades[i].Type)) orders = append(orders, order.Detail{ @@ -554,28 +574,6 @@ func (e *EXMO) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, err return orders, nil } -// SubscribeToWebsocketChannels appends to ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle subscribing -func (e *EXMO) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - return common.ErrFunctionNotSupported -} - -// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle unsubscribing -func (e *EXMO) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - return common.ErrFunctionNotSupported -} - -// GetSubscriptions returns a copied list of subscriptions -func (e *EXMO) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) { - return nil, common.ErrFunctionNotSupported -} - -// AuthenticateWebsocket sends an authentication message to the websocket -func (e *EXMO) AuthenticateWebsocket() error { - return common.ErrFunctionNotSupported -} - // ValidateCredentials validates current credentials used for wrapper // functionality func (e *EXMO) ValidateCredentials() error { diff --git a/exchanges/ftx/ftx.go b/exchanges/ftx/ftx.go index a86d0f0f..3fc09af6 100644 --- a/exchanges/ftx/ftx.go +++ b/exchanges/ftx/ftx.go @@ -17,13 +17,11 @@ import ( exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/request" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" ) // FTX is the overarching type across this package type FTX struct { exchange.Base - WebsocketConn *wshandler.WebsocketConnection } const ( diff --git a/exchanges/ftx/ftx_test.go b/exchanges/ftx/ftx_test.go index d1753249..22ae4db4 100644 --- a/exchanges/ftx/ftx_test.go +++ b/exchanges/ftx/ftx_test.go @@ -49,7 +49,7 @@ func TestMain(m *testing.M) { exchCfg.API.AuthenticatedWebsocketSupport = true exchCfg.API.Credentials.Key = apiKey exchCfg.API.Credentials.Secret = apiSecret - + f.Websocket = sharedtestvalues.NewTestWebsocket() err = f.Setup(exchCfg) if err != nil { log.Fatal(err) @@ -855,10 +855,13 @@ func TestGetFundingHistory(t *testing.T) { func TestGetHistoricCandles(t *testing.T) { t.Parallel() - currencyPair := currency.NewPairFromString(spotPair) + currencyPair, err := currency.NewPairFromString(spotPair) + if err != nil { + t.Fatal(err) + } start := time.Date(2019, 11, 12, 0, 0, 0, 0, time.UTC) end := start.AddDate(0, 0, 5) - _, err := f.GetHistoricCandles(currencyPair, asset.Spot, start, end, kline.OneDay) + _, err = f.GetHistoricCandles(currencyPair, asset.Spot, start, end, kline.OneDay) if err != nil { t.Fatal(err) } @@ -866,10 +869,13 @@ func TestGetHistoricCandles(t *testing.T) { func TestGetHistoricCandlesExtended(t *testing.T) { t.Parallel() - currencyPair := currency.NewPairFromString(spotPair) + currencyPair, err := currency.NewPairFromString(spotPair) + if err != nil { + t.Fatal(err) + } start := time.Date(2019, 11, 12, 0, 0, 0, 0, time.UTC) end := start.AddDate(0, 0, 5) - _, err := f.GetHistoricCandlesExtended(currencyPair, asset.Spot, start, end, kline.OneMin) + _, err = f.GetHistoricCandlesExtended(currencyPair, asset.Spot, start, end, kline.OneMin) if err != nil { t.Fatal(err) } @@ -1093,7 +1099,10 @@ func TestAcceptOTCQuote(t *testing.T) { func TestGetExchangeHistory(t *testing.T) { t.Parallel() - p := currency.NewPairFromString("ADA-PERP") + p, err := currency.NewPairFromString("ADA-PERP") + if err != nil { + t.Fatal(err) + } a, err := f.GetPairAssetType(p) if err != nil { t.Error(err) diff --git a/exchanges/ftx/ftx_websocket.go b/exchanges/ftx/ftx_websocket.go index 34703676..97fd83e1 100644 --- a/exchanges/ftx/ftx_websocket.go +++ b/exchanges/ftx/ftx_websocket.go @@ -11,15 +11,16 @@ import ( "time" "github.com/gorilla/websocket" + "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/crypto" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream/buffer" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook" "github.com/thrasher-corp/gocryptotrader/log" ) @@ -43,31 +44,35 @@ var obSuccess = make(map[currency.Pair]bool) // WsConnect connects to a websocket feed func (f *FTX) WsConnect() error { if !f.Websocket.IsEnabled() || !f.IsEnabled() { - return errors.New(wshandler.WebsocketNotEnabled) + return errors.New(stream.WebsocketNotEnabled) } var dialer websocket.Dialer - err := f.WebsocketConn.Dial(&dialer, http.Header{}) + err := f.Websocket.Conn.Dial(&dialer, http.Header{}) if err != nil { return err } - f.WebsocketConn.SetupPingHandler(wshandler.WebsocketPingHandler{ + f.Websocket.Conn.SetupPingHandler(stream.PingHandler{ MessageType: websocket.PingMessage, Delay: ftxWebsocketTimer, }) if f.Verbose { log.Debugf(log.ExchangeSys, "%s Connected to Websocket.\n", f.Name) } - f.GenerateDefaultSubscriptions() + go f.wsReadData() if f.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) { - err := f.WsAuth() + err = f.WsAuth() if err != nil { f.Websocket.DataHandler <- err f.Websocket.SetCanUseAuthenticatedEndpoints(false) } - f.GenerateAuthSubscriptions() } - return nil + + subs, err := f.GenerateDefaultSubscriptions() + if err != nil { + return err + } + return f.Websocket.SubscribeToChannels(subs) } // WsAuth sends an authentication message to receive auth data @@ -87,81 +92,138 @@ func (f *FTX) WsAuth() error { Time: intNonce, }, } - return f.WebsocketConn.SendJSONMessage(req) + return f.Websocket.Conn.SendJSONMessage(req) } // Subscribe sends a websocket message to receive data from the channel -func (f *FTX) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error { - var sub WsSub - switch channelToSubscribe.Channel { - case wsFills, wsOrders, wsMarkets: +func (f *FTX) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error { + var errs common.Errors +channels: + for i := range channelsToSubscribe { + var sub WsSub + sub.Channel = channelsToSubscribe[i].Channel sub.Operation = subscribe - sub.Channel = channelToSubscribe.Channel - default: - a, err := f.GetPairAssetType(channelToSubscribe.Currency) - if err != nil { - return err + + switch channelsToSubscribe[i].Channel { + case wsFills, wsOrders, wsMarkets: + default: + a, err := f.GetPairAssetType(channelsToSubscribe[i].Currency) + if err != nil { + errs = append(errs, err) + continue channels + } + + formattedPair, err := f.FormatExchangeCurrency(channelsToSubscribe[i].Currency, a) + if err != nil { + errs = append(errs, err) + continue channels + } + sub.Market = formattedPair.String() } - sub.Operation = subscribe - sub.Channel = channelToSubscribe.Channel - sub.Market = f.FormatExchangeCurrency(channelToSubscribe.Currency, a).String() + err := f.Websocket.Conn.SendJSONMessage(sub) + if err != nil { + errs = append(errs, err) + continue + } + f.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe[i]) } - return f.WebsocketConn.SendJSONMessage(sub) + if errs != nil { + return errs + } + return nil +} + +// Unsubscribe sends a websocket message to stop receiving data from the channel +func (f *FTX) Unsubscribe(channelsToUnsubscribe []stream.ChannelSubscription) error { + var errs common.Errors +channels: + for i := range channelsToUnsubscribe { + var unSub WsSub + unSub.Operation = unsubscribe + unSub.Channel = channelsToUnsubscribe[i].Channel + switch channelsToUnsubscribe[i].Channel { + case wsFills, wsOrders, wsMarkets: + default: + a, err := f.GetPairAssetType(channelsToUnsubscribe[i].Currency) + if err != nil { + errs = append(errs, err) + continue channels + } + + formattedPair, err := f.FormatExchangeCurrency(channelsToUnsubscribe[i].Currency, a) + if err != nil { + errs = append(errs, err) + continue channels + } + unSub.Market = formattedPair.String() + } + err := f.Websocket.Conn.SendJSONMessage(unSub) + if err != nil { + errs = append(errs, err) + continue + } + f.Websocket.RemoveSuccessfulUnsubscriptions(channelsToUnsubscribe[i]) + } + if errs != nil { + return errs + } + return nil } // GenerateDefaultSubscriptions generates default subscription -func (f *FTX) GenerateDefaultSubscriptions() { - var subscriptions []wshandler.WebsocketChannelSubscription - subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{ +func (f *FTX) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, error) { + var subscriptions []stream.ChannelSubscription + subscriptions = append(subscriptions, stream.ChannelSubscription{ Channel: wsMarkets, }) var channels = []string{wsTicker, wsTrades, wsOrderbook} - for a := range f.CurrencyPairs.AssetTypes { - pairs := f.GetEnabledPairs(f.CurrencyPairs.AssetTypes[a]) + assets := f.GetAssetTypes() + for a := range assets { + pairs, err := f.GetEnabledPairs(assets[a]) + if err != nil { + return nil, err + } for z := range pairs { - newPair := currency.NewPairWithDelimiter(pairs[z].Base.String(), pairs[z].Quote.String(), "-") + newPair := currency.NewPairWithDelimiter(pairs[z].Base.String(), + pairs[z].Quote.String(), + "-") for x := range channels { - subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{ - Channel: channels[x], - Currency: newPair, - }) + subscriptions = append(subscriptions, + stream.ChannelSubscription{ + Channel: channels[x], + Currency: newPair, + Asset: assets[a], + }) } } } - f.Websocket.SubscribeToChannels(subscriptions) -} - -// GenerateAuthSubscriptions generates default subscription -func (f *FTX) GenerateAuthSubscriptions() { - var subscriptions []wshandler.WebsocketChannelSubscription - var channels = []string{wsOrders, wsFills} - for x := range channels { - subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{ - Channel: channels[x], - }) + if f.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) { + var authchan = []string{wsOrders, wsFills} + for x := range authchan { + subscriptions = append(subscriptions, stream.ChannelSubscription{ + Channel: authchan[x], + }) + } } - f.Websocket.SubscribeToChannels(subscriptions) + return subscriptions, nil } // wsReadData gets and passes on websocket messages for processing func (f *FTX) wsReadData() { f.Websocket.Wg.Add(1) - defer f.Websocket.Wg.Done() for { select { case <-f.Websocket.ShutdownC: return - default: - resp, err := f.WebsocketConn.ReadMessage() - if err != nil { - f.Websocket.ReadMessageErrors <- err + resp := f.Websocket.Conn.ReadMessage() + if resp.Raw == nil { return } - f.Websocket.TrafficAlert <- struct{}{} - err = f.wsHandleData(resp.Raw) + + err := f.wsHandleData(resp.Raw) if err != nil { f.Websocket.DataHandler <- err } @@ -187,7 +249,10 @@ func (f *FTX) wsHandleData(respRaw []byte) error { var a asset.Item market, ok := result["market"] if ok { - p = currency.NewPairFromString(market.(string)) + p, err = currency.NewPairFromString(market.(string)) + if err != nil { + return err + } a, err = f.GetPairAssetType(p) if err != nil { return err @@ -220,7 +285,10 @@ func (f *FTX) wsHandleData(respRaw []byte) error { } err = f.WsProcessUpdateOB(&resultData.OBData, p, a) if err != nil { - f.wsResubToOB(p) + err2 := f.wsResubToOB(p) + if err2 != nil { + f.Websocket.DataHandler <- err2 + } return err } case wsTrades: @@ -238,7 +306,7 @@ func (f *FTX) wsHandleData(respRaw []byte) error { Err: err, } } - f.Websocket.DataHandler <- wshandler.TradeData{ + f.Websocket.DataHandler <- stream.TradeData{ Timestamp: resultData.TradeData[z].Time, CurrencyPair: p, AssetType: a, @@ -254,7 +322,11 @@ func (f *FTX) wsHandleData(respRaw []byte) error { if err != nil { return err } - pair := currency.NewPairFromString(resultData.OrderData.Market) + var pair currency.Pair + pair, err = currency.NewPairFromString(resultData.OrderData.Market) + if err != nil { + return err + } var assetType asset.Item assetType, err = f.GetPairAssetType(pair) if err != nil { @@ -301,7 +373,7 @@ func (f *FTX) wsHandleData(respRaw []byte) error { } f.Websocket.DataHandler <- resultData.FillsData default: - f.Websocket.DataHandler <- wshandler.UnhandledMessageWarning{Message: f.Name + wshandler.UnhandledMessage + string(respRaw)} + f.Websocket.DataHandler <- stream.UnhandledMessageWarning{Message: f.Name + stream.UnhandledMessage + string(respRaw)} } case wsPartial: switch result["channel"] { @@ -310,7 +382,10 @@ func (f *FTX) wsHandleData(respRaw []byte) error { var a asset.Item market, ok := result["market"] if ok { - p = currency.NewPairFromString(market.(string)) + p, err = currency.NewPairFromString(market.(string)) + if err != nil { + return err + } a, err = f.GetPairAssetType(p) if err != nil { return err @@ -323,7 +398,10 @@ func (f *FTX) wsHandleData(respRaw []byte) error { } err = f.WsProcessPartialOB(&resultData.OBData, p, a) if err != nil { - f.wsResubToOB(p) + err2 := f.wsResubToOB(p) + if err2 != nil { + f.Websocket.DataHandler <- err2 + } return err } // reset obchecksum failure blockage for pair @@ -337,27 +415,16 @@ func (f *FTX) wsHandleData(respRaw []byte) error { f.Websocket.DataHandler <- resultData.Data } case "error": - f.Websocket.DataHandler <- wshandler.UnhandledMessageWarning{Message: f.Name + wshandler.UnhandledMessage + string(respRaw)} + f.Websocket.DataHandler <- stream.UnhandledMessageWarning{ + Message: f.Name + stream.UnhandledMessage + string(respRaw), + } } return nil } -// Unsubscribe sends a websocket message to stop receiving data from the channel -func (f *FTX) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error { - var unSub WsSub - a, err := f.GetPairAssetType(channelToSubscribe.Currency) - if err != nil { - return err - } - unSub.Operation = unsubscribe - unSub.Channel = channelToSubscribe.Channel - unSub.Market = f.FormatExchangeCurrency(channelToSubscribe.Currency, a).String() - return f.WebsocketConn.SendJSONMessage(unSub) -} - // WsProcessUpdateOB processes an update on the orderbook func (f *FTX) WsProcessUpdateOB(data *WsOrderbookData, p currency.Pair, a asset.Item) error { - update := wsorderbook.WebsocketOrderbookUpdate{ + update := buffer.Update{ Asset: a, Pair: p, UpdateTime: timestampFromFloat64(data.Time), @@ -391,27 +458,25 @@ func (f *FTX) WsProcessUpdateOB(data *WsOrderbookData, p currency.Pair, a asset. p) return errors.New("checksum failed") } - f.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{ - Exchange: f.Name, - Asset: a, - Pair: p, - } - return nil } -func (f *FTX) wsResubToOB(p currency.Pair) { +func (f *FTX) wsResubToOB(p currency.Pair) error { if ok := obSuccess[p]; ok { - return + return nil } obSuccess[p] = true - channelToResubscribe := wshandler.WebsocketChannelSubscription{ + channelToResubscribe := &stream.ChannelSubscription{ Channel: wsOrderbook, Currency: p, } - f.Websocket.ResubscribeToChannel(channelToResubscribe) + err := f.Websocket.ResubscribeToChannel(channelToResubscribe) + if err != nil { + return fmt.Errorf("%s resubscribe to orderbook failure %s", f.Name, err) + } + return nil } // WsProcessPartialOB creates an OB from websocket data @@ -445,17 +510,7 @@ func (f *FTX) WsProcessPartialOB(data *WsOrderbookData, p currency.Pair, a asset Pair: p, ExchangeName: f.Name, } - - if err := f.Websocket.Orderbook.LoadSnapshot(&newOrderBook); err != nil { - return err - } - - f.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{ - Exchange: f.Name, - Asset: a, - Pair: p, - } - return nil + return f.Websocket.Orderbook.LoadSnapshot(&newOrderBook) } // CalcPartialOBChecksum calculates checksum of partial OB data received from WS diff --git a/exchanges/ftx/ftx_wrapper.go b/exchanges/ftx/ftx_wrapper.go index 7dcdfed2..89591206 100644 --- a/exchanges/ftx/ftx_wrapper.go +++ b/exchanges/ftx/ftx_wrapper.go @@ -18,8 +18,8 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" "github.com/thrasher-corp/gocryptotrader/log" "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) @@ -53,12 +53,7 @@ func (f *FTX) SetDefaults() { f.Verbose = true f.API.CredentialsValidator.RequiresKey = true f.API.CredentialsValidator.RequiresSecret = true - f.CurrencyPairs = currency.PairsManager{ - AssetTypes: asset.Items{ - asset.Spot, - asset.Futures, - }, - } + spot := currency.PairStore{ RequestFormat: ¤cy.PairFormat{ Uppercase: true, @@ -79,8 +74,17 @@ func (f *FTX) SetDefaults() { Delimiter: "-", }, } - f.CurrencyPairs.Store(asset.Spot, spot) - f.CurrencyPairs.Store(asset.Futures, futures) + + err := f.StoreAssetPairFormat(asset.Spot, spot) + if err != nil { + log.Errorln(log.ExchangeSys, err) + } + + err = f.StoreAssetPairFormat(asset.Futures, futures) + if err != nil { + log.Errorln(log.ExchangeSys, err) + } + f.Features = exchange.Features{ Supports: exchange.FeaturesSupported{ REST: true, @@ -139,7 +143,7 @@ func (f *FTX) SetDefaults() { f.API.Endpoints.URLDefault = ftxAPIURL f.API.Endpoints.URL = f.API.Endpoints.URLDefault - f.Websocket = wshandler.New() + f.Websocket = stream.New() f.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit f.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout f.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit @@ -157,41 +161,28 @@ func (f *FTX) Setup(exch *config.ExchangeConfig) error { return err } - err = f.Websocket.Setup( - &wshandler.WebsocketSetup{ - Enabled: exch.Features.Enabled.Websocket, - Verbose: exch.Verbose, - AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, - WebsocketTimeout: exch.WebsocketTrafficTimeout, - DefaultURL: ftxWSURL, - ExchangeName: exch.Name, - RunningURL: exch.API.Endpoints.WebsocketURL, - Connector: f.WsConnect, - Subscriber: f.Subscribe, - UnSubscriber: f.Unsubscribe, - Features: &f.Features.Supports.WebsocketCapabilities, - }) + err = f.Websocket.Setup(&stream.WebsocketSetup{ + Enabled: exch.Features.Enabled.Websocket, + Verbose: exch.Verbose, + AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, + WebsocketTimeout: exch.WebsocketTrafficTimeout, + DefaultURL: ftxWSURL, + ExchangeName: exch.Name, + RunningURL: exch.API.Endpoints.WebsocketURL, + Connector: f.WsConnect, + Subscriber: f.Subscribe, + UnSubscriber: f.Unsubscribe, + GenerateSubscriptions: f.GenerateDefaultSubscriptions, + Features: &f.Features.Supports.WebsocketCapabilities, + OrderbookBufferLimit: exch.WebsocketOrderbookBufferLimit, + }) if err != nil { return err } - - f.WebsocketConn = &wshandler.WebsocketConnection{ - ExchangeName: f.Name, - URL: f.Websocket.GetWebsocketURL(), - ProxyURL: f.Websocket.GetProxyAddress(), - Verbose: f.Verbose, + return f.Websocket.SetupNewConnection(stream.ConnectionSetup{ ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout, ResponseMaxLimit: exch.WebsocketResponseMaxLimit, - } - - f.Websocket.Orderbook.Setup( - exch.WebsocketOrderbookBufferLimit, - false, - false, - false, - false, - exch.Name) - return nil + }) } // Start starts the FTX go routine @@ -256,13 +247,17 @@ func (f *FTX) FetchTradablePairs(a asset.Item) ([]string, error) { // UpdateTradablePairs updates the exchanges available pairs and stores // them in the exchanges config func (f *FTX) UpdateTradablePairs(forceUpdate bool) error { - for x := range f.CurrencyPairs.AssetTypes { - pairs, err := f.FetchTradablePairs(f.CurrencyPairs.AssetTypes[x]) + assets := f.GetAssetTypes() + for x := range assets { + pairs, err := f.FetchTradablePairs(assets[x]) if err != nil { return err } - err = f.UpdatePairs(currency.NewPairsFromStrings(pairs), - f.CurrencyPairs.AssetTypes[x], false, forceUpdate) + p, err := currency.NewPairsFromStrings(pairs) + if err != nil { + return err + } + err = f.UpdatePairs(p, assets[x], false, forceUpdate) if err != nil { return err } @@ -272,26 +267,41 @@ func (f *FTX) UpdateTradablePairs(forceUpdate bool) error { // UpdateTicker updates and returns the ticker for a currency pair func (f *FTX) UpdateTicker(p currency.Pair, assetType asset.Item) (*ticker.Price, error) { - allPairs := f.GetEnabledPairs(assetType) + allPairs, err := f.GetEnabledPairs(assetType) + if err != nil { + return nil, err + } + if !allPairs.Contains(p, true) { allPairs = append(allPairs, p) } + markets, err := f.GetMarkets() if err != nil { return nil, err } for a := range allPairs { + formattedPair, err := f.FormatExchangeCurrency(allPairs[a], assetType) + if err != nil { + return nil, err + } + for x := range markets { - if markets[x].Name != f.FormatExchangeCurrency(allPairs[a], assetType).String() { + if markets[x].Name != formattedPair.String() { continue } var resp ticker.Price - resp.Pair = currency.NewPairFromString(markets[x].Name) + resp.Pair, err = currency.NewPairFromString(markets[x].Name) + if err != nil { + return nil, err + } resp.Last = markets[x].Last resp.Bid = markets[x].Bid resp.Ask = markets[x].Ask resp.LastUpdated = time.Now() - err = ticker.ProcessTicker(f.Name, &resp, assetType) + resp.AssetType = assetType + resp.ExchangeName = f.Name + err = ticker.ProcessTicker(&resp) if err != nil { return nil, err } @@ -321,7 +331,11 @@ func (f *FTX) FetchOrderbook(currency currency.Pair, assetType asset.Item) (*ord // UpdateOrderbook updates and returns the orderbook for a currency pair func (f *FTX) UpdateOrderbook(p currency.Pair, assetType asset.Item) (*orderbook.Base, error) { orderBook := new(orderbook.Base) - tempResp, err := f.GetOrderbook(f.FormatExchangeCurrency(p, assetType).String(), 0) + formattedPair, err := f.FormatExchangeCurrency(p, assetType) + if err != nil { + return nil, err + } + tempResp, err := f.GetOrderbook(formattedPair.String(), 0) if err != nil { return orderBook, err } @@ -424,9 +438,15 @@ func (f *FTX) GetFundingHistory() ([]exchange.FundHistory, error) { // GetExchangeHistory returns historic trade data within the timeframe provided. func (f *FTX) GetExchangeHistory(p currency.Pair, assetType asset.Item, timestampStart, timestampEnd time.Time) ([]exchange.TradeHistory, error) { - marketName := f.FormatExchangeCurrency(p, assetType).String() + marketName, err := f.FormatExchangeCurrency(p, assetType) + if err != nil { + return nil, err + } var resp []exchange.TradeHistory - trades, err := f.GetTrades(marketName, time.Unix(timestampStart.Unix(), 0), time.Unix(timestampEnd.Unix(), 0), 100) + trades, err := f.GetTrades(marketName.String(), + time.Unix(timestampStart.Unix(), 0), + time.Unix(timestampEnd.Unix(), 0), + 100) if err != nil { return nil, err } @@ -453,7 +473,10 @@ func (f *FTX) GetExchangeHistory(p currency.Pair, assetType asset.Item, timestam if len(trades) != 100 { break } - trades, err = f.GetTrades(marketName, time.Unix(timestampStart.Unix(), 0), time.Unix(trades[len(trades)-1].Time.Unix(), 0), 100) + trades, err = f.GetTrades(marketName.String(), + time.Unix(timestampStart.Unix(), 0), + time.Unix(trades[len(trades)-1].Time.Unix(), 0), + 100) if err != nil { return resp, err } @@ -475,7 +498,12 @@ func (f *FTX) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { s.Side = order.Bid } - tempResp, err := f.Order(f.FormatExchangeCurrency(s.Pair, s.AssetType).String(), + formattedPair, err := f.FormatExchangeCurrency(s.Pair, s.AssetType) + if err != nil { + return resp, err + } + + tempResp, err := f.Order(formattedPair.String(), s.Side.String(), s.Type.String(), "", @@ -533,11 +561,16 @@ func (f *FTX) CancelOrder(order *order.Cancel) error { // CancelAllOrders cancels all orders associated with a currency pair func (f *FTX) CancelAllOrders(orderCancellation *order.Cancel) (order.CancelAllResponse, error) { var resp order.CancelAllResponse - tempMap := make(map[string]string) - orders, err := f.GetOpenOrders(f.FormatExchangeCurrency(orderCancellation.Pair, orderCancellation.AssetType).String()) + formattedPair, err := f.FormatExchangeCurrency(orderCancellation.Pair, orderCancellation.AssetType) if err != nil { return resp, err } + orders, err := f.GetOpenOrders(formattedPair.String()) + if err != nil { + return resp, err + } + + tempMap := make(map[string]string) for x := range orders { _, err := f.DeleteOrder(strconv.FormatInt(orders[x].ID, 10)) if err != nil { @@ -598,7 +631,10 @@ func (f *FTX) GetOrderInfo(orderID string) (order.Detail, error) { if err != nil { return resp, err } - p := currency.NewPairFromString(orderData.Market) + p, err := currency.NewPairFromString(orderData.Market) + if err != nil { + return resp, err + } assetType, err := f.GetPairAssetType(p) if err != nil { return resp, err @@ -669,7 +705,7 @@ func (f *FTX) WithdrawFiatFundsToInternationalBank(_ *withdraw.Request) (*withdr } // GetWebsocket returns a pointer to the exchange websocket -func (f *FTX) GetWebsocket() (*wshandler.Websocket, error) { +func (f *FTX) GetWebsocket() (*stream.Websocket, error) { return f.Websocket, nil } @@ -681,12 +717,24 @@ func (f *FTX) GetActiveOrders(getOrdersRequest *order.GetOrdersRequest) ([]order if err != nil { return resp, err } + + formattedPair, err := f.FormatExchangeCurrency(getOrdersRequest.Pairs[x], assetType) + if err != nil { + return nil, err + } + var tempResp order.Detail - orderData, err := f.GetOpenOrders(f.FormatExchangeCurrency(getOrdersRequest.Pairs[x], assetType).String()) + orderData, err := f.GetOpenOrders(formattedPair.String()) if err != nil { return resp, err } for y := range orderData { + var p currency.Pair + p, err = currency.NewPairFromString(orderData[y].Market) + if err != nil { + return nil, err + } + tempResp.ID = strconv.FormatInt(orderData[y].ID, 10) tempResp.Amount = orderData[y].Size tempResp.AssetType = assetType @@ -694,7 +742,7 @@ func (f *FTX) GetActiveOrders(getOrdersRequest *order.GetOrdersRequest) ([]order tempResp.Date = orderData[y].CreatedAt tempResp.Exchange = f.Name tempResp.ExecutedAmount = orderData[y].Size - orderData[y].RemainingSize - tempResp.Pair = currency.NewPairFromString(orderData[y].Market) + tempResp.Pair = p tempResp.Price = orderData[y].Price tempResp.RemainingAmount = orderData[y].RemainingSize var orderVars OrderVars @@ -713,18 +761,25 @@ func (f *FTX) GetActiveOrders(getOrdersRequest *order.GetOrdersRequest) ([]order tempResp.Fee = orderVars.Fee resp = append(resp, tempResp) } - triggerOrderData, err := f.GetOpenTriggerOrders(f.FormatExchangeCurrency(getOrdersRequest.Pairs[x], assetType).String(), getOrdersRequest.Type.String()) + + triggerOrderData, err := f.GetOpenTriggerOrders(formattedPair.String(), + getOrdersRequest.Type.String()) if err != nil { return resp, err } for z := range triggerOrderData { + var p currency.Pair + p, err = currency.NewPairFromString(triggerOrderData[z].Market) + if err != nil { + return nil, err + } tempResp.ID = strconv.FormatInt(triggerOrderData[z].ID, 10) tempResp.Amount = triggerOrderData[z].Size tempResp.AssetType = assetType tempResp.Date = triggerOrderData[z].CreatedAt tempResp.Exchange = f.Name tempResp.ExecutedAmount = triggerOrderData[z].FilledSize - tempResp.Pair = currency.NewPairFromString(triggerOrderData[z].Market) + tempResp.Pair = p tempResp.Price = triggerOrderData[z].AvgFillPrice tempResp.RemainingAmount = triggerOrderData[z].Size - triggerOrderData[z].FilledSize tempResp.TriggerPrice = triggerOrderData[z].TriggerPrice @@ -757,12 +812,24 @@ func (f *FTX) GetOrderHistory(getOrdersRequest *order.GetOrdersRequest) ([]order if err != nil { return resp, err } - orderData, err := f.FetchOrderHistory(f.FormatExchangeCurrency(getOrdersRequest.Pairs[x], assetType).String(), + + formattedPair, err := f.FormatExchangeCurrency(getOrdersRequest.Pairs[x], + assetType) + if err != nil { + return nil, err + } + + orderData, err := f.FetchOrderHistory(formattedPair.String(), getOrdersRequest.StartTicks, getOrdersRequest.EndTicks, "") if err != nil { return resp, err } for y := range orderData { + var p currency.Pair + p, err = currency.NewPairFromString(orderData[y].Market) + if err != nil { + return nil, err + } tempResp.ID = strconv.FormatInt(orderData[y].ID, 10) tempResp.Amount = orderData[y].Size tempResp.AssetType = assetType @@ -770,7 +837,7 @@ func (f *FTX) GetOrderHistory(getOrdersRequest *order.GetOrdersRequest) ([]order tempResp.Date = orderData[y].CreatedAt tempResp.Exchange = f.Name tempResp.ExecutedAmount = orderData[y].Size - orderData[y].RemainingSize - tempResp.Pair = currency.NewPairFromString(orderData[y].Market) + tempResp.Pair = p tempResp.Price = orderData[y].Price tempResp.RemainingAmount = orderData[y].RemainingSize var orderVars OrderVars @@ -789,19 +856,28 @@ func (f *FTX) GetOrderHistory(getOrdersRequest *order.GetOrdersRequest) ([]order tempResp.Fee = orderVars.Fee resp = append(resp, tempResp) } - triggerOrderData, err := f.GetTriggerOrderHistory(f.FormatExchangeCurrency(getOrdersRequest.Pairs[x], assetType).String(), - getOrdersRequest.StartTicks, getOrdersRequest.EndTicks, strings.ToLower(getOrdersRequest.Side.String()), strings.ToLower(getOrdersRequest.Type.String()), "") + triggerOrderData, err := f.GetTriggerOrderHistory(formattedPair.String(), + getOrdersRequest.StartTicks, + getOrdersRequest.EndTicks, + strings.ToLower(getOrdersRequest.Side.String()), + strings.ToLower(getOrdersRequest.Type.String()), + "") if err != nil { return resp, err } for z := range triggerOrderData { + var p currency.Pair + p, err = currency.NewPairFromString(triggerOrderData[z].Market) + if err != nil { + return nil, err + } tempResp.ID = strconv.FormatInt(triggerOrderData[z].ID, 10) tempResp.Amount = triggerOrderData[z].Size tempResp.AssetType = assetType tempResp.Date = triggerOrderData[z].CreatedAt tempResp.Exchange = f.Name tempResp.ExecutedAmount = triggerOrderData[z].FilledSize - tempResp.Pair = currency.NewPairFromString(triggerOrderData[z].Market) + tempResp.Pair = p tempResp.Price = triggerOrderData[z].AvgFillPrice tempResp.RemainingAmount = triggerOrderData[z].Size - triggerOrderData[z].FilledSize tempResp.TriggerPrice = triggerOrderData[z].TriggerPrice @@ -831,21 +907,14 @@ func (f *FTX) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { // SubscribeToWebsocketChannels appends to ChannelsToSubscribe // which lets websocket.manageSubscriptions handle subscribing -func (f *FTX) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - f.Websocket.SubscribeToChannels(channels) - return nil +func (f *FTX) SubscribeToWebsocketChannels(channels []stream.ChannelSubscription) error { + return f.Websocket.SubscribeToChannels(channels) } // UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe // which lets websocket.manageSubscriptions handle unsubscribing -func (f *FTX) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - f.Websocket.RemoveSubscribedChannels(channels) - return nil -} - -// GetSubscriptions returns a copied list of subscriptions -func (f *FTX) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) { - return f.Websocket.GetSubscriptions(), nil +func (f *FTX) UnsubscribeToWebsocketChannels(channels []stream.ChannelSubscription) error { + return f.Websocket.UnsubscribeChannels(channels) } // AuthenticateWebsocket sends an authentication message to the websocket @@ -868,7 +937,12 @@ func (f *FTX) GetHistoricCandles(p currency.Pair, a asset.Item, start, end time. } } - ohlcData, err := f.GetHistoricalData(f.FormatExchangeCurrency(p, a).String(), + formattedPair, err := f.FormatExchangeCurrency(p, a) + if err != nil { + return kline.Item{}, err + } + + ohlcData, err := f.GetHistoricalData(formattedPair.String(), f.FormatExchangeKlineInterval(interval), strconv.FormatInt(int64(f.Features.Enabled.Kline.ResultLimit), 10), start, end) @@ -912,8 +986,14 @@ func (f *FTX) GetHistoricCandlesExtended(p currency.Pair, a asset.Item, start, e } dates := kline.CalcDateRanges(start, end, interval, f.Features.Enabled.Kline.ResultLimit) + + formattedPair, err := f.FormatExchangeCurrency(p, a) + if err != nil { + return kline.Item{}, err + } + for x := range dates { - ohlcData, err := f.GetHistoricalData(f.FormatExchangeCurrency(p, a).String(), + ohlcData, err := f.GetHistoricalData(formattedPair.String(), f.FormatExchangeKlineInterval(interval), strconv.FormatInt(int64(f.Features.Enabled.Kline.ResultLimit), 10), dates[x].Start, dates[x].End) diff --git a/exchanges/gateio/gateio.go b/exchanges/gateio/gateio.go index ba22b045..b0f19d2c 100644 --- a/exchanges/gateio/gateio.go +++ b/exchanges/gateio/gateio.go @@ -15,7 +15,6 @@ import ( exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/kline" "github.com/thrasher-corp/gocryptotrader/exchanges/request" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) @@ -44,7 +43,6 @@ const ( // Gateio is the overarching type across this package type Gateio struct { - WebsocketConn *wshandler.WebsocketConnection exchange.Base } diff --git a/exchanges/gateio/gateio_test.go b/exchanges/gateio/gateio_test.go index 394c3139..daa1c993 100644 --- a/exchanges/gateio/gateio_test.go +++ b/exchanges/gateio/gateio_test.go @@ -18,7 +18,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/kline" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) @@ -48,7 +48,7 @@ func TestMain(m *testing.M) { gConf.API.AuthenticatedWebsocketSupport = true gConf.API.Credentials.Key = apiKey gConf.API.Credentials.Secret = apiSecret - + g.Websocket = sharedtestvalues.NewTestWebsocket() err = g.Setup(gConf) if err != nil { log.Fatal("GateIO setup error", err) @@ -76,14 +76,14 @@ func TestGetMarketInfo(t *testing.T) { func TestSpotNewOrder(t *testing.T) { t.Parallel() - if !areTestAPIKeysSet() && !canManipulateRealOrders { + if !areTestAPIKeysSet() || !canManipulateRealOrders { t.Skip() } _, err := g.SpotNewOrder(SpotNewOrderRequestParams{ Symbol: "btc_usdt", - Amount: 1.1, - Price: 10.1, + Amount: -1, + Price: 100000, Type: order.Sell.Lower(), }) if err != nil { @@ -94,7 +94,7 @@ func TestSpotNewOrder(t *testing.T) { func TestCancelExistingOrder(t *testing.T) { t.Parallel() - if !areTestAPIKeysSet() && !canManipulateRealOrders { + if !areTestAPIKeysSet() || !canManipulateRealOrders { t.Skip() } @@ -485,29 +485,18 @@ func TestGetOrderInfo(t *testing.T) { // TestWsGetBalance dials websocket, sends balance request. func TestWsGetBalance(t *testing.T) { if !g.Websocket.IsEnabled() && !g.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() { - t.Skip(wshandler.WebsocketNotEnabled) - } - g.WebsocketConn = &wshandler.WebsocketConnection{ - ExchangeName: g.Name, - URL: gateioWebsocketEndpoint, - Verbose: g.Verbose, - RateLimit: gateioWebsocketRateLimit, - ResponseMaxLimit: exchange.DefaultWebsocketResponseMaxLimit, - ResponseCheckTimeout: exchange.DefaultWebsocketResponseCheckTimeout, + t.Skip(stream.WebsocketNotEnabled) } var dialer websocket.Dialer - err := g.WebsocketConn.Dial(&dialer, http.Header{}) + err := g.Websocket.Conn.Dial(&dialer, http.Header{}) if err != nil { t.Fatal(err) } go g.wsReadData() - resp, err := g.wsServerSignIn() + err = g.wsServerSignIn() if err != nil { t.Fatal(err) } - if resp.Result.Status != "success" { - t.Fatal("Unsuccessful login") - } _, err = g.wsGetBalance([]string{"EOS", "BTC"}) if err != nil { t.Error(err) @@ -521,30 +510,19 @@ func TestWsGetBalance(t *testing.T) { // TestWsGetOrderInfo dials websocket, sends order info request. func TestWsGetOrderInfo(t *testing.T) { if !g.Websocket.IsEnabled() && !g.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() { - t.Skip(wshandler.WebsocketNotEnabled) - } - g.WebsocketConn = &wshandler.WebsocketConnection{ - ExchangeName: g.Name, - URL: gateioWebsocketEndpoint, - Verbose: g.Verbose, - RateLimit: gateioWebsocketRateLimit, - ResponseMaxLimit: exchange.DefaultWebsocketResponseMaxLimit, - ResponseCheckTimeout: exchange.DefaultWebsocketResponseCheckTimeout, + t.Skip(stream.WebsocketNotEnabled) } var dialer websocket.Dialer - err := g.WebsocketConn.Dial(&dialer, http.Header{}) + err := g.Websocket.Conn.Dial(&dialer, http.Header{}) if err != nil { t.Fatal(err) } go g.wsReadData() - resp, err := g.wsServerSignIn() + err = g.wsServerSignIn() if err != nil { t.Fatal(err) } - if resp.Result.Status != "success" { - t.Fatal("Unsuccessful login") - } - _, err = g.wsGetOrderInfo("EOS_USDT", 0, 1000) + _, err = g.wsGetOrderInfo("EOS_USDT", 0, 100) if err != nil { t.Error(err) } @@ -554,47 +532,34 @@ func setupWSTestAuth(t *testing.T) { if wsSetupRan { return } - if !g.Websocket.IsEnabled() && !g.API.AuthenticatedWebsocketSupport { - t.Skip(wshandler.WebsocketNotEnabled) + if !g.Websocket.IsEnabled() && !g.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() { + t.Skip(stream.WebsocketNotEnabled) } - g.WebsocketConn = &wshandler.WebsocketConnection{ - ExchangeName: g.Name, - URL: gateioWebsocketEndpoint, - Verbose: g.Verbose, - RateLimit: gateioWebsocketRateLimit, - ResponseMaxLimit: exchange.DefaultWebsocketResponseMaxLimit, - ResponseCheckTimeout: exchange.DefaultWebsocketResponseCheckTimeout, - } - var dialer websocket.Dialer - err := g.WebsocketConn.Dial(&dialer, http.Header{}) - - g.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride() - g.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride() + err := g.Websocket.Connect() if err != nil { t.Fatal(err) } - go g.wsReadData() wsSetupRan = true } -// TestWsUnsubscribe dials websocket, sends an unsubscribe request. -func TestWsUnsubscribe(t *testing.T) { - setupWSTestAuth(t) - err := g.Unsubscribe(wshandler.WebsocketChannelSubscription{ - Channel: "ticker.subscribe", - Currency: currency.NewPairWithDelimiter(currency.BTC.String(), currency.USDT.String(), "_"), - }) - if err != nil { - t.Error(err) - } -} - // TestWsSubscribe dials websocket, sends a subscribe request. func TestWsSubscribe(t *testing.T) { setupWSTestAuth(t) - err := g.Subscribe(wshandler.WebsocketChannelSubscription{ - Channel: "ticker.subscribe", - Currency: currency.NewPairWithDelimiter(currency.BTC.String(), currency.USDT.String(), "_"), + err := g.Subscribe([]stream.ChannelSubscription{ + { + Channel: "ticker.subscribe", + Currency: currency.NewPairWithDelimiter(currency.BTC.String(), currency.USDT.String(), "_"), + }, + }) + if err != nil { + t.Error(err) + } + + err = g.Unsubscribe([]stream.ChannelSubscription{ + { + Channel: "ticker.subscribe", + Currency: currency.NewPairWithDelimiter(currency.BTC.String(), currency.USDT.String(), "_"), + }, }) if err != nil { t.Error(err) @@ -603,12 +568,12 @@ func TestWsSubscribe(t *testing.T) { func TestWsTicker(t *testing.T) { pressXToJSON := []byte(`{ - "method": "ticker.update", - "params": + "method": "ticker.update", + "params": [ - "BTC_USDT", + "BTC_USDT", { - "period": 86400, + "period": 86400, "open": "0", "close": "0", "high": "0", @@ -630,9 +595,9 @@ func TestWsTicker(t *testing.T) { func TestWsTrade(t *testing.T) { pressXToJSON := []byte(`{ "method": "trades.update", - "params": + "params": [ - "BTC_USDT", + "BTC_USDT", [ { "id": 7172173, @@ -654,23 +619,23 @@ func TestWsTrade(t *testing.T) { func TestWsDepth(t *testing.T) { pressXToJSON := []byte(`{ - "method": "depth.update", + "method": "depth.update", "params": [ - true, + true, { "asks": [ - [ + [ "8000.00", "9.6250" ] ], - "bids": [ - [ + "bids": [ + [ "8000.00", "9.6250" - ] + ] ] - }, + }, "BTC_USDT" ], "id": null @@ -736,7 +701,7 @@ func TestWsOrderUpdate(t *testing.T) { func TestWsBalanceUpdate(t *testing.T) { pressXToJSON := []byte(`{ - "method": "balance.update", + "method": "balance.update", "params": [{"EOS": {"available": "96.765323611874", "freeze": "11"}}], "id": 1234 }`) @@ -765,18 +730,24 @@ func TestParseTime(t *testing.T) { } func TestGetHistoricCandles(t *testing.T) { - currencyPair := currency.NewPairFromString("BTC_USDT") + currencyPair, err := currency.NewPairFromString("BTC_USDT") + if err != nil { + t.Fatal(err) + } startTime := time.Now().Add(-time.Hour * 6) - _, err := g.GetHistoricCandles(currencyPair, asset.Spot, startTime, time.Now(), kline.OneMin) + _, err = g.GetHistoricCandles(currencyPair, asset.Spot, startTime, time.Now(), kline.OneMin) if err != nil { t.Fatal(err) } } func TestGetHistoricCandlesExtended(t *testing.T) { - currencyPair := currency.NewPairFromString("BTC_USDT") + currencyPair, err := currency.NewPairFromString("BTC_USDT") + if err != nil { + t.Fatal(err) + } startTime := time.Now().Add(-time.Hour * 6) - _, err := g.GetHistoricCandlesExtended(currencyPair, asset.Spot, startTime, time.Now(), kline.OneMin) + _, err = g.GetHistoricCandlesExtended(currencyPair, asset.Spot, startTime, time.Now(), kline.OneMin) if err != nil { t.Fatal(err) } @@ -812,3 +783,26 @@ func Test_FormatExchangeKlineInterval(t *testing.T) { }) } } + +func TestGenerateDefaultSubscriptions(t *testing.T) { + err := g.CurrencyPairs.EnablePair(asset.Spot, currency.NewPair( + currency.LTC, + currency.USDT, + )) + if err != nil { + t.Fatal(err) + } + subs, err := g.GenerateDefaultSubscriptions() + if err != nil { + t.Fatal(err) + } + + payload, err := g.generatePayload(subs) + if err != nil { + t.Fatal(err) + } + + if len(payload) != 4 { + t.Fatal("unexpected payload length") + } +} diff --git a/exchanges/gateio/gateio_types.go b/exchanges/gateio/gateio_types.go index a8483a73..184923b4 100644 --- a/exchanges/gateio/gateio_types.go +++ b/exchanges/gateio/gateio_types.go @@ -5,6 +5,7 @@ import ( "time" "github.com/thrasher-corp/gocryptotrader/currency" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" ) // TimeInterval Interval represents interval enum. @@ -379,9 +380,10 @@ var WithdrawalFees = map[currency.Code]float64{ // WebsocketRequest defines the initial request in JSON type WebsocketRequest struct { - ID int64 `json:"id"` - Method string `json:"method"` - Params []interface{} `json:"params"` + ID int64 `json:"id"` + Method string `json:"method"` + Params []interface{} `json:"params"` + Channels []stream.ChannelSubscription `json:"-"` // used for tracking associated channel subs on batched requests } // WebsocketResponse defines a websocket response from gateio @@ -437,6 +439,7 @@ type WebsocketBalanceCurrency struct { // WebSocketOrderQueryResult data returned from a websocket ordre query holds slice of WebSocketOrderQueryRecords type WebSocketOrderQueryResult struct { + Error WebsocketError `json:"error"` Limit int `json:"limit"` Offset int `json:"offset"` Total int `json:"total"` @@ -462,7 +465,10 @@ type WebSocketOrderQueryRecords struct { // WebsocketAuthenticationResponse contains the result of a login request type WebsocketAuthenticationResponse struct { - Error string `json:"error,omitempty"` + Error struct { + Code int `json:"code"` + Message string `json:"message"` + } `json:"error"` Result struct { Status string `json:"status"` } `json:"result"` @@ -478,7 +484,7 @@ type wsGetBalanceRequest struct { // WsGetBalanceResponse stores WS GetBalance response type WsGetBalanceResponse struct { - Error interface{} `json:"error"` + Error WebsocketError `json:"error"` Result map[string]WsGetBalanceResponseData `json:"result"` ID int64 `json:"id"` } diff --git a/exchanges/gateio/gateio_websocket.go b/exchanges/gateio/gateio_websocket.go index ac2bd682..40a400c3 100644 --- a/exchanges/gateio/gateio_websocket.go +++ b/exchanges/gateio/gateio_websocket.go @@ -10,6 +10,7 @@ import ( "time" "github.com/gorilla/websocket" + "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/convert" "github.com/thrasher-corp/gocryptotrader/common/crypto" "github.com/thrasher-corp/gocryptotrader/currency" @@ -17,10 +18,9 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream/buffer" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook" - "github.com/thrasher-corp/gocryptotrader/log" ) const ( @@ -31,77 +31,87 @@ const ( // WsConnect initiates a websocket connection func (g *Gateio) WsConnect() error { if !g.Websocket.IsEnabled() || !g.IsEnabled() { - return errors.New(wshandler.WebsocketNotEnabled) + return errors.New(stream.WebsocketNotEnabled) } var dialer websocket.Dialer - err := g.WebsocketConn.Dial(&dialer, http.Header{}) + err := g.Websocket.Conn.Dial(&dialer, http.Header{}) if err != nil { return err } go g.wsReadData() - _, err = g.wsServerSignIn() - if err != nil { - log.Errorf(log.ExchangeSys, "%v - authentication failed: %v\n", g.Name, err) - g.Websocket.SetCanUseAuthenticatedEndpoints(false) + + if g.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) { + err = g.wsServerSignIn() + if err != nil { + g.Websocket.DataHandler <- err + g.Websocket.SetCanUseAuthenticatedEndpoints(false) + } else { + var authsubs []stream.ChannelSubscription + authsubs, err = g.GenerateAuthenticatedSubscriptions() + if err != nil { + g.Websocket.DataHandler <- err + g.Websocket.SetCanUseAuthenticatedEndpoints(false) + } else { + err = g.Websocket.SubscribeToChannels(authsubs) + if err != nil { + g.Websocket.DataHandler <- err + g.Websocket.SetCanUseAuthenticatedEndpoints(false) + } + } + } } - g.GenerateAuthenticatedSubscriptions() - g.GenerateDefaultSubscriptions() - return nil + + subs, err := g.GenerateDefaultSubscriptions() + if err != nil { + return err + } + return g.Websocket.SubscribeToChannels(subs) } -func (g *Gateio) wsServerSignIn() (*WebsocketAuthenticationResponse, error) { - if !g.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) { - return nil, fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", g.Name) - } +func (g *Gateio) wsServerSignIn() error { nonce := int(time.Now().Unix() * 1000) sigTemp := g.GenerateSignature(strconv.Itoa(nonce)) signature := crypto.Base64Encode(sigTemp) signinWsRequest := WebsocketRequest{ - ID: g.WebsocketConn.GenerateMessageID(true), + ID: g.Websocket.Conn.GenerateMessageID(false), Method: "server.sign", Params: []interface{}{g.API.Credentials.Key, signature, nonce}, } - resp, err := g.WebsocketConn.SendMessageReturnResponse(signinWsRequest.ID, signinWsRequest) + resp, err := g.Websocket.Conn.SendMessageReturnResponse(signinWsRequest.ID, + signinWsRequest) if err != nil { g.Websocket.SetCanUseAuthenticatedEndpoints(false) - return nil, err + return err } var response WebsocketAuthenticationResponse err = json.Unmarshal(resp, &response) if err != nil { g.Websocket.SetCanUseAuthenticatedEndpoints(false) - return nil, err + return err } if response.Result.Status == "success" { g.Websocket.SetCanUseAuthenticatedEndpoints(true) + return nil } - return &response, nil + + return fmt.Errorf("%s cannot authenticate websocket connection: %s", + g.Name, + response.Result.Status) } // wsReadData receives and passes on websocket messages for processing func (g *Gateio) wsReadData() { g.Websocket.Wg.Add(1) - - defer func() { - g.Websocket.Wg.Done() - }() + defer g.Websocket.Wg.Done() for { - select { - case <-g.Websocket.ShutdownC: + resp := g.Websocket.Conn.ReadMessage() + if resp.Raw == nil { return - - default: - resp, err := g.WebsocketConn.ReadMessage() - if err != nil { - g.Websocket.ReadMessageErrors <- err - return - } - g.Websocket.TrafficAlert <- struct{}{} - err = g.wsHandleData(resp.Raw) - if err != nil { - g.Websocket.DataHandler <- err - } + } + err := g.wsHandleData(resp.Raw) + if err != nil { + g.Websocket.DataHandler <- err } } } @@ -114,8 +124,7 @@ func (g *Gateio) wsHandleData(respRaw []byte) error { } if result.ID > 0 { - if g.WebsocketConn.IsIDWaitingForResponse(result.ID) { - g.WebsocketConn.SetResponseIDAndData(result.ID, respRaw) + if g.Websocket.Match.IncomingWithData(result.ID, respRaw) { return nil } } @@ -125,8 +134,7 @@ func (g *Gateio) wsHandleData(respRaw []byte) error { g.Websocket.SetCanUseAuthenticatedEndpoints(false) return fmt.Errorf("%v - authentication failed: %v", g.Name, err) } - return fmt.Errorf("%v error %s", - g.Name, result.Error.Message) + return fmt.Errorf("%v error %s", g.Name, result.Error.Message) } switch { @@ -142,6 +150,12 @@ func (g *Gateio) wsHandleData(respRaw []byte) error { return err } + var p currency.Pair + p, err = currency.NewPairFromString(c) + if err != nil { + return err + } + g.Websocket.DataHandler <- &ticker.Price{ ExchangeName: g.Name, Open: wsTicker.Open, @@ -152,7 +166,7 @@ func (g *Gateio) wsHandleData(respRaw []byte) error { Low: wsTicker.Low, Last: wsTicker.Last, AssetType: asset.Spot, - Pair: currency.NewPairFromString(c), + Pair: p, } case strings.Contains(result.Method, "trades"): @@ -167,6 +181,12 @@ func (g *Gateio) wsHandleData(respRaw []byte) error { return err } + var p currency.Pair + p, err = currency.NewPairFromString(c) + if err != nil { + return err + } + for i := range trades { var tSide order.Side tSide, err = order.StringToOrderSide(trades[i].Type) @@ -176,9 +196,9 @@ func (g *Gateio) wsHandleData(respRaw []byte) error { Err: err, } } - g.Websocket.DataHandler <- wshandler.TradeData{ + g.Websocket.DataHandler <- stream.TradeData{ Timestamp: time.Now(), - CurrencyPair: currency.NewPairFromString(c), + CurrencyPair: p, AssetType: asset.Spot, Exchange: g.Name, Price: trades[i].Price, @@ -244,7 +264,13 @@ func (g *Gateio) wsHandleData(respRaw []byte) error { if err != nil { return err } - p := currency.NewPairFromString(invalidJSON["market"].(string)) + + var p currency.Pair + p, err = currency.NewPairFromString(invalidJSON["market"].(string)) + if err != nil { + return err + } + var a asset.Item a, err = g.GetPairAssetType(p) if err != nil { @@ -324,6 +350,12 @@ func (g *Gateio) wsHandleData(respRaw []byte) error { g.Websocket.DataHandler <- errors.New("gatio websocket error - cannot access ask or bid data") } + var p currency.Pair + p, err = currency.NewPairFromString(c) + if err != nil { + return err + } + if IsSnapshot { if !askOk { g.Websocket.DataHandler <- errors.New("gatio websocket error - cannot access ask data") @@ -337,7 +369,7 @@ func (g *Gateio) wsHandleData(respRaw []byte) error { newOrderBook.Asks = asks newOrderBook.Bids = bids newOrderBook.AssetType = asset.Spot - newOrderBook.Pair = currency.NewPairFromString(c) + newOrderBook.Pair = p newOrderBook.ExchangeName = g.Name err = g.Websocket.Orderbook.LoadSnapshot(&newOrderBook) @@ -345,25 +377,17 @@ func (g *Gateio) wsHandleData(respRaw []byte) error { return err } } else { - err = g.Websocket.Orderbook.Update( - &wsorderbook.WebsocketOrderbookUpdate{ - Asks: asks, - Bids: bids, - Pair: currency.NewPairFromString(c), - UpdateTime: time.Now(), - Asset: asset.Spot, - }) + err = g.Websocket.Orderbook.Update(&buffer.Update{ + Asks: asks, + Bids: bids, + Pair: p, + UpdateTime: time.Now(), + Asset: asset.Spot, + }) if err != nil { return err } } - - g.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{ - Pair: currency.NewPairFromString(c), - Asset: asset.Spot, - Exchange: g.Name, - } - case strings.Contains(result.Method, "kline"): var data []interface{} err = json.Unmarshal(result.Params[0], &data) @@ -391,9 +415,14 @@ func (g *Gateio) wsHandleData(respRaw []byte) error { return err } - g.Websocket.DataHandler <- wshandler.KlineData{ + p, err := currency.NewPairFromString(data[7].(string)) + if err != nil { + return err + } + + g.Websocket.DataHandler <- stream.KlineData{ Timestamp: time.Now(), - Pair: currency.NewPairFromString(data[7].(string)), + Pair: p, AssetType: asset.Spot, Exchange: g.Name, OpenPrice: open, @@ -403,36 +432,48 @@ func (g *Gateio) wsHandleData(respRaw []byte) error { Volume: volume, } default: - g.Websocket.DataHandler <- wshandler.UnhandledMessageWarning{Message: g.Name + wshandler.UnhandledMessage + string(respRaw)} + g.Websocket.DataHandler <- stream.UnhandledMessageWarning{ + Message: g.Name + stream.UnhandledMessage + string(respRaw), + } return nil } return nil } -// GenerateAuthenticatedSubscriptions Adds authenticated subscriptions to websocket to be handled by ManageSubscriptions() -func (g *Gateio) GenerateAuthenticatedSubscriptions() { +// GenerateAuthenticatedSubscriptions returns authenticated subscriptions +func (g *Gateio) GenerateAuthenticatedSubscriptions() ([]stream.ChannelSubscription, error) { if !g.Websocket.CanUseAuthenticatedEndpoints() { - return + return nil, nil } var channels = []string{"balance.subscribe", "order.subscribe"} - var subscriptions []wshandler.WebsocketChannelSubscription - enabledCurrencies := g.GetEnabledPairs(asset.Spot) + var subscriptions []stream.ChannelSubscription + enabledCurrencies, err := g.GetEnabledPairs(asset.Spot) + if err != nil { + return nil, err + } for i := range channels { for j := range enabledCurrencies { - subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{ + subscriptions = append(subscriptions, stream.ChannelSubscription{ Channel: channels[i], Currency: enabledCurrencies[j], + Asset: asset.Spot, }) } } - g.Websocket.SubscribeToChannels(subscriptions) + return subscriptions, nil } -// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() -func (g *Gateio) GenerateDefaultSubscriptions() { - var channels = []string{"ticker.subscribe", "trades.subscribe", "depth.subscribe", "kline.subscribe"} - var subscriptions []wshandler.WebsocketChannelSubscription - enabledCurrencies := g.GetEnabledPairs(asset.Spot) +// GenerateDefaultSubscriptions returns default subscriptions +func (g *Gateio) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, error) { + var channels = []string{"ticker.subscribe", + "trades.subscribe", + "depth.subscribe", + "kline.subscribe"} + var subscriptions []stream.ChannelSubscription + enabledCurrencies, err := g.GetEnabledPairs(asset.Spot) + if err != nil { + return nil, err + } for i := range channels { for j := range enabledCurrencies { params := make(map[string]interface{}) @@ -442,66 +483,157 @@ func (g *Gateio) GenerateDefaultSubscriptions() { } else if strings.EqualFold(channels[i], "kline.subscribe") { params["interval"] = 1800 } - subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{ + + fpair, err := g.FormatExchangeCurrency(enabledCurrencies[j], + asset.Spot) + if err != nil { + return nil, err + } + + subscriptions = append(subscriptions, stream.ChannelSubscription{ Channel: channels[i], - Currency: enabledCurrencies[j], + Currency: fpair.Upper(), Params: params, + Asset: asset.Spot, }) } } - g.Websocket.SubscribeToChannels(subscriptions) + return subscriptions, nil } // Subscribe sends a websocket message to receive data from the channel -func (g *Gateio) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error { - params := []interface{}{g.FormatExchangeCurrency(channelToSubscribe.Currency, - asset.Spot).Upper()} - - for i := range channelToSubscribe.Params { - params = append(params, channelToSubscribe.Params[i]) - } - - subscribe := WebsocketRequest{ - ID: g.WebsocketConn.GenerateMessageID(true), - Method: channelToSubscribe.Channel, - Params: params, - } - - resp, err := g.WebsocketConn.SendMessageReturnResponse(subscribe.ID, subscribe) +func (g *Gateio) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error { + payloads, err := g.generatePayload(channelsToSubscribe) if err != nil { return err } - var response WebsocketAuthenticationResponse - err = json.Unmarshal(resp, &response) - if err != nil { - return err + + var errs common.Errors + for k := range payloads { + resp, err := g.Websocket.Conn.SendMessageReturnResponse(payloads[k].ID, payloads[k]) + if err != nil { + errs = append(errs, err) + continue + } + var response WebsocketAuthenticationResponse + err = json.Unmarshal(resp, &response) + if err != nil { + errs = append(errs, err) + continue + } + if response.Result.Status != "success" { + errs = append(errs, fmt.Errorf("%v could not subscribe to %v", + g.Name, + payloads[k].Method)) + continue + } + g.Websocket.AddSuccessfulSubscriptions(payloads[k].Channels...) } - if response.Result.Status != "success" { - return fmt.Errorf("%v could not subscribe to %v", g.Name, channelToSubscribe.Channel) + if errs != nil { + return errs } return nil } +func (g *Gateio) generatePayload(channelsToSubscribe []stream.ChannelSubscription) ([]WebsocketRequest, error) { + if len(channelsToSubscribe) == 0 { + return nil, errors.New("cannot generate payload, no channels supplied") + } + + var payloads []WebsocketRequest +channels: + for i := range channelsToSubscribe { + // Ensures params are in order + params := []interface{}{channelsToSubscribe[i].Currency} + if strings.EqualFold(channelsToSubscribe[i].Channel, "depth.subscribe") { + params = append(params, + channelsToSubscribe[i].Params["limit"], + channelsToSubscribe[i].Params["interval"]) + } else if strings.EqualFold(channelsToSubscribe[i].Channel, "kline.subscribe") { + params = append(params, channelsToSubscribe[i].Params["interval"]) + } + + for j := range payloads { + if payloads[j].Method == channelsToSubscribe[i].Channel { + switch { + case strings.EqualFold(channelsToSubscribe[i].Channel, "depth.subscribe"): + if len(payloads[j].Params) == 3 { + // If more than one currency pair we need to send as + // matrix + _, ok := payloads[j].Params[0].(currency.Pair) + if ok { + var bucket = payloads[j].Params + payloads[j].Params = nil + payloads[j].Params = append(payloads[j].Params, bucket) + } + } + + payloads[j].Params = append(payloads[j].Params, params) + case strings.EqualFold(channelsToSubscribe[i].Channel, "kline.subscribe"): + // Can only subscribe one market at the same time, market + // list is not supported currently. For multiple + // subscriptions, only the last one takes effect. + default: + payloads[j].Params = append(payloads[j].Params, params...) + } + payloads[j].Channels = append(payloads[j].Channels, channelsToSubscribe[i]) + continue channels + } + } + + payloads = append(payloads, WebsocketRequest{ + ID: g.Websocket.Conn.GenerateMessageID(false), + Method: channelsToSubscribe[i].Channel, + Params: params, + Channels: []stream.ChannelSubscription{channelsToSubscribe[i]}, + }) + } + return payloads, nil +} + // Unsubscribe sends a websocket message to stop receiving data from the channel -func (g *Gateio) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error { - unsbuscribeText := strings.Replace(channelToSubscribe.Channel, "subscribe", "unsubscribe", 1) - subscribe := WebsocketRequest{ - ID: g.WebsocketConn.GenerateMessageID(true), - Method: unsbuscribeText, - Params: []interface{}{g.FormatExchangeCurrency(channelToSubscribe.Currency, - asset.Spot).Upper(), 1800}, - } - resp, err := g.WebsocketConn.SendMessageReturnResponse(subscribe.ID, subscribe) - if err != nil { - return err - } - var response WebsocketAuthenticationResponse - err = json.Unmarshal(resp, &response) - if err != nil { - return err - } - if response.Result.Status != "success" { - return fmt.Errorf("%v could not subscribe to %v", g.Name, channelToSubscribe.Channel) +func (g *Gateio) Unsubscribe(channelsToUnsubscribe []stream.ChannelSubscription) error { + // NOTE: This function does not take in parameters, it cannot unsubscribe a + // single item but a full channel. i.e. if you subscribe to ticker BTC_USDT + // & LTC_USDT this function will unsubscribe both. This function will be + // kept unlinked to the websocket subsystem and a full connection flush will + // occur when currency items are disabled. + var channelsThusFar []string + for i := range channelsToUnsubscribe { + if common.StringDataCompare(channelsThusFar, + channelsToUnsubscribe[i].Channel) { + continue + } + + channelsThusFar = append(channelsThusFar, + channelsToUnsubscribe[i].Channel) + + unsubscribeText := strings.Replace(channelsToUnsubscribe[i].Channel, + "subscribe", + "unsubscribe", + 1) + + unsubscribe := WebsocketRequest{ + ID: g.Websocket.Conn.GenerateMessageID(false), + Method: unsubscribeText, + Params: []interface{}{channelsToUnsubscribe[i].Currency.String()}, + } + + resp, err := g.Websocket.Conn.SendMessageReturnResponse(unsubscribe.ID, + unsubscribe) + if err != nil { + return err + } + var response WebsocketAuthenticationResponse + err = json.Unmarshal(resp, &response) + if err != nil { + return err + } + if response.Result.Status != "success" { + return fmt.Errorf("%v could not subscribe to %v", + g.Name, + channelsToUnsubscribe[i].Channel) + } } return nil } @@ -511,11 +643,11 @@ func (g *Gateio) wsGetBalance(currencies []string) (*WsGetBalanceResponse, error return nil, fmt.Errorf("%v not authorised to get balance", g.Name) } balanceWsRequest := wsGetBalanceRequest{ - ID: g.WebsocketConn.GenerateMessageID(false), + ID: g.Websocket.Conn.GenerateMessageID(false), Method: "balance.query", Params: currencies, } - resp, err := g.WebsocketConn.SendMessageReturnResponse(balanceWsRequest.ID, balanceWsRequest) + resp, err := g.Websocket.Conn.SendMessageReturnResponse(balanceWsRequest.ID, balanceWsRequest) if err != nil { return nil, err } @@ -525,6 +657,12 @@ func (g *Gateio) wsGetBalance(currencies []string) (*WsGetBalanceResponse, error return &balance, err } + if balance.Error.Message != "" { + return nil, fmt.Errorf("%s websocket error: %s", + g.Name, + balance.Error.Message) + } + return &balance, nil } @@ -533,7 +671,7 @@ func (g *Gateio) wsGetOrderInfo(market string, offset, limit int) (*WebSocketOrd return nil, fmt.Errorf("%v not authorised to get order info", g.Name) } ord := WebsocketRequest{ - ID: g.WebsocketConn.GenerateMessageID(true), + ID: g.Websocket.Conn.GenerateMessageID(false), Method: "order.query", Params: []interface{}{ market, @@ -541,14 +679,23 @@ func (g *Gateio) wsGetOrderInfo(market string, offset, limit int) (*WebSocketOrd limit, }, } - resp, err := g.WebsocketConn.SendMessageReturnResponse(ord.ID, ord) + + resp, err := g.Websocket.Conn.SendMessageReturnResponse(ord.ID, ord) if err != nil { return nil, err } + var orderQuery WebSocketOrderQueryResult err = json.Unmarshal(resp, &orderQuery) if err != nil { return &orderQuery, err } + + if orderQuery.Error.Message != "" { + return nil, fmt.Errorf("%s websocket error: %s", + g.Name, + orderQuery.Error.Message) + } + return &orderQuery, nil } diff --git a/exchanges/gateio/gateio_wrapper.go b/exchanges/gateio/gateio_wrapper.go index d240aa05..60e59104 100644 --- a/exchanges/gateio/gateio_wrapper.go +++ b/exchanges/gateio/gateio_wrapper.go @@ -20,8 +20,8 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" "github.com/thrasher-corp/gocryptotrader/log" "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) @@ -57,18 +57,11 @@ func (g *Gateio) SetDefaults() { g.API.CredentialsValidator.RequiresKey = true g.API.CredentialsValidator.RequiresSecret = true - g.CurrencyPairs = currency.PairsManager{ - AssetTypes: asset.Items{ - asset.Spot, - }, - UseGlobalFormat: true, - RequestFormat: ¤cy.PairFormat{ - Delimiter: "_", - }, - ConfigFormat: ¤cy.PairFormat{ - Delimiter: "_", - Uppercase: true, - }, + requestFmt := ¤cy.PairFormat{Delimiter: currency.UnderscoreDelimiter} + configFmt := ¤cy.PairFormat{Delimiter: currency.UnderscoreDelimiter, Uppercase: true} + err := g.SetGlobalPairsManager(requestFmt, configFmt, asset.Spot) + if err != nil { + log.Errorln(log.ExchangeSys, err) } g.Features = exchange.Features{ @@ -99,8 +92,7 @@ func (g *Gateio) SetDefaults() { OrderbookFetching: true, TradeFetching: true, KlineFetching: true, - Subscribe: true, - Unsubscribe: true, + FullPayloadSubscribe: true, AuthenticatedEndpoints: true, MessageCorrelation: true, GetOrder: true, @@ -139,7 +131,7 @@ func (g *Gateio) SetDefaults() { g.API.Endpoints.URLSecondaryDefault = gateioMarketURL g.API.Endpoints.URLSecondary = g.API.Endpoints.URLSecondaryDefault g.API.Endpoints.WebsocketURL = gateioWebsocketEndpoint - g.Websocket = wshandler.New() + g.Websocket = stream.New() g.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit g.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout g.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit @@ -157,42 +149,30 @@ func (g *Gateio) Setup(exch *config.ExchangeConfig) error { return err } - err = g.Websocket.Setup( - &wshandler.WebsocketSetup{ - Enabled: exch.Features.Enabled.Websocket, - Verbose: exch.Verbose, - AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, - WebsocketTimeout: exch.WebsocketTrafficTimeout, - DefaultURL: gateioWebsocketEndpoint, - ExchangeName: exch.Name, - RunningURL: exch.API.Endpoints.WebsocketURL, - Connector: g.WsConnect, - Subscriber: g.Subscribe, - UnSubscriber: g.Unsubscribe, - Features: &g.Features.Supports.WebsocketCapabilities, - }) + err = g.Websocket.Setup(&stream.WebsocketSetup{ + Enabled: exch.Features.Enabled.Websocket, + Verbose: exch.Verbose, + AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, + WebsocketTimeout: exch.WebsocketTrafficTimeout, + DefaultURL: gateioWebsocketEndpoint, + ExchangeName: exch.Name, + RunningURL: exch.API.Endpoints.WebsocketURL, + Connector: g.WsConnect, + Subscriber: g.Subscribe, + GenerateSubscriptions: g.GenerateDefaultSubscriptions, + Features: &g.Features.Supports.WebsocketCapabilities, + OrderbookBufferLimit: exch.WebsocketOrderbookBufferLimit, + BufferEnabled: true, + }) if err != nil { return err } - g.WebsocketConn = &wshandler.WebsocketConnection{ - ExchangeName: g.Name, - URL: g.Websocket.GetWebsocketURL(), - ProxyURL: g.Websocket.GetProxyAddress(), - Verbose: g.Verbose, + return g.Websocket.SetupNewConnection(stream.ConnectionSetup{ + RateLimit: gateioWebsocketRateLimit, ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout, ResponseMaxLimit: exch.WebsocketResponseMaxLimit, - RateLimit: gateioWebsocketRateLimit, - } - - g.Websocket.Orderbook.Setup( - exch.WebsocketOrderbookBufferLimit, - true, - false, - false, - false, - exch.Name) - return nil + }) } // Start starts the GateIO go routine @@ -233,35 +213,42 @@ func (g *Gateio) UpdateTradablePairs(forceUpdate bool) error { return err } - return g.UpdatePairs(currency.NewPairsFromStrings(pairs), asset.Spot, false, forceUpdate) + p, err := currency.NewPairsFromStrings(pairs) + if err != nil { + return err + } + return g.UpdatePairs(p, asset.Spot, false, forceUpdate) } // UpdateTicker updates and returns the ticker for a currency pair func (g *Gateio) UpdateTicker(p currency.Pair, assetType asset.Item) (*ticker.Price, error) { - tickerPrice := new(ticker.Price) result, err := g.GetTickers() if err != nil { - return tickerPrice, err + return nil, err + } + pairs, err := g.GetEnabledPairs(assetType) + if err != nil { + return nil, err } - pairs := g.GetEnabledPairs(assetType) for i := range pairs { for k := range result { if !strings.EqualFold(k, pairs[i].String()) { continue } - tickerPrice = &ticker.Price{ - Last: result[k].Last, - High: result[k].High, - Low: result[k].Low, - Volume: result[k].BaseVolume, - QuoteVolume: result[k].QuoteVolume, - Open: result[k].Open, - Close: result[k].Close, - Pair: pairs[i], - } - err = ticker.ProcessTicker(g.Name, tickerPrice, assetType) + + err = ticker.ProcessTicker(&ticker.Price{ + Last: result[k].Last, + High: result[k].High, + Low: result[k].Low, + Volume: result[k].BaseVolume, + QuoteVolume: result[k].QuoteVolume, + Open: result[k].Open, + Close: result[k].Close, + Pair: pairs[i], + ExchangeName: g.Name, + AssetType: assetType}) if err != nil { - log.Error(log.Ticker, err) + return nil, err } } } @@ -290,9 +277,12 @@ func (g *Gateio) FetchOrderbook(p currency.Pair, assetType asset.Item) (*orderbo // UpdateOrderbook updates and returns the orderbook for a currency pair func (g *Gateio) UpdateOrderbook(p currency.Pair, assetType asset.Item) (*orderbook.Base, error) { orderBook := new(orderbook.Base) - curr := g.FormatExchangeCurrency(p, assetType).String() + curr, err := g.FormatExchangeCurrency(p, assetType) + if err != nil { + return nil, err + } - orderbookNew, err := g.GetOrderbook(curr) + orderbookNew, err := g.GetOrderbook(curr.String()) if err != nil { return orderBook, err } @@ -479,8 +469,13 @@ func (g *Gateio) CancelOrder(order *order.Cancel) error { if err != nil { return err } - _, err = g.CancelExistingOrder(orderIDInt, - g.FormatExchangeCurrency(order.Pair, order.AssetType).String()) + + fpair, err := g.FormatExchangeCurrency(order.Pair, order.AssetType) + if err != nil { + return err + } + + _, err = g.CancelExistingOrder(orderIDInt, fpair.String()) return err } @@ -516,6 +511,12 @@ func (g *Gateio) GetOrderInfo(orderID string) (order.Detail, error) { if err != nil { return orderDetail, errors.New("failed to get open orders") } + + format, err := g.GetPairFormat(asset.Spot, false) + if err != nil { + return orderDetail, err + } + for x := range orders.Orders { if orders.Orders[x].OrderNumber != orderID { continue @@ -528,8 +529,11 @@ func (g *Gateio) GetOrderInfo(orderID string) (order.Detail, error) { orderDetail.Date = time.Unix(orders.Orders[x].Timestamp, 0) orderDetail.Status = order.Status(orders.Orders[x].Status) orderDetail.Price = orders.Orders[x].Rate - orderDetail.Pair = currency.NewPairDelimiter(orders.Orders[x].CurrencyPair, - g.GetPairFormat(asset.Spot, false).Delimiter) + orderDetail.Pair, err = currency.NewPairDelimiter(orders.Orders[x].CurrencyPair, + format.Delimiter) + if err != nil { + return orderDetail, err + } if strings.EqualFold(orders.Orders[x].Type, order.Ask.String()) { orderDetail.Side = order.Ask } else if strings.EqualFold(orders.Orders[x].Type, order.Bid.String()) { @@ -572,11 +576,6 @@ func (g *Gateio) WithdrawFiatFundsToInternationalBank(withdrawRequest *withdraw. return nil, common.ErrFunctionNotSupported } -// GetWebsocket returns a pointer to the exchange websocket -func (g *Gateio) GetWebsocket() (*wshandler.Websocket, error) { - return g.Websocket, nil -} - // GetFeeByType returns an estimate of fee based on type of transaction func (g *Gateio) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { if !g.AllowAuthenticatedRequest() && // Todo check connection status @@ -609,11 +608,15 @@ func (g *Gateio) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, e if resp.WebSocketOrderQueryRecords[j].OrderType == 1 { orderType = order.Limit } + p, err := currency.NewPairFromString(resp.WebSocketOrderQueryRecords[j].Market) + if err != nil { + return nil, err + } orders = append(orders, order.Detail{ Exchange: g.Name, AccountID: strconv.FormatInt(resp.WebSocketOrderQueryRecords[j].User, 10), ID: strconv.FormatInt(resp.WebSocketOrderQueryRecords[j].ID, 10), - Pair: currency.NewPairFromString(resp.WebSocketOrderQueryRecords[j].Market), + Pair: p, Side: orderSide, Type: orderType, Date: convert.TimeFromUnixTimestampDecimal(resp.WebSocketOrderQueryRecords[j].Ctime), @@ -634,13 +637,21 @@ func (g *Gateio) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, e return nil, err } + format, err := g.GetPairFormat(asset.Spot, false) + if err != nil { + return nil, err + } + for i := range resp.Orders { if resp.Orders[i].Status != "open" { continue } - - symbol := currency.NewPairDelimiter(resp.Orders[i].CurrencyPair, - g.GetPairFormat(asset.Spot, false).Delimiter) + var symbol currency.Pair + symbol, err = currency.NewPairDelimiter(resp.Orders[i].CurrencyPair, + format.Delimiter) + if err != nil { + return nil, err + } side := order.Side(strings.ToUpper(resp.Orders[i].Type)) orderDate := time.Unix(resp.Orders[i].Timestamp, 0) orders = append(orders, order.Detail{ @@ -673,16 +684,24 @@ func (g *Gateio) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, e trades = append(trades, resp.Trades...) } + format, err := g.GetPairFormat(asset.Spot, false) + if err != nil { + return nil, err + } + var orders []order.Detail - for _, trade := range trades { - symbol := currency.NewPairDelimiter(trade.Pair, - g.GetPairFormat(asset.Spot, false).Delimiter) - side := order.Side(strings.ToUpper(trade.Type)) - orderDate := time.Unix(trade.TimeUnix, 0) + for i := range trades { + var symbol currency.Pair + symbol, err = currency.NewPairDelimiter(trades[i].Pair, format.Delimiter) + if err != nil { + return nil, err + } + side := order.Side(strings.ToUpper(trades[i].Type)) + orderDate := time.Unix(trades[i].TimeUnix, 0) orders = append(orders, order.Detail{ - ID: strconv.FormatInt(trade.OrderID, 10), - Amount: trade.Amount, - Price: trade.Rate, + ID: strconv.FormatInt(trades[i].OrderID, 10), + Amount: trades[i].Amount, + Price: trades[i].Rate, Date: orderDate, Side: side, Exchange: g.Name, @@ -695,29 +714,9 @@ func (g *Gateio) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, e return orders, nil } -// SubscribeToWebsocketChannels appends to ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle subscribing -func (g *Gateio) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - g.Websocket.SubscribeToChannels(channels) - return nil -} - -// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle unsubscribing -func (g *Gateio) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - g.Websocket.RemoveSubscribedChannels(channels) - return nil -} - -// GetSubscriptions returns a copied list of subscriptions -func (g *Gateio) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) { - return g.Websocket.GetSubscriptions(), nil -} - // AuthenticateWebsocket sends an authentication message to the websocket func (g *Gateio) AuthenticateWebsocket() error { - _, err := g.wsServerSignIn() - return err + return g.wsServerSignIn() } // ValidateCredentials validates current credentials used for wrapper @@ -741,8 +740,13 @@ func (g *Gateio) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end } hours := end.Sub(start).Hours() + formattedPair, err := g.FormatExchangeCurrency(pair, a) + if err != nil { + return kline.Item{}, err + } + params := KlinesRequestParams{ - Symbol: g.FormatExchangeCurrency(pair, a).String(), + Symbol: formattedPair.String(), GroupSec: g.FormatExchangeKlineInterval(interval), HourSize: int(hours), } diff --git a/exchanges/gemini/gemini.go b/exchanges/gemini/gemini.go index b5e72a55..98cf8eed 100644 --- a/exchanges/gemini/gemini.go +++ b/exchanges/gemini/gemini.go @@ -14,7 +14,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/common/crypto" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/request" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" "github.com/thrasher-corp/gocryptotrader/log" ) @@ -58,11 +58,10 @@ const ( // AddSession, if sandbox test is needed append a new session with with the same // API keys and change the IsSandbox variable to true. type Gemini struct { - WebsocketConn *wshandler.WebsocketConnection - AuthenticatedWebsocketConn *wshandler.WebsocketConnection exchange.Base Role string RequiresHeartBeat bool + connections []stream.Connection } // GetSymbols returns all available symbols for trading diff --git a/exchanges/gemini/gemini_live_test.go b/exchanges/gemini/gemini_live_test.go index 1b75e0dd..66f4e67f 100644 --- a/exchanges/gemini/gemini_live_test.go +++ b/exchanges/gemini/gemini_live_test.go @@ -29,13 +29,12 @@ func TestMain(m *testing.M) { geminiConfig.API.Credentials.Key = apiKey geminiConfig.API.Credentials.Secret = apiSecret g.SetDefaults() + g.Websocket = sharedtestvalues.NewTestWebsocket() err = g.Setup(geminiConfig) if err != nil { log.Fatal("Gemini setup error", err) } g.API.Endpoints.URL = geminiSandboxAPIURL - g.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride() - g.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride() log.Printf(sharedtestvalues.LiveTesting, g.Name, g.API.Endpoints.URL) os.Exit(m.Run()) } diff --git a/exchanges/gemini/gemini_mock_test.go b/exchanges/gemini/gemini_mock_test.go index 37a9b91a..0f7833b4 100644 --- a/exchanges/gemini/gemini_mock_test.go +++ b/exchanges/gemini/gemini_mock_test.go @@ -33,6 +33,7 @@ func TestMain(m *testing.M) { geminiConfig.API.Credentials.Key = apiKey geminiConfig.API.Credentials.Secret = apiSecret g.SetDefaults() + g.Websocket = sharedtestvalues.NewTestWebsocket() err = g.Setup(geminiConfig) if err != nil { log.Fatal("Gemini setup error", err) @@ -45,8 +46,6 @@ func TestMain(m *testing.M) { g.HTTPClient = newClient g.API.Endpoints.URL = serverDetails - g.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride() - g.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride() log.Printf(sharedtestvalues.MockTesting, g.Name, g.API.Endpoints.URL) os.Exit(m.Run()) } diff --git a/exchanges/gemini/gemini_test.go b/exchanges/gemini/gemini_test.go index 1e4c509d..92f730e8 100644 --- a/exchanges/gemini/gemini_test.go +++ b/exchanges/gemini/gemini_test.go @@ -12,7 +12,7 @@ import ( exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) @@ -550,7 +550,7 @@ func TestWsAuth(t *testing.T) { if !g.Websocket.IsEnabled() && !g.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() { - t.Skip(wshandler.WebsocketNotEnabled) + t.Skip(stream.WebsocketNotEnabled) } var dialer websocket.Dialer go g.wsReadData() @@ -571,18 +571,27 @@ func TestWsAuth(t *testing.T) { } func TestWsMissingRole(t *testing.T) { + pair, err := currency.NewPairFromString("BTCUSD") + if err != nil { + t.Fatal(err) + } + pressXToJSON := []byte(`{ "result":"error", "reason":"MissingRole", "message":"To access this endpoint, you need to log in to the website and go to the settings page to assign one of these roles [FundManager] to API key wujB3szN54gtJ4QDhqRJ which currently has roles [Trader]" }`) - err := g.wsHandleData(pressXToJSON, currency.NewPairFromString("BTCUSD")) + err = g.wsHandleData(pressXToJSON, pair) if err == nil { t.Error("Expected error") } } func TestWsOrderEventSubscriptionResponse(t *testing.T) { + pair, err := currency.NewPairFromString("BTCUSD") + if err != nil { + t.Fatal(err) + } pressXToJSON := []byte(`[ { "type" : "accepted", "order_id" : "372456298", @@ -601,7 +610,7 @@ func TestWsOrderEventSubscriptionResponse(t *testing.T) { "original_amount" : "14.0296", "price" : "1059.54" } ]`) - err := g.wsHandleData(pressXToJSON, currency.NewPairFromString("BTCUSD")) + err = g.wsHandleData(pressXToJSON, pair) if err != nil { t.Error(err) } @@ -623,7 +632,7 @@ func TestWsOrderEventSubscriptionResponse(t *testing.T) { "price": "3592.00", "socket_sequence": 13 }]`) - err = g.wsHandleData(pressXToJSON, currency.NewPairFromString("BTCUSD")) + err = g.wsHandleData(pressXToJSON, pair) if err != nil { t.Error(err) } @@ -644,7 +653,7 @@ func TestWsOrderEventSubscriptionResponse(t *testing.T) { "total_spend": "200.00", "socket_sequence": 29 }]`) - err = g.wsHandleData(pressXToJSON, currency.NewPairFromString("BTCUSD")) + err = g.wsHandleData(pressXToJSON, pair) if err != nil { t.Error(err) } @@ -665,7 +674,7 @@ func TestWsOrderEventSubscriptionResponse(t *testing.T) { "original_amount": "25", "socket_sequence": 26 }]`) - err = g.wsHandleData(pressXToJSON, currency.NewPairFromString("BTCUSD")) + err = g.wsHandleData(pressXToJSON, pair) if err != nil { t.Error(err) } @@ -687,13 +696,17 @@ func TestWsOrderEventSubscriptionResponse(t *testing.T) { "original_amount" : "500", "socket_sequence" : 32307 } ]`) - err = g.wsHandleData(pressXToJSON, currency.NewPairFromString("BTCUSD")) + err = g.wsHandleData(pressXToJSON, pair) if err != nil { t.Error(err) } } func TestWsSubAck(t *testing.T) { + pair, err := currency.NewPairFromString("BTCUSD") + if err != nil { + t.Fatal(err) + } pressXToJSON := []byte(`{ "type": "subscription_ack", "accountId": 5365, @@ -709,13 +722,17 @@ func TestWsSubAck(t *testing.T) { "closed" ] }`) - err := g.wsHandleData(pressXToJSON, currency.NewPairFromString("BTCUSD")) + err = g.wsHandleData(pressXToJSON, pair) if err != nil { t.Error(err) } } func TestWsHeartbeat(t *testing.T) { + pair, err := currency.NewPairFromString("BTCUSD") + if err != nil { + t.Fatal(err) + } pressXToJSON := []byte(`{ "type": "heartbeat", "timestampms": 1547742998508, @@ -723,13 +740,17 @@ func TestWsHeartbeat(t *testing.T) { "trace_id": "b8biknoqppr32kc7gfgg", "socket_sequence": 37 }`) - err := g.wsHandleData(pressXToJSON, currency.NewPairFromString("BTCUSD")) + err = g.wsHandleData(pressXToJSON, pair) if err != nil { t.Error(err) } } func TestWsUnsubscribe(t *testing.T) { + pair, err := currency.NewPairFromString("BTCUSD") + if err != nil { + t.Fatal(err) + } pressXToJSON := []byte(`{ "type": "unsubscribe", "subscriptions": [{ @@ -745,13 +766,17 @@ func TestWsUnsubscribe(t *testing.T) { ]} ] }`) - err := g.wsHandleData(pressXToJSON, currency.NewPairFromString("BTCUSD")) + err = g.wsHandleData(pressXToJSON, pair) if err != nil { t.Error(err) } } func TestWsTradeData(t *testing.T) { + pair, err := currency.NewPairFromString("BTCUSD") + if err != nil { + t.Fatal(err) + } pressXToJSON := []byte(`{ "type": "update", "eventId": 5375547515, @@ -768,13 +793,17 @@ func TestWsTradeData(t *testing.T) { } ] }`) - err := g.wsHandleData(pressXToJSON, currency.NewPairFromString("BTCUSD")) + err = g.wsHandleData(pressXToJSON, pair) if err != nil { t.Error(err) } } func TestWsAuctionData(t *testing.T) { + pair, err := currency.NewPairFromString("BTCUSD") + if err != nil { + t.Fatal(err) + } pressXToJSON := []byte(`{ "eventId": 371469414, "socket_sequence":4009, @@ -801,13 +830,17 @@ func TestWsAuctionData(t *testing.T) { ], "type": "update" }`) - err := g.wsHandleData(pressXToJSON, currency.NewPairFromString("BTCUSD")) + err = g.wsHandleData(pressXToJSON, pair) if err != nil { t.Error(err) } } func TestWsBlockTrade(t *testing.T) { + pair, err := currency.NewPairFromString("BTCUSD") + if err != nil { + t.Fatal(err) + } pressXToJSON := []byte(`{ "type":"update", "eventId":1111597035, @@ -823,13 +856,17 @@ func TestWsBlockTrade(t *testing.T) { } ] }`) - err := g.wsHandleData(pressXToJSON, currency.NewPairFromString("BTCUSD")) + err = g.wsHandleData(pressXToJSON, pair) if err != nil { t.Error(err) } } func TestWsCandles(t *testing.T) { + pair, err := currency.NewPairFromString("BTCUSD") + if err != nil { + t.Fatal(err) + } pressXToJSON := []byte(`{ "type": "candles_15m_updates", "symbol": "BTCUSD", @@ -852,13 +889,17 @@ func TestWsCandles(t *testing.T) { ] ] }`) - err := g.wsHandleData(pressXToJSON, currency.NewPairFromString("BTCUSD")) + err = g.wsHandleData(pressXToJSON, pair) if err != nil { t.Error(err) } } func TestWsAuctions(t *testing.T) { + pair, err := currency.NewPairFromString("BTCUSD") + if err != nil { + t.Fatal(err) + } pressXToJSON := []byte(`{ "eventId": 372481811, "socket_sequence":23, @@ -875,7 +916,7 @@ func TestWsAuctions(t *testing.T) { ], "type": "update" }`) - err := g.wsHandleData(pressXToJSON, currency.NewPairFromString("BTCUSD")) + err = g.wsHandleData(pressXToJSON, pair) if err != nil { t.Error(err) } @@ -900,7 +941,7 @@ func TestWsAuctions(t *testing.T) { } ] }`) - err = g.wsHandleData(pressXToJSON, currency.NewPairFromString("BTCUSD")) + err = g.wsHandleData(pressXToJSON, pair) if err != nil { t.Error(err) } @@ -932,13 +973,17 @@ func TestWsAuctions(t *testing.T) { } ] }`) - err = g.wsHandleData(pressXToJSON, currency.NewPairFromString("BTCUSD")) + err = g.wsHandleData(pressXToJSON, pair) if err != nil { t.Error(err) } } func TestWsMarketData(t *testing.T) { + pair, err := currency.NewPairFromString("BTCUSD") + if err != nil { + t.Fatal(err) + } pressXToJSON := []byte(`{ "type": "update", "eventId": 5375461993, @@ -962,7 +1007,7 @@ func TestWsMarketData(t *testing.T) { } ] } `) - err := g.wsHandleData(pressXToJSON, currency.NewPairFromString("BTCUSD")) + err = g.wsHandleData(pressXToJSON, pair) if err != nil { t.Error(err) } @@ -990,7 +1035,7 @@ func TestWsMarketData(t *testing.T) { } ] } `) - err = g.wsHandleData(pressXToJSON, currency.NewPairFromString("BTCUSD")) + err = g.wsHandleData(pressXToJSON, pair) if err != nil { t.Error(err) } @@ -1012,7 +1057,7 @@ func TestWsMarketData(t *testing.T) { } ] } `) - err = g.wsHandleData(pressXToJSON, currency.NewPairFromString("BTCUSD")) + err = g.wsHandleData(pressXToJSON, pair) if err != nil { t.Error(err) } diff --git a/exchanges/gemini/gemini_websocket.go b/exchanges/gemini/gemini_websocket.go index af5d0e34..0915f38e 100644 --- a/exchanges/gemini/gemini_websocket.go +++ b/exchanges/gemini/gemini_websocket.go @@ -18,8 +18,8 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream/buffer" "github.com/thrasher-corp/gocryptotrader/log" ) @@ -39,7 +39,7 @@ var responseCheckTimeout time.Duration // WsConnect initiates a websocket connection func (g *Gemini) WsConnect() error { if !g.Websocket.IsEnabled() || !g.IsEnabled() { - return errors.New(wshandler.WebsocketNotEnabled) + return errors.New(stream.WebsocketNotEnabled) } var dialer websocket.Dialer @@ -61,31 +61,36 @@ func (g *Gemini) WsConnect() error { // WsSubscribe subscribes to the full websocket suite on gemini exchange func (g *Gemini) WsSubscribe(dialer *websocket.Dialer) error { - enabledCurrencies := g.GetEnabledPairs(asset.Spot) + enabledCurrencies, err := g.GetEnabledPairs(asset.Spot) + if err != nil { + return err + } for i := range enabledCurrencies { val := url.Values{} val.Set("heartbeat", "true") + val.Set("bids", "true") + val.Set("offers", "true") + val.Set("trades", "true") endpoint := fmt.Sprintf("%s%s/%s?%s", g.API.Endpoints.WebsocketURL, geminiWsMarketData, enabledCurrencies[i].String(), val.Encode()) - connection := &wshandler.WebsocketConnection{ - ExchangeName: g.Name, - URL: endpoint, - Verbose: g.Verbose, - ResponseCheckTimeout: responseCheckTimeout, - ResponseMaxLimit: responseMaxLimit, + connection := &stream.WebsocketConnection{ + ExchangeName: g.Name, + URL: endpoint, + Verbose: g.Verbose, + ResponseMaxLimit: responseMaxLimit, + Traffic: g.Websocket.TrafficAlert, + Match: g.Websocket.Match, } err := connection.Dial(dialer, http.Header{}) if err != nil { return fmt.Errorf("%v Websocket connection %v error. Error %v", g.Name, endpoint, err) } + g.connections = append(g.connections, connection) go g.wsFunnelConnectionData(connection, enabledCurrencies[i]) - if len(enabledCurrencies)-1 == i { - return nil - } } return nil } @@ -115,38 +120,31 @@ func (g *Gemini) WsSecureSubscribe(dialer *websocket.Dialer, url string) error { headers.Add("X-GEMINI-SIGNATURE", crypto.HexEncodeToString(hmac)) headers.Add("Cache-Control", "no-cache") - g.AuthenticatedWebsocketConn = &wshandler.WebsocketConnection{ - ExchangeName: g.Name, - URL: endpoint, - Verbose: g.Verbose, - ResponseCheckTimeout: responseCheckTimeout, - ResponseMaxLimit: responseMaxLimit, + g.Websocket.AuthConn = &stream.WebsocketConnection{ + ExchangeName: g.Name, + URL: endpoint, + Verbose: g.Verbose, + ResponseMaxLimit: responseMaxLimit, + Match: g.Websocket.Match, } - err = g.AuthenticatedWebsocketConn.Dial(dialer, headers) + err = g.Websocket.AuthConn.Dial(dialer, headers) if err != nil { return fmt.Errorf("%v Websocket connection %v error. Error %v", g.Name, endpoint, err) } - go g.wsFunnelConnectionData(g.AuthenticatedWebsocketConn, currency.Pair{}) + go g.wsFunnelConnectionData(g.Websocket.AuthConn, currency.Pair{}) return nil } // wsFunnelConnectionData receives data from multiple connections and passes it to wsReadData -func (g *Gemini) wsFunnelConnectionData(ws *wshandler.WebsocketConnection, c currency.Pair) { +func (g *Gemini) wsFunnelConnectionData(ws stream.Connection, c currency.Pair) { g.Websocket.Wg.Add(1) defer g.Websocket.Wg.Done() for { - select { - case <-g.Websocket.ShutdownC: + resp := ws.ReadMessage() + if resp.Raw == nil { return - default: - resp, err := ws.ReadMessage() - if err != nil { - g.Websocket.DataHandler <- err - return - } - g.Websocket.TrafficAlert <- struct{}{} - comms <- ReadData{Raw: resp.Raw, Currency: c} } + comms <- ReadData{Raw: resp.Raw, Currency: c} } } @@ -157,6 +155,14 @@ func (g *Gemini) wsReadData() { for { select { case <-g.Websocket.ShutdownC: + for i := range g.connections { + err := g.connections[i].Shutdown() + if err != nil { + log.Errorln(log.ExchangeSys, err) + } + g.connections[i] = nil + } + g.connections = nil return case resp := <-comms: // Gemini likes to send empty arrays @@ -207,7 +213,16 @@ func (g *Gemini) wsHandleData(respRaw []byte, curr currency.Pair) error { Err: err, } } - p := currency.NewPairFromString(result[i].Symbol) + + p, err := currency.NewPairFromString(result[i].Symbol) + if err != nil { + g.Websocket.DataHandler <- order.ClassificationError{ + Exchange: g.Name, + OrderID: result[i].OrderID, + Err: err, + } + } + var a asset.Item a, err = g.GetPairAssetType(p) if err != nil { @@ -260,12 +275,7 @@ func (g *Gemini) wsHandleData(respRaw []byte, curr currency.Pair) error { } g.Websocket.DataHandler <- result case "heartbeat": - var result WsHeartbeatResponse - err := json.Unmarshal(respRaw, &result) - if err != nil { - return err - } - g.Websocket.DataHandler <- result + return nil case "update": if curr.IsEmpty() { return fmt.Errorf("%v - `update` response error. Currency is empty %s", @@ -290,7 +300,7 @@ func (g *Gemini) wsHandleData(respRaw []byte, curr currency.Pair) error { return err } for i := range candle.Changes { - g.Websocket.DataHandler <- wshandler.KlineData{ + g.Websocket.DataHandler <- stream.KlineData{ Timestamp: time.Unix(int64(candle.Changes[i][0])*1000, 0), Pair: curr, AssetType: asset.Spot, @@ -305,7 +315,7 @@ func (g *Gemini) wsHandleData(respRaw []byte, curr currency.Pair) error { } default: - g.Websocket.DataHandler <- wshandler.UnhandledMessageWarning{Message: g.Name + wshandler.UnhandledMessage + string(respRaw)} + g.Websocket.DataHandler <- stream.UnhandledMessageWarning{Message: g.Name + stream.UnhandledMessage + string(respRaw)} return nil } } else if _, ok := result["result"]; ok { @@ -318,7 +328,7 @@ func (g *Gemini) wsHandleData(respRaw []byte, curr currency.Pair) error { } return fmt.Errorf("%v Unhandled websocket error %s", g.Name, respRaw) default: - g.Websocket.DataHandler <- wshandler.UnhandledMessageWarning{Message: g.Name + wshandler.UnhandledMessage + string(respRaw)} + g.Websocket.DataHandler <- stream.UnhandledMessageWarning{Message: g.Name + stream.UnhandledMessage + string(respRaw)} return nil } } @@ -349,7 +359,8 @@ func stringToOrderType(oType string) (order.Type, error) { case "exchange limit", "auction-only limit", "indication-of-interest limit": return order.Limit, nil case "market buy", "market sell", "block_trade": - // block trades are conducted off order-book, so their type is market, but would be considered a hidden trade + // block trades are conducted off order-book, so their type is market, + // but would be considered a hidden trade return order.Market, nil default: return order.UnknownType, errors.New(oType + " not recognised as order type") @@ -388,9 +399,6 @@ func (g *Gemini) wsProcessUpdate(result WsMarketUpdateResponse, pair currency.Pa g.Websocket.DataHandler <- err return } - g.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{Pair: pair, - Asset: asset.Spot, - Exchange: g.Name} } else { var asks, bids []orderbook.Item for i := range result.Events { @@ -403,8 +411,8 @@ func (g *Gemini) wsProcessUpdate(result WsMarketUpdateResponse, pair currency.Pa Err: err, } } - g.Websocket.DataHandler <- wshandler.TradeData{ - Timestamp: time.Unix(0, result.Timestamp), + g.Websocket.DataHandler <- stream.TradeData{ + Timestamp: time.Unix(0, result.TimestampMS*int64(time.Millisecond)), CurrencyPair: pair, AssetType: asset.Spot, Exchange: g.Name, @@ -429,18 +437,15 @@ func (g *Gemini) wsProcessUpdate(result WsMarketUpdateResponse, pair currency.Pa if len(asks) == 0 && len(bids) == 0 { return } - err := g.Websocket.Orderbook.Update(&wsorderbook.WebsocketOrderbookUpdate{ + err := g.Websocket.Orderbook.Update(&buffer.Update{ Asks: asks, Bids: bids, Pair: pair, - UpdateTime: time.Unix(0, result.TimestampMS), + UpdateTime: time.Unix(0, result.TimestampMS*int64(time.Millisecond)), Asset: asset.Spot, }) if err != nil { g.Websocket.DataHandler <- fmt.Errorf("%v %v", g.Name, err) } - g.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{Pair: pair, - Asset: asset.Spot, - Exchange: g.Name} } } diff --git a/exchanges/gemini/gemini_wrapper.go b/exchanges/gemini/gemini_wrapper.go index 4af50c81..b555f09b 100644 --- a/exchanges/gemini/gemini_wrapper.go +++ b/exchanges/gemini/gemini_wrapper.go @@ -19,8 +19,8 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" "github.com/thrasher-corp/gocryptotrader/log" "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) @@ -39,7 +39,7 @@ func (g *Gemini) GetDefaultConfig() (*config.ExchangeConfig, error) { } if g.Features.Supports.RESTCapabilities.AutoPairUpdates { - err = g.UpdateTradablePairs(true) + err := g.UpdateTradablePairs(true) if err != nil { return nil, err } @@ -56,17 +56,11 @@ func (g *Gemini) SetDefaults() { g.API.CredentialsValidator.RequiresKey = true g.API.CredentialsValidator.RequiresSecret = true - g.CurrencyPairs = currency.PairsManager{ - AssetTypes: asset.Items{ - asset.Spot, - }, - UseGlobalFormat: true, - RequestFormat: ¤cy.PairFormat{ - Uppercase: true, - }, - ConfigFormat: ¤cy.PairFormat{ - Uppercase: true, - }, + requestFmt := ¤cy.PairFormat{Uppercase: true} + configFmt := ¤cy.PairFormat{Uppercase: true} + err := g.SetGlobalPairsManager(requestFmt, configFmt, asset.Spot) + if err != nil { + log.Errorln(log.ExchangeSys, err) } g.Features = exchange.Features{ @@ -95,8 +89,6 @@ func (g *Gemini) SetDefaults() { TradeFetching: true, AuthenticatedEndpoints: true, MessageSequenceNumbers: true, - Subscribe: true, - Unsubscribe: true, KlineFetching: true, }, WithdrawPermissions: exchange.AutoWithdrawCryptoWithAPIPermission | @@ -115,7 +107,7 @@ func (g *Gemini) SetDefaults() { g.API.Endpoints.URLDefault = geminiAPIURL g.API.Endpoints.URL = g.API.Endpoints.URLDefault g.API.Endpoints.WebsocketURL = geminiWebsocketEndpoint - g.Websocket = wshandler.New() + g.Websocket = stream.New() g.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit g.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout g.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit @@ -137,39 +129,20 @@ func (g *Gemini) Setup(exch *config.ExchangeConfig) error { g.API.Endpoints.URL = geminiSandboxAPIURL } - err = g.Websocket.Setup( - &wshandler.WebsocketSetup{ - Enabled: exch.Features.Enabled.Websocket, - Verbose: exch.Verbose, - AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, - WebsocketTimeout: exch.WebsocketTrafficTimeout, - DefaultURL: geminiWebsocketEndpoint, - ExchangeName: exch.Name, - RunningURL: exch.API.Endpoints.WebsocketURL, - Connector: g.WsConnect, - Features: &g.Features.Supports.WebsocketCapabilities, - }) - if err != nil { - return err - } - - g.WebsocketConn = &wshandler.WebsocketConnection{ - ExchangeName: g.Name, - URL: g.Websocket.GetWebsocketURL(), - ProxyURL: g.Websocket.GetProxyAddress(), - Verbose: g.Verbose, - ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout, - ResponseMaxLimit: exch.WebsocketResponseMaxLimit, - } - - g.Websocket.Orderbook.Setup( - exch.WebsocketOrderbookBufferLimit, - true, - true, - false, - false, - exch.Name) - return nil + return g.Websocket.Setup(&stream.WebsocketSetup{ + Enabled: exch.Features.Enabled.Websocket, + Verbose: exch.Verbose, + AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, + WebsocketTimeout: exch.WebsocketTrafficTimeout, + DefaultURL: geminiWebsocketEndpoint, + ExchangeName: exch.Name, + RunningURL: exch.API.Endpoints.WebsocketURL, + Connector: g.WsConnect, + Features: &g.Features.Supports.WebsocketCapabilities, + OrderbookBufferLimit: exch.WebsocketOrderbookBufferLimit, + BufferEnabled: true, + SortBuffer: true, + }) } // Start starts the Gemini go routine @@ -210,7 +183,12 @@ func (g *Gemini) UpdateTradablePairs(forceUpdate bool) error { return err } - return g.UpdatePairs(currency.NewPairsFromStrings(pairs), asset.Spot, false, forceUpdate) + p, err := currency.NewPairsFromStrings(pairs) + if err != nil { + return err + } + + return g.UpdatePairs(p, asset.Spot, false, forceUpdate) } // UpdateAccountInfo Retrieves balances for all enabled currencies for the @@ -256,23 +234,23 @@ func (g *Gemini) FetchAccountInfo() (account.Holdings, error) { // UpdateTicker updates and returns the ticker for a currency pair func (g *Gemini) UpdateTicker(p currency.Pair, assetType asset.Item) (*ticker.Price, error) { - tickerPrice := new(ticker.Price) tick, err := g.GetTicker(p.String()) if err != nil { - return tickerPrice, err + return nil, err } - tickerPrice = &ticker.Price{ - High: tick.High, - Low: tick.Low, - Bid: tick.Bid, - Ask: tick.Ask, - Open: tick.Open, - Close: tick.Close, - Pair: p, - } - err = ticker.ProcessTicker(g.Name, tickerPrice, assetType) + + err = ticker.ProcessTicker(&ticker.Price{ + High: tick.High, + Low: tick.Low, + Bid: tick.Bid, + Ask: tick.Ask, + Open: tick.Open, + Close: tick.Close, + Pair: p, + ExchangeName: g.Name, + AssetType: assetType}) if err != nil { - return tickerPrice, err + return nil, err } return ticker.GetTicker(g.Name, p, assetType) @@ -347,8 +325,12 @@ func (g *Gemini) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { errors.New("only limit orders are enabled through this exchange") } - response, err := g.NewOrder( - g.FormatExchangeCurrency(s.Pair, asset.Spot).String(), + fpair, err := g.FormatExchangeCurrency(s.Pair, asset.Spot) + if err != nil { + return submitOrderResponse, err + } + + response, err := g.NewOrder(fpair.String(), s.Amount, s.Price, s.Side.String(), @@ -442,11 +424,6 @@ func (g *Gemini) WithdrawFiatFundsToInternationalBank(withdrawRequest *withdraw. return nil, common.ErrFunctionNotSupported } -// GetWebsocket returns a pointer to the exchange websocket -func (g *Gemini) GetWebsocket() (*wshandler.Websocket, error) { - return g.Websocket, nil -} - // GetFeeByType returns an estimate of fee based on type of transaction func (g *Gemini) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { if (!g.AllowAuthenticatedRequest() || g.SkipAuthCheck) && // Todo check connection status @@ -463,10 +440,18 @@ func (g *Gemini) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, e return nil, err } + format, err := g.GetPairFormat(asset.Spot, false) + if err != nil { + return nil, err + } + var orders []order.Detail for i := range resp { - symbol := currency.NewPairDelimiter(resp[i].Symbol, - g.GetPairFormat(asset.Spot, false).Delimiter) + var symbol currency.Pair + symbol, err = currency.NewPairDelimiter(resp[i].Symbol, format.Delimiter) + if err != nil { + return nil, err + } var orderType order.Type if resp[i].Type == "exchange limit" { orderType = order.Limit @@ -507,9 +492,12 @@ func (g *Gemini) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, e var trades []TradeHistory for j := range req.Pairs { - resp, err := g.GetTradeHistory(g.FormatExchangeCurrency(req.Pairs[j], - asset.Spot).String(), - req.StartTicks.Unix()) + fpair, err := g.FormatExchangeCurrency(req.Pairs[j], asset.Spot) + if err != nil { + return nil, err + } + + resp, err := g.GetTradeHistory(fpair.String(), req.StartTicks.Unix()) if err != nil { return nil, err } @@ -521,6 +509,11 @@ func (g *Gemini) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, e } } + format, err := g.GetPairFormat(asset.Spot, false) + if err != nil { + return nil, err + } + var orders []order.Detail for i := range trades { side := order.Side(strings.ToUpper(trades[i].Type)) @@ -536,7 +529,7 @@ func (g *Gemini) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, e Price: trades[i].Price, Pair: currency.NewPairWithDelimiter(trades[i].BaseCurrency, trades[i].QuoteCurrency, - g.GetPairFormat(asset.Spot, false).Delimiter), + format.Delimiter), }) } @@ -545,28 +538,6 @@ func (g *Gemini) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, e return orders, nil } -// SubscribeToWebsocketChannels appends to ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle subscribing -func (g *Gemini) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - return common.ErrFunctionNotSupported -} - -// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle unsubscribing -func (g *Gemini) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - return common.ErrFunctionNotSupported -} - -// GetSubscriptions returns a copied list of subscriptions -func (g *Gemini) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) { - return nil, common.ErrFunctionNotSupported -} - -// AuthenticateWebsocket sends an authentication message to the websocket -func (g *Gemini) AuthenticateWebsocket() error { - return common.ErrFunctionNotSupported -} - // ValidateCredentials validates current credentials used for wrapper // functionality func (g *Gemini) ValidateCredentials() error { diff --git a/exchanges/hitbtc/hitbtc.go b/exchanges/hitbtc/hitbtc.go index 1141ea1d..6b36f1f1 100644 --- a/exchanges/hitbtc/hitbtc.go +++ b/exchanges/hitbtc/hitbtc.go @@ -14,7 +14,6 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/request" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" ) const ( @@ -47,7 +46,6 @@ const ( // HitBTC is the overarching type across the hitbtc package type HitBTC struct { exchange.Base - WebsocketConn *wshandler.WebsocketConnection } // Public Market Data diff --git a/exchanges/hitbtc/hitbtc_test.go b/exchanges/hitbtc/hitbtc_test.go index aaaf7b2b..f2835502 100644 --- a/exchanges/hitbtc/hitbtc_test.go +++ b/exchanges/hitbtc/hitbtc_test.go @@ -17,7 +17,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/kline" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) @@ -46,14 +46,11 @@ func TestMain(m *testing.M) { hitbtcConfig.API.AuthenticatedWebsocketSupport = true hitbtcConfig.API.Credentials.Key = apiKey hitbtcConfig.API.Credentials.Secret = apiSecret - + h.Websocket = sharedtestvalues.NewTestWebsocket() err = h.Setup(hitbtcConfig) if err != nil { log.Fatal("HitBTC setup error", err) } - - h.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride() - h.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride() os.Exit(m.Run()) } @@ -79,10 +76,13 @@ func TestGetChartCandles(t *testing.T) { } func TestGetHistoricCandles(t *testing.T) { - currencyPair := currency.NewPairFromString("BTCUSD") + currencyPair, err := currency.NewPairFromString("BTCUSD") + if err != nil { + t.Fatal(err) + } startTime := time.Now().Add(-time.Hour * 24) end := time.Now() - _, err := h.GetHistoricCandles(currencyPair, asset.Spot, startTime, end, kline.OneMin) + _, err = h.GetHistoricCandles(currencyPair, asset.Spot, startTime, end, kline.OneMin) if err != nil { t.Fatal(err) } @@ -94,10 +94,13 @@ func TestGetHistoricCandles(t *testing.T) { } func TestGetHistoricCandlesExtended(t *testing.T) { - currencyPair := currency.NewPairFromString("BTCUSD") + currencyPair, err := currency.NewPairFromString("BTCUSD") + if err != nil { + t.Fatal(err) + } startTime := time.Unix(1546300800, 0) end := time.Unix(1577836799, 0) - _, err := h.GetHistoricCandlesExtended(currencyPair, asset.Spot, startTime, end, kline.OneHour) + _, err = h.GetHistoricCandlesExtended(currencyPair, asset.Spot, startTime, end, kline.OneHour) if err != nil { t.Fatal(err) } @@ -142,8 +145,12 @@ func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { } func TestUpdateTicker(t *testing.T) { - h.CurrencyPairs.StorePairs(asset.Spot, currency.NewPairsFromStrings([]string{"BTC-USD", "XRP-USD"}), true) - _, err := h.UpdateTicker(currency.NewPair(currency.BTC, currency.USD), asset.Spot) + pairs, err := currency.NewPairsFromStrings([]string{"BTC-USD", "XRP-USD"}) + if err != nil { + t.Fatal(err) + } + h.CurrencyPairs.StorePairs(asset.Spot, pairs, true) + _, err = h.UpdateTicker(currency.NewPair(currency.BTC, currency.USD), asset.Spot) if err != nil { t.Error(err) } @@ -438,19 +445,11 @@ func setupWsAuth(t *testing.T) { return } if !h.Websocket.IsEnabled() && !h.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() { - t.Skip(wshandler.WebsocketNotEnabled) - } - h.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride() - h.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride() - h.WebsocketConn = &wshandler.WebsocketConnection{ - ExchangeName: h.Name, - URL: hitbtcWebsocketAddress, - Verbose: h.Verbose, - ResponseMaxLimit: exchange.DefaultWebsocketResponseMaxLimit, - ResponseCheckTimeout: exchange.DefaultWebsocketResponseCheckTimeout, + t.Skip(stream.WebsocketNotEnabled) } + var dialer websocket.Dialer - err := h.WebsocketConn.Dial(&dialer, http.Header{}) + err := h.Websocket.Conn.Dial(&dialer, http.Header{}) if err != nil { t.Fatal(err) } diff --git a/exchanges/hitbtc/hitbtc_types.go b/exchanges/hitbtc/hitbtc_types.go index 183c8052..38eb426a 100644 --- a/exchanges/hitbtc/hitbtc_types.go +++ b/exchanges/hitbtc/hitbtc_types.go @@ -300,23 +300,25 @@ type ResponseError struct { // WsRequest defines a request obj for the JSON-RPC and gets a websocket // response type WsRequest struct { - Method string `json:"method"` - Params interface{} `json:"params,omitempty"` - ID interface{} `json:"id"` + Method string `json:"method"` + Params Params `json:"params,omitempty"` + ID int64 `json:"id"` } // WsNotification defines a notification obj for the JSON-RPC this does not get // a websocket response type WsNotification struct { - JSONRPCVersion string `json:"jsonrpc,omitempty"` - Method string `json:"method"` - Params interface{} `json:"params"` + JSONRPCVersion string `json:"jsonrpc,omitempty"` + Method string `json:"method"` + Params Params `json:"params"` } -type params struct { - Symbol string `json:"symbol,omitempty"` - Period string `json:"period,omitempty"` - Limit int64 `json:"limit,omitempty"` +// Params is params +type Params struct { + Symbol string `json:"symbol,omitempty"` + Period string `json:"period,omitempty"` + Limit int64 `json:"limit,omitempty"` + Symbols []string `json:"symbols,omitempty"` } // WsTicker defines websocket ticker feed return params @@ -369,6 +371,7 @@ type WsTrade struct { type WsLoginRequest struct { Method string `json:"method"` Params WsLoginData `json:"params"` + ID int64 `json:"id,omitempty"` } // WsLoginData sets credentials for WsLoginRequest diff --git a/exchanges/hitbtc/hitbtc_websocket.go b/exchanges/hitbtc/hitbtc_websocket.go index a5867de1..aed37f5b 100644 --- a/exchanges/hitbtc/hitbtc_websocket.go +++ b/exchanges/hitbtc/hitbtc_websocket.go @@ -10,6 +10,7 @@ import ( "time" "github.com/gorilla/websocket" + "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/crypto" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" @@ -17,9 +18,9 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/nonce" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream/buffer" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook" "github.com/thrasher-corp/gocryptotrader/log" ) @@ -35,10 +36,10 @@ var requestID nonce.Nonce // WsConnect starts a new connection with the websocket API func (h *HitBTC) WsConnect() error { if !h.Websocket.IsEnabled() || !h.IsEnabled() { - return errors.New(wshandler.WebsocketNotEnabled) + return errors.New(stream.WebsocketNotEnabled) } var dialer websocket.Dialer - err := h.WebsocketConn.Dial(&dialer, http.Header{}) + err := h.Websocket.Conn.Dial(&dialer, http.Header{}) if err != nil { return err } @@ -48,35 +49,27 @@ func (h *HitBTC) WsConnect() error { log.Errorf(log.ExchangeSys, "%v - authentication failed: %v\n", h.Name, err) } - h.GenerateDefaultSubscriptions() - - return nil + subs, err := h.GenerateDefaultSubscriptions() + if err != nil { + return err + } + return h.Websocket.SubscribeToChannels(subs) } // wsReadData receives and passes on websocket messages for processing func (h *HitBTC) wsReadData() { h.Websocket.Wg.Add(1) - - defer func() { - h.Websocket.Wg.Done() - }() + defer h.Websocket.Wg.Done() for { - select { - case <-h.Websocket.ShutdownC: + resp := h.Websocket.Conn.ReadMessage() + if resp.Raw == nil { return - default: - resp, err := h.WebsocketConn.ReadMessage() - if err != nil { - h.Websocket.ReadMessageErrors <- err - return - } - h.Websocket.TrafficAlert <- struct{}{} + } - err = h.wsHandleData(resp.Raw) - if err != nil { - h.Websocket.DataHandler <- err - } + err := h.wsHandleData(resp.Raw) + if err != nil { + h.Websocket.DataHandler <- err } } } @@ -91,8 +84,7 @@ func (h *HitBTC) wsGetTableName(respRaw []byte) (string, error) { h.Websocket.SetCanUseAuthenticatedEndpoints(false) } if init.ID > 0 { - if h.WebsocketConn.IsIDWaitingForResponse(init.ID) { - h.WebsocketConn.SetResponseIDAndData(init.ID, respRaw) + if h.Websocket.Match.IncomingWithData(init.ID, respRaw) { return "", nil } } @@ -132,7 +124,7 @@ func (h *HitBTC) wsGetTableName(respRaw []byte) (string, error) { return "trading", nil } } - h.Websocket.DataHandler <- wshandler.UnhandledMessageWarning{Message: h.Name + wshandler.UnhandledMessage + string(respRaw)} + h.Websocket.DataHandler <- stream.UnhandledMessageWarning{Message: h.Name + stream.UnhandledMessage + string(respRaw)} return "", nil } @@ -150,6 +142,24 @@ func (h *HitBTC) wsHandleData(respRaw []byte) error { if err != nil { return err } + + pairs, err := h.GetEnabledPairs(asset.Spot) + if err != nil { + return err + } + + format, err := h.GetPairFormat(asset.Spot, true) + if err != nil { + return err + } + + p, err := currency.NewPairFromFormattedPairs(wsTicker.Params.Symbol, + pairs, + format) + if err != nil { + return err + } + h.Websocket.DataHandler <- &ticker.Price{ ExchangeName: h.Name, Open: wsTicker.Params.Open, @@ -162,8 +172,7 @@ func (h *HitBTC) wsHandleData(respRaw []byte) error { Last: wsTicker.Params.Last, LastUpdated: wsTicker.Params.Timestamp, AssetType: asset.Spot, - Pair: currency.NewPairFromFormattedPairs(wsTicker.Params.Symbol, - h.GetEnabledPairs(asset.Spot), h.GetPairFormat(asset.Spot, true)), + Pair: p, } case "snapshotOrderbook": var obSnapshot WsOrderbook @@ -252,7 +261,7 @@ func (h *HitBTC) wsHandleData(respRaw []byte) error { return err } default: - h.Websocket.DataHandler <- wshandler.UnhandledMessageWarning{Message: h.Name + wshandler.UnhandledMessage + string(respRaw)} + h.Websocket.DataHandler <- stream.UnhandledMessageWarning{Message: h.Name + stream.UnhandledMessage + string(respRaw)} return nil } return nil @@ -279,24 +288,28 @@ func (h *HitBTC) WsProcessOrderbookSnapshot(ob WsOrderbook) error { }) } - p := currency.NewPairFromFormattedPairs(ob.Params.Symbol, - h.GetEnabledPairs(asset.Spot), h.GetPairFormat(asset.Spot, true)) - newOrderBook.AssetType = asset.Spot - newOrderBook.Pair = p - newOrderBook.ExchangeName = h.Name - - err := h.Websocket.Orderbook.LoadSnapshot(&newOrderBook) + pairs, err := h.GetEnabledPairs(asset.Spot) if err != nil { return err } - h.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{ - Exchange: h.Name, - Asset: asset.Spot, - Pair: p, + format, err := h.GetPairFormat(asset.Spot, true) + if err != nil { + return err } - return nil + p, err := currency.NewPairFromFormattedPairs(ob.Params.Symbol, + pairs, + format) + if err != nil { + h.Websocket.DataHandler <- err + return err + } + newOrderBook.AssetType = asset.Spot + newOrderBook.Pair = p + newOrderBook.ExchangeName = h.Name + + return h.Websocket.Orderbook.LoadSnapshot(&newOrderBook) } func (h *HitBTC) wsHandleOrderData(o *wsOrderData) error { @@ -336,7 +349,16 @@ func (h *HitBTC) wsHandleOrderData(o *wsOrderData) error { Err: err, } } - p := currency.NewPairFromString(o.Symbol) + + p, err := currency.NewPairFromString(o.Symbol) + if err != nil { + h.Websocket.DataHandler <- order.ClassificationError{ + Exchange: h.Name, + OrderID: o.ID, + Err: err, + } + } + var a asset.Item a, err = h.GetPairAssetType(p) if err != nil { @@ -364,7 +386,9 @@ func (h *HitBTC) wsHandleOrderData(o *wsOrderData) error { // WsProcessOrderbookUpdate updates a local cache func (h *HitBTC) WsProcessOrderbookUpdate(update WsOrderbook) error { if len(update.Params.Bid) == 0 && len(update.Params.Ask) == 0 { - return errors.New("hitbtc_websocket.go error - no data") + // Periodically HitBTC sends empty updates which includes a sequence + // can return this as nil. + return nil } var bids, asks []orderbook.Item @@ -382,105 +406,132 @@ func (h *HitBTC) WsProcessOrderbookUpdate(update WsOrderbook) error { }) } - p := currency.NewPairFromFormattedPairs(update.Params.Symbol, - h.GetEnabledPairs(asset.Spot), h.GetPairFormat(asset.Spot, true)) - err := h.Websocket.Orderbook.Update(&wsorderbook.WebsocketOrderbookUpdate{ + pairs, err := h.GetEnabledPairs(asset.Spot) + if err != nil { + return err + } + + format, err := h.GetPairFormat(asset.Spot, true) + if err != nil { + return err + } + + p, err := currency.NewPairFromFormattedPairs(update.Params.Symbol, + pairs, + format) + if err != nil { + return err + } + + return h.Websocket.Orderbook.Update(&buffer.Update{ Asks: asks, Bids: bids, Pair: p, UpdateID: update.Params.Sequence, Asset: asset.Spot, }) - if err != nil { - return err - } +} - h.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{ - Exchange: h.Name, - Asset: asset.Spot, - Pair: p, +// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() +func (h *HitBTC) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, error) { + var channels = []string{"subscribeTicker", + "subscribeOrderbook", + "subscribeTrades", + "subscribeCandles"} + + var subscriptions []stream.ChannelSubscription + if h.Websocket.CanUseAuthenticatedEndpoints() { + subscriptions = append(subscriptions, stream.ChannelSubscription{ + Channel: "subscribeReports", + }) + } + enabledCurrencies, err := h.GetEnabledPairs(asset.Spot) + if err != nil { + return nil, err + } + for i := range channels { + for j := range enabledCurrencies { + fpair, err := h.FormatExchangeCurrency(enabledCurrencies[j], asset.Spot) + if err != nil { + return nil, err + } + + enabledCurrencies[j].Delimiter = "" + subscriptions = append(subscriptions, stream.ChannelSubscription{ + Channel: channels[i], + Currency: fpair, + Asset: asset.Spot, + }) + } + } + return subscriptions, nil +} + +// Subscribe sends a websocket message to receive data from the channel +func (h *HitBTC) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error { + var errs common.Errors + for i := range channelsToSubscribe { + subscribe := WsRequest{ + Method: channelsToSubscribe[i].Channel, + ID: h.Websocket.Conn.GenerateMessageID(false), + } + + if channelsToSubscribe[i].Currency.String() != "" { + subscribe.Params.Symbol = channelsToSubscribe[i].Currency.String() + } + if strings.EqualFold(channelsToSubscribe[i].Channel, "subscribeTrades") { + subscribe.Params.Limit = 100 + } else if strings.EqualFold(channelsToSubscribe[i].Channel, "subscribeCandles") { + subscribe.Params.Period = "M30" + subscribe.Params.Limit = 100 + } + + err := h.Websocket.Conn.SendJSONMessage(subscribe) + if err != nil { + errs = append(errs, err) + continue + } + h.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe[i]) + } + if errs != nil { + return errs } return nil } -// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() -func (h *HitBTC) GenerateDefaultSubscriptions() { - var channels = []string{"subscribeTicker", "subscribeOrderbook", "subscribeTrades", "subscribeCandles"} - var subscriptions []wshandler.WebsocketChannelSubscription - if h.Websocket.CanUseAuthenticatedEndpoints() { - subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{ - Channel: "subscribeReports", - }) - } - enabledCurrencies := h.GetEnabledPairs(asset.Spot) - for i := range channels { - for j := range enabledCurrencies { - enabledCurrencies[j].Delimiter = "" - subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{ - Channel: channels[i], - Currency: enabledCurrencies[j], - }) - } - } - h.Websocket.SubscribeToChannels(subscriptions) -} - -// Subscribe sends a websocket message to receive data from the channel -func (h *HitBTC) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error { - subscribe := WsNotification{ - Method: channelToSubscribe.Channel, - } - if channelToSubscribe.Currency.String() != "" { - subscribe.Params = params{ - Symbol: h.FormatExchangeCurrency(channelToSubscribe.Currency, - asset.Spot).String(), - } - } - if strings.EqualFold(channelToSubscribe.Channel, "subscribeTrades") { - subscribe.Params = params{ - Symbol: h.FormatExchangeCurrency(channelToSubscribe.Currency, - asset.Spot).String(), - Limit: 100, - } - } else if strings.EqualFold(channelToSubscribe.Channel, "subscribeCandles") { - subscribe.Params = params{ - Symbol: h.FormatExchangeCurrency(channelToSubscribe.Currency, - asset.Spot).String(), - Period: "M30", - Limit: 100, - } - } - - return h.WebsocketConn.SendJSONMessage(subscribe) -} - // Unsubscribe sends a websocket message to stop receiving data from the channel -func (h *HitBTC) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error { - unsubscribeChannel := strings.Replace(channelToSubscribe.Channel, "subscribe", "unsubscribe", 1) - subscribe := WsNotification{ - JSONRPCVersion: rpcVersion, - Method: unsubscribeChannel, - Params: params{ - Symbol: h.FormatExchangeCurrency(channelToSubscribe.Currency, - asset.Spot).String(), - }, - } - if strings.EqualFold(unsubscribeChannel, "unsubscribeTrades") { - subscribe.Params = params{ - Symbol: h.FormatExchangeCurrency(channelToSubscribe.Currency, - asset.Spot).String(), - Limit: 100, - } - } else if strings.EqualFold(unsubscribeChannel, "unsubscribeCandles") { - subscribe.Params = params{ - Symbol: h.FormatExchangeCurrency(channelToSubscribe.Currency, - asset.Spot).String(), - Period: "M30", - Limit: 100, - } - } +func (h *HitBTC) Unsubscribe(channelsToUnsubscribe []stream.ChannelSubscription) error { + var errs common.Errors + for i := range channelsToUnsubscribe { + unsubscribeChannel := strings.Replace(channelsToUnsubscribe[i].Channel, + "subscribe", + "unsubscribe", + 1) - return h.WebsocketConn.SendJSONMessage(subscribe) + unsubscribe := WsNotification{ + JSONRPCVersion: rpcVersion, + Method: unsubscribeChannel, + } + + unsubscribe.Params.Symbol = channelsToUnsubscribe[i].Currency.String() + if strings.EqualFold(unsubscribeChannel, "unsubscribeTrades") { + unsubscribe.Params.Limit = 100 + } else if strings.EqualFold(unsubscribeChannel, "unsubscribeCandles") { + unsubscribe.Params.Period = "M30" + unsubscribe.Params.Limit = 100 + } + + err := h.Websocket.Conn.SendJSONMessage(unsubscribe) + if err != nil { + errs = append(errs, err) + continue + } + h.Websocket.RemoveSuccessfulUnsubscriptions(channelsToUnsubscribe[i]) + } + if errs != nil { + return errs + } + return nil } // Unsubscribe sends a websocket message to stop receiving data from the channel @@ -499,13 +550,15 @@ func (h *HitBTC) wsLogin() error { Nonce: n, Signature: crypto.HexEncodeToString(hmac), }, + ID: h.Websocket.Conn.GenerateMessageID(false), } - err := h.WebsocketConn.SendJSONMessage(request) + err := h.Websocket.Conn.SendJSONMessage(request) if err != nil { h.Websocket.SetCanUseAuthenticatedEndpoints(false) return err } + return nil } @@ -514,19 +567,25 @@ func (h *HitBTC) wsPlaceOrder(pair currency.Pair, side string, price, quantity f if !h.Websocket.CanUseAuthenticatedEndpoints() { return nil, fmt.Errorf("%v not authenticated, cannot place order", h.Name) } - id := h.WebsocketConn.GenerateMessageID(false) + + id := h.Websocket.Conn.GenerateMessageID(false) + fpair, err := h.FormatExchangeCurrency(pair, asset.Spot) + if err != nil { + return nil, err + } + request := WsSubmitOrderRequest{ Method: "newOrder", Params: WsSubmitOrderRequestData{ ClientOrderID: id, - Symbol: h.FormatExchangeCurrency(pair, asset.Spot).String(), + Symbol: fpair.String(), Side: strings.ToLower(side), Price: price, Quantity: quantity, }, ID: id, } - resp, err := h.WebsocketConn.SendMessageReturnResponse(id, request) + resp, err := h.Websocket.Conn.SendMessageReturnResponse(id, request) if err != nil { return nil, fmt.Errorf("%v %v", h.Name, err) } @@ -551,9 +610,9 @@ func (h *HitBTC) wsCancelOrder(clientOrderID string) (*WsCancelOrderResponse, er Params: WsCancelOrderRequestData{ ClientOrderID: clientOrderID, }, - ID: h.WebsocketConn.GenerateMessageID(false), + ID: h.Websocket.Conn.GenerateMessageID(false), } - resp, err := h.WebsocketConn.SendMessageReturnResponse(request.ID, request) + resp, err := h.Websocket.Conn.SendMessageReturnResponse(request.ID, request) if err != nil { return nil, fmt.Errorf("%v %v", h.Name, err) } @@ -581,9 +640,9 @@ func (h *HitBTC) wsReplaceOrder(clientOrderID string, quantity, price float64) ( Quantity: quantity, Price: price, }, - ID: h.WebsocketConn.GenerateMessageID(false), + ID: h.Websocket.Conn.GenerateMessageID(false), } - resp, err := h.WebsocketConn.SendMessageReturnResponse(request.ID, request) + resp, err := h.Websocket.Conn.SendMessageReturnResponse(request.ID, request) if err != nil { return nil, fmt.Errorf("%v %v", h.Name, err) } @@ -601,14 +660,14 @@ func (h *HitBTC) wsReplaceOrder(clientOrderID string, quantity, price float64) ( // wsGetActiveOrders sends a websocket message to get all active orders func (h *HitBTC) wsGetActiveOrders() (*wsActiveOrdersResponse, error) { if !h.Websocket.CanUseAuthenticatedEndpoints() { - return nil, fmt.Errorf("%v not authenticated, cannot place order", h.Name) + return nil, fmt.Errorf("%v not authenticated, cannot get active orders", h.Name) } request := WsReplaceOrderRequest{ Method: "getOrders", Params: WsReplaceOrderRequestData{}, - ID: h.WebsocketConn.GenerateMessageID(false), + ID: h.Websocket.Conn.GenerateMessageID(false), } - resp, err := h.WebsocketConn.SendMessageReturnResponse(request.ID, request) + resp, err := h.Websocket.Conn.SendMessageReturnResponse(request.ID, request) if err != nil { return nil, fmt.Errorf("%v %v", h.Name, err) } @@ -631,9 +690,9 @@ func (h *HitBTC) wsGetTradingBalance() (*WsGetTradingBalanceResponse, error) { request := WsReplaceOrderRequest{ Method: "getTradingBalance", Params: WsReplaceOrderRequestData{}, - ID: h.WebsocketConn.GenerateMessageID(false), + ID: h.Websocket.Conn.GenerateMessageID(false), } - resp, err := h.WebsocketConn.SendMessageReturnResponse(request.ID, request) + resp, err := h.Websocket.Conn.SendMessageReturnResponse(request.ID, request) if err != nil { return nil, fmt.Errorf("%v %v", h.Name, err) } @@ -655,9 +714,9 @@ func (h *HitBTC) wsGetCurrencies(currencyItem currency.Code) (*WsGetCurrenciesRe Params: WsGetCurrenciesRequestParameters{ Currency: currencyItem, }, - ID: h.WebsocketConn.GenerateMessageID(false), + ID: h.Websocket.Conn.GenerateMessageID(false), } - resp, err := h.WebsocketConn.SendMessageReturnResponse(request.ID, request) + resp, err := h.Websocket.Conn.SendMessageReturnResponse(request.ID, request) if err != nil { return nil, fmt.Errorf("%v %v", h.Name, err) } @@ -673,15 +732,20 @@ func (h *HitBTC) wsGetCurrencies(currencyItem currency.Code) (*WsGetCurrenciesRe } // wsGetSymbols sends a websocket message to get trading balance -func (h *HitBTC) wsGetSymbols(currencyItem currency.Pair) (*WsGetSymbolsResponse, error) { +func (h *HitBTC) wsGetSymbols(c currency.Pair) (*WsGetSymbolsResponse, error) { + fpair, err := h.FormatExchangeCurrency(c, asset.Spot) + if err != nil { + return nil, err + } + request := WsGetSymbolsRequest{ Method: "getSymbol", Params: WsGetSymbolsRequestParameters{ - Symbol: h.FormatExchangeCurrency(currencyItem, asset.Spot).String(), + Symbol: fpair.String(), }, - ID: h.WebsocketConn.GenerateMessageID(false), + ID: h.Websocket.Conn.GenerateMessageID(false), } - resp, err := h.WebsocketConn.SendMessageReturnResponse(request.ID, request) + resp, err := h.Websocket.Conn.SendMessageReturnResponse(request.ID, request) if err != nil { return nil, fmt.Errorf("%v %v", h.Name, err) } @@ -697,18 +761,23 @@ func (h *HitBTC) wsGetSymbols(currencyItem currency.Pair) (*WsGetSymbolsResponse } // wsGetSymbols sends a websocket message to get trading balance -func (h *HitBTC) wsGetTrades(currencyItem currency.Pair, limit int64, sort, by string) (*WsGetTradesResponse, error) { +func (h *HitBTC) wsGetTrades(c currency.Pair, limit int64, sort, by string) (*WsGetTradesResponse, error) { + fpair, err := h.FormatExchangeCurrency(c, asset.Spot) + if err != nil { + return nil, err + } + request := WsGetTradesRequest{ Method: "getTrades", Params: WsGetTradesRequestParameters{ - Symbol: h.FormatExchangeCurrency(currencyItem, asset.Spot).String(), + Symbol: fpair.String(), Limit: limit, Sort: sort, By: by, }, - ID: h.WebsocketConn.GenerateMessageID(false), + ID: h.Websocket.Conn.GenerateMessageID(false), } - resp, err := h.WebsocketConn.SendMessageReturnResponse(request.ID, request) + resp, err := h.Websocket.Conn.SendMessageReturnResponse(request.ID, request) if err != nil { return nil, fmt.Errorf("%v %v", h.Name, err) } diff --git a/exchanges/hitbtc/hitbtc_wrapper.go b/exchanges/hitbtc/hitbtc_wrapper.go index cfbdcb32..f23872d2 100644 --- a/exchanges/hitbtc/hitbtc_wrapper.go +++ b/exchanges/hitbtc/hitbtc_wrapper.go @@ -19,8 +19,8 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" "github.com/thrasher-corp/gocryptotrader/log" "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) @@ -56,18 +56,11 @@ func (h *HitBTC) SetDefaults() { h.API.CredentialsValidator.RequiresKey = true h.API.CredentialsValidator.RequiresSecret = true - h.CurrencyPairs = currency.PairsManager{ - AssetTypes: asset.Items{ - asset.Spot, - }, - UseGlobalFormat: true, - RequestFormat: ¤cy.PairFormat{ - Uppercase: true, - }, - ConfigFormat: ¤cy.PairFormat{ - Delimiter: "-", - Uppercase: true, - }, + requestFmt := ¤cy.PairFormat{Uppercase: true} + configFmt := ¤cy.PairFormat{Delimiter: currency.DashDelimiter, Uppercase: true} + err := h.SetGlobalPairsManager(requestFmt, configFmt, asset.Spot) + if err != nil { + log.Errorln(log.ExchangeSys, err) } h.Features = exchange.Features{ @@ -139,7 +132,7 @@ func (h *HitBTC) SetDefaults() { h.API.Endpoints.URLDefault = apiURL h.API.Endpoints.URL = h.API.Endpoints.URLDefault h.API.Endpoints.WebsocketURL = hitbtcWebsocketAddress - h.Websocket = wshandler.New() + h.Websocket = stream.New() h.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit h.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout h.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit @@ -157,42 +150,33 @@ func (h *HitBTC) Setup(exch *config.ExchangeConfig) error { return err } - err = h.Websocket.Setup( - &wshandler.WebsocketSetup{ - Enabled: exch.Features.Enabled.Websocket, - Verbose: exch.Verbose, - AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, - WebsocketTimeout: exch.WebsocketTrafficTimeout, - DefaultURL: hitbtcWebsocketAddress, - ExchangeName: exch.Name, - RunningURL: exch.API.Endpoints.WebsocketURL, - Connector: h.WsConnect, - Subscriber: h.Subscribe, - UnSubscriber: h.Unsubscribe, - Features: &h.Features.Supports.WebsocketCapabilities, - }) + err = h.Websocket.Setup(&stream.WebsocketSetup{ + Enabled: exch.Features.Enabled.Websocket, + Verbose: exch.Verbose, + AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, + WebsocketTimeout: exch.WebsocketTrafficTimeout, + DefaultURL: hitbtcWebsocketAddress, + ExchangeName: exch.Name, + RunningURL: exch.API.Endpoints.WebsocketURL, + Connector: h.WsConnect, + Subscriber: h.Subscribe, + UnSubscriber: h.Unsubscribe, + GenerateSubscriptions: h.GenerateDefaultSubscriptions, + Features: &h.Features.Supports.WebsocketCapabilities, + OrderbookBufferLimit: exch.WebsocketOrderbookBufferLimit, + BufferEnabled: true, + SortBuffer: true, + SortBufferByUpdateIDs: true, + }) if err != nil { return err } - h.WebsocketConn = &wshandler.WebsocketConnection{ - ExchangeName: h.Name, - URL: h.Websocket.GetWebsocketURL(), - ProxyURL: h.Websocket.GetProxyAddress(), - Verbose: h.Verbose, + return h.Websocket.SetupNewConnection(stream.ConnectionSetup{ RateLimit: rateLimit, ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout, ResponseMaxLimit: exch.WebsocketResponseMaxLimit, - } - - h.Websocket.Orderbook.Setup( - exch.WebsocketOrderbookBufferLimit, - true, - true, - true, - false, - exch.Name) - return nil + }) } // Start starts the HitBTC go routine @@ -212,16 +196,52 @@ func (h *HitBTC) Run() { } forceUpdate := false - delim := h.GetPairFormat(asset.Spot, false).Delimiter - if !common.StringDataContains(h.GetEnabledPairs(asset.Spot).Strings(), delim) || - !common.StringDataContains(h.GetAvailablePairs(asset.Spot).Strings(), delim) { - enabledPairs := []string{currency.BTC.String() + delim + currency.USD.String()} - log.Warn(log.ExchangeSys, "Available pairs for HitBTC reset due to config upgrade, please enable the ones you would like again.") - forceUpdate = true + format, err := h.GetPairFormat(asset.Spot, false) + if err != nil { + log.Errorf(log.ExchangeSys, + "%s failed to update tradable pairs. Err: %s", + h.Name, + err) + return + } + enabled, err := h.GetEnabledPairs(asset.Spot) + if err != nil { + log.Errorf(log.ExchangeSys, + "%s failed to update tradable pairs. Err: %s", + h.Name, + err) + return + } - err := h.UpdatePairs(currency.NewPairsFromStrings(enabledPairs), asset.Spot, true, true) + avail, err := h.GetAvailablePairs(asset.Spot) + if err != nil { + log.Errorf(log.ExchangeSys, + "%s failed to update tradable pairs. Err: %s", + h.Name, + err) + return + } + + if !common.StringDataContains(enabled.Strings(), format.Delimiter) || + !common.StringDataContains(avail.Strings(), format.Delimiter) { + enabledPairs := []string{currency.BTC.String() + format.Delimiter + currency.USD.String()} + log.Warn(log.ExchangeSys, + "Available pairs for HitBTC reset due to config upgrade, please enable the ones you would like again.") + forceUpdate = true + var p currency.Pairs + p, err = currency.NewPairsFromStrings(enabledPairs) if err != nil { - log.Errorf(log.ExchangeSys, "%s failed to update enabled currencies.\n", h.Name) + log.Errorf(log.ExchangeSys, + "%s failed to update tradable pairs. Err: %s", + h.Name, + err) + return + } + err = h.UpdatePairs(p, asset.Spot, true, true) + if err != nil { + log.Errorf(log.ExchangeSys, + "%s failed to update enabled currencies.\n", + h.Name) } } @@ -229,9 +249,12 @@ func (h *HitBTC) Run() { return } - err := h.UpdateTradablePairs(forceUpdate) + err = h.UpdateTradablePairs(forceUpdate) if err != nil { - log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", h.Name, err) + log.Errorf(log.ExchangeSys, + "%s failed to update tradable pairs. Err: %s", + h.Name, + err) } } @@ -242,10 +265,16 @@ func (h *HitBTC) FetchTradablePairs(asset asset.Item) ([]string, error) { return nil, err } + format, err := h.GetPairFormat(asset, false) + if err != nil { + return nil, err + } + var pairs []string for x := range symbols { pairs = append(pairs, symbols[x].BaseCurrency+ - h.GetPairFormat(asset, false).Delimiter+symbols[x].QuoteCurrency) + format.Delimiter+ + symbols[x].QuoteCurrency) } return pairs, nil } @@ -258,24 +287,34 @@ func (h *HitBTC) UpdateTradablePairs(forceUpdate bool) error { return err } - return h.UpdatePairs(currency.NewPairsFromStrings(pairs), asset.Spot, false, forceUpdate) + p, err := currency.NewPairsFromStrings(pairs) + if err != nil { + return err + } + return h.UpdatePairs(p, asset.Spot, false, forceUpdate) } // UpdateTicker updates and returns the ticker for a currency pair -func (h *HitBTC) UpdateTicker(currencyPair currency.Pair, assetType asset.Item) (*ticker.Price, error) { - tickerPrice := new(ticker.Price) +func (h *HitBTC) UpdateTicker(p currency.Pair, a asset.Item) (*ticker.Price, error) { tick, err := h.GetTickers() if err != nil { - return tickerPrice, err + return nil, err + } + pairs, err := h.GetEnabledPairs(a) + if err != nil { + return nil, err } - pairs := h.GetEnabledPairs(assetType) for i := range pairs { for j := range tick { - pairFmt := h.FormatExchangeCurrency(pairs[i], assetType).String() - if tick[j].Symbol != pairFmt { + pairFmt, err := h.FormatExchangeCurrency(pairs[i], a) + if err != nil { + return nil, err + } + + if tick[j].Symbol != pairFmt.String() { found := false if strings.Contains(tick[j].Symbol, "USDT") { - if pairFmt == tick[j].Symbol[0:len(tick[j].Symbol)-1] { + if pairFmt.String() == tick[j].Symbol[0:len(tick[j].Symbol)-1] { found = true } } @@ -283,25 +322,26 @@ func (h *HitBTC) UpdateTicker(currencyPair currency.Pair, assetType asset.Item) continue } } - tickerPrice := &ticker.Price{ - Last: tick[j].Last, - High: tick[j].High, - Low: tick[j].Low, - Bid: tick[j].Bid, - Ask: tick[j].Ask, - Volume: tick[j].Volume, - QuoteVolume: tick[j].VolumeQuote, - Open: tick[j].Open, - Pair: pairs[i], - LastUpdated: tick[j].Timestamp, - } - err = ticker.ProcessTicker(h.Name, tickerPrice, assetType) + + err = ticker.ProcessTicker(&ticker.Price{ + Last: tick[j].Last, + High: tick[j].High, + Low: tick[j].Low, + Bid: tick[j].Bid, + Ask: tick[j].Ask, + Volume: tick[j].Volume, + QuoteVolume: tick[j].VolumeQuote, + Open: tick[j].Open, + Pair: pairs[i], + LastUpdated: tick[j].Timestamp, + ExchangeName: h.Name, + AssetType: a}) if err != nil { - log.Error(log.Ticker, err) + return nil, err } } } - return ticker.GetTicker(h.Name, currencyPair, assetType) + return ticker.GetTicker(h.Name, p, a) } // FetchTicker returns the ticker for a currency pair @@ -323,13 +363,18 @@ func (h *HitBTC) FetchOrderbook(p currency.Pair, assetType asset.Item) (*orderbo } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (h *HitBTC) UpdateOrderbook(currencyPair currency.Pair, assetType asset.Item) (*orderbook.Base, error) { - orderBook := new(orderbook.Base) - orderbookNew, err := h.GetOrderbook(h.FormatExchangeCurrency(currencyPair, assetType).String(), 1000) +func (h *HitBTC) UpdateOrderbook(c currency.Pair, assetType asset.Item) (*orderbook.Base, error) { + fpair, err := h.FormatExchangeCurrency(c, assetType) if err != nil { - return orderBook, err + return nil, err } + orderbookNew, err := h.GetOrderbook(fpair.String(), 1000) + if err != nil { + return nil, err + } + + orderBook := new(orderbook.Base) for x := range orderbookNew.Bids { orderBook.Bids = append(orderBook.Bids, orderbook.Item{ Amount: orderbookNew.Bids[x].Amount, @@ -344,7 +389,7 @@ func (h *HitBTC) UpdateOrderbook(currencyPair currency.Pair, assetType asset.Ite }) } - orderBook.Pair = currencyPair + orderBook.Pair = c orderBook.ExchangeName = h.Name orderBook.AssetType = assetType @@ -353,7 +398,7 @@ func (h *HitBTC) UpdateOrderbook(currencyPair currency.Pair, assetType asset.Ite return orderBook, err } - return orderbook.Get(h.Name, currencyPair, assetType) + return orderbook.Get(h.Name, c, assetType) } // UpdateAccountInfo retrieves balances for all enabled currencies for the @@ -527,11 +572,6 @@ func (h *HitBTC) WithdrawFiatFundsToInternationalBank(withdrawRequest *withdraw. return nil, common.ErrFunctionNotSupported } -// GetWebsocket returns a pointer to the exchange websocket -func (h *HitBTC) GetWebsocket() (*wshandler.Websocket, error) { - return h.Websocket, nil -} - // GetFeeByType returns an estimate of fee based on type of transaction func (h *HitBTC) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { if !h.AllowAuthenticatedRequest() && // Todo check connection status @@ -556,10 +596,19 @@ func (h *HitBTC) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, e allOrders = append(allOrders, resp...) } + format, err := h.GetPairFormat(asset.Spot, false) + if err != nil { + return nil, err + } + var orders []order.Detail for i := range allOrders { - symbol := currency.NewPairDelimiter(allOrders[i].Symbol, - h.GetPairFormat(asset.Spot, false).Delimiter) + var symbol currency.Pair + symbol, err = currency.NewPairDelimiter(allOrders[i].Symbol, + format.Delimiter) + if err != nil { + return nil, err + } side := order.Side(strings.ToUpper(allOrders[i].Side)) orders = append(orders, order.Detail{ ID: allOrders[i].ID, @@ -593,10 +642,19 @@ func (h *HitBTC) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, e allOrders = append(allOrders, resp...) } + format, err := h.GetPairFormat(asset.Spot, false) + if err != nil { + return nil, err + } + var orders []order.Detail for i := range allOrders { - symbol := currency.NewPairDelimiter(allOrders[i].Symbol, - h.GetPairFormat(asset.Spot, false).Delimiter) + var symbol currency.Pair + symbol, err = currency.NewPairDelimiter(allOrders[i].Symbol, + format.Delimiter) + if err != nil { + return nil, err + } side := order.Side(strings.ToUpper(allOrders[i].Side)) orders = append(orders, order.Detail{ ID: allOrders[i].ID, @@ -614,25 +672,6 @@ func (h *HitBTC) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, e return orders, nil } -// SubscribeToWebsocketChannels appends to ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle subscribing -func (h *HitBTC) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - h.Websocket.SubscribeToChannels(channels) - return nil -} - -// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle unsubscribing -func (h *HitBTC) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - h.Websocket.RemoveSubscribedChannels(channels) - return nil -} - -// GetSubscriptions returns a copied list of subscriptions -func (h *HitBTC) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) { - return h.Websocket.GetSubscriptions(), nil -} - // AuthenticateWebsocket sends an authentication message to the websocket func (h *HitBTC) AuthenticateWebsocket() error { return h.wsLogin() @@ -666,7 +705,13 @@ func (h *HitBTC) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end Interval: interval, } } - data, err := h.GetCandles(h.FormatExchangeCurrency(pair, a).String(), + + formattedPair, err := h.FormatExchangeCurrency(pair, a) + if err != nil { + return kline.Item{}, err + } + + data, err := h.GetCandles(formattedPair.String(), strconv.FormatInt(int64(h.Features.Enabled.Kline.ResultLimit), 10), h.FormatExchangeKlineInterval(interval), start, end) @@ -711,8 +756,13 @@ func (h *HitBTC) GetHistoricCandlesExtended(pair currency.Pair, a asset.Item, st } dates := kline.CalcDateRanges(start, end, interval, h.Features.Enabled.Kline.ResultLimit) + formattedPair, err := h.FormatExchangeCurrency(pair, a) + if err != nil { + return kline.Item{}, err + } + for y := range dates { - data, err := h.GetCandles(h.FormatExchangeCurrency(pair, a).String(), + data, err := h.GetCandles(formattedPair.String(), strconv.FormatInt(int64(h.Features.Enabled.Kline.ResultLimit), 10), h.FormatExchangeKlineInterval(interval), dates[y].Start, dates[y].End) diff --git a/exchanges/huobi/huobi.go b/exchanges/huobi/huobi.go index 3f543fae..c0fc5d8a 100644 --- a/exchanges/huobi/huobi.go +++ b/exchanges/huobi/huobi.go @@ -16,7 +16,6 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/request" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" ) const ( @@ -62,9 +61,7 @@ const ( // HUOBI is the overarching type across this package type HUOBI struct { exchange.Base - AccountID string - WebsocketConn *wshandler.WebsocketConnection - AuthenticatedWebsocketConn *wshandler.WebsocketConnection + AccountID string } // GetSpotKline returns kline data diff --git a/exchanges/huobi/huobi_test.go b/exchanges/huobi/huobi_test.go index 6bf3148b..03d692c1 100644 --- a/exchanges/huobi/huobi_test.go +++ b/exchanges/huobi/huobi_test.go @@ -17,7 +17,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/kline" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) @@ -47,13 +47,11 @@ func TestMain(m *testing.M) { hConfig.API.AuthenticatedWebsocketSupport = true hConfig.API.Credentials.Key = apiKey hConfig.API.Credentials.Secret = apiSecret - + h.Websocket = sharedtestvalues.NewTestWebsocket() err = h.Setup(hConfig) if err != nil { log.Fatal("Huobi setup error", err) } - h.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride() - h.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride() os.Exit(m.Run()) } @@ -62,26 +60,10 @@ func setupWsTests(t *testing.T) { return } if !h.Websocket.IsEnabled() && !h.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() { - t.Skip(wshandler.WebsocketNotEnabled) + t.Skip(stream.WebsocketNotEnabled) } comms = make(chan WsMessage, sharedtestvalues.WebsocketChannelOverrideCapacity) - h.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride() - h.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride() go h.wsReadData() - h.AuthenticatedWebsocketConn = &wshandler.WebsocketConnection{ - ExchangeName: h.Name, - URL: wsAccountsOrdersURL, - Verbose: h.Verbose, - ResponseMaxLimit: exchange.DefaultWebsocketResponseMaxLimit, - ResponseCheckTimeout: exchange.DefaultWebsocketResponseCheckTimeout, - } - h.WebsocketConn = &wshandler.WebsocketConnection{ - ExchangeName: h.Name, - URL: wsMarketURL, - Verbose: h.Verbose, - ResponseMaxLimit: exchange.DefaultWebsocketResponseMaxLimit, - ResponseCheckTimeout: exchange.DefaultWebsocketResponseCheckTimeout, - } var dialer websocket.Dialer err := h.wsAuthenticatedDial(&dialer) if err != nil { @@ -108,9 +90,12 @@ func TestGetSpotKline(t *testing.T) { } func TestGetHistoricCandles(t *testing.T) { - currencyPair := currency.NewPairFromString("BTCUSDT") + currencyPair, err := currency.NewPairFromString("BTCUSDT") + if err != nil { + t.Fatal(err) + } startTime := time.Now().Add(-time.Hour * 1) - _, err := h.GetHistoricCandles(currencyPair, asset.Spot, startTime, time.Now(), kline.OneMin) + _, err = h.GetHistoricCandles(currencyPair, asset.Spot, startTime, time.Now(), kline.OneMin) if err != nil { t.Fatal(err) } @@ -127,9 +112,12 @@ func TestGetHistoricCandles(t *testing.T) { } func TestGetHistoricCandlesExtended(t *testing.T) { - currencyPair := currency.NewPairFromString("BTCUSDT") + currencyPair, err := currency.NewPairFromString("BTCUSDT") + if err != nil { + t.Fatal(err) + } startTime := time.Now().Add(-time.Hour * 1) - _, err := h.GetHistoricCandlesExtended(currencyPair, asset.Spot, startTime, time.Now(), kline.OneMin) + _, err = h.GetHistoricCandlesExtended(currencyPair, asset.Spot, startTime, time.Now(), kline.OneMin) if err != nil { t.Fatal(err) } @@ -663,24 +651,22 @@ func TestQueryWithdrawQuota(t *testing.T) { // TestWsGetAccountsList connects to WS, logs in, gets account list func TestWsGetAccountsList(t *testing.T) { setupWsTests(t) - resp, err := h.wsGetAccountsList() + _, err := h.wsGetAccountsList() if err != nil { t.Fatal(err) } - if resp.ErrorCode > 0 { - t.Error(resp.ErrorMessage) - } } // TestWsGetOrderList connects to WS, logs in, gets order list func TestWsGetOrderList(t *testing.T) { setupWsTests(t) - resp, err := h.wsGetOrdersList(1, currency.NewPairFromString("ethbtc")) + p, err := currency.NewPairFromString("ethbtc") if err != nil { t.Fatal(err) } - if resp.ErrorCode > 0 { - t.Error(resp.ErrorMessage) + _, err = h.wsGetOrdersList(1, p) + if err != nil { + t.Fatal(err) } } @@ -688,13 +674,10 @@ func TestWsGetOrderList(t *testing.T) { func TestWsGetOrderDetails(t *testing.T) { setupWsTests(t) orderID := "123" - resp, err := h.wsGetOrderDetails(orderID) + _, err := h.wsGetOrderDetails(orderID) if err != nil { t.Fatal(err) } - if resp.ErrorCode > 0 && resp.ErrorCode != 10022 { - t.Error(resp.ErrorMessage) - } } func TestWsSubResponse(t *testing.T) { diff --git a/exchanges/huobi/huobi_types.go b/exchanges/huobi/huobi_types.go index b2e6cdb6..b435b0c3 100644 --- a/exchanges/huobi/huobi_types.go +++ b/exchanges/huobi/huobi_types.go @@ -315,18 +315,19 @@ type WsRequest struct { // WsResponse defines a response from the websocket connection when there // is an error type WsResponse struct { - Op string `json:"op"` - TS int64 `json:"ts"` - Status string `json:"status"` - ErrorCode int64 `json:"err-code"` - ErrorMessage string `json:"err-msg"` - Ping int64 `json:"ping"` - Channel string `json:"ch"` - Rep string `json:"rep"` - Topic string `json:"topic"` - Subscribed string `json:"subbed"` - UnSubscribed string `json:"unsubbed"` - ClientID int64 `json:"cid,string"` + Op string `json:"op"` + TS int64 `json:"ts"` + Status string `json:"status"` + // ErrorCode returns either an integer or a string + ErrorCode interface{} `json:"err-code"` + ErrorMessage string `json:"err-msg"` + Ping int64 `json:"ping"` + Channel string `json:"ch"` + Rep string `json:"rep"` + Topic string `json:"topic"` + Subscribed string `json:"subbed"` + UnSubscribed string `json:"unsubbed"` + ClientID int64 `json:"cid,string"` } // WsHeartBeat defines a heartbeat request @@ -359,7 +360,7 @@ type WsKline struct { Amount float64 `json:"amount"` Volume float64 `json:"vol"` Count int64 `json:"count"` - } + } `json:"tick"` } // WsTick stores websocket ticker data @@ -577,7 +578,7 @@ type WsPong struct { Pong int64 `json:"pong"` } -type wsKLineResponseThing struct { +type wsKlineResponse struct { Data []struct { Amount float64 `json:"amount"` Close float64 `json:"close"` @@ -591,3 +592,8 @@ type wsKLineResponseThing struct { Rep string `json:"rep"` Status string `json:"status"` } + +type authenticationPing struct { + OP string `json:"op"` + TS int64 `json:"ts"` +} diff --git a/exchanges/huobi/huobi_websocket.go b/exchanges/huobi/huobi_websocket.go index 4f773411..9bebc259 100644 --- a/exchanges/huobi/huobi_websocket.go +++ b/exchanges/huobi/huobi_websocket.go @@ -11,14 +11,15 @@ import ( "time" "github.com/gorilla/websocket" + "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/crypto" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" "github.com/thrasher-corp/gocryptotrader/log" ) @@ -57,7 +58,7 @@ var comms = make(chan WsMessage) // WsConnect initiates a new websocket connection func (h *HUOBI) WsConnect() error { if !h.Websocket.IsEnabled() || !h.IsEnabled() { - return errors.New(wshandler.WebsocketNotEnabled) + return errors.New(stream.WebsocketNotEnabled) } var dialer websocket.Dialer err := h.wsDial(&dialer) @@ -66,58 +67,61 @@ func (h *HUOBI) WsConnect() error { } err = h.wsAuthenticatedDial(&dialer) if err != nil { - log.Errorf(log.ExchangeSys, "%v - authenticated dial failed: %v\n", h.Name, err) + log.Errorf(log.ExchangeSys, + "%v - authenticated dial failed: %v\n", + h.Name, + err) } err = h.wsLogin() if err != nil { - log.Errorf(log.ExchangeSys, "%v - authentication failed: %v\n", h.Name, err) + log.Errorf(log.ExchangeSys, + "%v - authentication failed: %v\n", + h.Name, + err) h.Websocket.SetCanUseAuthenticatedEndpoints(false) } go h.wsReadData() - h.GenerateDefaultSubscriptions() - - return nil -} - -func (h *HUOBI) wsDial(dialer *websocket.Dialer) error { - err := h.WebsocketConn.Dial(dialer, http.Header{}) + subs, err := h.GenerateDefaultSubscriptions() if err != nil { return err } - go h.wsFunnelConnectionData(h.WebsocketConn, wsMarketURL) + return h.Websocket.SubscribeToChannels(subs) +} + +func (h *HUOBI) wsDial(dialer *websocket.Dialer) error { + err := h.Websocket.Conn.Dial(dialer, http.Header{}) + if err != nil { + return err + } + go h.wsFunnelConnectionData(h.Websocket.Conn, wsMarketURL) return nil } func (h *HUOBI) wsAuthenticatedDial(dialer *websocket.Dialer) error { if !h.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) { - return fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", h.Name) + return fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", + h.Name) } - err := h.AuthenticatedWebsocketConn.Dial(dialer, http.Header{}) + err := h.Websocket.AuthConn.Dial(dialer, http.Header{}) if err != nil { return err } - go h.wsFunnelConnectionData(h.AuthenticatedWebsocketConn, wsAccountsOrdersURL) + go h.wsFunnelConnectionData(h.Websocket.AuthConn, wsAccountsOrdersURL) return nil } -// wsFunnelConnectionData manages data from multiple endpoints and passes it to a channel -func (h *HUOBI) wsFunnelConnectionData(ws *wshandler.WebsocketConnection, url string) { +// wsFunnelConnectionData manages data from multiple endpoints and passes it to +// a channel +func (h *HUOBI) wsFunnelConnectionData(ws stream.Connection, url string) { h.Websocket.Wg.Add(1) defer h.Websocket.Wg.Done() for { - select { - case <-h.Websocket.ShutdownC: + resp := ws.ReadMessage() + if resp.Raw == nil { return - default: - resp, err := ws.ReadMessage() - if err != nil { - h.Websocket.DataHandler <- err - return - } - h.Websocket.TrafficAlert <- struct{}{} - comms <- WsMessage{Raw: resp.Raw, URL: url} } + comms <- WsMessage{Raw: resp.Raw, URL: url} } } @@ -126,14 +130,10 @@ func (h *HUOBI) wsReadData() { h.Websocket.Wg.Add(1) defer h.Websocket.Wg.Done() for { - select { - case <-h.Websocket.ShutdownC: - return - case resp := <-comms: - err := h.wsHandleData(resp.Raw) - if err != nil { - h.Websocket.DataHandler <- err - } + resp := <-comms + err := h.wsHandleData(resp.Raw) + if err != nil { + h.Websocket.DataHandler <- err } } } @@ -149,7 +149,8 @@ func stringToOrderStatus(status string) (order.Status, error) { case "partial-canceled": return order.PartiallyCancelled, nil default: - return order.UnknownStatus, errors.New(status + " not recognised as order status") + return order.UnknownStatus, + errors.New(status + " not recognised as order status") } } @@ -161,7 +162,8 @@ func stringToOrderSide(side string) (order.Side, error) { return order.Sell, nil } - return order.UnknownSide, errors.New(side + " not recognised as order side") + return order.UnknownSide, + errors.New(side + " not recognised as order side") } func stringToOrderType(oType string) (order.Type, error) { @@ -172,7 +174,8 @@ func stringToOrderType(oType string) (order.Type, error) { return order.Market, nil } - return order.UnknownType, errors.New(oType + " not recognised as order type") + return order.UnknownType, + errors.New(oType + " not recognised as order type") } func (h *HUOBI) wsHandleData(respRaw []byte) error { @@ -192,13 +195,32 @@ func (h *HUOBI) wsHandleData(respRaw []byte) error { h.sendPingResponse(init.Ping) return nil } - if init.ErrorMessage == "api-signature-not-valid" { - h.Websocket.SetCanUseAuthenticatedEndpoints(false) - return errors.New(h.Name + " - invalid credentials. Authenticated requests disabled") + + if init.Op == "ping" { + authPing := authenticationPing{ + OP: "pong", + TS: init.TS, + } + err := h.Websocket.AuthConn.SendJSONMessage(authPing) + if err != nil { + log.Error(log.ExchangeSys, err) + } + return nil } + + if init.ErrorMessage != "" { + if init.ErrorMessage == "api-signature-not-valid" { + h.Websocket.SetCanUseAuthenticatedEndpoints(false) + return errors.New(h.Name + + " - invalid credentials. Authenticated requests disabled") + } + + codes, _ := init.ErrorCode.(string) + return errors.New(h.Name + " Code:" + codes + " Message:" + init.ErrorMessage) + } + if init.ClientID > 0 { - if h.WebsocketConn.IsIDWaitingForResponse(init.ClientID) { - h.WebsocketConn.SetResponseIDAndData(init.ClientID, respRaw) + if h.Websocket.Match.IncomingWithData(init.ClientID, respRaw) { return nil } } @@ -206,12 +228,8 @@ func (h *HUOBI) wsHandleData(respRaw []byte) error { switch { case strings.EqualFold(init.Op, authOp): h.Websocket.SetCanUseAuthenticatedEndpoints(true) - err := json.Unmarshal(respRaw, &init) - if err != nil { - return err - } - h.Websocket.DataHandler <- init - + // Auth captured + return nil case strings.EqualFold(init.Topic, "accounts"): var response WsAuthenticatedAccountsResponse err := json.Unmarshal(respRaw, &response) @@ -229,7 +247,8 @@ func (h *HUOBI) wsHandleData(respRaw []byte) error { } data := strings.Split(response.Topic, ".") if len(data) < 2 { - return errors.New(h.Name + " - currency could not be extracted from response") + return errors.New(h.Name + + " - currency could not be extracted from response") } orderID := strconv.FormatInt(response.Data.OrderID, 10) var oSide order.Side @@ -299,27 +318,6 @@ func (h *HUOBI) wsHandleData(respRaw []byte) error { if err != nil { return err } - case strings.Contains(init.Rep, "kline"): - var kline wsKLineResponseThing - err := json.Unmarshal(respRaw, &kline) - if err != nil { - return err - } - var curr = strings.Split(init.Rep, ".") - for i := range kline.Data { - h.Websocket.DataHandler <- wshandler.KlineData{ - Timestamp: time.Now(), - Exchange: h.Name, - AssetType: asset.Spot, - Pair: currency.NewPairFromFormattedPairs(curr[1], - h.GetEnabledPairs(asset.Spot), h.GetPairFormat(asset.Spot, true)), - OpenPrice: kline.Data[i].Open, - ClosePrice: kline.Data[i].Close, - HighPrice: kline.Data[i].High, - LowPrice: kline.Data[i].Low, - Volume: kline.Data[i].Volume, - } - } case strings.Contains(init.Channel, "kline"): var kline WsKline err := json.Unmarshal(respRaw, &kline) @@ -327,17 +325,23 @@ func (h *HUOBI) wsHandleData(respRaw []byte) error { return err } data := strings.Split(kline.Channel, ".") - h.Websocket.DataHandler <- wshandler.KlineData{ - Timestamp: time.Unix(0, kline.Timestamp*int64(time.Millisecond)), - Exchange: h.Name, - AssetType: asset.Spot, - Pair: currency.NewPairFromFormattedPairs(data[1], - h.GetEnabledPairs(asset.Spot), h.GetPairFormat(asset.Spot, true)), + var p currency.Pair + var a asset.Item + p, a, err = h.GetRequestFormattedPairAndAssetType(data[1]) + if err != nil { + return err + } + h.Websocket.DataHandler <- stream.KlineData{ + Timestamp: time.Unix(0, kline.Timestamp*int64(time.Millisecond)), + Exchange: h.Name, + AssetType: a, + Pair: p, OpenPrice: kline.Tick.Open, ClosePrice: kline.Tick.Close, HighPrice: kline.Tick.High, LowPrice: kline.Tick.Low, Volume: kline.Tick.Volume, + Interval: data[3], } case strings.Contains(init.Channel, "trade.detail"): var trade WsTrade @@ -346,12 +350,28 @@ func (h *HUOBI) wsHandleData(respRaw []byte) error { return err } data := strings.Split(trade.Channel, ".") - h.Websocket.DataHandler <- wshandler.TradeData{ - Exchange: h.Name, - AssetType: asset.Spot, - CurrencyPair: currency.NewPairFromFormattedPairs(data[1], - h.GetEnabledPairs(asset.Spot), h.GetPairFormat(asset.Spot, true)), - Timestamp: time.Unix(0, trade.Tick.Timestamp*int64(time.Millisecond)), + var p currency.Pair + var a asset.Item + p, a, err = h.GetRequestFormattedPairAndAssetType(data[1]) + if err != nil { + return err + } + + for i := range trade.Tick.Data { + side := order.Buy + if trade.Tick.Data[i].Direction != "buy" { + side = order.Sell + } + h.Websocket.DataHandler <- stream.TradeData{ + Exchange: h.Name, + AssetType: a, + CurrencyPair: p, + Timestamp: time.Unix(0, + trade.Tick.Data[i].Timestamp*int64(time.Millisecond)), + Amount: trade.Tick.Data[i].Amount, + Price: trade.Tick.Data[i].Price, + Side: side, + } } case strings.Contains(init.Channel, "detail"), strings.Contains(init.Rep, "detail"): @@ -367,6 +387,14 @@ func (h *HUOBI) wsHandleData(respRaw []byte) error { if wsTicker.Rep != "" { data = strings.Split(wsTicker.Rep, ".") } + + var p currency.Pair + var a asset.Item + p, a, err = h.GetRequestFormattedPairAndAssetType(data[1]) + if err != nil { + return err + } + h.Websocket.DataHandler <- &ticker.Price{ ExchangeName: h.Name, Open: wsTicker.Tick.Open, @@ -376,19 +404,20 @@ func (h *HUOBI) wsHandleData(respRaw []byte) error { High: wsTicker.Tick.High, Low: wsTicker.Tick.Low, LastUpdated: time.Unix(0, wsTicker.Timestamp*int64(time.Millisecond)), - AssetType: asset.Spot, - Pair: currency.NewPairFromFormattedPairs(data[1], - h.GetEnabledPairs(asset.Spot), h.GetPairFormat(asset.Spot, true)), + AssetType: a, + Pair: p, } default: - h.Websocket.DataHandler <- wshandler.UnhandledMessageWarning{Message: h.Name + wshandler.UnhandledMessage + string(respRaw)} + h.Websocket.DataHandler <- stream.UnhandledMessageWarning{ + Message: h.Name + stream.UnhandledMessage + string(respRaw), + } return nil } return nil } func (h *HUOBI) sendPingResponse(pong int64) { - err := h.WebsocketConn.SendJSONMessage(WsPong{Pong: pong}) + err := h.Websocket.Conn.SendJSONMessage(WsPong{Pong: pong}) if err != nil { log.Error(log.ExchangeSys, err) } @@ -396,9 +425,22 @@ func (h *HUOBI) sendPingResponse(pong int64) { // WsProcessOrderbook processes new orderbook data func (h *HUOBI) WsProcessOrderbook(update *WsDepth, symbol string) error { - p := currency.NewPairFromFormattedPairs(symbol, - h.GetEnabledPairs(asset.Spot), - h.GetPairFormat(asset.Spot, true)) + pairs, err := h.GetEnabledPairs(asset.Spot) + if err != nil { + return err + } + + format, err := h.GetPairFormat(asset.Spot, true) + if err != nil { + return err + } + + p, err := currency.NewPairFromFormattedPairs(symbol, + pairs, + format) + if err != nil { + return err + } var bids, asks []orderbook.Item for i := range update.Tick.Bids { @@ -422,59 +464,100 @@ func (h *HUOBI) WsProcessOrderbook(update *WsDepth, symbol string) error { newOrderBook.AssetType = asset.Spot newOrderBook.ExchangeName = h.Name - err := h.Websocket.Orderbook.LoadSnapshot(&newOrderBook) - if err != nil { - return err - } - - h.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{ - Pair: p, - Exchange: h.Name, - Asset: asset.Spot, - } - return nil + return h.Websocket.Orderbook.LoadSnapshot(&newOrderBook) } // GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() -func (h *HUOBI) GenerateDefaultSubscriptions() { - var channels = []string{wsMarketKline, wsMarketDepth, wsMarketTrade, wsMarketTicker} - var subscriptions []wshandler.WebsocketChannelSubscription +func (h *HUOBI) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, error) { + var channels = []string{wsMarketKline, + wsMarketDepth, + wsMarketTrade, + wsMarketTicker} + var subscriptions []stream.ChannelSubscription if h.Websocket.CanUseAuthenticatedEndpoints() { channels = append(channels, "orders.%v", "orders.%v.update") - subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{ + subscriptions = append(subscriptions, stream.ChannelSubscription{ Channel: "accounts", }) } - enabledCurrencies := h.GetEnabledPairs(asset.Spot) + enabledCurrencies, err := h.GetEnabledPairs(asset.Spot) + if err != nil { + return nil, err + } for i := range channels { for j := range enabledCurrencies { enabledCurrencies[j].Delimiter = "" - channel := fmt.Sprintf(channels[i], enabledCurrencies[j].Lower().String()) - subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{ + channel := fmt.Sprintf(channels[i], + enabledCurrencies[j].Lower().String()) + subscriptions = append(subscriptions, stream.ChannelSubscription{ Channel: channel, Currency: enabledCurrencies[j], }) } } - h.Websocket.SubscribeToChannels(subscriptions) + return subscriptions, nil } // Subscribe sends a websocket message to receive data from the channel -func (h *HUOBI) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error { - if strings.Contains(channelToSubscribe.Channel, "orders.") || - strings.Contains(channelToSubscribe.Channel, "accounts") { - return h.wsAuthenticatedSubscribe("sub", wsAccountsOrdersEndPoint+channelToSubscribe.Channel, channelToSubscribe.Channel) +func (h *HUOBI) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error { + var errs common.Errors + for i := range channelsToSubscribe { + if strings.Contains(channelsToSubscribe[i].Channel, "orders.") || + strings.Contains(channelsToSubscribe[i].Channel, "accounts") { + err := h.wsAuthenticatedSubscribe("sub", + wsAccountsOrdersEndPoint+channelsToSubscribe[i].Channel, + channelsToSubscribe[i].Channel) + if err != nil { + errs = append(errs, err) + continue + } + h.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe[i]) + continue + } + err := h.Websocket.Conn.SendJSONMessage(WsRequest{ + Subscribe: channelsToSubscribe[i].Channel, + }) + if err != nil { + errs = append(errs, err) + continue + } + h.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe[i]) } - return h.WebsocketConn.SendJSONMessage(WsRequest{Subscribe: channelToSubscribe.Channel}) + if errs != nil { + return errs + } + return nil } // Unsubscribe sends a websocket message to stop receiving data from the channel -func (h *HUOBI) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error { - if strings.Contains(channelToSubscribe.Channel, "orders.") || - strings.Contains(channelToSubscribe.Channel, "accounts") { - return h.wsAuthenticatedSubscribe("unsub", wsAccountsOrdersEndPoint+channelToSubscribe.Channel, channelToSubscribe.Channel) +func (h *HUOBI) Unsubscribe(channelsToUnsubscribe []stream.ChannelSubscription) error { + var errs common.Errors + for i := range channelsToUnsubscribe { + if strings.Contains(channelsToUnsubscribe[i].Channel, "orders.") || + strings.Contains(channelsToUnsubscribe[i].Channel, "accounts") { + err := h.wsAuthenticatedSubscribe("unsub", + wsAccountsOrdersEndPoint+channelsToUnsubscribe[i].Channel, + channelsToUnsubscribe[i].Channel) + if err != nil { + errs = append(errs, err) + continue + } + h.Websocket.RemoveSuccessfulUnsubscriptions(channelsToUnsubscribe[i]) + continue + } + err := h.Websocket.Conn.SendJSONMessage(WsRequest{ + Unsubscribe: channelsToUnsubscribe[i].Channel, + }) + if err != nil { + errs = append(errs, err) + continue + } + h.Websocket.RemoveSuccessfulUnsubscriptions(channelsToUnsubscribe[i]) } - return h.WebsocketConn.SendJSONMessage(WsRequest{Unsubscribe: channelToSubscribe.Channel}) + if errs != nil { + return errs + } + return nil } func (h *HUOBI) wsGenerateSignature(timestamp, endpoint string) []byte { @@ -504,7 +587,7 @@ func (h *HUOBI) wsLogin() error { } hmac := h.wsGenerateSignature(timestamp, wsAccountsOrdersEndPoint) request.Signature = crypto.Base64Encode(hmac) - err := h.AuthenticatedWebsocketConn.SendJSONMessage(request) + err := h.Websocket.AuthConn.SendJSONMessage(request) if err != nil { h.Websocket.SetCanUseAuthenticatedEndpoints(false) return err @@ -526,7 +609,7 @@ func (h *HUOBI) wsAuthenticatedSubscribe(operation, endpoint, topic string) erro } hmac := h.wsGenerateSignature(timestamp, endpoint) request.Signature = crypto.Base64Encode(hmac) - return h.AuthenticatedWebsocketConn.SendJSONMessage(request) + return h.Websocket.AuthConn.SendJSONMessage(request) } func (h *HUOBI) wsGetAccountsList() (*WsAuthenticatedAccountsListResponse, error) { @@ -544,20 +627,34 @@ func (h *HUOBI) wsGetAccountsList() (*WsAuthenticatedAccountsListResponse, error } hmac := h.wsGenerateSignature(timestamp, wsAccountListEndpoint) request.Signature = crypto.Base64Encode(hmac) - request.ClientID = h.AuthenticatedWebsocketConn.GenerateMessageID(true) - resp, err := h.AuthenticatedWebsocketConn.SendMessageReturnResponse(request.ClientID, request) + request.ClientID = h.Websocket.AuthConn.GenerateMessageID(true) + resp, err := h.Websocket.AuthConn.SendMessageReturnResponse(request.ClientID, request) if err != nil { return nil, err } var response WsAuthenticatedAccountsListResponse err = json.Unmarshal(resp, &response) - return &response, err + if err != nil { + return nil, err + } + + code, _ := response.ErrorCode.(int) + if code != 0 { + return nil, errors.New(response.ErrorMessage) + } + return &response, nil } func (h *HUOBI) wsGetOrdersList(accountID int64, pair currency.Pair) (*WsAuthenticatedOrdersResponse, error) { if !h.Websocket.CanUseAuthenticatedEndpoints() { return nil, fmt.Errorf("%v not authenticated cannot get orders list", h.Name) } + + fpair, err := h.FormatExchangeCurrency(pair, asset.Spot) + if err != nil { + return nil, err + } + timestamp := time.Now().UTC().Format(wsDateTimeFormatting) request := WsAuthenticatedOrdersListRequest{ Op: requestOp, @@ -567,19 +664,30 @@ func (h *HUOBI) wsGetOrdersList(accountID int64, pair currency.Pair) (*WsAuthent Timestamp: timestamp, Topic: wsOrdersList, AccountID: accountID, - Symbol: h.FormatExchangeCurrency(pair, asset.Spot).String(), + Symbol: fpair.String(), States: "submitted,partial-filled", } + hmac := h.wsGenerateSignature(timestamp, wsOrdersListEndpoint) request.Signature = crypto.Base64Encode(hmac) - request.ClientID = h.AuthenticatedWebsocketConn.GenerateMessageID(true) - resp, err := h.AuthenticatedWebsocketConn.SendMessageReturnResponse(request.ClientID, request) + request.ClientID = h.Websocket.AuthConn.GenerateMessageID(true) + + resp, err := h.Websocket.AuthConn.SendMessageReturnResponse(request.ClientID, request) if err != nil { return nil, err } + var response WsAuthenticatedOrdersResponse err = json.Unmarshal(resp, &response) - return &response, err + if err != nil { + return nil, err + } + + code, _ := response.ErrorCode.(int) + if code != 0 { + return nil, errors.New(response.ErrorMessage) + } + return &response, nil } func (h *HUOBI) wsGetOrderDetails(orderID string) (*WsAuthenticatedOrderDetailResponse, error) { @@ -598,12 +706,20 @@ func (h *HUOBI) wsGetOrderDetails(orderID string) (*WsAuthenticatedOrderDetailRe } hmac := h.wsGenerateSignature(timestamp, wsOrdersDetailEndpoint) request.Signature = crypto.Base64Encode(hmac) - request.ClientID = h.AuthenticatedWebsocketConn.GenerateMessageID(true) - resp, err := h.AuthenticatedWebsocketConn.SendMessageReturnResponse(request.ClientID, request) + request.ClientID = h.Websocket.AuthConn.GenerateMessageID(true) + resp, err := h.Websocket.AuthConn.SendMessageReturnResponse(request.ClientID, request) if err != nil { return nil, err } var response WsAuthenticatedOrderDetailResponse err = json.Unmarshal(resp, &response) - return &response, err + if err != nil { + return nil, err + } + + code, _ := response.ErrorCode.(int) + if code != 0 { + return nil, errors.New(response.ErrorMessage) + } + return &response, nil } diff --git a/exchanges/huobi/huobi_wrapper.go b/exchanges/huobi/huobi_wrapper.go index 7d9f2889..65a1a825 100644 --- a/exchanges/huobi/huobi_wrapper.go +++ b/exchanges/huobi/huobi_wrapper.go @@ -19,8 +19,8 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" "github.com/thrasher-corp/gocryptotrader/log" "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) @@ -56,19 +56,11 @@ func (h *HUOBI) SetDefaults() { h.API.CredentialsValidator.RequiresKey = true h.API.CredentialsValidator.RequiresSecret = true - h.CurrencyPairs = currency.PairsManager{ - AssetTypes: asset.Items{ - asset.Spot, - }, - - UseGlobalFormat: true, - RequestFormat: ¤cy.PairFormat{ - Uppercase: false, - }, - ConfigFormat: ¤cy.PairFormat{ - Delimiter: "-", - Uppercase: true, - }, + requestFmt := ¤cy.PairFormat{} + configFmt := ¤cy.PairFormat{Delimiter: currency.DashDelimiter, Uppercase: true} + err := h.SetGlobalPairsManager(requestFmt, configFmt, asset.Spot) + if err != nil { + log.Errorln(log.ExchangeSys, err) } h.Features = exchange.Features{ @@ -138,7 +130,7 @@ func (h *HUOBI) SetDefaults() { h.API.Endpoints.URLDefault = huobiAPIURL h.API.Endpoints.URL = h.API.Endpoints.URLDefault h.API.Endpoints.WebsocketURL = wsMarketURL - h.Websocket = wshandler.New() + h.Websocket = stream.New() h.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit h.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout h.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit @@ -156,51 +148,41 @@ func (h *HUOBI) Setup(exch *config.ExchangeConfig) error { return err } - err = h.Websocket.Setup( - &wshandler.WebsocketSetup{ - Enabled: exch.Features.Enabled.Websocket, - Verbose: exch.Verbose, - AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, - WebsocketTimeout: exch.WebsocketTrafficTimeout, - DefaultURL: wsMarketURL, - ExchangeName: exch.Name, - RunningURL: exch.API.Endpoints.WebsocketURL, - Connector: h.WsConnect, - Subscriber: h.Subscribe, - UnSubscriber: h.Unsubscribe, - Features: &h.Features.Supports.WebsocketCapabilities, - }) + err = h.Websocket.Setup(&stream.WebsocketSetup{ + Enabled: exch.Features.Enabled.Websocket, + Verbose: exch.Verbose, + AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, + WebsocketTimeout: exch.WebsocketTrafficTimeout, + DefaultURL: wsMarketURL, + ExchangeName: exch.Name, + RunningURL: exch.API.Endpoints.WebsocketURL, + Connector: h.WsConnect, + Subscriber: h.Subscribe, + UnSubscriber: h.Unsubscribe, + GenerateSubscriptions: h.GenerateDefaultSubscriptions, + Features: &h.Features.Supports.WebsocketCapabilities, + OrderbookBufferLimit: exch.WebsocketOrderbookBufferLimit, + }) if err != nil { return err } - h.WebsocketConn = &wshandler.WebsocketConnection{ - ExchangeName: h.Name, - URL: wsMarketURL, - ProxyURL: h.Websocket.GetProxyAddress(), - Verbose: h.Verbose, - RateLimit: rateLimit, - ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout, - ResponseMaxLimit: exch.WebsocketResponseMaxLimit, - } - h.AuthenticatedWebsocketConn = &wshandler.WebsocketConnection{ - ExchangeName: h.Name, - URL: wsAccountsOrdersURL, - ProxyURL: h.Websocket.GetProxyAddress(), - Verbose: h.Verbose, + err = h.Websocket.SetupNewConnection(stream.ConnectionSetup{ RateLimit: rateLimit, ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout, ResponseMaxLimit: exch.WebsocketResponseMaxLimit, + }) + if err != nil { + return err } - h.Websocket.Orderbook.Setup( - exch.WebsocketOrderbookBufferLimit, - false, - false, - false, - false, - exch.Name) - return nil + return h.Websocket.SetupNewConnection(stream.ConnectionSetup{ + RateLimit: rateLimit, + ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout, + ResponseMaxLimit: exch.WebsocketResponseMaxLimit, + URL: wsAccountsOrdersURL, + Authenticated: true, + }) } // Start starts the HUOBI go routine @@ -224,14 +206,31 @@ func (h *HUOBI) Run() { } var forceUpdate bool - if common.StringDataContains(h.GetEnabledPairs(asset.Spot).Strings(), currency.CNY.String()) || - common.StringDataContains(h.GetAvailablePairs(asset.Spot).Strings(), currency.CNY.String()) { + enabled, err := h.GetEnabledPairs(asset.Spot) + if err != nil { + log.Errorf(log.ExchangeSys, + "%s Failed to update enabled currencies. Err:%s\n", + h.Name, + err) + } + + avail, err := h.GetAvailablePairs(asset.Spot) + if err != nil { + log.Errorf(log.ExchangeSys, + "%s Failed to update enabled currencies. Err:%s\n", + h.Name, + err) + } + + if common.StringDataContains(enabled.Strings(), currency.CNY.String()) || + common.StringDataContains(avail.Strings(), currency.CNY.String()) { forceUpdate = true } if common.StringDataContains(h.BaseCurrencies.Strings(), currency.CNY.String()) { cfg := config.GetConfig() - exchCfg, err := cfg.GetExchangeConfig(h.Name) + var exchCfg *config.ExchangeConfig + exchCfg, err = cfg.GetExchangeConfig(h.Name) if err != nil { log.Errorf(log.ExchangeSys, "%s failed to get exchange config. %s\n", @@ -244,20 +243,31 @@ func (h *HUOBI) Run() { } if forceUpdate { - enabledPairs := currency.Pairs{currency.Pair{ - Base: currency.BTC.Lower(), - Quote: currency.USDT.Lower(), - Delimiter: h.GetPairFormat(asset.Spot, false).Delimiter, - }, + var format currency.PairFormat + format, err = h.GetPairFormat(asset.Spot, false) + if err != nil { + log.Errorf(log.ExchangeSys, + "%s failed to get exchange config. %s\n", + h.Name, + err) + return + } + enabledPairs := currency.Pairs{ + currency.Pair{ + Base: currency.BTC.Lower(), + Quote: currency.USDT.Lower(), + Delimiter: format.Delimiter, + }, } log.Warn(log.ExchangeSys, "Available and enabled pairs for Huobi reset due to config upgrade, please enable the ones you would like again") - err := h.UpdatePairs(enabledPairs, asset.Spot, true, true) + err = h.UpdatePairs(enabledPairs, asset.Spot, true, true) if err != nil { log.Errorf(log.ExchangeSys, - "%s Failed to update enabled currencies.\n", - h.Name) + "%s Failed to update enabled currencies. Err:%s\n", + h.Name, + err) } } @@ -265,7 +275,7 @@ func (h *HUOBI) Run() { return } - err := h.UpdateTradablePairs(forceUpdate) + err = h.UpdateTradablePairs(forceUpdate) if err != nil { log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", @@ -281,13 +291,18 @@ func (h *HUOBI) FetchTradablePairs(asset asset.Item) ([]string, error) { return nil, err } + format, err := h.GetPairFormat(asset, false) + if err != nil { + return nil, err + } + var pairs []string for x := range symbols { if symbols[x].State != "online" { continue } pairs = append(pairs, symbols[x].BaseCurrency+ - h.GetPairFormat(asset, false).Delimiter+ + format.Delimiter+ symbols[x].QuoteCurrency) } @@ -302,37 +317,45 @@ func (h *HUOBI) UpdateTradablePairs(forceUpdate bool) error { return err } - return h.UpdatePairs(currency.NewPairsFromStrings(pairs), - asset.Spot, - false, - forceUpdate) + p, err := currency.NewPairsFromStrings(pairs) + if err != nil { + return err + } + return h.UpdatePairs(p, asset.Spot, false, forceUpdate) } // UpdateTicker updates and returns the ticker for a currency pair func (h *HUOBI) UpdateTicker(p currency.Pair, assetType asset.Item) (*ticker.Price, error) { - tickerPrice := new(ticker.Price) tickers, err := h.GetTickers() if err != nil { - return tickerPrice, err + return nil, err + } + pairs, err := h.GetEnabledPairs(assetType) + if err != nil { + return nil, err } - pairs := h.GetEnabledPairs(assetType) for i := range pairs { for j := range tickers.Data { - pairFmt := h.FormatExchangeCurrency(pairs[i], assetType).String() - if pairFmt != tickers.Data[j].Symbol { + pairFmt, err := h.FormatExchangeCurrency(pairs[i], assetType) + if err != nil { + return nil, err + } + + if pairFmt.String() != tickers.Data[j].Symbol { continue } - tickerPrice := &ticker.Price{ - High: tickers.Data[j].High, - Low: tickers.Data[j].Low, - Volume: tickers.Data[j].Volume, - Open: tickers.Data[j].Open, - Close: tickers.Data[j].Close, - Pair: pairs[i], - } - err = ticker.ProcessTicker(h.Name, tickerPrice, assetType) + + err = ticker.ProcessTicker(&ticker.Price{ + High: tickers.Data[j].High, + Low: tickers.Data[j].Low, + Volume: tickers.Data[j].Volume, + Open: tickers.Data[j].Open, + Close: tickers.Data[j].Close, + Pair: pairs[i], + ExchangeName: h.Name, + AssetType: assetType}) if err != nil { - log.Error(log.Ticker, err) + return nil, err } } } @@ -360,15 +383,19 @@ func (h *HUOBI) FetchOrderbook(p currency.Pair, assetType asset.Item) (*orderboo // UpdateOrderbook updates and returns the orderbook for a currency pair func (h *HUOBI) UpdateOrderbook(p currency.Pair, assetType asset.Item) (*orderbook.Base, error) { - orderBook := new(orderbook.Base) + fpair, err := h.FormatExchangeCurrency(p, assetType) + if err != nil { + return nil, err + } orderbookNew, err := h.GetDepth(OrderBookDataRequestParams{ - Symbol: h.FormatExchangeCurrency(p, assetType).String(), + Symbol: fpair.String(), Type: OrderBookDataRequestParamsTypeStep0, }) if err != nil { - return orderBook, err + return nil, err } + orderBook := new(orderbook.Base) for x := range orderbookNew.Bids { orderBook.Bids = append(orderBook.Bids, orderbook.Item{ Amount: orderbookNew.Bids[x][1], @@ -533,11 +560,16 @@ func (h *HUOBI) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { return submitOrderResponse, err } + p, err := h.FormatExchangeCurrency(s.Pair, s.AssetType) + if err != nil { + return submitOrderResponse, err + } + var formattedType SpotNewOrderRequestParamsType var params = SpotNewOrderRequestParams{ Amount: s.Amount, Source: "api", - Symbol: h.FormatExchangeCurrency(s.Pair, s.AssetType).String(), + Symbol: p.String(), AccountID: int(accountID), } @@ -590,10 +622,18 @@ func (h *HUOBI) CancelOrder(order *order.Cancel) error { // CancelAllOrders cancels all orders associated with a currency pair func (h *HUOBI) CancelAllOrders(orderCancellation *order.Cancel) (order.CancelAllResponse, error) { var cancelAllOrdersResponse order.CancelAllResponse - enabledPairs := h.GetEnabledPairs(asset.Spot) + enabledPairs, err := h.GetEnabledPairs(asset.Spot) + if err != nil { + return cancelAllOrdersResponse, err + } for i := range enabledPairs { + fpair, err := h.FormatExchangeCurrency(enabledPairs[i], asset.Spot) + if err != nil { + return cancelAllOrdersResponse, err + } + resp, err := h.CancelOpenOrdersBatch(orderCancellation.AccountID, - h.FormatExchangeCurrency(enabledPairs[i], asset.Spot).String()) + fpair.String()) if err != nil { return cancelAllOrdersResponse, err } @@ -683,7 +723,6 @@ func (h *HUOBI) GetOrderInfo(orderID string) (order.Detail, error) { if err != nil { return orderDetail, err } - orderDetail = order.Detail{ Exchange: h.Name, ID: orderID, @@ -732,11 +771,6 @@ func (h *HUOBI) WithdrawFiatFundsToInternationalBank(withdrawRequest *withdraw.R return nil, common.ErrFunctionNotSupported } -// GetWebsocket returns a pointer to the exchange websocket -func (h *HUOBI) GetWebsocket() (*wshandler.Websocket, error) { - return h.Websocket, nil -} - // GetFeeByType returns an estimate of fee based on type of transaction func (h *HUOBI) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { if !h.AllowAuthenticatedRequest() && // Todo check connection status @@ -814,8 +848,13 @@ func (h *HUOBI) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, er } } else { for i := range req.Pairs { + p, err := h.FormatExchangeCurrency(req.Pairs[i], asset.Spot) + if err != nil { + return nil, err + } + resp, err := h.GetOpenOrders(h.API.Credentials.ClientID, - h.FormatExchangeCurrency(req.Pairs[i], asset.Spot).String(), + p.String(), side, 500) if err != nil { @@ -857,8 +896,13 @@ func (h *HUOBI) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, er states := "partial-canceled,filled,canceled" var orders []order.Detail for i := range req.Pairs { + p, err := h.FormatExchangeCurrency(req.Pairs[i], asset.Spot) + if err != nil { + return nil, err + } + resp, err := h.GetOrders( - h.FormatExchangeCurrency(req.Pairs[i], asset.Spot).String(), + p.String(), "", "", "", @@ -911,25 +955,6 @@ func setOrderSideAndType(requestType string, orderDetail *order.Detail) { } } -// SubscribeToWebsocketChannels appends to ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle subscribing -func (h *HUOBI) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - h.Websocket.SubscribeToChannels(channels) - return nil -} - -// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle unsubscribing -func (h *HUOBI) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - h.Websocket.RemoveSubscribedChannels(channels) - return nil -} - -// GetSubscriptions returns a copied list of subscriptions -func (h *HUOBI) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) { - return h.Websocket.GetSubscriptions(), nil -} - // AuthenticateWebsocket sends an authentication message to the websocket func (h *HUOBI) AuthenticateWebsocket() error { return h.wsLogin() @@ -969,9 +994,14 @@ func (h *HUOBI) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end } } + formattedPair, err := h.FormatExchangeCurrency(pair, a) + if err != nil { + return kline.Item{}, err + } + klineParams := KlinesRequestParams{ Period: h.FormatExchangeKlineInterval(interval), - Symbol: h.FormatExchangeCurrency(pair, a).String(), + Symbol: formattedPair.String(), } candles, err := h.GetSpotKline(klineParams) diff --git a/exchanges/interfaces.go b/exchanges/interfaces.go index b0acce07..72afb5bb 100644 --- a/exchanges/interfaces.go +++ b/exchanges/interfaces.go @@ -11,8 +11,8 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/kline" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) @@ -32,8 +32,8 @@ type IBotExchange interface { UpdateOrderbook(p currency.Pair, a asset.Item) (*orderbook.Base, error) FetchTradablePairs(a asset.Item) ([]string, error) UpdateTradablePairs(forceUpdate bool) error - GetEnabledPairs(a asset.Item) currency.Pairs - GetAvailablePairs(a asset.Item) currency.Pairs + GetEnabledPairs(a asset.Item) (currency.Pairs, error) + GetAvailablePairs(a asset.Item) (currency.Pairs, error) FetchAccountInfo() (account.Holdings, error) UpdateAccountInfo() (account.Holdings, error) GetAuthenticatedAPISupport(endpoint uint8) bool @@ -62,14 +62,8 @@ type IBotExchange interface { SetHTTPClientUserAgent(ua string) GetHTTPClientUserAgent() string SetClientProxyAddress(addr string) error - SupportsWebsocket() bool SupportsREST() bool - IsWebsocketEnabled() bool - GetWebsocket() (*wshandler.Websocket, error) - SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error - UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error - AuthenticateWebsocket() error - GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) + GetSubscriptions() ([]stream.ChannelSubscription, error) GetDefaultConfig() (*config.ExchangeConfig, error) GetBase() *Base SupportsAsset(assetType asset.Item) bool @@ -77,4 +71,16 @@ type IBotExchange interface { GetHistoricCandlesExtended(p currency.Pair, a asset.Item, timeStart, timeEnd time.Time, interval kline.Interval) (kline.Item, error) DisableRateLimiter() error EnableRateLimiter() error + + // Websocket specific wrapper functionality + // GetWebsocket returns a pointer to the websocket + GetWebsocket() (*stream.Websocket, error) + IsWebsocketEnabled() bool + SupportsWebsocket() bool + SubscribeToWebsocketChannels(channels []stream.ChannelSubscription) error + UnsubscribeToWebsocketChannels(channels []stream.ChannelSubscription) error + // FlushWebsocketChannels checks and flushes subscriptions if there is a + // pair,asset, url/proxy or subscription change + FlushWebsocketChannels() error + AuthenticateWebsocket() error } diff --git a/exchanges/itbit/itbit_wrapper.go b/exchanges/itbit/itbit_wrapper.go index 59a6871b..db157315 100644 --- a/exchanges/itbit/itbit_wrapper.go +++ b/exchanges/itbit/itbit_wrapper.go @@ -20,7 +20,6 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" "github.com/thrasher-corp/gocryptotrader/log" "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) @@ -56,17 +55,11 @@ func (i *ItBit) SetDefaults() { i.API.CredentialsValidator.RequiresClientID = true i.API.CredentialsValidator.RequiresSecret = true - i.CurrencyPairs = currency.PairsManager{ - AssetTypes: asset.Items{ - asset.Spot, - }, - UseGlobalFormat: true, - RequestFormat: ¤cy.PairFormat{ - Uppercase: true, - }, - ConfigFormat: ¤cy.PairFormat{ - Uppercase: true, - }, + requestFmt := ¤cy.PairFormat{Uppercase: true} + configFmt := ¤cy.PairFormat{Uppercase: true} + err := i.SetGlobalPairsManager(requestFmt, configFmt, asset.Spot) + if err != nil { + log.Errorln(log.ExchangeSys, err) } i.Features = exchange.Features{ @@ -110,7 +103,6 @@ func (i *ItBit) Setup(exch *config.ExchangeConfig) error { i.SetEnabled(false) return nil } - return i.SetupDefaults(exch) } @@ -143,25 +135,30 @@ func (i *ItBit) UpdateTradablePairs(forceUpdate bool) error { // UpdateTicker updates and returns the ticker for a currency pair func (i *ItBit) UpdateTicker(p currency.Pair, assetType asset.Item) (*ticker.Price, error) { - tickerPrice := new(ticker.Price) - tick, err := i.GetTicker(i.FormatExchangeCurrency(p, assetType).String()) + fpair, err := i.FormatExchangeCurrency(p, assetType) if err != nil { - return tickerPrice, err + return nil, err } - tickerPrice = &ticker.Price{ - Last: tick.LastPrice, - High: tick.High24h, - Low: tick.Low24h, - Bid: tick.Bid, - Ask: tick.Ask, - Volume: tick.Volume24h, - Open: tick.OpenToday, - Pair: p, - LastUpdated: tick.ServertimeUTC, - } - err = ticker.ProcessTicker(i.Name, tickerPrice, assetType) + + tick, err := i.GetTicker(fpair.String()) if err != nil { - return tickerPrice, err + return nil, err + } + + err = ticker.ProcessTicker(&ticker.Price{ + Last: tick.LastPrice, + High: tick.High24h, + Low: tick.Low24h, + Bid: tick.Bid, + Ask: tick.Ask, + Volume: tick.Volume24h, + Open: tick.OpenToday, + Pair: p, + LastUpdated: tick.ServertimeUTC, + ExchangeName: i.Name, + AssetType: assetType}) + if err != nil { + return nil, err } return ticker.GetTicker(i.Name, p, assetType) @@ -187,12 +184,17 @@ func (i *ItBit) FetchOrderbook(p currency.Pair, assetType asset.Item) (*orderboo // UpdateOrderbook updates and returns the orderbook for a currency pair func (i *ItBit) UpdateOrderbook(p currency.Pair, assetType asset.Item) (*orderbook.Base, error) { - orderBook := new(orderbook.Base) - orderbookNew, err := i.GetOrderbook(i.FormatExchangeCurrency(p, assetType).String()) + fpair, err := i.FormatExchangeCurrency(p, assetType) if err != nil { - return orderBook, err + return nil, err } + orderbookNew, err := i.GetOrderbook(fpair.String()) + if err != nil { + return nil, err + } + + orderBook := new(orderbook.Base) for x := range orderbookNew.Bids { var price, amount float64 price, err = strconv.ParseFloat(orderbookNew.Bids[x][0], 64) @@ -424,11 +426,6 @@ func (i *ItBit) WithdrawFiatFundsToInternationalBank(withdrawRequest *withdraw.R return nil, common.ErrFunctionNotSupported } -// GetWebsocket returns a pointer to the exchange websocket -func (i *ItBit) GetWebsocket() (*wshandler.Websocket, error) { - return nil, common.ErrFunctionNotSupported -} - // GetFeeByType returns an estimate of fee based on type of transaction func (i *ItBit) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { if !i.AllowAuthenticatedRequest() && // Todo check connection status @@ -447,17 +444,27 @@ func (i *ItBit) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, er var allOrders []Order for x := range wallets { - resp, err := i.GetOrders(wallets[x].ID, "", "open", 0, 0) + var resp []Order + resp, err = i.GetOrders(wallets[x].ID, "", "open", 0, 0) if err != nil { return nil, err } allOrders = append(allOrders, resp...) } + format, err := i.GetPairFormat(asset.Spot, false) + if err != nil { + return nil, err + } + var orders []order.Detail for j := range allOrders { - symbol := currency.NewPairDelimiter(allOrders[j].Instrument, - i.GetPairFormat(asset.Spot, false).Delimiter) + var symbol currency.Pair + symbol, err := currency.NewPairDelimiter(allOrders[j].Instrument, + format.Delimiter) + if err != nil { + return nil, err + } side := order.Side(strings.ToUpper(allOrders[j].Side)) orderDate, err := time.Parse(time.RFC3339, allOrders[j].CreatedTime) if err != nil { @@ -497,21 +504,31 @@ func (i *ItBit) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, er var allOrders []Order for x := range wallets { - resp, err := i.GetOrders(wallets[x].ID, "", "", 0, 0) + var resp []Order + resp, err = i.GetOrders(wallets[x].ID, "", "", 0, 0) if err != nil { return nil, err } allOrders = append(allOrders, resp...) } + format, err := i.GetPairFormat(asset.Spot, false) + if err != nil { + return nil, err + } + var orders []order.Detail for j := range allOrders { if allOrders[j].Type == "open" { continue } + var symbol currency.Pair + symbol, err = currency.NewPairDelimiter(allOrders[j].Instrument, + format.Delimiter) + if err != nil { + return nil, err + } - symbol := currency.NewPairDelimiter(allOrders[j].Instrument, - i.GetPairFormat(asset.Spot, false).Delimiter) side := order.Side(strings.ToUpper(allOrders[j].Side)) orderDate, err := time.Parse(time.RFC3339, allOrders[j].CreatedTime) if err != nil { @@ -541,28 +558,6 @@ func (i *ItBit) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, er return orders, nil } -// SubscribeToWebsocketChannels appends to ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle subscribing -func (i *ItBit) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - return common.ErrFunctionNotSupported -} - -// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle unsubscribing -func (i *ItBit) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - return common.ErrFunctionNotSupported -} - -// GetSubscriptions returns a copied list of subscriptions -func (i *ItBit) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) { - return nil, common.ErrFunctionNotSupported -} - -// AuthenticateWebsocket sends an authentication message to the websocket -func (i *ItBit) AuthenticateWebsocket() error { - return common.ErrFunctionNotSupported -} - // ValidateCredentials validates current credentials used for wrapper // functionality func (i *ItBit) ValidateCredentials() error { diff --git a/exchanges/kraken/kraken.go b/exchanges/kraken/kraken.go index 1398b141..af51ebb0 100644 --- a/exchanges/kraken/kraken.go +++ b/exchanges/kraken/kraken.go @@ -16,7 +16,6 @@ import ( exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/request" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" "github.com/thrasher-corp/gocryptotrader/log" ) @@ -64,9 +63,7 @@ var ( // Kraken is the overarching type across the alphapoint package type Kraken struct { exchange.Base - WebsocketConn *wshandler.WebsocketConnection - AuthenticatedWebsocketConn *wshandler.WebsocketConnection - wsRequestMtx sync.Mutex + wsRequestMtx sync.Mutex } // GetServerTime returns current server time diff --git a/exchanges/kraken/kraken_test.go b/exchanges/kraken/kraken_test.go index 02ef59f1..9ae3ca07 100644 --- a/exchanges/kraken/kraken_test.go +++ b/exchanges/kraken/kraken_test.go @@ -18,7 +18,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/kline" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) @@ -48,12 +48,11 @@ func TestMain(m *testing.M) { krakenConfig.API.Credentials.Key = apiKey krakenConfig.API.Credentials.Secret = apiSecret krakenConfig.API.Endpoints.WebsocketURL = k.API.Endpoints.WebsocketURL + k.Websocket = sharedtestvalues.NewTestWebsocket() err = k.Setup(krakenConfig) if err != nil { log.Fatal(err) } - k.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride() - k.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride() os.Exit(m.Run()) } @@ -697,31 +696,14 @@ func setupWsTests(t *testing.T) { return } if !k.Websocket.IsEnabled() && !k.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() { - t.Skip(wshandler.WebsocketNotEnabled) - } - k.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride() - comms = make(chan wshandler.WebsocketResponse, sharedtestvalues.WebsocketChannelOverrideCapacity) - k.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride() - k.WebsocketConn = &wshandler.WebsocketConnection{ - ExchangeName: k.Name, - URL: krakenWSURL, - Verbose: k.Verbose, - ResponseMaxLimit: exchange.DefaultWebsocketResponseMaxLimit, - ResponseCheckTimeout: exchange.DefaultWebsocketResponseCheckTimeout, - } - k.AuthenticatedWebsocketConn = &wshandler.WebsocketConnection{ - ExchangeName: k.Name, - URL: krakenAuthWSURL, - Verbose: k.Verbose, - ResponseMaxLimit: exchange.DefaultWebsocketResponseMaxLimit, - ResponseCheckTimeout: exchange.DefaultWebsocketResponseCheckTimeout, + t.Skip(stream.WebsocketNotEnabled) } var dialer websocket.Dialer - err := k.WebsocketConn.Dial(&dialer, http.Header{}) + err := k.Websocket.Conn.Dial(&dialer, http.Header{}) if err != nil { t.Fatal(err) } - err = k.AuthenticatedWebsocketConn.Dial(&dialer, http.Header{}) + err = k.Websocket.AuthConn.Dial(&dialer, http.Header{}) if err != nil { t.Fatal(err) } @@ -731,10 +713,10 @@ func setupWsTests(t *testing.T) { t.Error(err) } authToken = token - - go k.wsFunnelConnectionData(k.WebsocketConn) - go k.wsFunnelConnectionData(k.AuthenticatedWebsocketConn) - go k.wsReadData() + comms := make(chan stream.Response) + go k.wsFunnelConnectionData(k.Websocket.Conn, comms) + go k.wsFunnelConnectionData(k.Websocket.AuthConn, comms) + go k.wsReadData(comms) go k.wsPingHandler() wsSetupRan = true } @@ -742,9 +724,11 @@ func setupWsTests(t *testing.T) { // TestWebsocketSubscribe tests returning a message with an id func TestWebsocketSubscribe(t *testing.T) { setupWsTests(t) - err := k.Subscribe(wshandler.WebsocketChannelSubscription{ - Channel: defaultSubscribedChannels[0], - Currency: currency.NewPairWithDelimiter("XBT", "USD", "/"), + err := k.Subscribe([]stream.ChannelSubscription{ + { + Channel: defaultSubscribedChannels[0], + Currency: currency.NewPairWithDelimiter("XBT", "USD", "/"), + }, }) if err != nil { t.Error(err) @@ -1426,8 +1410,11 @@ func TestParseTime(t *testing.T) { } func TestGetHistoricCandles(t *testing.T) { - currencyPair := currency.NewPairFromString("XBTUSD") - _, err := k.GetHistoricCandles(currencyPair, asset.Spot, time.Now().AddDate(0, 0, -1), time.Now(), kline.OneMin) + currencyPair, err := currency.NewPairFromString("XBTUSD") + if err != nil { + t.Fatal(err) + } + _, err = k.GetHistoricCandles(currencyPair, asset.Spot, time.Now().AddDate(0, 0, -1), time.Now(), kline.OneMin) if err != nil { t.Fatal(err) } @@ -1439,8 +1426,11 @@ func TestGetHistoricCandles(t *testing.T) { } func TestGetHistoricCandlesExtended(t *testing.T) { - currencyPair := currency.NewPairFromString("XBTUSD") - _, err := k.GetHistoricCandlesExtended(currencyPair, asset.Spot, time.Now().AddDate(0, -6, 0), time.Now(), kline.OneDay) + currencyPair, err := currency.NewPairFromString("XBTUSD") + if err != nil { + t.Fatal(err) + } + _, err = k.GetHistoricCandlesExtended(currencyPair, asset.Spot, time.Now().AddDate(0, -6, 0), time.Now(), kline.OneDay) if err != nil { t.Fatal(err) } diff --git a/exchanges/kraken/kraken_types.go b/exchanges/kraken/kraken_types.go index 2077ce82..d9e7aafd 100644 --- a/exchanges/kraken/kraken_types.go +++ b/exchanges/kraken/kraken_types.go @@ -5,6 +5,7 @@ import ( "time" "github.com/thrasher-corp/gocryptotrader/currency" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" ) type assetTranslatorStore struct { @@ -400,10 +401,11 @@ type WithdrawStatusResponse struct { // WebsocketSubscriptionEventRequest handles WS subscription events type WebsocketSubscriptionEventRequest struct { - Event string `json:"event"` // subscribe - RequestID int64 `json:"reqid,omitempty"` // Optional, client originated ID reflected in response message. - Pairs []string `json:"pair,omitempty"` // Array of currency pairs (pair1,pair2,pair3). - Subscription WebsocketSubscriptionData `json:"subscription,omitempty"` + Event string `json:"event"` // subscribe + RequestID int64 `json:"reqid,omitempty"` // Optional, client originated ID reflected in response message. + Pairs []string `json:"pair,omitempty"` // Array of currency pairs (pair1,pair2,pair3). + Subscription WebsocketSubscriptionData `json:"subscription,omitempty"` + Channels []stream.ChannelSubscription `json:"-"` // Keeps track of associated subscriptions in batched outgoings } // WebsocketBaseEventRequest Just has an "event" property diff --git a/exchanges/kraken/kraken_websocket.go b/exchanges/kraken/kraken_websocket.go index 4db9b84b..7031181b 100644 --- a/exchanges/kraken/kraken_websocket.go +++ b/exchanges/kraken/kraken_websocket.go @@ -10,15 +10,16 @@ import ( "time" "github.com/gorilla/websocket" + "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/convert" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream/buffer" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook" "github.com/thrasher-corp/gocryptotrader/log" ) @@ -49,89 +50,110 @@ const ( krakenWsPingDelay = time.Second * 27 ) -// orderbookMutex Ensures if two entries arrive at once, only one can be processed at a time +// orderbookMutex Ensures if two entries arrive at once, only one can be +// processed at a time var subscriptionChannelPair []WebsocketChannelData -var comms = make(chan wshandler.WebsocketResponse) var authToken string -var pingRequest = WebsocketBaseEventRequest{Event: wshandler.Ping} +var pingRequest = WebsocketBaseEventRequest{Event: stream.Ping} // Channels require a topic and a currency // Format [[ticker,but-t4u],[orderbook,nce-btt]] -var defaultSubscribedChannels = []string{krakenWsTicker, krakenWsTrade, krakenWsOrderbook, krakenWsOHLC, krakenWsSpread} +var defaultSubscribedChannels = []string{krakenWsTicker, + krakenWsTrade, + krakenWsOrderbook, + krakenWsOHLC, + krakenWsSpread} var authenticatedChannels = []string{krakenWsOwnTrades, krakenWsOpenOrders} // WsConnect initiates a websocket connection func (k *Kraken) WsConnect() error { if !k.Websocket.IsEnabled() || !k.IsEnabled() { - return errors.New(wshandler.WebsocketNotEnabled) + return errors.New(stream.WebsocketNotEnabled) } + var dialer websocket.Dialer - err := k.WebsocketConn.Dial(&dialer, http.Header{}) + err := k.Websocket.Conn.Dial(&dialer, http.Header{}) if err != nil { return err } + + comms := make(chan stream.Response) + go k.wsReadData(comms) + go k.wsFunnelConnectionData(k.Websocket.Conn, comms) + if k.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) { authToken, err = k.GetWebsocketToken() if err != nil { k.Websocket.SetCanUseAuthenticatedEndpoints(false) - log.Errorf(log.ExchangeSys, "%v - authentication failed: %v\n", k.Name, err) + log.Errorf(log.ExchangeSys, + "%v - authentication failed: %v\n", + k.Name, + err) + } else { + err = k.Websocket.AuthConn.Dial(&dialer, http.Header{}) + if err != nil { + k.Websocket.SetCanUseAuthenticatedEndpoints(false) + log.Errorf(log.ExchangeSys, + "%v - failed to connect to authenticated endpoint: %v\n", + k.Name, + err) + } else { + go k.wsFunnelConnectionData(k.Websocket.AuthConn, comms) + var authsubs []stream.ChannelSubscription + authsubs, err = k.GenerateAuthenticatedSubscriptions() + if err != nil { + return err + } + err = k.Websocket.SubscribeToChannels(authsubs) + if err != nil { + return err + } + } } - err = k.AuthenticatedWebsocketConn.Dial(&dialer, http.Header{}) - if err != nil { - k.Websocket.SetCanUseAuthenticatedEndpoints(false) - log.Errorf(log.ExchangeSys, "%v - failed to connect to authenticated endpoint: %v\n", k.Name, err) - } - go k.wsFunnelConnectionData(k.AuthenticatedWebsocketConn) - k.GenerateAuthenticatedSubscriptions() } - go k.wsFunnelConnectionData(k.WebsocketConn) - go k.wsReadData() err = k.wsPingHandler() if err != nil { - log.Errorf(log.ExchangeSys, "%v - failed setup ping handler. Websocket may disconnect unexpectedly. %v\n", k.Name, err) + log.Errorf(log.ExchangeSys, + "%v - failed setup ping handler. Websocket may disconnect unexpectedly. %v\n", + k.Name, + err) } - k.GenerateDefaultSubscriptions() - - return nil + gensubs, err := k.GenerateDefaultSubscriptions() + if err != nil { + return err + } + return k.Websocket.SubscribeToChannels(gensubs) } // wsFunnelConnectionData funnels both auth and public ws data into one manageable place -func (k *Kraken) wsFunnelConnectionData(ws *wshandler.WebsocketConnection) { +func (k *Kraken) wsFunnelConnectionData(ws stream.Connection, comms chan stream.Response) { k.Websocket.Wg.Add(1) defer k.Websocket.Wg.Done() for { - select { - case <-k.Websocket.ShutdownC: + resp := ws.ReadMessage() + if resp.Raw == nil { return - default: - resp, err := ws.ReadMessage() - if err != nil { - k.Websocket.DataHandler <- err - return - } - k.Websocket.TrafficAlert <- struct{}{} - comms <- resp } + comms <- resp } } // wsReadData receives and passes on websocket messages for processing -func (k *Kraken) wsReadData() { +func (k *Kraken) wsReadData(comms chan stream.Response) { k.Websocket.Wg.Add(1) - defer func() { - k.Websocket.Wg.Done() - }() + defer k.Websocket.Wg.Done() for { select { case <-k.Websocket.ShutdownC: return - default: - resp := <-comms + case resp := <-comms: err := k.wsHandleData(resp.Raw) if err != nil { - k.Websocket.DataHandler <- fmt.Errorf("%s - unhandled websocket data: %v", k.Name, err) + k.Websocket.DataHandler <- fmt.Errorf("%s - unhandled websocket data: %v", + k.Name, + err) } } } @@ -160,34 +182,49 @@ func (k *Kraken) wsHandleData(respRaw []byte) error { var eventResponse map[string]interface{} err := json.Unmarshal(respRaw, &eventResponse) if err != nil { - return fmt.Errorf("%s - err %s could not parse websocket data: %s", k.Name, err, respRaw) + return fmt.Errorf("%s - err %s could not parse websocket data: %s", + k.Name, + err, + respRaw) } if event, ok := eventResponse["event"]; ok { switch event { - case wshandler.Pong, krakenWsHeartbeat, krakenWsCancelOrderStatus: + case stream.Pong, krakenWsHeartbeat, krakenWsCancelOrderStatus: return nil case krakenWsSystemStatus: var systemStatus wsSystemStatus err := json.Unmarshal(respRaw, &systemStatus) if err != nil { - return fmt.Errorf("%s - err %s unable to parse system status response: %s", k.Name, err, respRaw) + return fmt.Errorf("%s - err %s unable to parse system status response: %s", + k.Name, + err, + respRaw) } if systemStatus.Status != "online" { k.Websocket.DataHandler <- fmt.Errorf("%v Websocket status '%v'", - k.Name, systemStatus.Status) + k.Name, + systemStatus.Status) } if systemStatus.Version > krakenWSSupportedVersion { - log.Warnf(log.ExchangeSys, "%v New version of Websocket API released. Was %v Now %v", - k.Name, krakenWSSupportedVersion, systemStatus.Version) + log.Warnf(log.ExchangeSys, + "%v New version of Websocket API released. Was %v Now %v", + k.Name, + krakenWSSupportedVersion, + systemStatus.Version) } case krakenWsAddOrderStatus: var status WsAddOrderResponse err := json.Unmarshal(respRaw, &status) if err != nil { - return fmt.Errorf("%s - err %s unable to parse add order response: %s", k.Name, err, respRaw) + return fmt.Errorf("%s - err %s unable to parse add order response: %s", + k.Name, + err, + respRaw) } if status.ErrorMessage != "" { - return fmt.Errorf("%s - err %s", k.Name, status.ErrorMessage) + return fmt.Errorf("%s - err %s", + k.Name, + status.ErrorMessage) } k.Websocket.DataHandler <- &order.Detail{ Exchange: k.Name, @@ -198,20 +235,27 @@ func (k *Kraken) wsHandleData(respRaw []byte) error { var sub wsSubscription err := json.Unmarshal(respRaw, &sub) if err != nil { - return fmt.Errorf("%s - err %s unable to parse subscription response: %s", k.Name, err, respRaw) + return fmt.Errorf("%s - err %s unable to parse subscription response: %s", + k.Name, + err, + respRaw) } if sub.Status != "subscribed" && sub.Status != "unsubscribed" { - return fmt.Errorf("%v %v %v", k.Name, sub.RequestID, sub.ErrorMessage) + return fmt.Errorf("%v %v %v", + k.Name, + sub.RequestID, + sub.ErrorMessage) } k.addNewSubscriptionChannelData(&sub) if sub.RequestID > 0 { - if k.WebsocketConn.IsIDWaitingForResponse(sub.RequestID) { - k.WebsocketConn.SetResponseIDAndData(sub.RequestID, respRaw) + if k.Websocket.Match.IncomingWithData(sub.RequestID, respRaw) { return nil } } default: - k.Websocket.DataHandler <- wshandler.UnhandledMessageWarning{Message: k.Name + wshandler.UnhandledMessage + string(respRaw)} + k.Websocket.DataHandler <- stream.UnhandledMessageWarning{ + Message: k.Name + stream.UnhandledMessage + string(respRaw), + } } return nil } @@ -225,7 +269,7 @@ func (k *Kraken) wsPingHandler() error { if err != nil { return err } - k.WebsocketConn.SetupPingHandler(wshandler.WebsocketPingHandler{ + k.Websocket.Conn.SetupPingHandler(stream.PingHandler{ Message: message, Delay: krakenWsPingDelay, MessageType: websocket.TextMessage, @@ -366,7 +410,16 @@ func (k *Kraken) wsProcessOpenOrders(ownOrders interface{}) error { Err: err, } } - p := currency.NewPairFromString(val.Description.Pair) + + p, err := currency.NewPairFromString(val.Description.Pair) + if err != nil { + k.Websocket.DataHandler <- order.ClassificationError{ + Exchange: k.Name, + OrderID: key, + Err: err, + } + } + var a asset.Item a, err = k.GetPairAssetType(p) if err != nil { @@ -409,7 +462,12 @@ func (k *Kraken) addNewSubscriptionChannelData(response *wsSubscription) { // We change the / to - to maintain compatibility with REST/config var pair currency.Pair if response.Pair != "" { - pair = currency.NewPairFromString(response.Pair) + var err error + pair, err = currency.NewPairFromString(response.Pair) + if err != nil { + log.Errorf(log.ExchangeSys, "%s exchange error: %s", k.Name, err) + return + } pair.Delimiter = k.CurrencyPairs.RequestFormat.Delimiter } subscriptionChannelPair = append(subscriptionChannelPair, WebsocketChannelData{ @@ -525,7 +583,13 @@ func (k *Kraken) wsProcessTrades(channelData *WebsocketChannelData, data []inter if trade[3].(string) == "s" { tSide = order.Sell } - k.Websocket.DataHandler <- wshandler.TradeData{ + + var tType = order.Market + if trade[4].(string) == "l" { + tType = order.Limit + } + + k.Websocket.DataHandler <- stream.TradeData{ AssetType: asset.Spot, CurrencyPair: channelData.Pair, Exchange: k.Name, @@ -533,6 +597,7 @@ func (k *Kraken) wsProcessTrades(channelData *WebsocketChannelData, data []inter Amount: amount, Timestamp: convert.TimeFromUnixTimestampDecimal(timeData), Side: tSide, + EventType: tType, } } } @@ -554,9 +619,10 @@ func (k *Kraken) wsProcessOrderBook(channelData *WebsocketChannelData, data map[ defer k.wsRequestMtx.Unlock() err := k.wsProcessOrderBookUpdate(channelData, askData, bidData) if err != nil { - subscriptionToRemove := wshandler.WebsocketChannelSubscription{ + subscriptionToRemove := &stream.ChannelSubscription{ Channel: krakenWsOrderbook, Currency: channelData.Pair, + Asset: asset.Spot, } k.Websocket.ResubscribeToChannel(subscriptionToRemove) return err @@ -625,21 +691,12 @@ func (k *Kraken) wsProcessOrderBookPartial(channelData *WebsocketChannelData, as } base.LastUpdated = highestLastUpdate base.ExchangeName = k.Name - err := k.Websocket.Orderbook.LoadSnapshot(&base) - if err != nil { - return err - } - k.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{ - Exchange: k.Name, - Asset: asset.Spot, - Pair: channelData.Pair, - } - return nil + return k.Websocket.Orderbook.LoadSnapshot(&base) } // wsProcessOrderBookUpdate updates an orderbook entry for a given currency pair func (k *Kraken) wsProcessOrderBookUpdate(channelData *WebsocketChannelData, askData, bidData []interface{}) error { - update := wsorderbook.WebsocketOrderbookUpdate{ + update := buffer.Update{ Asset: asset.Spot, Pair: channelData.Pair, } @@ -701,17 +758,7 @@ func (k *Kraken) wsProcessOrderBookUpdate(channelData *WebsocketChannelData, ask } } update.UpdateTime = highestLastUpdate - err := k.Websocket.Orderbook.Update(&update) - if err != nil { - k.Websocket.DataHandler <- err - return err - } - k.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{ - Exchange: k.Name, - Asset: asset.Spot, - Pair: channelData.Pair, - } - return nil + return k.Websocket.Orderbook.Update(&update) } // wsProcessCandles converts candle data and sends it to the data handler @@ -751,7 +798,7 @@ func (k *Kraken) wsProcessCandles(channelData *WebsocketChannelData, data []inte return err } - k.Websocket.DataHandler <- wshandler.KlineData{ + k.Websocket.DataHandler <- stream.KlineData{ AssetType: asset.Spot, Pair: channelData.Pair, Timestamp: time.Now(), @@ -770,78 +817,177 @@ func (k *Kraken) wsProcessCandles(channelData *WebsocketChannelData, data []inte } // GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() -func (k *Kraken) GenerateDefaultSubscriptions() { - enabledCurrencies := k.GetEnabledPairs(asset.Spot) - var subscriptions []wshandler.WebsocketChannelSubscription +func (k *Kraken) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, error) { + enabledCurrencies, err := k.GetEnabledPairs(asset.Spot) + if err != nil { + return nil, err + } + var subscriptions []stream.ChannelSubscription for i := range defaultSubscribedChannels { for j := range enabledCurrencies { enabledCurrencies[j].Delimiter = "/" - subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{ + subscriptions = append(subscriptions, stream.ChannelSubscription{ Channel: defaultSubscribedChannels[i], Currency: enabledCurrencies[j], + Asset: asset.Spot, }) } } - k.Websocket.SubscribeToChannels(subscriptions) + return subscriptions, nil } // GenerateAuthenticatedSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() -func (k *Kraken) GenerateAuthenticatedSubscriptions() { - var subscriptions []wshandler.WebsocketChannelSubscription +func (k *Kraken) GenerateAuthenticatedSubscriptions() ([]stream.ChannelSubscription, error) { + var subscriptions []stream.ChannelSubscription for i := range authenticatedChannels { params := make(map[string]interface{}) - subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{ + subscriptions = append(subscriptions, stream.ChannelSubscription{ Channel: authenticatedChannels[i], Params: params, }) } - k.Websocket.SubscribeToChannels(subscriptions) + return subscriptions, nil } // Subscribe sends a websocket message to receive data from the channel -func (k *Kraken) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error { - resp := WebsocketSubscriptionEventRequest{ - Event: krakenWsSubscribe, - Subscription: WebsocketSubscriptionData{ - Name: channelToSubscribe.Channel, - }, - RequestID: k.WebsocketConn.GenerateMessageID(false), - } - if channelToSubscribe.Channel == "book" { - // TODO: Add ability to make depth customisable - resp.Subscription.Depth = 1000 - } - if !channelToSubscribe.Currency.IsEmpty() { - resp.Pairs = []string{channelToSubscribe.Currency.String()} - } - if channelToSubscribe.Params != nil { - resp.Subscription.Token = authToken +func (k *Kraken) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error { + var subs []WebsocketSubscriptionEventRequest +channels: + for x := range channelsToSubscribe { + for y := range subs { + if subs[y].Subscription.Name == channelsToSubscribe[x].Channel { + subs[y].Pairs = append(subs[y].Pairs, + channelsToSubscribe[x].Currency.String()) + subs[y].Channels = append(subs[y].Channels, channelsToSubscribe[x]) + continue channels + } + } + + var id int64 + if common.StringDataContains(authenticatedChannels, channelsToSubscribe[x].Channel) { + id = k.Websocket.AuthConn.GenerateMessageID(false) + } else { + id = k.Websocket.Conn.GenerateMessageID(false) + } + + resp := WebsocketSubscriptionEventRequest{ + Event: krakenWsSubscribe, + Subscription: WebsocketSubscriptionData{ + Name: channelsToSubscribe[x].Channel, + }, + RequestID: id, + } + if channelsToSubscribe[x].Channel == "book" { + // TODO: Add ability to make depth customisable + resp.Subscription.Depth = 1000 + } + if !channelsToSubscribe[x].Currency.IsEmpty() { + resp.Pairs = []string{channelsToSubscribe[x].Currency.String()} + } + if channelsToSubscribe[x].Params != nil { + resp.Subscription.Token = authToken + } + + resp.Channels = append(resp.Channels, channelsToSubscribe[x]) + subs = append(subs, resp) } - _, err := k.WebsocketConn.SendMessageReturnResponse(resp.RequestID, resp) - return err + var errs common.Errors + for i := range subs { + if common.StringDataContains(authenticatedChannels, subs[i].Subscription.Name) { + _, err := k.Websocket.AuthConn.SendMessageReturnResponse(subs[i].RequestID, subs[i]) + if err != nil { + errs = append(errs, err) + continue + } + k.Websocket.AddSuccessfulSubscriptions(subs[i].Channels...) + continue + } + + _, err := k.Websocket.Conn.SendMessageReturnResponse(subs[i].RequestID, subs[i]) + if err != nil { + errs = append(errs, err) + continue + } + k.Websocket.AddSuccessfulSubscriptions(subs[i].Channels...) + } + if errs != nil { + return errs + } + return nil } // Unsubscribe sends a websocket message to stop receiving data from the channel -func (k *Kraken) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error { - resp := WebsocketSubscriptionEventRequest{ - Event: krakenWsUnsubscribe, - Pairs: []string{channelToSubscribe.Currency.String()}, - Subscription: WebsocketSubscriptionData{ - Name: channelToSubscribe.Channel, - }, - RequestID: k.WebsocketConn.GenerateMessageID(false), +func (k *Kraken) Unsubscribe(channelsToUnsubscribe []stream.ChannelSubscription) error { + var unsubs []WebsocketSubscriptionEventRequest +channels: + for x := range channelsToUnsubscribe { + for y := range unsubs { + if unsubs[y].Subscription.Name == channelsToUnsubscribe[x].Channel { + unsubs[y].Pairs = append(unsubs[y].Pairs, + channelsToUnsubscribe[x].Currency.String()) + unsubs[y].Channels = append(unsubs[y].Channels, + channelsToUnsubscribe[x]) + continue channels + } + } + var depth int64 + if channelsToUnsubscribe[x].Channel == "book" { + // TODO: Add ability to make depth customisable + depth = 1000 + } + + var id int64 + if common.StringDataContains(authenticatedChannels, channelsToUnsubscribe[x].Channel) { + id = k.Websocket.AuthConn.GenerateMessageID(false) + } else { + id = k.Websocket.Conn.GenerateMessageID(false) + } + + unsub := WebsocketSubscriptionEventRequest{ + Event: krakenWsUnsubscribe, + Pairs: []string{channelsToUnsubscribe[x].Currency.String()}, + Subscription: WebsocketSubscriptionData{ + Name: channelsToUnsubscribe[x].Channel, + Depth: depth, + }, + RequestID: id, + } + unsub.Channels = append(unsub.Channels, channelsToUnsubscribe[x]) + unsubs = append(unsubs, unsub) } - _, err := k.WebsocketConn.SendMessageReturnResponse(resp.RequestID, resp) - return err + + var errs common.Errors + for i := range unsubs { + if common.StringDataContains(authenticatedChannels, unsubs[i].Subscription.Name) { + _, err := k.Websocket.AuthConn.SendMessageReturnResponse(unsubs[i].RequestID, unsubs[i]) + if err != nil { + errs = append(errs, err) + continue + } + k.Websocket.RemoveSuccessfulUnsubscriptions(unsubs[i].Channels...) + continue + } + + _, err := k.Websocket.Conn.SendMessageReturnResponse(unsubs[i].RequestID, unsubs[i]) + if err != nil { + errs = append(errs, err) + continue + } + k.Websocket.RemoveSuccessfulUnsubscriptions(unsubs[i].Channels...) + } + if errs != nil { + return errs + } + return nil } func (k *Kraken) wsAddOrder(request *WsAddOrderRequest) (string, error) { - id := k.AuthenticatedWebsocketConn.GenerateMessageID(false) + id := k.Websocket.AuthConn.GenerateMessageID(false) request.UserReferenceID = strconv.FormatInt(id, 10) request.Event = krakenWsAddOrder request.Token = authToken - jsonResp, err := k.AuthenticatedWebsocketConn.SendMessageReturnResponse(id, request) + jsonResp, err := k.Websocket.AuthConn.SendMessageReturnResponse(id, request) if err != nil { return "", err } @@ -862,5 +1008,5 @@ func (k *Kraken) wsCancelOrders(orderIDs []string) error { Token: authToken, TransactionIDs: orderIDs, } - return k.AuthenticatedWebsocketConn.SendJSONMessage(request) + return k.Websocket.AuthConn.SendJSONMessage(request) } diff --git a/exchanges/kraken/kraken_wrapper.go b/exchanges/kraken/kraken_wrapper.go index 1e39c9e4..c55af9fc 100644 --- a/exchanges/kraken/kraken_wrapper.go +++ b/exchanges/kraken/kraken_wrapper.go @@ -19,8 +19,8 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" "github.com/thrasher-corp/gocryptotrader/log" "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) @@ -57,21 +57,18 @@ func (k *Kraken) SetDefaults() { k.API.CredentialsValidator.RequiresSecret = true k.API.CredentialsValidator.RequiresBase64DecodeSecret = true - k.CurrencyPairs = currency.PairsManager{ - AssetTypes: asset.Items{ - asset.Spot, - }, - - UseGlobalFormat: true, - RequestFormat: ¤cy.PairFormat{ - Uppercase: true, - Separator: ",", - }, - ConfigFormat: ¤cy.PairFormat{ - Uppercase: true, - Delimiter: "-", - Separator: ",", - }, + requestFmt := ¤cy.PairFormat{ + Uppercase: true, + Separator: ",", + } + configFmt := ¤cy.PairFormat{ + Uppercase: true, + Delimiter: currency.DashDelimiter, + Separator: ",", + } + err := k.SetGlobalPairsManager(requestFmt, configFmt, asset.Spot) + if err != nil { + log.Errorln(log.ExchangeSys, err) } k.Features = exchange.Features{ @@ -149,7 +146,7 @@ func (k *Kraken) SetDefaults() { k.API.Endpoints.URLDefault = krakenAPIURL k.API.Endpoints.URL = k.API.Endpoints.URLDefault - k.Websocket = wshandler.New() + k.Websocket = stream.New() k.API.Endpoints.WebsocketURL = krakenWSURL k.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit k.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout @@ -173,52 +170,44 @@ func (k *Kraken) Setup(exch *config.ExchangeConfig) error { return err } - err = k.Websocket.Setup( - &wshandler.WebsocketSetup{ - Enabled: exch.Features.Enabled.Websocket, - Verbose: exch.Verbose, - AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, - WebsocketTimeout: exch.WebsocketTrafficTimeout, - DefaultURL: krakenWSURL, - ExchangeName: exch.Name, - RunningURL: exch.API.Endpoints.WebsocketURL, - Connector: k.WsConnect, - Subscriber: k.Subscribe, - UnSubscriber: k.Unsubscribe, - Features: &k.Features.Supports.WebsocketCapabilities, - }) + err = k.Websocket.Setup(&stream.WebsocketSetup{ + Enabled: exch.Features.Enabled.Websocket, + Verbose: exch.Verbose, + AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, + WebsocketTimeout: exch.WebsocketTrafficTimeout, + DefaultURL: krakenWSURL, + ExchangeName: exch.Name, + RunningURL: exch.API.Endpoints.WebsocketURL, + Connector: k.WsConnect, + Subscriber: k.Subscribe, + UnSubscriber: k.Unsubscribe, + GenerateSubscriptions: k.GenerateDefaultSubscriptions, + Features: &k.Features.Supports.WebsocketCapabilities, + OrderbookBufferLimit: exch.WebsocketOrderbookBufferLimit, + BufferEnabled: true, + SortBuffer: true, + }) if err != nil { return err } - k.WebsocketConn = &wshandler.WebsocketConnection{ - ExchangeName: k.Name, - URL: k.Websocket.GetWebsocketURL(), - ProxyURL: k.Websocket.GetProxyAddress(), - Verbose: k.Verbose, + err = k.Websocket.SetupNewConnection(stream.ConnectionSetup{ RateLimit: krakenWsRateLimit, ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout, ResponseMaxLimit: exch.WebsocketResponseMaxLimit, + URL: krakenWSURL, + }) + if err != nil { + return err } - k.AuthenticatedWebsocketConn = &wshandler.WebsocketConnection{ - ExchangeName: k.Name, + return k.Websocket.SetupNewConnection(stream.ConnectionSetup{ + RateLimit: krakenWsRateLimit, + ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout, + ResponseMaxLimit: exch.WebsocketResponseMaxLimit, URL: krakenAuthWSURL, - ProxyURL: k.Websocket.GetProxyAddress(), - Verbose: k.Verbose, - RateLimit: krakenWsRateLimit, - ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout, - ResponseMaxLimit: exch.WebsocketResponseMaxLimit, - } - - k.Websocket.Orderbook.Setup( - exch.WebsocketOrderbookBufferLimit, - true, - true, - false, - false, - exch.Name) - return nil + Authenticated: true, + }) } // Start starts the Kraken go routine @@ -237,22 +226,55 @@ func (k *Kraken) Run() { } forceUpdate := false - delim := k.GetPairFormat(asset.Spot, false).Delimiter - if !common.StringDataContains(k.GetEnabledPairs(asset.Spot).Strings(), delim) || - !common.StringDataContains(k.GetAvailablePairs(asset.Spot).Strings(), delim) || - common.StringDataContains(k.GetAvailablePairs(asset.Spot).Strings(), "ZUSD") { - enabledPairs := currency.NewPairsFromStrings( - []string{currency.XBT.String() + delim + currency.USD.String()}, - ) - log.Warn(log.ExchangeSys, "Available pairs for Kraken reset due to config upgrade, please enable the ones you would like again") - forceUpdate = true + format, err := k.GetPairFormat(asset.Spot, false) + if err != nil { + log.Errorf(log.ExchangeSys, + "%s failed to update tradable pairs. Err: %s", + k.Name, + err) + return + } + enabled, err := k.GetEnabledPairs(asset.Spot) + if err != nil { + log.Errorf(log.ExchangeSys, + "%s failed to update tradable pairs. Err: %s", + k.Name, + err) + return + } - err := k.UpdatePairs(enabledPairs, asset.Spot, true, true) + avail, err := k.GetAvailablePairs(asset.Spot) + if err != nil { + log.Errorf(log.ExchangeSys, + "%s failed to update tradable pairs. Err: %s", + k.Name, + err) + return + } + + if !common.StringDataContains(enabled.Strings(), format.Delimiter) || + !common.StringDataContains(avail.Strings(), format.Delimiter) || + common.StringDataContains(avail.Strings(), "ZUSD") { + var p currency.Pairs + p, err = currency.NewPairsFromStrings([]string{currency.XBT.String() + + format.Delimiter + + currency.USD.String()}) if err != nil { log.Errorf(log.ExchangeSys, "%s failed to update currencies. Err: %s\n", k.Name, err) + } else { + log.Warn(log.ExchangeSys, "Available pairs for Kraken reset due to config upgrade, please enable the ones you would like again") + forceUpdate = true + + err = k.UpdatePairs(p, asset.Spot, true, true) + if err != nil { + log.Errorf(log.ExchangeSys, + "%s failed to update currencies. Err: %s\n", + k.Name, + err) + } } } @@ -260,7 +282,7 @@ func (k *Kraken) Run() { return } - err := k.UpdateTradablePairs(forceUpdate) + err = k.UpdateTradablePairs(forceUpdate) if err != nil { log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", @@ -282,6 +304,11 @@ func (k *Kraken) FetchTradablePairs(asset asset.Item) ([]string, error) { return nil, err } + format, err := k.GetPairFormat(asset, false) + if err != nil { + return nil, err + } + var products []string for i := range pairs { if strings.Contains(pairs[i].Altname, ".d") { @@ -305,8 +332,7 @@ func (k *Kraken) FetchTradablePairs(asset asset.Item) ([]string, error) { pairs[i].Quote) continue } - products = append(products, - base+k.GetPairFormat(asset, false).Delimiter+quote) + products = append(products, base+format.Delimiter+quote) } return products, nil } @@ -319,48 +345,57 @@ func (k *Kraken) UpdateTradablePairs(forceUpdate bool) error { return err } - return k.UpdatePairs(currency.NewPairsFromStrings(pairs), asset.Spot, false, forceUpdate) + p, err := currency.NewPairsFromStrings(pairs) + if err != nil { + return err + } + return k.UpdatePairs(p, asset.Spot, false, forceUpdate) } // UpdateTicker updates and returns the ticker for a currency pair func (k *Kraken) UpdateTicker(p currency.Pair, assetType asset.Item) (*ticker.Price, error) { - tickerPrice := new(ticker.Price) - pairs := k.GetEnabledPairs(assetType) + pairs, err := k.GetEnabledPairs(assetType) + if err != nil { + return nil, err + } pairsCollated, err := k.FormatExchangeCurrencies(pairs, assetType) if err != nil { - return tickerPrice, err + return nil, err } tickers, err := k.GetTickers(pairsCollated) if err != nil { - return tickerPrice, err + return nil, err } for i := range pairs { for c, t := range tickers { - pairFmt := k.FormatExchangeCurrency(pairs[i], assetType).String() - if !strings.EqualFold(pairFmt, c) { + pairFmt, err := k.FormatExchangeCurrency(pairs[i], assetType) + if err != nil { + return nil, err + } + if !strings.EqualFold(pairFmt.String(), c) { altCurrency := assetTranslator.LookupAltname(c) if altCurrency == "" { continue } - if !strings.EqualFold(pairFmt, altCurrency) { - continue + if !strings.EqualFold(pairFmt.String(), altCurrency) { + continue // This looks dodge } } - tickerPrice = &ticker.Price{ - Last: t.Last, - High: t.High, - Low: t.Low, - Bid: t.Bid, - Ask: t.Ask, - Volume: t.Volume, - Open: t.Open, - Pair: pairs[i], - } - err = ticker.ProcessTicker(k.Name, tickerPrice, assetType) + err = ticker.ProcessTicker(&ticker.Price{ + Last: t.Last, + High: t.High, + Low: t.Low, + Bid: t.Bid, + Ask: t.Ask, + Volume: t.Volume, + Open: t.Open, + Pair: pairs[i], + ExchangeName: k.Name, + AssetType: assetType}) if err != nil { - log.Error(log.Ticker, err) + return nil, err } } } @@ -387,19 +422,29 @@ func (k *Kraken) FetchOrderbook(p currency.Pair, assetType asset.Item) (*orderbo // UpdateOrderbook updates and returns the orderbook for a currency pair func (k *Kraken) UpdateOrderbook(p currency.Pair, assetType asset.Item) (*orderbook.Base, error) { - orderBook := new(orderbook.Base) - orderbookNew, err := k.GetDepth(k.FormatExchangeCurrency(p, - assetType).String()) + fpair, err := k.FormatExchangeCurrency(p, assetType) if err != nil { - return orderBook, err + return nil, err } + orderbookNew, err := k.GetDepth(fpair.String()) + if err != nil { + return nil, err + } + + var orderBook = new(orderbook.Base) for x := range orderbookNew.Bids { - orderBook.Bids = append(orderBook.Bids, orderbook.Item{Amount: orderbookNew.Bids[x].Amount, Price: orderbookNew.Bids[x].Price}) + orderBook.Bids = append(orderBook.Bids, orderbook.Item{ + Amount: orderbookNew.Bids[x].Amount, + Price: orderbookNew.Bids[x].Price, + }) } for x := range orderbookNew.Asks { - orderBook.Asks = append(orderBook.Asks, orderbook.Item{Amount: orderbookNew.Asks[x].Amount, Price: orderbookNew.Asks[x].Price}) + orderBook.Asks = append(orderBook.Asks, orderbook.Item{ + Amount: orderbookNew.Asks[x].Amount, + Price: orderbookNew.Asks[x].Price, + }) } orderBook.Pair = p @@ -569,6 +614,16 @@ func (k *Kraken) GetOrderInfo(orderID string) (order.Detail, error) { return orderDetail, err } if orderInfo, ok := openOrders.Open[orderID]; ok { + avail, err := k.GetAvailablePairs(asset.Spot) + if err != nil { + return orderDetail, err + } + + fmt, err := k.GetPairFormat(asset.Spot, false) + if err != nil { + return orderDetail, err + } + var trades []order.TradeHistory for i := range orderInfo.Trades { trades = append(trades, order.TradeHistory{ @@ -588,11 +643,16 @@ func (k *Kraken) GetOrderInfo(orderID string) (order.Detail, error) { return orderDetail, err } + p, err := currency.NewPairFromFormattedPairs(orderInfo.Description.Pair, + avail, + fmt) + if err != nil { + return orderDetail, err + } orderDetail = order.Detail{ - Exchange: k.Name, - ID: orderID, - Pair: currency.NewPairFromFormattedPairs(orderInfo.Description.Pair, - k.GetAvailablePairs(asset.Spot), k.GetPairFormat(asset.Spot, true)), + Exchange: k.Name, + ID: orderID, + Pair: p, Side: side, Type: oType, Date: convert.TimeFromUnixTimestampDecimal(orderInfo.OpenTime), @@ -666,11 +726,6 @@ func (k *Kraken) WithdrawFiatFundsToInternationalBank(withdrawRequest *withdraw. }, nil } -// GetWebsocket returns a pointer to the exchange websocket -func (k *Kraken) GetWebsocket() (*wshandler.Websocket, error) { - return k.Websocket, nil -} - // GetFeeByType returns an estimate of fee based on type of transaction func (k *Kraken) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { if !k.AllowAuthenticatedRequest() && // Todo check connection status @@ -687,8 +742,25 @@ func (k *Kraken) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, e return nil, err } + avail, err := k.GetAvailablePairs(asset.Spot) + if err != nil { + return nil, err + } + + fmt, err := k.GetPairFormat(asset.Spot, true) + if err != nil { + return nil, err + } + var orders []order.Detail for i := range resp.Open { + p, err := currency.NewPairFromFormattedPairs(resp.Open[i].Description.Pair, + avail, + fmt) + if err != nil { + return nil, err + } + side := order.Side(strings.ToUpper(resp.Open[i].Description.Type)) orderType := order.Type(strings.ToUpper(resp.Open[i].Description.OrderType)) orders = append(orders, order.Detail{ @@ -701,8 +773,7 @@ func (k *Kraken) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, e Price: resp.Open[i].Description.Price, Side: side, Type: orderType, - Pair: currency.NewPairFromFormattedPairs(resp.Open[i].Description.Pair, - k.GetAvailablePairs(asset.Spot), k.GetPairFormat(asset.Spot, true)), + Pair: p, }) } @@ -723,6 +794,16 @@ func (k *Kraken) GetOrderHistory(getOrdersRequest *order.GetOrdersRequest) ([]or req.End = strconv.FormatInt(getOrdersRequest.EndTicks.Unix(), 10) } + avail, err := k.GetAvailablePairs(asset.Spot) + if err != nil { + return nil, err + } + + fmt, err := k.GetPairFormat(asset.Spot, true) + if err != nil { + return nil, err + } + resp, err := k.GetClosedOrders(req) if err != nil { return nil, err @@ -730,6 +811,13 @@ func (k *Kraken) GetOrderHistory(getOrdersRequest *order.GetOrdersRequest) ([]or var orders []order.Detail for i := range resp.Closed { + p, err := currency.NewPairFromFormattedPairs(resp.Closed[i].Description.Pair, + avail, + fmt) + if err != nil { + return nil, err + } + side := order.Side(strings.ToUpper(resp.Closed[i].Description.Type)) orderType := order.Type(strings.ToUpper(resp.Closed[i].Description.OrderType)) orders = append(orders, order.Detail{ @@ -743,8 +831,7 @@ func (k *Kraken) GetOrderHistory(getOrdersRequest *order.GetOrdersRequest) ([]or Price: resp.Closed[i].Description.Price, Side: side, Type: orderType, - Pair: currency.NewPairFromFormattedPairs(resp.Closed[i].Description.Pair, - k.GetAvailablePairs(asset.Spot), k.GetPairFormat(asset.Spot, true)), + Pair: p, }) } @@ -753,25 +840,6 @@ func (k *Kraken) GetOrderHistory(getOrdersRequest *order.GetOrdersRequest) ([]or return orders, nil } -// SubscribeToWebsocketChannels appends to ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle subscribing -func (k *Kraken) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - k.Websocket.SubscribeToChannels(channels) - return nil -} - -// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle unsubscribing -func (k *Kraken) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - k.Websocket.RemoveSubscribedChannels(channels) - return nil -} - -// GetSubscriptions returns a copied list of subscriptions -func (k *Kraken) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) { - return k.Websocket.GetSubscriptions(), nil -} - // AuthenticateWebsocket sends an authentication message to the websocket func (k *Kraken) AuthenticateWebsocket() error { resp, err := k.GetWebsocketToken() @@ -807,7 +875,13 @@ func (k *Kraken) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end Asset: a, Interval: interval, } - candles, err := k.GetOHLC(assetTranslator.LookupCurrency(k.FormatExchangeCurrency(pair, a).Upper().String()), k.FormatExchangeKlineInterval(interval)) + + formattedPair, err := k.FormatExchangeCurrency(pair, a) + if err != nil { + return kline.Item{}, err + } + + candles, err := k.GetOHLC(assetTranslator.LookupCurrency(formattedPair.Upper().String()), k.FormatExchangeKlineInterval(interval)) if err != nil { return kline.Item{}, err } @@ -848,7 +922,12 @@ func (k *Kraken) GetHistoricCandlesExtended(pair currency.Pair, a asset.Item, st Interval: interval, } - candles, err := k.GetOHLC(assetTranslator.LookupCurrency(k.FormatExchangeCurrency(pair, a).Upper().String()), k.FormatExchangeKlineInterval(interval)) + formattedPair, err := k.FormatExchangeCurrency(pair, a) + if err != nil { + return kline.Item{}, err + } + + candles, err := k.GetOHLC(assetTranslator.LookupCurrency(formattedPair.Upper().String()), k.FormatExchangeKlineInterval(interval)) if err != nil { return kline.Item{}, err } diff --git a/exchanges/lakebtc/lakebtc_test.go b/exchanges/lakebtc/lakebtc_test.go index 460f35b9..d1d38320 100644 --- a/exchanges/lakebtc/lakebtc_test.go +++ b/exchanges/lakebtc/lakebtc_test.go @@ -14,7 +14,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) @@ -36,18 +36,17 @@ func TestMain(m *testing.M) { } lakebtcConfig, err := cfg.GetExchangeConfig("LakeBTC") if err != nil { - log.Fatal("LakeBTC Setup() init error") + log.Fatal("LakeBTC Setup() init error", err) } lakebtcConfig.API.AuthenticatedSupport = true lakebtcConfig.API.Credentials.Key = apiKey lakebtcConfig.API.Credentials.Secret = apiSecret lakebtcConfig.Features.Enabled.Websocket = true + l.Websocket = sharedtestvalues.NewTestWebsocket() err = l.Setup(lakebtcConfig) if err != nil { log.Fatal("LakeBTC setup error", err) } - l.API.Endpoints.WebsocketURL = lakeBTCWSURL - os.Exit(m.Run()) } @@ -441,10 +440,8 @@ func TestGetDepositAddress(t *testing.T) { // TestWsConn websocket connection test func TestWsConn(t *testing.T) { if !l.Websocket.IsEnabled() { - t.Skip(wshandler.WebsocketNotEnabled) + t.Skip(stream.WebsocketNotEnabled) } - l.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride() - l.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride() err := l.WsConnect() if err != nil { t.Fatal(err) @@ -453,8 +450,6 @@ func TestWsConn(t *testing.T) { // TestWsTradeProcessing logic test func TestWsTradeProcessing(t *testing.T) { - l.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride() - l.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride() json := `{"trades":[{"type":"sell","date":1564985787,"price":"11913.02","amount":"0.49"}]}` err := l.processTrades(json, "market-btcusd-global") if err != nil { @@ -464,9 +459,6 @@ func TestWsTradeProcessing(t *testing.T) { // TestWsTickerProcessing logic test func TestWsTickerProcessing(t *testing.T) { - const testChanSize = 26 - l.Websocket.DataHandler = make(chan interface{}, testChanSize) - l.Websocket.TrafficAlert = make(chan struct{}, testChanSize) json := `{"btcusd":{"low":"10990.05","high":"11966.24","last":"11903.29","volume":"1803.967079","sell":"11912.39","buy":"11902.2"},"btceur":{"low":"9886.87","high":"10732.72","last":"10691.44","volume":"87.994478","sell":"10711.62","buy":"10691.44"},"btchkd":{"low":null,"high":null,"last":"51776.98","volume":null,"sell":"93307.37","buy":"93177.56"},"btcjpy":{"low":"1176039.0","high":"1272246.0","last":"1265680.0","volume":"129.021421","sell":"1266764.0","buy":"1265680.0"},"btcgbp":{"low":"9157.12","high":"9953.43","last":"9941.28","volume":"10.4997","sell":"10007.89","buy":"9941.28"},"btcaud":{"low":"16102.57","high":"17594.22","last":"17548.16","volume":"7.338316","sell":"17616.67","buy":"17549.69"},"btccad":{"low":"14541.69","high":"15834.87","last":"15763.54","volume":"30.480309","sell":"15793.45","buy":"15756.13"},"btcsgd":{"low":"15133.82","high":"16501.62","last":"16455.53","volume":"4.044026","sell":"16484.37","buy":"16462.18"},"btcchf":{"low":"10800.58","high":"11526.24","last":"11526.24","volume":"0.1765","sell":"11675.34","buy":"11632.02"},"btcnzd":{"low":null,"high":null,"last":"8340.98","volume":null,"sell":"18315.49","buy":"18221.37"},"btcngn":{"low":null,"high":null,"last":"600000.0","volume":null,"sell":null,"buy":null},"eurusd":{"low":"1.1088","high":"1.1138","last":"1.1125","volume":"2680.105249","sell":"1.1142","buy":"1.1121"},"gbpusd":{"low":"1.1934","high":"1.1958","last":"1.1934","volume":"1493.923823","sell":"1.1979","buy":"1.1903"},"usdjpy":{"low":"105.26","high":"107.25","last":"106.33","volume":"114490.2179","sell":"106.34","buy":"106.27"},"usdhkd":{"low":null,"high":null,"last":"7.851","volume":null,"sell":"7.8328","buy":"7.8286"},"usdcad":{"low":"1.3225","high":"1.3272","last":"1.3255","volume":"11033.9877","sell":"1.3258","buy":"1.3238"},"usdsgd":{"low":"1.3776","high":"1.3839","last":"1.3838","volume":"2523.75","sell":"1.3838","buy":"1.3819"},"audusd":{"low":"0.6764","high":"0.6853","last":"0.6771","volume":"5442.608321","sell":"0.6782","buy":"0.6762"},"nzdusd":{"low":null,"high":null,"last":"0.6758","volume":null,"sell":"0.6532","buy":"0.6504"},"usdchf":{"low":"0.9838","high":"0.9838","last":"0.9838","volume":"108.3352","sell":"0.9801","buy":"0.9773"},"usdngn":{"low":null,"high":null,"last":"200.0","volume":null,"sell":null,"buy":null},"ethbtc":{"low":"0.0205","high":"0.025","last":"0.0205","volume":null,"sell":"0.03","buy":"0.0194"},"ltcbtc":{"low":null,"high":null,"last":"0.0114","volume":null,"sell":"0.009","buy":"0.0073"},"bchbtc":{"low":null,"high":null,"last":"0.0544","volume":null,"sell":"0.0322","buy":"0.0274"},"xrpbtc":{"low":"0.000042","high":"0.000042","last":"0.000042","volume":null,"sell":"0.000037","buy":"0.000022"},"baceth":{"low":"0.000035","high":"0.000035","last":"0.000035","volume":null,"sell":"0.0015","buy":null}}` err := l.processTicker(json) if err != nil { @@ -476,16 +468,20 @@ func TestWsTickerProcessing(t *testing.T) { func TestGetCurrencyFromChannel(t *testing.T) { curr := currency.NewPair(currency.LTC, currency.BTC) - result := l.getCurrencyFromChannel(marketSubstring + curr.String() + globalSubstring) - if curr != result { + result, err := l.getCurrencyFromChannel(marketSubstring + + curr.String() + + globalSubstring) + if err != nil { + t.Fatal(err) + } + + if !curr.Equal(result) { t.Errorf("currency result is not equal. Expected %v", curr) } } // TestWsOrderbookProcessing logic test func TestWsOrderbookProcessing(t *testing.T) { - l.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride() - l.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride() json := `{"asks":[["11905.66","0.0019"],["11905.73","0.0015"],["11906.43","0.0013"],["11906.62","0.0019"],["11907.25","11.087"],["11907.66","0.0006"],["11907.73","0.3113"],["11907.84","0.0006"],["11908.37","0.0016"],["11908.86","10.3786"],["11909.54","4.2955"],["11910.15","0.0012"],["11910.56","13.5505"],["11911.06","0.0011"],["11911.37","0.0023"]],"bids":[["11905.55","0.0171"],["11904.43","0.0225"],["11903.31","0.0223"],["11902.2","0.0027"],["11901.92","1.002"],["11901.6","0.0015"],["11901.49","0.0012"],["11901.08","0.0227"],["11900.93","0.0009"],["11900.53","1.662"],["11900.08","0.001"],["11900.01","3.6745"],["11899.96","0.003"],["11899.91","0.0006"],["11899.44","0.0013"]]}` err := l.processOrderbook(json, "market-btcusd-global") if err != nil { diff --git a/exchanges/lakebtc/lakebtc_types.go b/exchanges/lakebtc/lakebtc_types.go index f0949b85..4dde4310 100644 --- a/exchanges/lakebtc/lakebtc_types.go +++ b/exchanges/lakebtc/lakebtc_types.go @@ -125,8 +125,8 @@ type WebsocketConn struct { // WsOrderbookUpdate contains orderbook data from websocket type WsOrderbookUpdate struct { - Asks [][]string `json:"asks"` - Bids [][]string `json:"bids"` + Asks [][2]string `json:"asks"` + Bids [][2]string `json:"bids"` } // WsTrades contains trade data from websocket @@ -141,3 +141,12 @@ type WsTrade struct { Price float64 `json:"price,string"` Amount float64 `json:"amount,string"` } + +type wsTicker struct { + Low float64 `json:"low,string"` + High float64 `json:"high,string"` + Last float64 `json:"last,string"` + Volume float64 `json:"volume,string"` + Sell float64 `json:"sell,string"` + Buy float64 `json:"buy,string"` +} diff --git a/exchanges/lakebtc/lakebtc_websocket.go b/exchanges/lakebtc/lakebtc_websocket.go index 7af6622f..f197ba79 100644 --- a/exchanges/lakebtc/lakebtc_websocket.go +++ b/exchanges/lakebtc/lakebtc_websocket.go @@ -8,18 +8,19 @@ import ( "strings" "time" + "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" "github.com/thrasher-corp/gocryptotrader/log" "github.com/toorop/go-pusher" ) const ( - lakeBTCWSURL = "ws.lakebtc.com:8085" + lakeBTCWSURL = "wss://ws.lakebtc.com:8085" marketGlobalEndpoint = "market-global" marketSubstring = "market-" globalSubstring = "-global" @@ -33,10 +34,14 @@ const ( // WsConnect initiates a new websocket connection func (l *LakeBTC) WsConnect() error { if !l.Websocket.IsEnabled() || !l.IsEnabled() { - return errors.New(wshandler.WebsocketNotEnabled) + return errors.New(stream.WebsocketNotEnabled) } + + url := strings.Split(lakeBTCWSURL, "://") var err error - l.WebsocketConn.Client, err = pusher.NewCustomClient(strings.ToLower(l.Name), lakeBTCWSURL, wssSchem) + l.WebsocketConn.Client, err = pusher.NewCustomClient(strings.ToLower(l.Name), + url[1], + wssSchem) if err != nil { return err } @@ -44,13 +49,17 @@ func (l *LakeBTC) WsConnect() error { if err != nil { return err } - l.GenerateDefaultSubscriptions() + err = l.listenToEndpoints() if err != nil { return err } go l.wsHandleIncomingData() - return nil + subs, err := l.GenerateDefaultSubscriptions() + if err != nil { + return err + } + return l.Websocket.SubscribeToChannels(subs) } func (l *LakeBTC) listenToEndpoints() error { @@ -71,9 +80,12 @@ func (l *LakeBTC) listenToEndpoints() error { } // GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() -func (l *LakeBTC) GenerateDefaultSubscriptions() { - var subscriptions []wshandler.WebsocketChannelSubscription - enabledCurrencies := l.GetEnabledPairs(asset.Spot) +func (l *LakeBTC) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, error) { + var subscriptions []stream.ChannelSubscription + enabledCurrencies, err := l.GetEnabledPairs(asset.Spot) + if err != nil { + return nil, err + } for j := range enabledCurrencies { enabledCurrencies[j].Delimiter = "" @@ -81,23 +93,54 @@ func (l *LakeBTC) GenerateDefaultSubscriptions() { enabledCurrencies[j].Lower().String() + globalSubstring - subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{ + subscriptions = append(subscriptions, stream.ChannelSubscription{ Channel: channel, Currency: enabledCurrencies[j], + Asset: asset.Spot, }) } - l.Websocket.SubscribeToChannels(subscriptions) + return subscriptions, nil } // Subscribe sends a websocket message to receive data from the channel -func (l *LakeBTC) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error { - return l.WebsocketConn.Client.Subscribe(channelToSubscribe.Channel) +func (l *LakeBTC) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error { + var errs common.Errors + for i := range channelsToSubscribe { + err := l.WebsocketConn.Client.Subscribe(channelsToSubscribe[i].Channel) + if err != nil { + errs = append(errs, err) + continue + } + l.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe[i]) + } + if errs != nil { + return errs + } + return nil +} + +// Unsubscribe sends a websocket message to unsubscribe from the channel +func (l *LakeBTC) Unsubscribe(channelsToUnsubscribe []stream.ChannelSubscription) error { + var errs common.Errors + for i := range channelsToUnsubscribe { + err := l.WebsocketConn.Client.Unsubscribe(channelsToUnsubscribe[i].Channel) + if err != nil { + errs = append(errs, err) + continue + } + l.Websocket.RemoveSuccessfulUnsubscriptions(channelsToUnsubscribe[i]) + } + if errs != nil { + return errs + } + return nil } // wsHandleIncomingData services incoming data from the websocket connection func (l *LakeBTC) wsHandleIncomingData() { l.Websocket.Wg.Add(1) defer l.Websocket.Wg.Done() + for { select { case <-l.Websocket.ShutdownC: @@ -107,14 +150,11 @@ func (l *LakeBTC) wsHandleIncomingData() { log.Debugf(log.ExchangeSys, "%v Websocket message received: %v", l.Name, data) } - l.Websocket.TrafficAlert <- struct{}{} err := l.processTicker(data.Data) if err != nil { l.Websocket.DataHandler <- err - return } case data := <-l.WebsocketConn.Trade: - l.Websocket.TrafficAlert <- struct{}{} if l.Verbose { log.Debugf(log.ExchangeSys, "%v Websocket message received: %v", l.Name, data) @@ -122,10 +162,8 @@ func (l *LakeBTC) wsHandleIncomingData() { err := l.processTrades(data.Data, data.Channel) if err != nil { l.Websocket.DataHandler <- err - return } case data := <-l.WebsocketConn.Orderbook: - l.Websocket.TrafficAlert <- struct{}{} if l.Verbose { log.Debugf(log.ExchangeSys, "%v Websocket message received: %v", l.Name, data) @@ -133,9 +171,12 @@ func (l *LakeBTC) wsHandleIncomingData() { err := l.processOrderbook(data.Data, data.Channel) if err != nil { l.Websocket.DataHandler <- err - return } } + select { + case l.Websocket.TrafficAlert <- struct{}{}: + default: + } } } @@ -145,7 +186,11 @@ func (l *LakeBTC) processTrades(data, channel string) error { if err != nil { return err } - curr := l.getCurrencyFromChannel(channel) + curr, err := l.getCurrencyFromChannel(channel) + if err != nil { + return err + } + for i := range tradeData.Trades { tSide, err := order.StringToOrderSide(tradeData.Trades[i].Type) if err != nil { @@ -154,7 +199,7 @@ func (l *LakeBTC) processTrades(data, channel string) error { Err: err, } } - l.Websocket.DataHandler <- wshandler.TradeData{ + l.Websocket.DataHandler <- stream.TradeData{ Timestamp: time.Unix(tradeData.Trades[i].Date, 0), CurrencyPair: curr, AssetType: asset.Spot, @@ -175,7 +220,10 @@ func (l *LakeBTC) processOrderbook(obUpdate, channel string) error { return err } - p := l.getCurrencyFromChannel(channel) + p, err := l.getCurrencyFromChannel(channel) + if err != nil { + return err + } book := orderbook.Base{ Pair: p, @@ -188,13 +236,11 @@ func (l *LakeBTC) processOrderbook(obUpdate, channel string) error { var amount, price float64 amount, err = strconv.ParseFloat(update.Asks[i][1], 64) if err != nil { - l.Websocket.DataHandler <- fmt.Errorf("%v error parsing ticker data 'low' %v", l.Name, update.Asks[i]) - continue + return err } price, err = strconv.ParseFloat(update.Asks[i][0], 64) if err != nil { - l.Websocket.DataHandler <- fmt.Errorf("%v error parsing orderbook price %v", l.Name, update.Asks[i]) - continue + return err } book.Asks = append(book.Asks, orderbook.Item{ Amount: amount, @@ -206,13 +252,11 @@ func (l *LakeBTC) processOrderbook(obUpdate, channel string) error { var amount, price float64 amount, err = strconv.ParseFloat(update.Bids[i][1], 64) if err != nil { - l.Websocket.DataHandler <- fmt.Errorf("%v error parsing ticker data 'low' %v", l.Name, update.Bids[i]) - continue + return err } price, err = strconv.ParseFloat(update.Bids[i][0], 64) if err != nil { - l.Websocket.DataHandler <- fmt.Errorf("%v error parsing orderbook price %v", l.Name, update.Bids[i]) - continue + return err } book.Bids = append(book.Bids, orderbook.Item{ Amount: amount, @@ -220,67 +264,46 @@ func (l *LakeBTC) processOrderbook(obUpdate, channel string) error { }) } - err = l.Websocket.Orderbook.LoadSnapshot(&book) - if err != nil { - return err - } - - l.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{ - Pair: p, - Asset: asset.Spot, - Exchange: l.Name, - } - - return nil + return l.Websocket.Orderbook.LoadSnapshot(&book) } -func (l *LakeBTC) getCurrencyFromChannel(channel string) currency.Pair { +func (l *LakeBTC) getCurrencyFromChannel(channel string) (currency.Pair, error) { curr := strings.Replace(channel, marketSubstring, "", 1) curr = strings.Replace(curr, globalSubstring, "", 1) return currency.NewPairFromString(curr) } -func (l *LakeBTC) processTicker(wsTicker string) error { - var tUpdate map[string]interface{} - err := json.Unmarshal([]byte(wsTicker), &tUpdate) +func (l *LakeBTC) processTicker(data string) error { + var tUpdate map[string]wsTicker + err := json.Unmarshal([]byte(data), &tUpdate) if err != nil { l.Websocket.DataHandler <- err return err } - enabled := l.GetEnabledPairs(asset.Spot) + enabled, err := l.GetEnabledPairs(asset.Spot) + if err != nil { + return err + } + for k, v := range tUpdate { - returnCurrency := currency.NewPairFromString(k) + returnCurrency, err := currency.NewPairFromString(k) + if err != nil { + return err + } + if !enabled.Contains(returnCurrency, true) { continue } - tickerData := v.(map[string]interface{}) - processTickerItem := func(tick map[string]interface{}, item string) float64 { - if tick[item] == nil { - return 0 - } - - p, err := strconv.ParseFloat(tick[item].(string), 64) - if err != nil { - l.Websocket.DataHandler <- fmt.Errorf("%s error parsing ticker data '%s' %v", - l.Name, - item, - tickerData) - return 0 - } - - return p - } - l.Websocket.DataHandler <- &ticker.Price{ ExchangeName: l.Name, - Bid: processTickerItem(tickerData, order.Buy.Lower()), - High: processTickerItem(tickerData, tickerHighString), - Last: processTickerItem(tickerData, tickerLastString), - Low: processTickerItem(tickerData, tickerLowString), - Ask: processTickerItem(tickerData, order.Sell.Lower()), - Volume: processTickerItem(tickerData, tickerVolumeString), + Bid: v.Buy, + High: v.High, + Last: v.Last, + Low: v.Low, + Ask: v.Sell, + Volume: v.Volume, AssetType: asset.Spot, Pair: returnCurrency, } diff --git a/exchanges/lakebtc/lakebtc_wrapper.go b/exchanges/lakebtc/lakebtc_wrapper.go index ec0007c7..1b4aee13 100644 --- a/exchanges/lakebtc/lakebtc_wrapper.go +++ b/exchanges/lakebtc/lakebtc_wrapper.go @@ -19,8 +19,8 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" "github.com/thrasher-corp/gocryptotrader/log" "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) @@ -56,18 +56,11 @@ func (l *LakeBTC) SetDefaults() { l.API.CredentialsValidator.RequiresKey = true l.API.CredentialsValidator.RequiresSecret = true - l.CurrencyPairs = currency.PairsManager{ - AssetTypes: asset.Items{ - asset.Spot, - }, - - UseGlobalFormat: true, - RequestFormat: ¤cy.PairFormat{ - Uppercase: true, - }, - ConfigFormat: ¤cy.PairFormat{ - Uppercase: true, - }, + requestFmt := ¤cy.PairFormat{Uppercase: true} + configFmt := ¤cy.PairFormat{Uppercase: true} + err := l.SetGlobalPairsManager(requestFmt, configFmt, asset.Spot) + if err != nil { + log.Errorln(log.ExchangeSys, err) } l.Features = exchange.Features{ @@ -95,6 +88,7 @@ func (l *LakeBTC) SetDefaults() { TradeFetching: true, OrderbookFetching: true, Subscribe: true, + Unsubscribe: true, }, WithdrawPermissions: exchange.AutoWithdrawCrypto | exchange.WithdrawFiatViaWebsiteOnly, @@ -109,7 +103,7 @@ func (l *LakeBTC) SetDefaults() { l.API.Endpoints.URLDefault = lakeBTCAPIURL l.API.Endpoints.URL = l.API.Endpoints.URLDefault - l.Websocket = wshandler.New() + l.Websocket = stream.New() l.API.Endpoints.WebsocketURL = lakeBTCWSURL l.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit l.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout @@ -128,31 +122,21 @@ func (l *LakeBTC) Setup(exch *config.ExchangeConfig) error { return err } - err = l.Websocket.Setup( - &wshandler.WebsocketSetup{ - Enabled: exch.Features.Enabled.Websocket, - Verbose: exch.Verbose, - AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, - WebsocketTimeout: exch.WebsocketTrafficTimeout, - DefaultURL: lakeBTCWSURL, - ExchangeName: exch.Name, - RunningURL: exch.API.Endpoints.WebsocketURL, - Connector: l.WsConnect, - Subscriber: l.Subscribe, - Features: &l.Features.Supports.WebsocketCapabilities, - }) - if err != nil { - return err - } - - l.Websocket.Orderbook.Setup( - exch.WebsocketOrderbookBufferLimit, - false, - false, - false, - false, - exch.Name) - return nil + return l.Websocket.Setup(&stream.WebsocketSetup{ + Enabled: exch.Features.Enabled.Websocket, + Verbose: exch.Verbose, + AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, + WebsocketTimeout: exch.WebsocketTrafficTimeout, + DefaultURL: lakeBTCWSURL, + ExchangeName: exch.Name, + RunningURL: exch.API.Endpoints.WebsocketURL, + Connector: l.WsConnect, + Subscriber: l.Subscribe, + UnSubscriber: l.Unsubscribe, + GenerateSubscriptions: l.GenerateDefaultSubscriptions, + Features: &l.Features.Supports.WebsocketCapabilities, + OrderbookBufferLimit: exch.WebsocketOrderbookBufferLimit, + }) } // Start starts the LakeBTC go routine @@ -203,7 +187,11 @@ func (l *LakeBTC) UpdateTradablePairs(forceUpdate bool) error { return err } - return l.UpdatePairs(currency.NewPairsFromStrings(pairs), asset.Spot, false, forceUpdate) + p, err := currency.NewPairsFromStrings(pairs) + if err != nil { + return err + } + return l.UpdatePairs(p, asset.Spot, false, forceUpdate) } // UpdateTicker updates and returns the ticker for a currency pair @@ -213,9 +201,18 @@ func (l *LakeBTC) UpdateTicker(p currency.Pair, assetType asset.Item) (*ticker.P return nil, err } - pairs := l.GetEnabledPairs(assetType) + pairs, err := l.GetEnabledPairs(assetType) + if err != nil { + return nil, err + } + for i := range pairs { - c, ok := ticks[l.FormatExchangeCurrency(pairs[i], assetType).String()] + fpair, err := l.FormatExchangeCurrency(pairs[i], assetType) + if err != nil { + return nil, err + } + + c, ok := ticks[fpair.String()] if !ok { continue } @@ -228,10 +225,12 @@ func (l *LakeBTC) UpdateTicker(p currency.Pair, assetType asset.Item) (*ticker.P tickerPrice.High = c.High tickerPrice.Low = c.Low tickerPrice.Last = c.Last + tickerPrice.ExchangeName = l.Name + tickerPrice.AssetType = assetType - err = ticker.ProcessTicker(l.Name, tickerPrice, assetType) + err = ticker.ProcessTicker(tickerPrice) if err != nil { - log.Error(log.Ticker, err) + return nil, err } } return ticker.GetTicker(l.Name, p, assetType) @@ -447,11 +446,6 @@ func (l *LakeBTC) WithdrawFiatFundsToInternationalBank(withdrawRequest *withdraw return nil, common.ErrFunctionNotSupported } -// GetWebsocket returns a pointer to the exchange websocket -func (l *LakeBTC) GetWebsocket() (*wshandler.Websocket, error) { - return l.Websocket, nil -} - // GetFeeByType returns an estimate of fee based on type of transaction func (l *LakeBTC) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { if !l.AllowAuthenticatedRequest() && // Todo check connection status @@ -468,10 +462,19 @@ func (l *LakeBTC) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, return nil, err } + format, err := l.GetPairFormat(asset.Spot, false) + if err != nil { + return nil, err + } + var orders []order.Detail for i := range resp { - symbol := currency.NewPairDelimiter(resp[i].Symbol, - l.GetPairFormat(asset.Spot, false).Delimiter) + var symbol currency.Pair + symbol, err = currency.NewPairDelimiter(resp[i].Symbol, + format.Delimiter) + if err != nil { + return nil, err + } orderDate := time.Unix(resp[i].At, 0) side := order.Side(strings.ToUpper(resp[i].Type)) @@ -501,14 +504,21 @@ func (l *LakeBTC) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, return nil, err } + format, err := l.GetPairFormat(asset.Spot, false) + if err != nil { + return nil, err + } + var orders []order.Detail for i := range resp { if resp[i].State == "active" { continue } - - symbol := currency.NewPairDelimiter(resp[i].Symbol, - l.GetPairFormat(asset.Spot, false).Delimiter) + var symbol currency.Pair + symbol, err = currency.NewPairDelimiter(resp[i].Symbol, format.Delimiter) + if err != nil { + return nil, err + } orderDate := time.Unix(resp[i].At, 0) side := order.Side(strings.ToUpper(resp[i].Type)) @@ -530,28 +540,6 @@ func (l *LakeBTC) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, return orders, nil } -// SubscribeToWebsocketChannels appends to ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle subscribing -func (l *LakeBTC) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - return common.ErrFunctionNotSupported -} - -// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle unsubscribing -func (l *LakeBTC) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - return common.ErrFunctionNotSupported -} - -// GetSubscriptions returns a copied list of subscriptions -func (l *LakeBTC) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) { - return nil, common.ErrFunctionNotSupported -} - -// AuthenticateWebsocket sends an authentication message to the websocket -func (l *LakeBTC) AuthenticateWebsocket() error { - return common.ErrFunctionNotSupported -} - // ValidateCredentials validates current credentials used for wrapper // functionality func (l *LakeBTC) ValidateCredentials() error { diff --git a/exchanges/lbank/lbank.go b/exchanges/lbank/lbank.go index c7c5ac45..b1d1c995 100644 --- a/exchanges/lbank/lbank.go +++ b/exchanges/lbank/lbank.go @@ -20,14 +20,14 @@ import ( exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/request" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" ) // Lbank is the overarching type across this package type Lbank struct { exchange.Base privateKey *rsa.PrivateKey - WebsocketConn *wshandler.WebsocketConnection + WebsocketConn *stream.WebsocketConnection } const ( diff --git a/exchanges/lbank/lbank_test.go b/exchanges/lbank/lbank_test.go index 2772c056..47c6a4e2 100644 --- a/exchanges/lbank/lbank_test.go +++ b/exchanges/lbank/lbank_test.go @@ -410,8 +410,11 @@ func TestGetOrderHistory(t *testing.T) { func TestGetHistoricCandles(t *testing.T) { t.Parallel() - pair := currency.NewPairFromString(testCurrencyPair) - _, err := l.GetHistoricCandles(pair, asset.Spot, time.Now().Add(-24*time.Hour), time.Now(), kline.OneMin) + pair, err := currency.NewPairFromString(testCurrencyPair) + if err != nil { + t.Fatal(err) + } + _, err = l.GetHistoricCandles(pair, asset.Spot, time.Now().Add(-24*time.Hour), time.Now(), kline.OneMin) if err != nil { t.Fatal(err) } @@ -427,8 +430,11 @@ func TestGetHistoricCandlesExtended(t *testing.T) { startTime := time.Now().Add(-time.Hour) end := time.Now() - pair := currency.NewPairFromString(testCurrencyPair) - _, err := l.GetHistoricCandlesExtended(pair, asset.Spot, startTime, end, kline.OneMin) + pair, err := currency.NewPairFromString(testCurrencyPair) + if err != nil { + t.Fatal(err) + } + _, err = l.GetHistoricCandlesExtended(pair, asset.Spot, startTime, end, kline.OneMin) if err != nil { t.Fatal(err) } diff --git a/exchanges/lbank/lbank_wrapper.go b/exchanges/lbank/lbank_wrapper.go index 4653a872..53dea512 100644 --- a/exchanges/lbank/lbank_wrapper.go +++ b/exchanges/lbank/lbank_wrapper.go @@ -19,7 +19,6 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" "github.com/thrasher-corp/gocryptotrader/log" "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) @@ -55,18 +54,11 @@ func (l *Lbank) SetDefaults() { l.API.CredentialsValidator.RequiresKey = true l.API.CredentialsValidator.RequiresSecret = true - l.CurrencyPairs = currency.PairsManager{ - AssetTypes: asset.Items{ - asset.Spot, - }, - - UseGlobalFormat: true, - RequestFormat: ¤cy.PairFormat{ - Delimiter: "_", - }, - ConfigFormat: ¤cy.PairFormat{ - Delimiter: "_", - }, + requestFmt := ¤cy.PairFormat{Delimiter: currency.UnderscoreDelimiter} + configFmt := ¤cy.PairFormat{Delimiter: currency.UnderscoreDelimiter} + err := l.SetGlobalPairsManager(requestFmt, configFmt, asset.Spot) + if err != nil { + log.Errorln(log.ExchangeSys, err) } l.Features = exchange.Features{ @@ -184,33 +176,40 @@ func (l *Lbank) UpdateTradablePairs(forceUpdate bool) error { return err } - return l.UpdatePairs(currency.NewPairsFromStrings(pairs), asset.Spot, false, forceUpdate) + p, err := currency.NewPairsFromStrings(pairs) + if err != nil { + return err + } + return l.UpdatePairs(p, asset.Spot, false, forceUpdate) } // UpdateTicker updates and returns the ticker for a currency pair func (l *Lbank) UpdateTicker(p currency.Pair, assetType asset.Item) (*ticker.Price, error) { - tickerPrice := new(ticker.Price) tickerInfo, err := l.GetTickers() if err != nil { - return tickerPrice, err + return nil, err + } + pairs, err := l.GetEnabledPairs(assetType) + if err != nil { + return nil, err } - pairs := l.GetEnabledPairs(assetType) for i := range pairs { for j := range tickerInfo { if !pairs[i].Equal(tickerInfo[j].Symbol) { continue } - tickerPrice = &ticker.Price{ - Last: tickerInfo[j].Ticker.Latest, - High: tickerInfo[j].Ticker.High, - Low: tickerInfo[j].Ticker.Low, - Volume: tickerInfo[j].Ticker.Volume, - Pair: tickerInfo[j].Symbol, - LastUpdated: time.Unix(0, tickerInfo[j].Timestamp), - } - err = ticker.ProcessTicker(l.Name, tickerPrice, assetType) + + err = ticker.ProcessTicker(&ticker.Price{ + Last: tickerInfo[j].Ticker.Latest, + High: tickerInfo[j].Ticker.High, + Low: tickerInfo[j].Ticker.Low, + Volume: tickerInfo[j].Ticker.Volume, + Pair: tickerInfo[j].Symbol, + LastUpdated: time.Unix(0, tickerInfo[j].Timestamp), + ExchangeName: l.Name, + AssetType: assetType}) if err != nil { - log.Error(log.Ticker, err) + return nil, err } } } @@ -219,8 +218,12 @@ func (l *Lbank) UpdateTicker(p currency.Pair, assetType asset.Item) (*ticker.Pri // FetchTicker returns the ticker for a currency pair func (l *Lbank) FetchTicker(p currency.Pair, assetType asset.Item) (*ticker.Price, error) { - tickerNew, err := ticker.GetTicker(l.Name, - l.FormatExchangeCurrency(p, assetType), assetType) + fpair, err := l.FormatExchangeCurrency(p, assetType) + if err != nil { + return nil, err + } + + tickerNew, err := ticker.GetTicker(l.Name, fpair, assetType) if err != nil { return l.UpdateTicker(p, assetType) } @@ -239,7 +242,11 @@ func (l *Lbank) FetchOrderbook(currency currency.Pair, assetType asset.Item) (*o // UpdateOrderbook updates and returns the orderbook for a currency pair func (l *Lbank) UpdateOrderbook(p currency.Pair, assetType asset.Item) (*orderbook.Base, error) { orderBook := new(orderbook.Base) - a, err := l.GetMarketDepths(l.FormatExchangeCurrency(p, assetType).String(), "60", "1") + fpair, err := l.FormatExchangeCurrency(p, assetType) + if err != nil { + return nil, err + } + a, err := l.GetMarketDepths(fpair.String(), "60", "1") if err != nil { return orderBook, err } @@ -336,8 +343,14 @@ func (l *Lbank) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { fmt.Errorf("%s order side is not supported by the exchange", s.Side) } + + fpair, err := l.FormatExchangeCurrency(s.Pair, asset.Spot) + if err != nil { + return resp, err + } + tempResp, err := l.CreateOrder( - l.FormatExchangeCurrency(s.Pair, asset.Spot).String(), + fpair.String(), s.Side.String(), s.Amount, s.Price) @@ -360,8 +373,11 @@ func (l *Lbank) ModifyOrder(action *order.Modify) (string, error) { // CancelOrder cancels an order by its corresponding ID number func (l *Lbank) CancelOrder(order *order.Cancel) error { - _, err := l.RemoveOrder(l.FormatExchangeCurrency(order.Pair, - order.AssetType).String(), order.ID) + fpair, err := l.FormatExchangeCurrency(order.Pair, order.AssetType) + if err != nil { + return err + } + _, err = l.RemoveOrder(fpair.String(), order.ID) return err } @@ -440,7 +456,11 @@ func (l *Lbank) GetOrderInfo(orderID string) (order.Detail, error) { return resp, err } resp.Exchange = l.Name - resp.Pair = currency.NewPairFromString(key) + resp.Pair, err = currency.NewPairFromString(key) + if err != nil { + return order.Detail{}, err + } + if strings.EqualFold(tempResp.Orders[0].Type, order.Buy.String()) { resp.Side = order.Buy } else { @@ -508,11 +528,6 @@ func (l *Lbank) WithdrawFiatFundsToInternationalBank(withdrawRequest *withdraw.R return nil, common.ErrFunctionNotSupported } -// GetWebsocket returns a pointer to the exchange websocket -func (l *Lbank) GetWebsocket() (*wshandler.Websocket, error) { - return nil, common.ErrNotYetImplemented -} - // GetActiveOrders retrieves any orders that are active/open func (l *Lbank) GetActiveOrders(getOrdersRequest *order.GetOrdersRequest) ([]order.Detail, error) { var finalResp []order.Detail @@ -529,7 +544,11 @@ func (l *Lbank) GetActiveOrders(getOrdersRequest *order.GetOrdersRequest) ([]ord return finalResp, err } resp.Exchange = l.Name - resp.Pair = currency.NewPairFromString(key) + resp.Pair, err = currency.NewPairFromString(key) + if err != nil { + return nil, err + } + if strings.EqualFold(tempResp.Orders[0].Type, order.Buy.String()) { resp.Side = order.Buy } else { @@ -587,25 +606,37 @@ func (l *Lbank) GetOrderHistory(getOrdersRequest *order.GetOrdersRequest) ([]ord var resp order.Detail var tempCurr currency.Pairs if len(getOrdersRequest.Pairs) == 0 { - tempCurr = l.GetEnabledPairs(asset.Spot) + var err error + tempCurr, err = l.GetEnabledPairs(asset.Spot) + if err != nil { + return nil, err + } } else { tempCurr = getOrdersRequest.Pairs } for a := range tempCurr { - p := l.FormatExchangeCurrency(tempCurr[a], asset.Spot).String() + fpair, err := l.FormatExchangeCurrency(tempCurr[a], asset.Spot) + if err != nil { + return nil, err + } + b := int64(1) - tempResp, err := l.QueryOrderHistory(p, strconv.FormatInt(b, 10), "200") + tempResp, err := l.QueryOrderHistory(fpair.String(), strconv.FormatInt(b, 10), "200") if err != nil { return finalResp, err } for len(tempResp.Orders) != 0 { - tempResp, err = l.QueryOrderHistory(p, strconv.FormatInt(b, 10), "200") + tempResp, err = l.QueryOrderHistory(fpair.String(), strconv.FormatInt(b, 10), "200") if err != nil { return finalResp, err } for x := 0; x < len(tempResp.Orders); x++ { resp.Exchange = l.Name - resp.Pair = currency.NewPairFromString(tempResp.Orders[x].Symbol) + resp.Pair, err = currency.NewPairFromString(tempResp.Orders[x].Symbol) + if err != nil { + return nil, err + } + if strings.EqualFold(tempResp.Orders[x].Type, order.Buy.String()) { resp.Side = order.Buy } else { @@ -674,18 +705,28 @@ func (l *Lbank) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { // GetAllOpenOrderID returns all open orders by currency pairs func (l *Lbank) getAllOpenOrderID() (map[string][]string, error) { - allPairs := l.GetEnabledPairs(asset.Spot) + allPairs, err := l.GetEnabledPairs(asset.Spot) + if err != nil { + return nil, err + } resp := make(map[string][]string) for a := range allPairs { - p := l.FormatExchangeCurrency(allPairs[a], asset.Spot).String() + fpair, err := l.FormatExchangeCurrency(allPairs[a], asset.Spot) + if err != nil { + return nil, err + } b := int64(1) - tempResp, err := l.GetOpenOrders(p, strconv.FormatInt(b, 10), "200") + tempResp, err := l.GetOpenOrders(fpair.String(), + strconv.FormatInt(b, 10), + "200") if err != nil { return resp, err } tempData := len(tempResp.Orders) for tempData != 0 { - tempResp, err = l.GetOpenOrders(p, strconv.FormatInt(b, 10), "200") + tempResp, err = l.GetOpenOrders(fpair.String(), + strconv.FormatInt(b, 10), + "200") if err != nil { return resp, err } @@ -695,7 +736,8 @@ func (l *Lbank) getAllOpenOrderID() (map[string][]string, error) { } for c := 0; c < tempData; c++ { - resp[p] = append(resp[p], tempResp.Orders[c].OrderID) + resp[fpair.String()] = append(resp[fpair.String()], + tempResp.Orders[c].OrderID) } tempData = len(tempResp.Orders) b++ @@ -704,28 +746,6 @@ func (l *Lbank) getAllOpenOrderID() (map[string][]string, error) { return resp, nil } -// SubscribeToWebsocketChannels appends to ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle subscribing -func (l *Lbank) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - return common.ErrNotYetImplemented -} - -// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle unsubscribing -func (l *Lbank) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - return common.ErrNotYetImplemented -} - -// AuthenticateWebsocket authenticates it -func (l *Lbank) AuthenticateWebsocket() error { - return common.ErrNotYetImplemented -} - -// GetSubscriptions gets subscriptions -func (l *Lbank) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) { - return nil, common.ErrNotYetImplemented -} - // ValidateCredentials validates current credentials used for wrapper // functionality func (l *Lbank) ValidateCredentials() error { @@ -758,7 +778,12 @@ func (l *Lbank) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end } } - data, err := l.GetKlines(l.FormatExchangeCurrency(pair, a).String(), + formattedPair, err := l.FormatExchangeCurrency(pair, a) + if err != nil { + return kline.Item{}, err + } + + data, err := l.GetKlines(formattedPair.String(), strconv.FormatInt(int64(l.Features.Enabled.Kline.ResultLimit), 10), l.FormatExchangeKlineInterval(interval), strconv.FormatInt(start.Unix(), 10)) @@ -803,8 +828,13 @@ func (l *Lbank) GetHistoricCandlesExtended(pair currency.Pair, a asset.Item, sta } dates := kline.CalcDateRanges(start, end, interval, l.Features.Enabled.Kline.ResultLimit) + formattedPair, err := l.FormatExchangeCurrency(pair, a) + if err != nil { + return kline.Item{}, err + } + for x := range dates { - data, err := l.GetKlines(l.FormatExchangeCurrency(pair, a).String(), + data, err := l.GetKlines(formattedPair.String(), strconv.FormatInt(int64(l.Features.Enabled.Kline.ResultLimit), 10), l.FormatExchangeKlineInterval(interval), strconv.FormatInt(dates[x].Start.UTC().Unix(), 10)) diff --git a/exchanges/localbitcoins/localbitcoins_wrapper.go b/exchanges/localbitcoins/localbitcoins_wrapper.go index bbd6c3d2..e9ac3184 100644 --- a/exchanges/localbitcoins/localbitcoins_wrapper.go +++ b/exchanges/localbitcoins/localbitcoins_wrapper.go @@ -21,7 +21,6 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" "github.com/thrasher-corp/gocryptotrader/log" "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) @@ -57,18 +56,11 @@ func (l *LocalBitcoins) SetDefaults() { l.API.CredentialsValidator.RequiresKey = true l.API.CredentialsValidator.RequiresSecret = true - l.CurrencyPairs = currency.PairsManager{ - AssetTypes: asset.Items{ - asset.Spot, - }, - - UseGlobalFormat: true, - RequestFormat: ¤cy.PairFormat{ - Uppercase: true, - }, - ConfigFormat: ¤cy.PairFormat{ - Uppercase: true, - }, + requestFmt := ¤cy.PairFormat{Uppercase: true} + configFmt := ¤cy.PairFormat{Uppercase: true} + err := l.SetGlobalPairsManager(requestFmt, configFmt, asset.Spot) + if err != nil { + log.Errorln(log.ExchangeSys, err) } l.Features = exchange.Features{ @@ -110,7 +102,6 @@ func (l *LocalBitcoins) Setup(exch *config.ExchangeConfig) error { l.SetEnabled(false) return nil } - return l.SetupDefaults(exch) } @@ -161,18 +152,24 @@ func (l *LocalBitcoins) UpdateTradablePairs(forceUpdate bool) error { if err != nil { return err } - return l.UpdatePairs(currency.NewPairsFromStrings(pairs), asset.Spot, false, forceUpdate) + p, err := currency.NewPairsFromStrings(pairs) + if err != nil { + return err + } + return l.UpdatePairs(p, asset.Spot, false, forceUpdate) } // UpdateTicker updates and returns the ticker for a currency pair func (l *LocalBitcoins) UpdateTicker(p currency.Pair, assetType asset.Item) (*ticker.Price, error) { - tickerPrice := new(ticker.Price) tick, err := l.GetTicker() if err != nil { - return tickerPrice, err + return nil, err } - pairs := l.GetEnabledPairs(assetType) + pairs, err := l.GetEnabledPairs(assetType) + if err != nil { + return nil, err + } for i := range pairs { curr := pairs[i].Quote.String() if _, ok := tick[curr]; !ok { @@ -182,10 +179,12 @@ func (l *LocalBitcoins) UpdateTicker(p currency.Pair, assetType asset.Item) (*ti tp.Pair = pairs[i] tp.Last = tick[curr].Avg24h tp.Volume = tick[curr].VolumeBTC + tp.ExchangeName = l.Name + tp.AssetType = assetType - err = ticker.ProcessTicker(l.Name, &tp, assetType) + err = ticker.ProcessTicker(&tp) if err != nil { - log.Error(log.Ticker, err) + return nil, err } } @@ -432,11 +431,6 @@ func (l *LocalBitcoins) WithdrawFiatFundsToInternationalBank(withdrawRequest *wi return nil, common.ErrFunctionNotSupported } -// GetWebsocket returns a pointer to the exchange websocket -func (l *LocalBitcoins) GetWebsocket() (*wshandler.Websocket, error) { - return nil, common.ErrFunctionNotSupported -} - // GetFeeByType returns an estimate of fee based on type of transaction func (l *LocalBitcoins) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { if (!l.AllowAuthenticatedRequest() || l.SkipAuthCheck) && // Todo check connection status @@ -453,6 +447,11 @@ func (l *LocalBitcoins) GetActiveOrders(getOrdersRequest *order.GetOrdersRequest return nil, err } + format, err := l.GetPairFormat(asset.Spot, false) + if err != nil { + return nil, err + } + var orders []order.Detail for i := range resp { orderDate, err := time.Parse(time.RFC3339, resp[i].Data.CreatedAt) @@ -480,7 +479,7 @@ func (l *LocalBitcoins) GetActiveOrders(getOrdersRequest *order.GetOrdersRequest Side: side, Pair: currency.NewPairWithDelimiter(currency.BTC.String(), resp[i].Data.Currency, - l.GetPairFormat(asset.Spot, false).Delimiter), + format.Delimiter), Exchange: l.Name, }) } @@ -514,6 +513,11 @@ func (l *LocalBitcoins) GetOrderHistory(getOrdersRequest *order.GetOrdersRequest } allTrades = append(allTrades, resp...) + format, err := l.GetPairFormat(asset.Spot, false) + if err != nil { + return nil, err + } + var orders []order.Detail for i := range allTrades { orderDate, err := time.Parse(time.RFC3339, allTrades[i].Data.CreatedAt) @@ -557,7 +561,7 @@ func (l *LocalBitcoins) GetOrderHistory(getOrdersRequest *order.GetOrdersRequest Status: order.Status(status), Pair: currency.NewPairWithDelimiter(currency.BTC.String(), allTrades[i].Data.Currency, - l.GetPairFormat(asset.Spot, false).Delimiter), + format.Delimiter), Exchange: l.Name, }) } @@ -569,28 +573,6 @@ func (l *LocalBitcoins) GetOrderHistory(getOrdersRequest *order.GetOrdersRequest return orders, nil } -// SubscribeToWebsocketChannels appends to ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle subscribing -func (l *LocalBitcoins) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - return common.ErrFunctionNotSupported -} - -// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle unsubscribing -func (l *LocalBitcoins) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - return common.ErrFunctionNotSupported -} - -// GetSubscriptions returns a copied list of subscriptions -func (l *LocalBitcoins) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) { - return nil, common.ErrFunctionNotSupported -} - -// AuthenticateWebsocket sends an authentication message to the websocket -func (l *LocalBitcoins) AuthenticateWebsocket() error { - return common.ErrFunctionNotSupported -} - // ValidateCredentials validates current credentials used for wrapper // functionality func (l *LocalBitcoins) ValidateCredentials() error { diff --git a/exchanges/okcoin/okcoin_test.go b/exchanges/okcoin/okcoin_test.go index 35dc8a68..9669a609 100644 --- a/exchanges/okcoin/okcoin_test.go +++ b/exchanges/okcoin/okcoin_test.go @@ -6,7 +6,6 @@ import ( "net/http" "os" "strings" - "sync" "testing" "time" @@ -21,7 +20,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/okgroup" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) @@ -69,14 +68,12 @@ func TestMain(m *testing.M) { okcoinConfig.API.Credentials.Secret = apiSecret okcoinConfig.API.Credentials.ClientID = passphrase okcoinConfig.API.Endpoints.WebsocketURL = o.API.Endpoints.WebsocketURL + o.Websocket = sharedtestvalues.NewTestWebsocket() err = o.Setup(okcoinConfig) if err != nil { log.Fatal("OKCoin setup error", err) } testSetupRan = true - o.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride() - o.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride() - os.Exit(m.Run()) } @@ -752,33 +749,27 @@ func TestGetMarginTransactionDetails(t *testing.T) { // Will log in if credentials are present func TestSendWsMessages(t *testing.T) { if !o.Websocket.IsEnabled() && !o.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() { - t.Skip(wshandler.WebsocketNotEnabled) + t.Skip(stream.WebsocketNotEnabled) } var ok bool - o.WebsocketConn = &wshandler.WebsocketConnection{ - ExchangeName: o.Name, - URL: o.Websocket.GetWebsocketURL(), - Verbose: o.Verbose, - ResponseMaxLimit: exchange.DefaultWebsocketResponseMaxLimit, - ResponseCheckTimeout: exchange.DefaultWebsocketResponseCheckTimeout, - } var dialer websocket.Dialer - err := o.WebsocketConn.Dial(&dialer, http.Header{}) + err := o.Websocket.Conn.Dial(&dialer, http.Header{}) if err != nil { t.Fatal(err) } - wg := sync.WaitGroup{} - wg.Add(1) - go o.WsReadData(&wg) - wg.Wait() - - subscription := wshandler.WebsocketChannelSubscription{ - Channel: "badChannel", + go o.WsReadData() + subscriptions := []stream.ChannelSubscription{ + { + Channel: "badChannel", + }, + } + err = o.Subscribe(subscriptions) + if err != nil { + t.Fatal(err) } - o.Subscribe(subscription) response := <-o.Websocket.DataHandler if err, ok = response.(error); ok && err != nil { - if !strings.Contains(response.(error).Error(), subscription.Channel) { + if !strings.Contains(response.(error).Error(), subscriptions[0].Channel) { t.Error("Expecting OKEX error - 30040 message: Channel badChannel doesn't exist") } } @@ -1099,18 +1090,24 @@ func TestGetOrderbook(t *testing.T) { } func TestGetHistoricCandles(t *testing.T) { - currencyPair := currency.NewPairFromString("BTCUSDT") + currencyPair, err := currency.NewPairFromString("BTCUSDT") + if err != nil { + t.Fatal(err) + } startTime := time.Unix(1588636800, 0) - _, err := o.GetHistoricCandles(currencyPair, asset.Spot, startTime, time.Now(), kline.OneMin) + _, err = o.GetHistoricCandles(currencyPair, asset.Spot, startTime, time.Now(), kline.OneMin) if err != nil { t.Fatal(err) } } func TestGetHistoricCandlesExtended(t *testing.T) { - currencyPair := currency.NewPairFromString("BTCUSDT") + currencyPair, err := currency.NewPairFromString("BTCUSDT") + if err != nil { + t.Fatal(err) + } startTime := time.Unix(1588636800, 0) - _, err := o.GetHistoricCandlesExtended(currencyPair, asset.Spot, startTime, time.Now(), kline.OneMin) + _, err = o.GetHistoricCandlesExtended(currencyPair, asset.Spot, startTime, time.Now(), kline.OneMin) if err != nil { t.Fatal(err) } diff --git a/exchanges/okcoin/okcoin_wrapper.go b/exchanges/okcoin/okcoin_wrapper.go index 934493b0..a65fe8a9 100644 --- a/exchanges/okcoin/okcoin_wrapper.go +++ b/exchanges/okcoin/okcoin_wrapper.go @@ -15,8 +15,8 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/okgroup" "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" "github.com/thrasher-corp/gocryptotrader/log" ) @@ -55,23 +55,9 @@ func (o *OKCoin) SetDefaults() { o.API.CredentialsValidator.RequiresSecret = true o.API.CredentialsValidator.RequiresClientID = true - o.CurrencyPairs = currency.PairsManager{ - AssetTypes: asset.Items{ - asset.Spot, - asset.Margin, - }, - - UseGlobalFormat: true, - RequestFormat: ¤cy.PairFormat{ - Uppercase: true, - Delimiter: "-", - }, - - ConfigFormat: ¤cy.PairFormat{ - Uppercase: true, - Delimiter: "-", - }, - } + requestFmt := ¤cy.PairFormat{Uppercase: true, Delimiter: currency.DashDelimiter} + configFmt := ¤cy.PairFormat{Uppercase: true, Delimiter: currency.DashDelimiter} + o.SetGlobalPairsManager(requestFmt, configFmt, asset.Spot, asset.Margin) o.Features = exchange.Features{ Supports: exchange.FeaturesSupported{ @@ -152,7 +138,7 @@ func (o *OKCoin) SetDefaults() { o.API.Endpoints.URL = okCoinAPIURL o.API.Endpoints.WebsocketURL = okCoinWebsocketURL o.APIVersion = okCoinAPIVersion - o.Websocket = wshandler.New() + o.Websocket = stream.New() o.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit o.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout o.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit @@ -178,25 +164,56 @@ func (o *OKCoin) Run() { } forceUpdate := false - delim := o.GetPairFormat(asset.Spot, false).Delimiter - if !common.StringDataContains(o.CurrencyPairs.GetPairs(asset.Spot, - true).Strings(), delim) || - !common.StringDataContains(o.CurrencyPairs.GetPairs(asset.Spot, - false).Strings(), delim) { - enabledPairs := currency.NewPairsFromStrings( - []string{currency.BTC.String() + delim + currency.USD.String()}, - ) - log.Warnf(log.ExchangeSys, - "Enabled pairs for %v reset due to config upgrade, please enable the ones you would like again.\n", - o.Name) - forceUpdate = true + format, err := o.GetPairFormat(asset.Spot, false) + if err != nil { + log.Errorf(log.ExchangeSys, + "%s failed to update currencies. Err: %s\n", + o.Name, + err) + return + } + enabled, err := o.CurrencyPairs.GetPairs(asset.Spot, true) + if err != nil { + log.Errorf(log.ExchangeSys, + "%s failed to update currencies. Err: %s\n", + o.Name, + err) + return + } - err := o.UpdatePairs(enabledPairs, asset.Spot, true, true) + avail, err := o.CurrencyPairs.GetPairs(asset.Spot, false) + if err != nil { + log.Errorf(log.ExchangeSys, + "%s failed to update currencies. Err: %s\n", + o.Name, + err) + return + } + + if !common.StringDataContains(enabled.Strings(), format.Delimiter) || + !common.StringDataContains(avail.Strings(), format.Delimiter) { + var p currency.Pairs + p, err = currency.NewPairsFromStrings([]string{currency.BTC.String() + + format.Delimiter + + currency.USD.String()}) if err != nil { log.Errorf(log.ExchangeSys, "%s failed to update currencies.\n", o.Name) - return + } else { + log.Warnf(log.ExchangeSys, + "Enabled pairs for %v reset due to config upgrade, please enable the ones you would like again.\n", + o.Name) + forceUpdate = true + + err = o.UpdatePairs(p, asset.Spot, true, true) + if err != nil { + log.Errorf(log.ExchangeSys, + "%s failed to update currencies. Err: %s\n", + o.Name, + err) + return + } } } @@ -204,7 +221,7 @@ func (o *OKCoin) Run() { return } - err := o.UpdateTradablePairs(forceUpdate) + err = o.UpdateTradablePairs(forceUpdate) if err != nil { log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", @@ -220,10 +237,15 @@ func (o *OKCoin) FetchTradablePairs(asset asset.Item) ([]string, error) { return nil, err } + format, err := o.GetPairFormat(asset, false) + if err != nil { + return nil, err + } + var pairs []string for x := range prods { pairs = append(pairs, prods[x].BaseCurrency+ - o.GetPairFormat(asset, false).Delimiter+ + format.Delimiter+ prods[x].QuoteCurrency) } @@ -237,40 +259,45 @@ func (o *OKCoin) UpdateTradablePairs(forceUpdate bool) error { if err != nil { return err } - - return o.UpdatePairs(currency.NewPairsFromStrings(pairs), - asset.Spot, false, forceUpdate) + p, err := currency.NewPairsFromStrings(pairs) + if err != nil { + return err + } + return o.UpdatePairs(p, asset.Spot, false, forceUpdate) } // UpdateTicker updates and returns the ticker for a currency pair func (o *OKCoin) UpdateTicker(p currency.Pair, assetType asset.Item) (*ticker.Price, error) { - var tickerData ticker.Price if assetType == asset.Spot { resp, err := o.GetSpotAllTokenPairsInformation() if err != nil { return nil, err } - pairs := o.GetEnabledPairs(assetType) + pairs, err := o.GetEnabledPairs(assetType) + if err != nil { + return nil, err + } for i := range pairs { for j := range resp { if !pairs[i].Equal(resp[j].InstrumentID) { continue } - tickerData = ticker.Price{ - Last: resp[j].Last, - High: resp[j].High24h, - Low: resp[j].Low24h, - Bid: resp[j].BestBid, - Ask: resp[j].BestAsk, - Volume: resp[j].BaseVolume24h, - QuoteVolume: resp[j].QuoteVolume24h, - Open: resp[j].Open24h, - Pair: pairs[i], - LastUpdated: resp[j].Timestamp, - } - err = ticker.ProcessTicker(o.Name, &tickerData, assetType) + + err = ticker.ProcessTicker(&ticker.Price{ + Last: resp[j].Last, + High: resp[j].High24h, + Low: resp[j].Low24h, + Bid: resp[j].BestBid, + Ask: resp[j].BestAsk, + Volume: resp[j].BaseVolume24h, + QuoteVolume: resp[j].QuoteVolume24h, + Open: resp[j].Open24h, + Pair: pairs[i], + LastUpdated: resp[j].Timestamp, + ExchangeName: o.Name, + AssetType: assetType}) if err != nil { - log.Error(log.Ticker, err) + return nil, err } } } @@ -295,12 +322,17 @@ func (o *OKCoin) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end } } + formattedPair, err := o.FormatExchangeCurrency(pair, a) + if err != nil { + return kline.Item{}, err + } + req := &okgroup.GetMarketDataRequest{ Asset: a, Start: start.UTC().Format(time.RFC3339), End: end.UTC().Format(time.RFC3339), Granularity: o.FormatExchangeKlineInterval(interval), - InstrumentID: o.FormatExchangeCurrency(pair, a).String(), + InstrumentID: formattedPair.String(), } candles, err := o.GetMarketData(req) @@ -371,13 +403,18 @@ func (o *OKCoin) GetHistoricCandlesExtended(pair currency.Pair, a asset.Item, st } dates := kline.CalcDateRanges(start, end, interval, o.Features.Enabled.Kline.ResultLimit) + formattedPair, err := o.FormatExchangeCurrency(pair, a) + if err != nil { + return kline.Item{}, err + } + for x := range dates { req := &okgroup.GetMarketDataRequest{ Asset: a, Start: dates[x].Start.UTC().Format(time.RFC3339), End: dates[x].End.UTC().Format(time.RFC3339), Granularity: o.FormatExchangeKlineInterval(interval), - InstrumentID: o.FormatExchangeCurrency(pair, a).String(), + InstrumentID: formattedPair.String(), } candles, err := o.GetMarketData(req) diff --git a/exchanges/okex/okex_test.go b/exchanges/okex/okex_test.go index b617be77..9985f346 100644 --- a/exchanges/okex/okex_test.go +++ b/exchanges/okex/okex_test.go @@ -8,7 +8,6 @@ import ( "os" "strconv" "strings" - "sync" "testing" "time" @@ -23,7 +22,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/okgroup" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) @@ -70,13 +69,11 @@ func TestMain(m *testing.M) { okexConfig.API.Credentials.Secret = apiSecret okexConfig.API.Credentials.ClientID = passphrase okexConfig.API.Endpoints.WebsocketURL = o.API.Endpoints.WebsocketURL + o.Websocket = sharedtestvalues.NewTestWebsocket() err = o.Setup(okexConfig) if err != nil { log.Fatal("Okex setup error", err) } - o.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride() - o.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride() - os.Exit(m.Run()) } @@ -532,9 +529,12 @@ func TestGetSpotMarketData(t *testing.T) { } func TestGetHistoricCandles(t *testing.T) { - currencyPair := currency.NewPairFromString("BTCUSDT") + currencyPair, err := currency.NewPairFromString("BTCUSDT") + if err != nil { + t.Fatal(err) + } startTime := time.Unix(1588636800, 0) - _, err := o.GetHistoricCandles(currencyPair, asset.Spot, startTime, time.Now(), kline.OneMin) + _, err = o.GetHistoricCandles(currencyPair, asset.Spot, startTime, time.Now(), kline.OneMin) if err != nil { t.Fatal(err) } @@ -549,7 +549,10 @@ func TestGetHistoricCandles(t *testing.T) { t.Fatal("unexpected result") } - swapPair := currency.NewPairFromString("BTC-USD_SWAP") + swapPair, err := currency.NewPairFromString("BTC-USD_SWAP") + if err != nil { + t.Fatal(err) + } _, err = o.GetHistoricCandles(swapPair, asset.PerpetualSwap, startTime, time.Now(), kline.OneDay) if err != nil { t.Fatal(err) @@ -557,9 +560,12 @@ func TestGetHistoricCandles(t *testing.T) { } func TestGetHistoricCandlesExtended(t *testing.T) { - currencyPair := currency.NewPairFromString("BTCUSDT") + currencyPair, err := currency.NewPairFromString("BTCUSDT") + if err != nil { + t.Fatal(err) + } startTime := time.Unix(1588636800, 0) - _, err := o.GetHistoricCandlesExtended(currencyPair, asset.Spot, startTime, time.Now(), kline.OneMin) + _, err = o.GetHistoricCandlesExtended(currencyPair, asset.Spot, startTime, time.Now(), kline.OneMin) if err != nil { t.Fatal(err) } @@ -1461,33 +1467,27 @@ func TestGetETTSettlementPriceHistory(t *testing.T) { // Will log in if credentials are present func TestSendWsMessages(t *testing.T) { if !o.Websocket.IsEnabled() && !o.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() { - t.Skip(wshandler.WebsocketNotEnabled) + t.Skip(stream.WebsocketNotEnabled) } var ok bool - o.WebsocketConn = &wshandler.WebsocketConnection{ - ExchangeName: o.Name, - URL: o.Websocket.GetWebsocketURL(), - Verbose: o.Verbose, - ResponseMaxLimit: exchange.DefaultWebsocketResponseMaxLimit, - ResponseCheckTimeout: exchange.DefaultWebsocketResponseCheckTimeout, - } var dialer websocket.Dialer - err := o.WebsocketConn.Dial(&dialer, http.Header{}) + err := o.Websocket.Conn.Dial(&dialer, http.Header{}) if err != nil { t.Fatal(err) } - wg := sync.WaitGroup{} - wg.Add(1) - go o.WsReadData(&wg) - wg.Wait() - - subscription := wshandler.WebsocketChannelSubscription{ - Channel: "badChannel", + go o.WsReadData() + subscriptions := []stream.ChannelSubscription{ + { + Channel: "badChannel", + }, + } + err = o.Subscribe(subscriptions) + if err != nil { + t.Fatal(err) } - o.Subscribe(subscription) response := <-o.Websocket.DataHandler if err, ok = response.(error); ok && err != nil { - if !strings.Contains(response.(error).Error(), subscription.Channel) { + if !strings.Contains(response.(error).Error(), subscriptions[0].Channel) { t.Error("Expecting OKEX error - 30040 message: Channel badChannel doesn't exist") } } @@ -1821,6 +1821,13 @@ func TestGetOrderbook(t *testing.T) { } } +func TestUpdateTradablePairs(t *testing.T) { + err := o.UpdateTradablePairs(true) + if err != nil { + t.Fatal(err) + } +} + func TestWsSubscribe(t *testing.T) { pressXToJSON := []byte(`{"event":"subscribe","channel":"spot/ticker:ETH-USDT"}`) err := o.WsHandleData(pressXToJSON) diff --git a/exchanges/okex/okex_wrapper.go b/exchanges/okex/okex_wrapper.go index 2f83ac16..989dee68 100644 --- a/exchanges/okex/okex_wrapper.go +++ b/exchanges/okex/okex_wrapper.go @@ -17,16 +17,11 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/okgroup" "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" "github.com/thrasher-corp/gocryptotrader/log" ) -const ( - delimiterDash = "-" - delimiterUnderscore = "_" -) - // GetDefaultConfig returns a default exchange config func (o *OKEX) GetDefaultConfig() (*config.ExchangeConfig, error) { o.SetDefaults() @@ -61,32 +56,43 @@ func (o *OKEX) SetDefaults() { o.API.CredentialsValidator.RequiresSecret = true o.API.CredentialsValidator.RequiresClientID = true - o.CurrencyPairs = currency.PairsManager{ - AssetTypes: asset.Items{ - asset.Spot, - asset.Futures, - asset.PerpetualSwap, - asset.Index, - }, - } // Same format used for perpetual swap and futures - fmt1 := currency.PairStore{ + futures := currency.PairStore{ RequestFormat: ¤cy.PairFormat{ Uppercase: true, - Delimiter: delimiterDash, + Delimiter: currency.DashDelimiter, }, ConfigFormat: ¤cy.PairFormat{ Uppercase: true, - Delimiter: delimiterUnderscore, + Delimiter: currency.UnderscoreDelimiter, }, } - o.CurrencyPairs.Store(asset.PerpetualSwap, fmt1) - o.CurrencyPairs.Store(asset.Futures, fmt1) + + swap := currency.PairStore{ + RequestFormat: ¤cy.PairFormat{ + Uppercase: true, + Delimiter: currency.DashDelimiter, + }, + ConfigFormat: ¤cy.PairFormat{ + Uppercase: true, + Delimiter: currency.UnderscoreDelimiter, + }, + } + + err := o.StoreAssetPairFormat(asset.PerpetualSwap, swap) + if err != nil { + log.Errorln(log.ExchangeSys, err) + } + + err = o.StoreAssetPairFormat(asset.Futures, futures) + if err != nil { + log.Errorln(log.ExchangeSys, err) + } index := currency.PairStore{ RequestFormat: ¤cy.PairFormat{ Uppercase: true, - Delimiter: delimiterDash, + Delimiter: currency.DashDelimiter, }, ConfigFormat: ¤cy.PairFormat{ Uppercase: true, @@ -96,15 +102,23 @@ func (o *OKEX) SetDefaults() { spot := currency.PairStore{ RequestFormat: ¤cy.PairFormat{ Uppercase: true, - Delimiter: delimiterDash, + Delimiter: currency.DashDelimiter, }, ConfigFormat: ¤cy.PairFormat{ Uppercase: true, - Delimiter: delimiterDash, + Delimiter: currency.DashDelimiter, }, } - o.CurrencyPairs.Store(asset.Spot, spot) - o.CurrencyPairs.Store(asset.Index, index) + + err = o.StoreAssetPairFormat(asset.Spot, spot) + if err != nil { + log.Errorln(log.ExchangeSys, err) + } + + err = o.StoreAssetPairFormat(asset.Index, index) + if err != nil { + log.Errorln(log.ExchangeSys, err) + } o.Features = exchange.Features{ Supports: exchange.FeaturesSupported{ @@ -184,7 +198,7 @@ func (o *OKEX) SetDefaults() { o.API.Endpoints.URLDefault = okExAPIURL o.API.Endpoints.URL = okExAPIURL o.API.Endpoints.WebsocketURL = OkExWebsocketURL - o.Websocket = wshandler.New() + o.Websocket = stream.New() o.APIVersion = okExAPIVersion o.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit o.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout @@ -210,24 +224,57 @@ func (o *OKEX) Run() { o.API.Endpoints.WebsocketURL) } - delim := o.GetPairFormat(asset.Spot, false).Delimiter - forceUpdate := false - if !common.StringDataContains(o.GetEnabledPairs(asset.Spot).Strings(), delim) || - !common.StringDataContains(o.GetAvailablePairs(asset.Spot).Strings(), delim) { - forceUpdate = true - enabledPairs := currency.NewPairsFromStrings( - []string{currency.BTC.String() + delim + currency.USDT.String()}, - ) - log.Warnf(log.ExchangeSys, - "Enabled pairs for %v reset due to config upgrade, please enable the ones you would like again.", - o.Name) + format, err := o.GetPairFormat(asset.Spot, false) + if err != nil { + log.Errorf(log.ExchangeSys, + "%s failed to update tradable pairs. Err: %s", + o.Name, + err) + return + } - err := o.UpdatePairs(enabledPairs, asset.Spot, true, forceUpdate) + forceUpdate := false + enabled, err := o.GetEnabledPairs(asset.Spot) + if err != nil { + log.Errorf(log.ExchangeSys, + "%s failed to update tradable pairs. Err: %s", + o.Name, + err) + return + } + + avail, err := o.GetAvailablePairs(asset.Spot) + if err != nil { + log.Errorf(log.ExchangeSys, + "%s failed to update tradable pairs. Err: %s", + o.Name, + err) + return + } + + if !common.StringDataContains(enabled.Strings(), format.Delimiter) || + !common.StringDataContains(avail.Strings(), format.Delimiter) { + forceUpdate = true + var p currency.Pairs + p, err = currency.NewPairsFromStrings([]string{currency.BTC.String() + + format.Delimiter + + currency.USDT.String()}) if err != nil { log.Errorf(log.ExchangeSys, "%s failed to update currencies.\n", o.Name) - return + } else { + log.Warnf(log.ExchangeSys, + "Enabled pairs for %v reset due to config upgrade, please enable the ones you would like again.", + o.Name) + + err = o.UpdatePairs(p, asset.Spot, true, forceUpdate) + if err != nil { + log.Errorf(log.ExchangeSys, + "%s failed to update currencies.\n", + o.Name) + return + } } } @@ -235,7 +282,7 @@ func (o *OKEX) Run() { return } - err := o.UpdateTradablePairs(forceUpdate) + err = o.UpdateTradablePairs(forceUpdate) if err != nil { log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", @@ -247,6 +294,12 @@ func (o *OKEX) Run() { // FetchTradablePairs returns a list of the exchanges tradable pairs func (o *OKEX) FetchTradablePairs(i asset.Item) ([]string, error) { var pairs []string + + format, err := o.GetPairFormat(i, false) + if err != nil { + return nil, err + } + switch i { case asset.Spot: prods, err := o.GetSpotTokenPairDetails() @@ -258,7 +311,7 @@ func (o *OKEX) FetchTradablePairs(i asset.Item) ([]string, error) { pairs = append(pairs, currency.NewPairWithDelimiter(prods[x].BaseCurrency, prods[x].QuoteCurrency, - o.GetPairFormat(i, false).Delimiter).String()) + format.Delimiter).String()) } return pairs, nil case asset.Futures: @@ -268,9 +321,8 @@ func (o *OKEX) FetchTradablePairs(i asset.Item) ([]string, error) { } for x := range prods { - p := strings.Split(prods[x].InstrumentID, delimiterDash) - pairs = append(pairs, - p[0]+delimiterDash+p[1]+o.GetPairFormat(i, false).Delimiter+p[2]) + p := strings.Split(prods[x].InstrumentID, currency.DashDelimiter) + pairs = append(pairs, p[0]+currency.DashDelimiter+p[1]+format.Delimiter+p[2]) } return pairs, nil @@ -283,9 +335,9 @@ func (o *OKEX) FetchTradablePairs(i asset.Item) ([]string, error) { for x := range prods { pairs = append(pairs, prods[x].UnderlyingIndex+ - delimiterDash+ + currency.DashDelimiter+ prods[x].QuoteCurrency+ - o.GetPairFormat(i, false).Delimiter+ + format.Delimiter+ "SWAP") } return pairs, nil @@ -300,34 +352,62 @@ func (o *OKEX) FetchTradablePairs(i asset.Item) ([]string, error) { // UpdateTradablePairs updates the exchanges available pairs and stores // them in the exchanges config func (o *OKEX) UpdateTradablePairs(forceUpdate bool) error { - for x := range o.CurrencyPairs.AssetTypes { - if o.CurrencyPairs.AssetTypes[x] == asset.Index { + assets := o.CurrencyPairs.GetAssetTypes() + for x := range assets { + if assets[x] == asset.Index { // Update from futures continue } - pairs, err := o.FetchTradablePairs(o.CurrencyPairs.AssetTypes[x]) + pairs, err := o.FetchTradablePairs(assets[x]) if err != nil { return err } - if o.CurrencyPairs.AssetTypes[x] == asset.Futures { + if assets[x] == asset.Futures { var indexPairs []string + var futuresContracts []string for i := range pairs { - indexPairs = append(indexPairs, - strings.Split(pairs[i], delimiterUnderscore)[0]) + item := strings.Split(pairs[i], currency.UnderscoreDelimiter)[0] + futuresContracts = append(futuresContracts, pairs[i]) + if common.StringDataContains(indexPairs, item) { + continue + } + indexPairs = append(indexPairs, item) } - err = o.UpdatePairs(currency.NewPairsFromStrings(indexPairs), - asset.Index, - false, - forceUpdate) + var indexPair currency.Pairs + indexPair, err = currency.NewPairsFromStrings(indexPairs) if err != nil { return err } + + err = o.UpdatePairs(indexPair, asset.Index, false, forceUpdate) + if err != nil { + return err + } + + var futurePairs currency.Pairs + for i := range futuresContracts { + var c currency.Pair + c, err = currency.NewPairDelimiter(futuresContracts[i], currency.UnderscoreDelimiter) + if err != nil { + return err + } + futurePairs = append(futurePairs, c) + } + + err = o.UpdatePairs(futurePairs, asset.Futures, false, forceUpdate) + if err != nil { + return err + } + continue + } + p, err := currency.NewPairsFromStrings(pairs) + if err != nil { + return err } - err = o.UpdatePairs(currency.NewPairsFromStrings(pairs), - o.CurrencyPairs.AssetTypes[x], false, forceUpdate) + err = o.UpdatePairs(p, assets[x], false, forceUpdate) if err != nil { return err } @@ -344,25 +424,32 @@ func (o *OKEX) UpdateTicker(p currency.Pair, assetType asset.Item) (*ticker.Pric if err != nil { return tickerPrice, err } + + enabled, err := o.GetEnabledPairs(asset.Spot) + if err != nil { + return nil, err + } + for j := range resp { - if !o.GetEnabledPairs(assetType).Contains(resp[j].InstrumentID, true) { + if !enabled.Contains(resp[j].InstrumentID, true) { continue } - tickerPrice = &ticker.Price{ - Last: resp[j].Last, - High: resp[j].High24h, - Low: resp[j].Low24h, - Bid: resp[j].BestBid, - Ask: resp[j].BestAsk, - Volume: resp[j].BaseVolume24h, - QuoteVolume: resp[j].QuoteVolume24h, - Open: resp[j].Open24h, - Pair: resp[j].InstrumentID, - LastUpdated: resp[j].Timestamp, - } - err = ticker.ProcessTicker(o.Name, tickerPrice, assetType) + + err = ticker.ProcessTicker(&ticker.Price{ + Last: resp[j].Last, + High: resp[j].High24h, + Low: resp[j].Low24h, + Bid: resp[j].BestBid, + Ask: resp[j].BestAsk, + Volume: resp[j].BaseVolume24h, + QuoteVolume: resp[j].QuoteVolume24h, + Open: resp[j].Open24h, + Pair: resp[j].InstrumentID, + LastUpdated: resp[j].Timestamp, + ExchangeName: o.Name, + AssetType: assetType}) if err != nil { - log.Error(log.Ticker, err) + return nil, err } } @@ -372,27 +459,33 @@ func (o *OKEX) UpdateTicker(p currency.Pair, assetType asset.Item) (*ticker.Pric return nil, err } + enabled, err := o.GetEnabledPairs(asset.PerpetualSwap) + if err != nil { + return nil, err + } + for j := range resp { - p := strings.Split(resp[j].InstrumentID, delimiterDash) - nC := currency.NewPairWithDelimiter(p[0]+delimiterDash+p[1], + p := strings.Split(resp[j].InstrumentID, currency.DashDelimiter) + nC := currency.NewPairWithDelimiter(p[0]+currency.DashDelimiter+p[1], p[2], - delimiterUnderscore) - if !o.GetEnabledPairs(assetType).Contains(nC, true) { + currency.UnderscoreDelimiter) + if !enabled.Contains(nC, true) { continue } - tickerPrice = &ticker.Price{ - Last: resp[j].Last, - High: resp[j].High24H, - Low: resp[j].Low24H, - Bid: resp[j].BestBid, - Ask: resp[j].BestAsk, - Volume: resp[j].Volume24H, - Pair: nC, - LastUpdated: resp[j].Timestamp, - } - err = ticker.ProcessTicker(o.Name, tickerPrice, assetType) + + err = ticker.ProcessTicker(&ticker.Price{ + Last: resp[j].Last, + High: resp[j].High24H, + Low: resp[j].Low24H, + Bid: resp[j].BestBid, + Ask: resp[j].BestAsk, + Volume: resp[j].Volume24H, + Pair: nC, + LastUpdated: resp[j].Timestamp, + ExchangeName: o.Name, + AssetType: assetType}) if err != nil { - log.Error(log.Ticker, err) + return nil, err } } @@ -402,27 +495,33 @@ func (o *OKEX) UpdateTicker(p currency.Pair, assetType asset.Item) (*ticker.Pric return nil, err } + enabled, err := o.GetEnabledPairs(asset.Futures) + if err != nil { + return nil, err + } + for j := range resp { - p := strings.Split(resp[j].InstrumentID, delimiterDash) - nC := currency.NewPairWithDelimiter(p[0]+delimiterDash+p[1], + p := strings.Split(resp[j].InstrumentID, currency.DashDelimiter) + nC := currency.NewPairWithDelimiter(p[0]+currency.DashDelimiter+p[1], p[2], - delimiterUnderscore) - if !o.GetEnabledPairs(assetType).Contains(nC, true) { + currency.UnderscoreDelimiter) + if !enabled.Contains(nC, true) { continue } - tickerPrice = &ticker.Price{ - Last: resp[j].Last, - High: resp[j].High24h, - Low: resp[j].Low24h, - Bid: resp[j].BestBid, - Ask: resp[j].BestAsk, - Volume: resp[j].Volume24h, - Pair: nC, - LastUpdated: resp[j].Timestamp, - } - err = ticker.ProcessTicker(o.Name, tickerPrice, assetType) + + err = ticker.ProcessTicker(&ticker.Price{ + Last: resp[j].Last, + High: resp[j].High24h, + Low: resp[j].Low24h, + Bid: resp[j].BestBid, + Ask: resp[j].BestAsk, + Volume: resp[j].Volume24h, + Pair: nC, + LastUpdated: resp[j].Timestamp, + ExchangeName: o.Name, + AssetType: assetType}) if err != nil { - log.Error(log.Ticker, err) + return nil, err } } } @@ -450,12 +549,17 @@ func (o *OKEX) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end t } } + formattedPair, err := o.FormatExchangeCurrency(pair, a) + if err != nil { + return kline.Item{}, err + } + req := &okgroup.GetMarketDataRequest{ Asset: a, Start: start.UTC().Format(time.RFC3339), End: end.UTC().Format(time.RFC3339), Granularity: o.FormatExchangeKlineInterval(interval), - InstrumentID: o.FormatExchangeCurrency(pair, a).String(), + InstrumentID: formattedPair.String(), } candles, err := o.GetMarketData(req) @@ -527,13 +631,17 @@ func (o *OKEX) GetHistoricCandlesExtended(pair currency.Pair, a asset.Item, star } dates := kline.CalcDateRanges(start, end, interval, o.Features.Enabled.Kline.ResultLimit) + formattedPair, err := o.FormatExchangeCurrency(pair, a) + if err != nil { + return kline.Item{}, err + } for x := range dates { req := &okgroup.GetMarketDataRequest{ Asset: a, Start: dates[x].Start.UTC().Format(time.RFC3339), End: dates[x].End.UTC().Format(time.RFC3339), Granularity: o.FormatExchangeKlineInterval(interval), - InstrumentID: o.FormatExchangeCurrency(pair, a).String(), + InstrumentID: formattedPair.String(), } candles, err := o.GetMarketData(req) diff --git a/exchanges/okgroup/okgroup.go b/exchanges/okgroup/okgroup.go index c0eb4c1d..9137f9c0 100644 --- a/exchanges/okgroup/okgroup.go +++ b/exchanges/okgroup/okgroup.go @@ -18,7 +18,6 @@ import ( exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/request" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" "github.com/thrasher-corp/gocryptotrader/log" ) @@ -89,8 +88,7 @@ var errMissValue = errors.New("warning - resp value is missing from exchange") // OKGroup is the overaching type across the all of OKEx's exchange methods type OKGroup struct { exchange.Base - ExchangeName string - WebsocketConn *wshandler.WebsocketConnection + ExchangeName string // Spot and contract market error codes as per https://www.okex.com/rest_request.html ErrorCodes map[string]error // Stores for corresponding variable checks diff --git a/exchanges/okgroup/okgroup_types.go b/exchanges/okgroup/okgroup_types.go index 3f70b66d..66d6a611 100644 --- a/exchanges/okgroup/okgroup_types.go +++ b/exchanges/okgroup/okgroup_types.go @@ -1317,19 +1317,22 @@ type WebsocketDataResponse struct { type WebsocketTickerData struct { Table string `json:"table"` Data []struct { - BaseVolume24h float64 `json:"base_volume_24h,string"` - BestAsk float64 `json:"best_ask,string"` - BestAskSize float64 `json:"best_ask_size,string"` - BestBid float64 `json:"best_bid,string"` - BestBidSize float64 `json:"best_bid_size,string"` - High24h float64 `json:"high_24h,string"` - InstrumentID string `json:"instrument_id"` - Last float64 `json:"last,string"` - LastQty float64 `json:"last_qty,string"` - Low24h float64 `json:"low_24h,string"` - Open24h float64 `json:"open_24h,string"` - QuoteVolume24h float64 `json:"quote_volume_24h,string"` - Timestamp time.Time `json:"timestamp"` + BaseVolume24h float64 `json:"base_volume_24h,string"` + BestAsk float64 `json:"best_ask,string"` + BestAskSize float64 `json:"best_ask_size,string"` + BestBid float64 `json:"best_bid,string"` + BestBidSize float64 `json:"best_bid_size,string"` + High24h float64 `json:"high_24h,string"` + InstrumentID string `json:"instrument_id"` + Last float64 `json:"last,string"` + LastQty float64 `json:"last_qty,string"` + Low24h float64 `json:"low_24h,string"` + Open24h float64 `json:"open_24h,string"` + QuoteVolume24h float64 `json:"quote_volume_24h,string"` + Timestamp time.Time `json:"timestamp"` + ContractVolume24h float64 `json:"volume_24h,string"` + TokenVolume24h float64 `json:"volume_token_24h,string"` + OpenInterest float64 `json:"open_interest,string"` } `json:"data"` } @@ -1343,6 +1346,8 @@ type WebsocketTradeResponse struct { Side string `json:"side"` Timestamp time.Time `json:"timestamp"` TradeID string `json:"trade_id"` + // Quantity - Futures amount is sent as a separate json field + Quantity float64 `json:"qty,string"` } `json:"data"` } diff --git a/exchanges/okgroup/okgroup_websocket.go b/exchanges/okgroup/okgroup_websocket.go index e2a69298..9e97877b 100644 --- a/exchanges/okgroup/okgroup_websocket.go +++ b/exchanges/okgroup/okgroup_websocket.go @@ -18,9 +18,9 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream/buffer" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook" "github.com/thrasher-corp/gocryptotrader/log" ) @@ -147,6 +147,8 @@ const ( delimiterColon = ":" delimiterDash = "-" delimiterUnderscore = "_" + + maxConnByteLen = 4096 ) // orderbookMutex Ensures if two entries arrive at once, only one can be @@ -176,10 +178,12 @@ var defaultSwapSubscribedChannels = []string{okGroupWsSwapDepth, // WsConnect initiates a websocket connection func (o *OKGroup) WsConnect() error { if !o.Websocket.IsEnabled() || !o.IsEnabled() { - return errors.New(wshandler.WebsocketNotEnabled) + return errors.New(stream.WebsocketNotEnabled) } var dialer websocket.Dialer - err := o.WebsocketConn.Dial(&dialer, http.Header{}) + dialer.ReadBufferSize = 8192 + dialer.WriteBufferSize = 8192 + err := o.Websocket.Conn.Dial(&dialer, http.Header{}) if err != nil { return err } @@ -187,9 +191,8 @@ func (o *OKGroup) WsConnect() error { log.Debugf(log.ExchangeSys, "Successful connection to %v\n", o.Websocket.GetWebsocketURL()) } - wg := sync.WaitGroup{} - wg.Add(1) - go o.WsReadData(&wg) + + go o.WsReadData() if o.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) { err = o.WsLogin() if err != nil { @@ -200,10 +203,11 @@ func (o *OKGroup) WsConnect() error { } } - o.GenerateDefaultSubscriptions() - // Ensures that we start the routines and we dont race when shutdown occurs - wg.Wait() - return nil + subs, err := o.GenerateDefaultSubscriptions() + if err != nil { + return err + } + return o.Websocket.SubscribeToChannels(subs) } // WsLogin sends a login request to websocket to enable access to authenticated endpoints @@ -225,7 +229,7 @@ func (o *OKGroup) WsLogin() error { base64, }, } - err := o.WebsocketConn.SendJSONMessage(request) + _, err := o.Websocket.Conn.SendMessageReturnResponse("login", request) if err != nil { o.Websocket.SetCanUseAuthenticatedEndpoints(false) return err @@ -234,28 +238,18 @@ func (o *OKGroup) WsLogin() error { } // WsReadData receives and passes on websocket messages for processing -func (o *OKGroup) WsReadData(wg *sync.WaitGroup) { +func (o *OKGroup) WsReadData() { o.Websocket.Wg.Add(1) - defer func() { - o.Websocket.Wg.Done() - }() - wg.Done() + defer o.Websocket.Wg.Done() for { - select { - case <-o.Websocket.ShutdownC: + resp := o.Websocket.Conn.ReadMessage() + if resp.Raw == nil { return - default: - resp, err := o.WebsocketConn.ReadMessage() - if err != nil { - o.Websocket.ReadMessageErrors <- err - return - } - o.Websocket.TrafficAlert <- struct{}{} - err = o.WsHandleData(resp.Raw) - if err != nil { - o.Websocket.DataHandler <- err - } + } + err := o.WsHandleData(resp.Raw) + if err != nil { + o.Websocket.DataHandler <- err } } } @@ -283,7 +277,9 @@ func (o *OKGroup) WsHandleData(respRaw []byte) error { case okGroupWsOrder: return o.wsProcessOrder(respRaw) } - o.Websocket.DataHandler <- wshandler.UnhandledMessageWarning{Message: o.Name + wshandler.UnhandledMessage + string(respRaw)} + o.Websocket.DataHandler <- stream.UnhandledMessageWarning{ + Message: o.Name + stream.UnhandledMessage + string(respRaw), + } return nil } @@ -299,7 +295,9 @@ func (o *OKGroup) WsHandleData(respRaw []byte) error { err = json.Unmarshal(respRaw, &eventResponse) if err == nil && eventResponse.Event != "" { if eventResponse.Event == "login" { - o.Websocket.SetCanUseAuthenticatedEndpoints(eventResponse.Success) + if o.Websocket.Match.Incoming("login") { + o.Websocket.SetCanUseAuthenticatedEndpoints(eventResponse.Success) + } } if o.Verbose { log.Debug(log.ExchangeSys, @@ -365,6 +363,16 @@ func (o *OKGroup) wsProcessOrder(respRaw []byte) error { Err: err, } } + + pair, err := currency.NewPairFromString(resp.Data[i].InstrumentID) + if err != nil { + o.Websocket.DataHandler <- order.ClassificationError{ + Exchange: o.Name, + OrderID: resp.Data[i].OrderID, + Err: err, + } + } + o.Websocket.DataHandler <- &order.Detail{ ImmediateOrCancel: resp.Data[i].OrderType == 3, FillOrKill: resp.Data[i].OrderType == 2, @@ -380,7 +388,7 @@ func (o *OKGroup) wsProcessOrder(respRaw []byte) error { Status: oStatus, AssetType: o.GetAssetTypeFromTableName(resp.Table), Date: resp.Data[i].CreatedAt, - Pair: currency.NewPairFromString(resp.Data[i].InstrumentID), + Pair: pair, } } return nil @@ -393,23 +401,36 @@ func (o *OKGroup) wsProcessTickers(respRaw []byte) error { if err != nil { return err } + a := o.GetAssetTypeFromTableName(response.Table) for i := range response.Data { - a := o.GetAssetTypeFromTableName(response.Table) + f := strings.Split(response.Data[i].InstrumentID, delimiterDash) + var c currency.Pair switch a { case asset.Futures, asset.PerpetualSwap: - f := strings.Split(response.Data[i].InstrumentID, delimiterDash) - c = currency.NewPairWithDelimiter(f[0]+delimiterDash+f[1], f[2], delimiterUnderscore) + c = currency.NewPairWithDelimiter(f[0]+delimiterDash+f[1], + f[2], + delimiterUnderscore) default: - f := strings.Split(response.Data[i].InstrumentID, delimiterDash) c = currency.NewPairWithDelimiter(f[0], f[1], delimiterDash) } + + baseVolume := response.Data[i].BaseVolume24h + if response.Data[i].ContractVolume24h != 0 { + baseVolume = response.Data[i].ContractVolume24h + } + + quoteVolume := response.Data[i].QuoteVolume24h + if response.Data[i].TokenVolume24h != 0 { + quoteVolume = response.Data[i].TokenVolume24h + } + o.Websocket.DataHandler <- &ticker.Price{ ExchangeName: o.Name, Open: response.Data[i].Open24h, Close: response.Data[i].Last, - Volume: response.Data[i].BaseVolume24h, - QuoteVolume: response.Data[i].QuoteVolume24h, + Volume: baseVolume, + QuoteVolume: quoteVolume, High: response.Data[i].High24h, Low: response.Data[i].Low24h, Bid: response.Data[i].BestBid, @@ -430,17 +451,21 @@ func (o *OKGroup) wsProcessTrades(respRaw []byte) error { if err != nil { return err } + + a := o.GetAssetTypeFromTableName(response.Table) for i := range response.Data { - a := o.GetAssetTypeFromTableName(response.Table) + f := strings.Split(response.Data[i].InstrumentID, delimiterDash) + var c currency.Pair switch a { case asset.Futures, asset.PerpetualSwap: - f := strings.Split(response.Data[i].InstrumentID, delimiterDash) - c = currency.NewPairWithDelimiter(f[0]+delimiterDash+f[1], f[2], delimiterUnderscore) + c = currency.NewPairWithDelimiter(f[0]+delimiterDash+f[1], + f[2], + delimiterUnderscore) default: - f := strings.Split(response.Data[i].InstrumentID, delimiterDash) c = currency.NewPairWithDelimiter(f[0], f[1], delimiterDash) } + tSide, err := order.StringToOrderSide(response.Data[i].Side) if err != nil { o.Websocket.DataHandler <- order.ClassificationError{ @@ -448,8 +473,14 @@ func (o *OKGroup) wsProcessTrades(respRaw []byte) error { Err: err, } } - o.Websocket.DataHandler <- wshandler.TradeData{ - Amount: response.Data[i].Size, + + amount := response.Data[i].Size + if response.Data[i].Quantity != 0 { + amount = response.Data[i].Quantity + } + + o.Websocket.DataHandler <- stream.TradeData{ + Amount: amount, AssetType: o.GetAssetTypeFromTableName(response.Table), CurrencyPair: c, Exchange: o.Name, @@ -468,15 +499,18 @@ func (o *OKGroup) wsProcessCandles(respRaw []byte) error { if err != nil { return err } + + a := o.GetAssetTypeFromTableName(response.Table) for i := range response.Data { - a := o.GetAssetTypeFromTableName(response.Table) + f := strings.Split(response.Data[i].InstrumentID, delimiterDash) + var c currency.Pair switch a { case asset.Futures, asset.PerpetualSwap: - f := strings.Split(response.Data[i].InstrumentID, delimiterDash) - c = currency.NewPairWithDelimiter(f[0]+delimiterDash+f[1], f[2], delimiterUnderscore) + c = currency.NewPairWithDelimiter(f[0]+delimiterDash+f[1], + f[2], + delimiterUnderscore) default: - f := strings.Split(response.Data[i].InstrumentID, delimiterDash) c = currency.NewPairWithDelimiter(f[0], f[1], delimiterDash) } @@ -489,13 +523,9 @@ func (o *OKGroup) wsProcessCandles(respRaw []byte) error { } candleIndex := strings.LastIndex(response.Table, okGroupWsCandle) - secondIndex := strings.LastIndex(response.Table, "0s") - candleInterval := "" - if candleIndex > 0 || secondIndex > 0 { - candleInterval = response.Table[candleIndex+len(okGroupWsCandle) : secondIndex] - } + candleInterval := response.Table[candleIndex+len(okGroupWsCandle):] - klineData := wshandler.KlineData{ + klineData := stream.KlineData{ AssetType: o.GetAssetTypeFromTableName(response.Table), Pair: c, Exchange: o.Name, @@ -522,7 +552,6 @@ func (o *OKGroup) wsProcessCandles(respRaw []byte) error { if err != nil { return err } - o.Websocket.DataHandler <- klineData } return nil @@ -537,22 +566,27 @@ func (o *OKGroup) WsProcessOrderBook(respRaw []byte) error { } orderbookMutex.Lock() defer orderbookMutex.Unlock() + a := o.GetAssetTypeFromTableName(response.Table) for i := range response.Data { - a := o.GetAssetTypeFromTableName(response.Table) + f := strings.Split(response.Data[i].InstrumentID, delimiterDash) + var c currency.Pair switch a { case asset.Futures, asset.PerpetualSwap: - f := strings.Split(response.Data[i].InstrumentID, delimiterDash) - c = currency.NewPairWithDelimiter(f[0]+delimiterDash+f[1], f[2], delimiterUnderscore) + c = currency.NewPairWithDelimiter(f[0]+delimiterDash+f[1], + f[2], + delimiterUnderscore) default: - f := strings.Split(response.Data[i].InstrumentID, delimiterDash) c = currency.NewPairWithDelimiter(f[0], f[1], delimiterDash) } if response.Action == okGroupWsOrderbookPartial { err := o.WsProcessPartialOrderBook(&response.Data[i], c, a) if err != nil { - o.wsResubscribeToOrderbook(&response) + err2 := o.wsResubscribeToOrderbook(&response) + if err2 != nil { + o.Websocket.DataHandler <- err2 + } return err } } else if response.Action == okGroupWsOrderbookUpdate { @@ -561,7 +595,10 @@ func (o *OKGroup) WsProcessOrderBook(respRaw []byte) error { } err := o.WsProcessUpdateOrderbook(&response.Data[i], c, a) if err != nil { - o.wsResubscribeToOrderbook(&response) + err2 := o.wsResubscribeToOrderbook(&response) + if err2 != nil { + o.Websocket.DataHandler <- err2 + } return err } } @@ -569,28 +606,34 @@ func (o *OKGroup) WsProcessOrderBook(respRaw []byte) error { return nil } -func (o *OKGroup) wsResubscribeToOrderbook(response *WebsocketOrderBooksData) { +func (o *OKGroup) wsResubscribeToOrderbook(response *WebsocketOrderBooksData) error { + a := o.GetAssetTypeFromTableName(response.Table) for i := range response.Data { - a := o.GetAssetTypeFromTableName(response.Table) + f := strings.Split(response.Data[i].InstrumentID, delimiterDash) + var c currency.Pair switch a { case asset.Futures, asset.PerpetualSwap: - f := strings.Split(response.Data[i].InstrumentID, delimiterDash) c = currency.NewPairWithDelimiter(f[0]+delimiterDash+f[1], f[2], delimiterDash) default: - f := strings.Split(response.Data[i].InstrumentID, delimiterDash) c = currency.NewPairWithDelimiter(f[0], f[1], delimiterDash) } - channelToResubscribe := wshandler.WebsocketChannelSubscription{ + channelToResubscribe := &stream.ChannelSubscription{ Channel: response.Table, Currency: c, + Asset: a, + } + err := o.Websocket.ResubscribeToChannel(channelToResubscribe) + if err != nil { + return fmt.Errorf("%s resubscribe to orderbook error %s", o.Name, err) } - o.Websocket.ResubscribeToChannel(channelToResubscribe) } + return nil } -// AppendWsOrderbookItems adds websocket orderbook data bid/asks into an orderbook item array +// AppendWsOrderbookItems adds websocket orderbook data bid/asks into an +// orderbook item array func (o *OKGroup) AppendWsOrderbookItems(entries [][]interface{}) ([]orderbook.Item, error) { var items []orderbook.Item for j := range entries { @@ -607,8 +650,8 @@ func (o *OKGroup) AppendWsOrderbookItems(entries [][]interface{}) ([]orderbook.I return items, nil } -// WsProcessPartialOrderBook takes websocket orderbook data and creates an orderbook -// Calculates checksum to ensure it is valid +// WsProcessPartialOrderBook takes websocket orderbook data and creates an +// orderbook Calculates checksum to ensure it is valid func (o *OKGroup) WsProcessPartialOrderBook(wsEventData *WebsocketOrderBook, instrument currency.Pair, a asset.Item) error { signedChecksum := o.CalculatePartialOrderbookChecksum(wsEventData) if signedChecksum != wsEventData.Checksum { @@ -642,24 +685,14 @@ func (o *OKGroup) WsProcessPartialOrderBook(wsEventData *WebsocketOrderBook, ins Pair: instrument, ExchangeName: o.Name, } - - err = o.Websocket.Orderbook.LoadSnapshot(&newOrderBook) - if err != nil { - return err - } - - o.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{ - Exchange: o.Name, - Asset: a, - Pair: instrument, - } - return nil + return o.Websocket.Orderbook.LoadSnapshot(&newOrderBook) } // WsProcessUpdateOrderbook updates an existing orderbook using websocket data -// After merging WS data, it will sort, validate and finally update the existing orderbook +// After merging WS data, it will sort, validate and finally update the existing +// orderbook func (o *OKGroup) WsProcessUpdateOrderbook(wsEventData *WebsocketOrderBook, instrument currency.Pair, a asset.Item) error { - update := wsorderbook.WebsocketOrderbookUpdate{ + update := buffer.Update{ Asset: a, Pair: instrument, UpdateTime: wsEventData.Timestamp, @@ -690,13 +723,6 @@ func (o *OKGroup) WsProcessUpdateOrderbook(wsEventData *WebsocketOrderBook, inst wsEventData.InstrumentID) return errors.New("checksum failed") } - - o.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{ - Exchange: o.Name, - Asset: a, - Pair: instrument, - } - return nil } @@ -750,95 +776,134 @@ func (o *OKGroup) CalculateUpdateOrderbookChecksum(orderbookData *orderbook.Base // GenerateDefaultSubscriptions Adds default subscriptions to websocket to be // handled by ManageSubscriptions() -func (o *OKGroup) GenerateDefaultSubscriptions() { - var subscriptions []wshandler.WebsocketChannelSubscription +func (o *OKGroup) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, error) { + var subscriptions []stream.ChannelSubscription assets := o.GetAssetTypes() for x := range assets { - enabledCurrencies := o.GetEnabledPairs(assets[x]) - if len(enabledCurrencies) == 0 { - continue + pairs, err := o.GetEnabledPairs(assets[x]) + if err != nil { + return nil, err } switch assets[x] { case asset.Spot: - for i := range enabledCurrencies { - for y := range defaultSpotSubscribedChannels { - subscriptions = append(subscriptions, - wshandler.WebsocketChannelSubscription{ - Channel: defaultSpotSubscribedChannels[y], - Currency: o.FormatExchangeCurrency(enabledCurrencies[i], - asset.Spot), - }) - } + channels := defaultSpotSubscribedChannels + if o.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) { + channels = append(channels, + okGroupWsSpotMarginAccount, + okGroupWsSpotAccount, + okGroupWsSpotOrder) } - if o.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) { - subscriptions = append(subscriptions, - wshandler.WebsocketChannelSubscription{ - Channel: okGroupWsSpotMarginAccount, - }, - wshandler.WebsocketChannelSubscription{ - Channel: okGroupWsSpotAccount, - }, - wshandler.WebsocketChannelSubscription{ - Channel: okGroupWsSpotOrder, - }) + for i := range pairs { + p, err := o.FormatExchangeCurrency(pairs[i], asset.Spot) + if err != nil { + return nil, err + } + for y := range channels { + subscriptions = append(subscriptions, + stream.ChannelSubscription{ + Channel: channels[y], + Currency: p, + Asset: asset.Spot, + }) + } } case asset.Futures: - for i := range enabledCurrencies { - for y := range defaultFuturesSubscribedChannels { + channels := defaultFuturesSubscribedChannels + if o.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) { + channels = append(channels, + okGroupWsFuturesAccount, + okGroupWsFuturesPosition, + okGroupWsFuturesOrder) + } + var futuresAccountPairs currency.Pairs + var futuresAccountCodes currency.Currencies + + for i := range pairs { + p, err := o.FormatExchangeCurrency(pairs[i], asset.Futures) + if err != nil { + return nil, err + } + for y := range channels { + if channels[y] == okGroupWsFuturesAccount { + currencyString := strings.Split(pairs[i].String(), + currency.UnderscoreDelimiter)[0] + newP, err := currency.NewPairFromString(currencyString) + if err != nil { + return nil, err + } + + if !futuresAccountCodes.Contains(newP.Base) { + // subscribe to coin-margin futures trading mode + subscriptions = append(subscriptions, + stream.ChannelSubscription{ + Channel: channels[y], + Currency: currency.NewPair(newP.Base, currency.Code{}), + Asset: asset.Futures, + }) + futuresAccountCodes = append(futuresAccountCodes, newP.Base) + } + + if newP.Quote != currency.USDT { + // Only allows subscription to USDT margined pair + continue + } + + if !futuresAccountPairs.Contains(newP, true) { + subscriptions = append(subscriptions, + stream.ChannelSubscription{ + Channel: channels[y], + Currency: newP, + Asset: asset.Futures, + }) + futuresAccountPairs = futuresAccountPairs.Add(newP) + } + + continue + } subscriptions = append(subscriptions, - wshandler.WebsocketChannelSubscription{ - Channel: defaultFuturesSubscribedChannels[y], - Currency: o.FormatExchangeCurrency(enabledCurrencies[i], - asset.Futures), + stream.ChannelSubscription{ + Channel: channels[y], + Currency: p, + Asset: asset.Futures, }) } } - - if o.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) { - subscriptions = append(subscriptions, - wshandler.WebsocketChannelSubscription{ - Channel: okGroupWsFuturesAccount, - }, - wshandler.WebsocketChannelSubscription{ - Channel: okGroupWsFuturesPosition, - }, - wshandler.WebsocketChannelSubscription{ - Channel: okGroupWsFuturesOrder, - }) - } case asset.PerpetualSwap: - for i := range enabledCurrencies { - for y := range defaultSwapSubscribedChannels { + channels := defaultSwapSubscribedChannels + if o.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) { + channels = append(channels, + okGroupWsSwapAccount, + okGroupWsSwapPosition, + okGroupWsSwapOrder) + } + for i := range pairs { + p, err := o.FormatExchangeCurrency(pairs[i], asset.PerpetualSwap) + if err != nil { + return nil, err + } + for y := range channels { subscriptions = append(subscriptions, - wshandler.WebsocketChannelSubscription{ - Channel: defaultSwapSubscribedChannels[y], - Currency: o.FormatExchangeCurrency(enabledCurrencies[i], - asset.PerpetualSwap), + stream.ChannelSubscription{ + Channel: channels[y], + Currency: p, + Asset: asset.PerpetualSwap, }) } } - - if o.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) { - subscriptions = append(subscriptions, - wshandler.WebsocketChannelSubscription{ - Channel: okGroupWsSwapAccount, - }, - wshandler.WebsocketChannelSubscription{ - Channel: okGroupWsSwapPosition, - }, - wshandler.WebsocketChannelSubscription{ - Channel: okGroupWsSwapOrder, - }) - } case asset.Index: - for i := range enabledCurrencies { + for i := range pairs { + p, err := o.FormatExchangeCurrency(pairs[i], asset.Index) + if err != nil { + return nil, err + } for y := range defaultIndexSubscribedChannels { subscriptions = append(subscriptions, - wshandler.WebsocketChannelSubscription{ + stream.ChannelSubscription{ Channel: defaultIndexSubscribedChannels[y], - Currency: o.FormatExchangeCurrency(enabledCurrencies[i], asset.Index), + Currency: p, + Asset: asset.Index, }) } } @@ -846,35 +911,84 @@ func (o *OKGroup) GenerateDefaultSubscriptions() { o.Websocket.DataHandler <- errors.New("unhandled asset type") } } - - o.Websocket.SubscribeToChannels(subscriptions) + return subscriptions, nil } // Subscribe sends a websocket message to receive data from the channel -func (o *OKGroup) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error { - c := channelToSubscribe.Currency.String() - request := WebsocketEventRequest{ - Operation: "subscribe", - Arguments: []string{channelToSubscribe.Channel + delimiterColon + c}, - } - if strings.EqualFold(channelToSubscribe.Channel, okGroupWsSpotAccount) { - request.Arguments = []string{channelToSubscribe.Channel + - delimiterColon + - channelToSubscribe.Currency.Base.String()} - } - - return o.WebsocketConn.SendJSONMessage(request) +func (o *OKGroup) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error { + return o.handleSubscriptions("subscribe", channelsToSubscribe) } // Unsubscribe sends a websocket message to stop receiving data from the channel -func (o *OKGroup) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error { +func (o *OKGroup) Unsubscribe(channelsToUnsubscribe []stream.ChannelSubscription) error { + return o.handleSubscriptions("unsubscribe", channelsToUnsubscribe) +} + +func (o *OKGroup) handleSubscriptions(operation string, subs []stream.ChannelSubscription) error { request := WebsocketEventRequest{ - Operation: "unsubscribe", - Arguments: []string{channelToSubscribe.Channel + - delimiterColon + - channelToSubscribe.Currency.String()}, + Operation: operation, } - return o.WebsocketConn.SendJSONMessage(request) + + var channels []stream.ChannelSubscription + for i := 0; i < len(subs); i++ { + // Temp type to evaluate max byte len after a marshal on batched unsubs + temp := WebsocketEventRequest{ + Operation: operation, + } + temp.Arguments = make([]string, len(request.Arguments)) + copy(temp.Arguments, request.Arguments) + + arg := subs[i].Channel + delimiterColon + if strings.EqualFold(subs[i].Channel, okGroupWsSpotAccount) { + arg += subs[i].Currency.Base.String() + } else { + arg += subs[i].Currency.String() + } + + temp.Arguments = append(temp.Arguments, arg) + chunk, err := json.Marshal(request) + if err != nil { + return err + } + + if len(chunk) > maxConnByteLen { + // If temp chunk exceeds max byte length determined by the exchange, + // commit last payload. + i-- // reverse position in range to reuse channel unsubscription on + // next iteration + err = o.Websocket.Conn.SendJSONMessage(request) + if err != nil { + return err + } + + if operation == "unsubscribe" { + o.Websocket.RemoveSuccessfulUnsubscriptions(channels...) + } else { + o.Websocket.AddSuccessfulSubscriptions(channels...) + } + + // Drop prior unsubs and chunked payload args on successful unsubscription + channels = nil + request.Arguments = nil + continue + } + // Add pending chained items + channels = append(channels, subs[i]) + request.Arguments = temp.Arguments + } + + // Commit left overs to payload + err := o.Websocket.Conn.SendJSONMessage(request) + if err != nil { + return err + } + + if operation == "unsubscribe" { + o.Websocket.RemoveSuccessfulUnsubscriptions(channels...) + } else { + o.Websocket.AddSuccessfulSubscriptions(channels...) + } + return nil } // GetWsChannelWithoutOrderType takes WebsocketDataResponse.Table and returns diff --git a/exchanges/okgroup/okgroup_wrapper.go b/exchanges/okgroup/okgroup_wrapper.go index d26ee67a..225cbd77 100644 --- a/exchanges/okgroup/okgroup_wrapper.go +++ b/exchanges/okgroup/okgroup_wrapper.go @@ -15,7 +15,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) @@ -35,7 +35,7 @@ func (o *OKGroup) Setup(exch *config.ExchangeConfig) error { return err } - err = o.Websocket.Setup(&wshandler.WebsocketSetup{ + err = o.Websocket.Setup(&stream.WebsocketSetup{ Enabled: exch.Features.Enabled.Websocket, Verbose: exch.Verbose, AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, @@ -46,30 +46,19 @@ func (o *OKGroup) Setup(exch *config.ExchangeConfig) error { Connector: o.WsConnect, Subscriber: o.Subscribe, UnSubscriber: o.Unsubscribe, + GenerateSubscriptions: o.GenerateDefaultSubscriptions, Features: &o.Features.Supports.WebsocketCapabilities, + OrderbookBufferLimit: exch.WebsocketOrderbookBufferLimit, }) if err != nil { return err } - o.WebsocketConn = &wshandler.WebsocketConnection{ - ExchangeName: o.Name, - URL: o.Websocket.GetWebsocketURL(), - ProxyURL: o.Websocket.GetProxyAddress(), - Verbose: o.Verbose, + return o.Websocket.SetupNewConnection(stream.ConnectionSetup{ RateLimit: okGroupWsRateLimit, ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout, ResponseMaxLimit: exch.WebsocketResponseMaxLimit, - } - - o.Websocket.Orderbook.Setup( - exch.WebsocketOrderbookBufferLimit, - false, - false, - false, - false, - exch.Name) - return nil + }) } // FetchOrderbook returns orderbook base on the currency pair @@ -88,8 +77,13 @@ func (o *OKGroup) UpdateOrderbook(p currency.Pair, a asset.Item) (*orderbook.Bas return orderBook, errors.New("no orderbooks for index") } + fpair, err := o.FormatExchangeCurrency(p, a) + if err != nil { + return nil, err + } + orderbookNew, err := o.GetOrderBook(GetOrderBookRequest{ - InstrumentID: o.FormatExchangeCurrency(p, a).String(), + InstrumentID: fpair.String(), }, a) if err != nil { return orderBook, err @@ -268,15 +262,20 @@ func (o *OKGroup) GetExchangeHistory(p currency.Pair, assetType asset.Item, time } // SubmitOrder submits a new order -func (o *OKGroup) SubmitOrder(s *order.Submit) (resp order.SubmitResponse, err error) { - err = s.Validate() +func (o *OKGroup) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { + err := s.Validate() if err != nil { - return resp, err + return order.SubmitResponse{}, err + } + + fpair, err := o.FormatExchangeCurrency(s.Pair, s.AssetType) + if err != nil { + return order.SubmitResponse{}, err } request := PlaceOrderRequest{ ClientOID: s.ClientID, - InstrumentID: o.FormatExchangeCurrency(s.Pair, asset.Spot).String(), + InstrumentID: fpair.String(), Side: s.Side.Lower(), Type: s.Type.Lower(), Size: strconv.FormatFloat(s.Amount, 'f', -1, 64), @@ -287,15 +286,17 @@ func (o *OKGroup) SubmitOrder(s *order.Submit) (resp order.SubmitResponse, err e orderResponse, err := o.PlaceSpotOrder(&request) if err != nil { - return + return order.SubmitResponse{}, err } + var resp order.SubmitResponse resp.IsOrderPlaced = orderResponse.Result resp.OrderID = orderResponse.OrderID if s.Type == order.Market { resp.FullyMatched = true } - return + + return resp, nil } // ModifyOrder will allow of changing orderbook placement and limit to @@ -310,11 +311,18 @@ func (o *OKGroup) CancelOrder(orderCancellation *order.Cancel) (err error) { if err != nil { return } + + fpair, err := o.FormatExchangeCurrency(orderCancellation.Pair, + orderCancellation.AssetType) + if err != nil { + return + } + orderCancellationResponse, err := o.CancelSpotOrder(CancelSpotOrderRequest{ - InstrumentID: o.FormatExchangeCurrency(orderCancellation.Pair, - asset.Spot).String(), - OrderID: orderID, + InstrumentID: fpair.String(), + OrderID: orderID, }) + if !orderCancellationResponse.Result { err = fmt.Errorf("order %d failed to be cancelled", orderCancellationResponse.OrderID) @@ -324,26 +332,32 @@ func (o *OKGroup) CancelOrder(orderCancellation *order.Cancel) (err error) { } // CancelAllOrders cancels all orders associated with a currency pair -func (o *OKGroup) CancelAllOrders(orderCancellation *order.Cancel) (resp order.CancelAllResponse, err error) { +func (o *OKGroup) CancelAllOrders(orderCancellation *order.Cancel) (order.CancelAllResponse, error) { orderIDs := strings.Split(orderCancellation.ID, ",") + resp := order.CancelAllResponse{} resp.Status = make(map[string]string) var orderIDNumbers []int64 for i := range orderIDs { - orderIDNumber, strConvErr := strconv.ParseInt(orderIDs[i], 10, 64) - if strConvErr != nil { - resp.Status[orderIDs[i]] = strConvErr.Error() + orderIDNumber, err := strconv.ParseInt(orderIDs[i], 10, 64) + if err != nil { + resp.Status[orderIDs[i]] = err.Error() continue } orderIDNumbers = append(orderIDNumbers, orderIDNumber) } + fpair, err := o.FormatExchangeCurrency(orderCancellation.Pair, + orderCancellation.AssetType) + if err != nil { + return resp, err + } + cancelOrdersResponse, err := o.CancelMultipleSpotOrders(CancelMultipleSpotOrdersRequest{ - InstrumentID: o.FormatExchangeCurrency(orderCancellation.Pair, - asset.Spot).String(), - OrderIDs: orderIDNumbers, + InstrumentID: fpair.String(), + OrderIDs: orderIDNumbers, }) if err != nil { - return + return resp, err } for x := range cancelOrdersResponse { @@ -352,7 +366,7 @@ func (o *OKGroup) CancelAllOrders(orderCancellation *order.Cancel) (resp order.C } } - return + return resp, err } // GetOrderInfo returns information on a current open order @@ -361,10 +375,20 @@ func (o *OKGroup) GetOrderInfo(orderID string) (resp order.Detail, err error) { if err != nil { return } + + format, err := o.GetPairFormat(asset.Spot, false) + if err != nil { + return resp, err + } + + p, err := currency.NewPairDelimiter(mOrder.InstrumentID, format.Delimiter) + if err != nil { + return resp, err + } + resp = order.Detail{ - Amount: mOrder.Size, - Pair: currency.NewPairDelimiter(mOrder.InstrumentID, - o.GetPairFormat(asset.Spot, false).Delimiter), + Amount: mOrder.Size, + Pair: p, Exchange: o.Name, Date: mOrder.Timestamp, ExecutedAmount: mOrder.FilledSize, @@ -424,9 +448,12 @@ func (o *OKGroup) WithdrawFiatFundsToInternationalBank(withdrawRequest *withdraw // GetActiveOrders retrieves any orders that are active/open func (o *OKGroup) GetActiveOrders(req *order.GetOrdersRequest) (resp []order.Detail, err error) { for x := range req.Pairs { + fpair, err := o.FormatExchangeCurrency(req.Pairs[x], asset.Spot) + if err != nil { + return nil, err + } spotOpenOrders, err := o.GetSpotOpenOrders(GetSpotOpenOrdersRequest{ - InstrumentID: o.FormatExchangeCurrency(req.Pairs[x], - asset.Spot).String(), + InstrumentID: fpair.String(), }) if err != nil { return resp, err @@ -454,10 +481,13 @@ func (o *OKGroup) GetActiveOrders(req *order.GetOrdersRequest) (resp []order.Det // Can Limit response to specific order status func (o *OKGroup) GetOrderHistory(req *order.GetOrdersRequest) (resp []order.Detail, err error) { for x := range req.Pairs { + fpair, err := o.FormatExchangeCurrency(req.Pairs[x], asset.Spot) + if err != nil { + return nil, err + } spotOpenOrders, err := o.GetSpotOrders(GetSpotOrdersRequest{ - Status: strings.Join([]string{"filled", "cancelled", "failure"}, "|"), - InstrumentID: o.FormatExchangeCurrency(req.Pairs[x], - asset.Spot).String(), + Status: strings.Join([]string{"filled", "cancelled", "failure"}, "|"), + InstrumentID: fpair.String(), }) if err != nil { return resp, err @@ -481,11 +511,6 @@ func (o *OKGroup) GetOrderHistory(req *order.GetOrdersRequest) (resp []order.Det return } -// GetWebsocket returns a pointer to the exchange websocket -func (o *OKGroup) GetWebsocket() (*wshandler.Websocket, error) { - return o.Websocket, nil -} - // GetFeeByType returns an estimate of fee based on type of transaction func (o *OKGroup) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { if !o.AllowAuthenticatedRequest() && // Todo check connection status @@ -500,25 +525,6 @@ func (o *OKGroup) GetWithdrawCapabilities() uint32 { return o.GetWithdrawPermissions() } -// SubscribeToWebsocketChannels appends to ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle subscribing -func (o *OKGroup) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - o.Websocket.SubscribeToChannels(channels) - return nil -} - -// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle unsubscribing -func (o *OKGroup) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - o.Websocket.RemoveSubscribedChannels(channels) - return nil -} - -// GetSubscriptions returns a copied list of subscriptions -func (o *OKGroup) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) { - return o.Websocket.GetSubscriptions(), nil -} - // AuthenticateWebsocket sends an authentication message to the websocket func (o *OKGroup) AuthenticateWebsocket() error { return o.WsLogin() diff --git a/exchanges/order/order_test.go b/exchanges/order/order_test.go index 3e83148f..35182379 100644 --- a/exchanges/order/order_test.go +++ b/exchanges/order/order_test.go @@ -12,72 +12,67 @@ import ( func TestValidate(t *testing.T) { testPair := currency.NewPair(currency.BTC, currency.LTC) tester := []struct { - Pair currency.Pair - Side - Type - Amount float64 - Price float64 ExpectedErr error + Submit *Submit }{ + { + ExpectedErr: ErrSubmissionIsNil, + Submit: nil, + }, // nil struct { ExpectedErr: ErrPairIsEmpty, + Submit: &Submit{}, }, // empty pair { - Pair: testPair, + ExpectedErr: ErrSideIsInvalid, + Submit: &Submit{Pair: testPair}, }, // valid pair but invalid order side { - Pair: testPair, - Side: Buy, ExpectedErr: ErrTypeIsInvalid, + Submit: &Submit{Pair: testPair, + Side: Buy}, }, // valid pair and order side but invalid order type { - Pair: testPair, - Side: Sell, ExpectedErr: ErrTypeIsInvalid, + Submit: &Submit{Pair: testPair, + Side: Sell}, }, // valid pair and order side but invalid order type { - Pair: testPair, - Side: Bid, ExpectedErr: ErrTypeIsInvalid, + Submit: &Submit{Pair: testPair, + Side: Bid}, }, // valid pair and order side but invalid order type { - Pair: testPair, - Side: Ask, ExpectedErr: ErrTypeIsInvalid, + Submit: &Submit{Pair: testPair, + Side: Ask}, }, // valid pair and order side but invalid order type { - Pair: testPair, - Side: Ask, - Type: Market, ExpectedErr: ErrAmountIsInvalid, + Submit: &Submit{Pair: testPair, + Side: Ask, + Type: Market}, }, // valid pair, order side, type but invalid amount { - Pair: testPair, - Side: Ask, - Type: Limit, - Amount: 1, ExpectedErr: ErrPriceMustBeSetIfLimitOrder, + Submit: &Submit{Pair: testPair, + Side: Ask, + Type: Limit, + Amount: 1}, }, // valid pair, order side, type, amount but invalid price { - Pair: testPair, - Side: Ask, - Type: Limit, - Amount: 1, - Price: 1000, ExpectedErr: nil, + Submit: &Submit{Pair: testPair, + Side: Ask, + Type: Limit, + Amount: 1, + Price: 1000}, }, // valid order! } for x := range tester { - s := Submit{ - Pair: tester[x].Pair, - Side: tester[x].Side, - Type: tester[x].Type, - Amount: tester[x].Amount, - Price: tester[x].Price, - } - if err := s.Validate(); err != tester[x].ExpectedErr { + if err := tester[x].Submit.Validate(); err != tester[x].ExpectedErr { t.Errorf("Unexpected result. Got: %s, want: %s", err, tester[x].ExpectedErr) } } @@ -465,10 +460,16 @@ var stringsToOrderType = []struct { {"stop", Stop, nil}, {"STOP", Stop, nil}, {"sToP", Stop, nil}, + {"sToP LiMit", StopLimit, nil}, + {"ExchangE sToP Limit", StopLimit, nil}, {"trailing_stop", TrailingStop, nil}, {"TRAILING_STOP", TrailingStop, nil}, {"tRaIlInG_sToP", TrailingStop, nil}, {"tRaIlInG sToP", TrailingStop, nil}, + {"fOk", FillOrKill, nil}, + {"exchange fOk", FillOrKill, nil}, + {"ios", IOS, nil}, + {"post_ONly", PostOnly, nil}, {"any", AnyType, nil}, {"ANY", AnyType, nil}, {"aNy", AnyType, nil}, @@ -532,6 +533,9 @@ var stringsToOrderStatus = []struct { {"insufficient_balance", InsufficientBalance, nil}, {"INSUFFICIENT_BALANCE", InsufficientBalance, nil}, {"iNsUfFiCiEnT_bAlAnCe", InsufficientBalance, nil}, + {"PARTIALLY_CANCELLEd", PartiallyCancelled, nil}, + {"partially canceLLed", PartiallyCancelled, nil}, + {"opeN", Open, nil}, {"woahMan", UnknownStatus, errors.New("woahMan not recognised as order status")}, } @@ -583,6 +587,12 @@ func TestUpdateOrderFromModify(t *testing.T) { Trades: nil, } updated := time.Now() + + pair, err := currency.NewPairFromString("BTCUSD") + if err != nil { + t.Fatal(err) + } + om := Modify{ ImmediateOrCancel: true, HiddenOrder: true, @@ -609,7 +619,7 @@ func TestUpdateOrderFromModify(t *testing.T) { Status: "1", AssetType: "1", LastUpdated: updated, - Pair: currency.NewPairFromString("BTCUSD"), + Pair: pair, Trades: []TradeHistory{}, } @@ -769,6 +779,12 @@ func TestUpdateOrderFromDetail(t *testing.T) { Trades: nil, } updated := time.Now() + + pair, err := currency.NewPairFromString("BTCUSD") + if err != nil { + t.Fatal(err) + } + om := Detail{ ImmediateOrCancel: true, HiddenOrder: true, @@ -795,7 +811,7 @@ func TestUpdateOrderFromDetail(t *testing.T) { Status: "1", AssetType: "1", LastUpdated: updated, - Pair: currency.NewPairFromString("BTCUSD"), + Pair: pair, Trades: []TradeHistory{}, } @@ -922,3 +938,14 @@ func TestUpdateOrderFromDetail(t *testing.T) { t.Error("Failed to update trades") } } + +func TestClassificationError_Error(t *testing.T) { + class := ClassificationError{OrderID: "1337", Exchange: "test", Err: errors.New("test error")} + if class.Error() != "test - OrderID: 1337 classification error: test error" { + t.Fatal("unexpected output") + } + class.OrderID = "" + if class.Error() != "test - classification error: test error" { + t.Fatal("unexpected output") + } +} diff --git a/exchanges/order/order_types.go b/exchanges/order/order_types.go index 3c443205..e70b78db 100644 --- a/exchanges/order/order_types.go +++ b/exchanges/order/order_types.go @@ -2,7 +2,6 @@ package order import ( "errors" - "fmt" "time" "github.com/thrasher-corp/gocryptotrader/currency" @@ -222,9 +221,13 @@ const ( AnyType Type = "ANY" Limit Type = "LIMIT" Market Type = "MARKET" + PostOnly Type = "POST_ONLY" ImmediateOrCancel Type = "IMMEDIATE_OR_CANCEL" Stop Type = "STOP" + StopLimit Type = "STOP LIMIT" TrailingStop Type = "TRAILING_STOP" + FillOrKill Type = "FOK" + IOS Type = "IOS" UnknownType Type = "UNKNOWN" ) @@ -263,15 +266,3 @@ type ClassificationError struct { OrderID string Err error } - -func (o *ClassificationError) Error() string { - if o.OrderID != "" { - return fmt.Sprintf("%s - OrderID: %s classification error: %v", - o.Exchange, - o.OrderID, - o.Err) - } - return fmt.Sprintf("%s - classification error: %v", - o.Exchange, - o.Err) -} diff --git a/exchanges/order/orders.go b/exchanges/order/orders.go index cab3812f..44154981 100644 --- a/exchanges/order/orders.go +++ b/exchanges/order/orders.go @@ -2,6 +2,7 @@ package order import ( "errors" + "fmt" "sort" "strings" "time" @@ -582,20 +583,36 @@ func StringToOrderSide(side string) (Side, error) { // and returning a real Type func StringToOrderType(oType string) (Type, error) { switch { - case strings.EqualFold(oType, Limit.String()): + case strings.EqualFold(oType, Limit.String()), + strings.EqualFold(oType, "EXCHANGE LIMIT"): return Limit, nil - case strings.EqualFold(oType, Market.String()): + case strings.EqualFold(oType, Market.String()), + strings.EqualFold(oType, "EXCHANGE MARKET"): return Market, nil case strings.EqualFold(oType, ImmediateOrCancel.String()), - strings.EqualFold(oType, "immediate or cancel"): + strings.EqualFold(oType, "immediate or cancel"), + strings.EqualFold(oType, "IOC"), + strings.EqualFold(oType, "EXCHANGE IOC"): return ImmediateOrCancel, nil case strings.EqualFold(oType, Stop.String()), strings.EqualFold(oType, "stop loss"), - strings.EqualFold(oType, "stop_loss"): + strings.EqualFold(oType, "stop_loss"), + strings.EqualFold(oType, "EXCHANGE STOP"): return Stop, nil + case strings.EqualFold(oType, StopLimit.String()), + strings.EqualFold(oType, "EXCHANGE STOP LIMIT"): + return StopLimit, nil case strings.EqualFold(oType, TrailingStop.String()), - strings.EqualFold(oType, "trailing stop"): + strings.EqualFold(oType, "trailing stop"), + strings.EqualFold(oType, "EXCHANGE TRAILING STOP"): return TrailingStop, nil + case strings.EqualFold(oType, FillOrKill.String()), + strings.EqualFold(oType, "EXCHANGE FOK"): + return FillOrKill, nil + case strings.EqualFold(oType, IOS.String()): + return IOS, nil + case strings.EqualFold(oType, PostOnly.String()): + return PostOnly, nil case strings.EqualFold(oType, AnyType.String()): return AnyType, nil default: @@ -647,3 +664,15 @@ func StringToOrderStatus(status string) (Status, error) { return UnknownStatus, errors.New(status + " not recognised as order status") } } + +func (o *ClassificationError) Error() string { + if o.OrderID != "" { + return fmt.Sprintf("%s - OrderID: %s classification error: %v", + o.Exchange, + o.OrderID, + o.Err) + } + return fmt.Sprintf("%s - classification error: %v", + o.Exchange, + o.Err) +} diff --git a/exchanges/orderbook/orderbook.go b/exchanges/orderbook/orderbook.go index aad84f22..66acf0f9 100644 --- a/exchanges/orderbook/orderbook.go +++ b/exchanges/orderbook/orderbook.go @@ -31,12 +31,12 @@ func SubscribeOrderbook(exchange string, p currency.Pair, a asset.Item) (dispatc defer service.RUnlock() book, ok := service.Books[exchange][p.Base.Item][p.Quote.Item][a] if !ok { - return dispatch.Pipe{}, fmt.Errorf("orderbook item not found for %s %s %s", - exchange, - p, - a) + return dispatch.Pipe{}, + fmt.Errorf("orderbook item not found for %s %s %s", + exchange, + p, + a) } - return service.mux.Subscribe(book.Main) } @@ -50,65 +50,46 @@ func SubscribeToExchangeOrderbooks(exchange string) (dispatch.Pipe, error) { return dispatch.Pipe{}, fmt.Errorf("%s exchange orderbooks not found", exchange) } - return service.mux.Subscribe(id) } // Update stores orderbook data func (s *Service) Update(b *Base) error { - var ids []uuid.UUID - + name := strings.ToLower(b.ExchangeName) s.Lock() - switch { - case s.Books[b.ExchangeName] == nil: - s.Books[b.ExchangeName] = make(map[*currency.Item]map[*currency.Item]map[asset.Item]*Book) - s.Books[b.ExchangeName][b.Pair.Base.Item] = make(map[*currency.Item]map[asset.Item]*Book) - s.Books[b.ExchangeName][b.Pair.Base.Item][b.Pair.Quote.Item] = make(map[asset.Item]*Book) - err := s.SetNewData(b) - if err != nil { - s.Unlock() - return err - } - - case s.Books[b.ExchangeName][b.Pair.Base.Item] == nil: - s.Books[b.ExchangeName][b.Pair.Base.Item] = make(map[*currency.Item]map[asset.Item]*Book) - s.Books[b.ExchangeName][b.Pair.Base.Item][b.Pair.Quote.Item] = make(map[asset.Item]*Book) - err := s.SetNewData(b) - if err != nil { - s.Unlock() - return err - } - - case s.Books[b.ExchangeName][b.Pair.Base.Item][b.Pair.Quote.Item] == nil: - s.Books[b.ExchangeName][b.Pair.Base.Item][b.Pair.Quote.Item] = make(map[asset.Item]*Book) - err := s.SetNewData(b) - if err != nil { - s.Unlock() - return err - } - - case s.Books[b.ExchangeName][b.Pair.Base.Item][b.Pair.Quote.Item][b.AssetType] == nil: - err := s.SetNewData(b) - if err != nil { - s.Unlock() - return err - } - - default: - book := s.Books[b.ExchangeName][b.Pair.Base.Item][b.Pair.Quote.Item][b.AssetType] + book, ok := s.Books[name][b.Pair.Base.Item][b.Pair.Quote.Item][b.AssetType] + if ok { book.b.Bids = b.Bids book.b.Asks = b.Asks book.b.LastUpdated = b.LastUpdated - ids = book.Assoc - ids = append(ids, book.Main) + ids := append(book.Assoc, book.Main) + s.Unlock() + return s.mux.Publish(ids, b) + } + + switch { + case s.Books[name] == nil: + s.Books[name] = make(map[*currency.Item]map[*currency.Item]map[asset.Item]*Book) + fallthrough + case s.Books[name][b.Pair.Base.Item] == nil: + s.Books[name][b.Pair.Base.Item] = make(map[*currency.Item]map[asset.Item]*Book) + fallthrough + case s.Books[name][b.Pair.Base.Item][b.Pair.Quote.Item] == nil: + s.Books[name][b.Pair.Base.Item][b.Pair.Quote.Item] = make(map[asset.Item]*Book) + } + + err := s.SetNewData(b, name) + if err != nil { + s.Unlock() + return err } s.Unlock() - return s.mux.Publish(ids, b) + return nil } // SetNewData sets new data -func (s *Service) SetNewData(b *Base) error { - ids, err := s.GetAssociations(b) +func (s *Service) SetNewData(b *Base, fmtName string) error { + ids, err := s.GetAssociations(b, fmtName) if err != nil { return err } @@ -126,7 +107,7 @@ func (s *Service) SetNewData(b *Base) error { cpyBook.Asks = make([]Item, len(b.Asks)) copy(cpyBook.Asks, b.Asks) - s.Books[b.ExchangeName][b.Pair.Base.Item][b.Pair.Quote.Item][b.AssetType] = &Book{ + s.Books[fmtName][b.Pair.Base.Item][b.Pair.Quote.Item][b.AssetType] = &Book{ b: &cpyBook, Main: singleID, Assoc: ids} @@ -134,20 +115,20 @@ func (s *Service) SetNewData(b *Base) error { } // GetAssociations links a singular book with it's dispatch associations -func (s *Service) GetAssociations(b *Base) ([]uuid.UUID, error) { +func (s *Service) GetAssociations(b *Base, fmtName string) ([]uuid.UUID, error) { if b == nil { return nil, errors.New("orderbook is nil") } var ids []uuid.UUID - exchangeID, ok := s.Exchange[b.ExchangeName] + exchangeID, ok := s.Exchange[fmtName] if !ok { var err error exchangeID, err = s.mux.GetID() if err != nil { return nil, err } - s.Exchange[b.ExchangeName] = exchangeID + s.Exchange[fmtName] = exchangeID } ids = append(ids, exchangeID) @@ -247,8 +228,6 @@ func (b *Base) Process() error { return errors.New(errExchangeNameUnset) } - b.ExchangeName = strings.ToLower(b.ExchangeName) - if b.Pair.IsEmpty() { return errors.New(errPairNotSet) } diff --git a/exchanges/orderbook/orderbook_test.go b/exchanges/orderbook/orderbook_test.go index 8915da03..01e8ad97 100644 --- a/exchanges/orderbook/orderbook_test.go +++ b/exchanges/orderbook/orderbook_test.go @@ -125,7 +125,7 @@ func TestSubscribeToExchangeOrderbooks(t *testing.T) { err = b.Process() if err != nil { - t.Error("", err) + t.Error(err) } _, err = SubscribeToExchangeOrderbooks("SubscribeToExchangeOrderbooks") @@ -158,7 +158,10 @@ func TestVerify(t *testing.T) { func TestCalculateTotalBids(t *testing.T) { t.Parallel() - curr := currency.NewPairFromStrings("BTC", "USD") + curr, err := currency.NewPairFromStrings("BTC", "USD") + if err != nil { + t.Fatal(err) + } base := Base{ Pair: curr, Bids: []Item{{Price: 100, Amount: 10}}, @@ -173,7 +176,10 @@ func TestCalculateTotalBids(t *testing.T) { func TestCalculateTotaAsks(t *testing.T) { t.Parallel() - curr := currency.NewPairFromStrings("BTC", "USD") + curr, err := currency.NewPairFromStrings("BTC", "USD") + if err != nil { + t.Fatal(err) + } base := Base{ Pair: curr, Asks: []Item{{Price: 100, Amount: 10}}, @@ -187,7 +193,10 @@ func TestCalculateTotaAsks(t *testing.T) { func TestUpdate(t *testing.T) { t.Parallel() - curr := currency.NewPairFromStrings("BTC", "USD") + curr, err := currency.NewPairFromStrings("BTC", "USD") + if err != nil { + t.Fatal(err) + } timeNow := time.Now() base := Base{ Pair: curr, @@ -217,7 +226,10 @@ func TestUpdate(t *testing.T) { } func TestGetOrderbook(t *testing.T) { - c := currency.NewPairFromStrings("BTC", "USD") + c, err := currency.NewPairFromStrings("BTC", "USD") + if err != nil { + t.Fatal(err) + } base := &Base{ Pair: c, Asks: []Item{{Price: 100, Amount: 10}}, @@ -226,7 +238,7 @@ func TestGetOrderbook(t *testing.T) { AssetType: asset.Spot, } - err := base.Process() + err = base.Process() if err != nil { t.Fatal(err) } @@ -251,7 +263,10 @@ func TestGetOrderbook(t *testing.T) { t.Fatal("TestGetOrderbook retrieved non-existent orderbook using invalid first currency") } - newCurrency := currency.NewPairFromStrings("BTC", "AUD") + newCurrency, err := currency.NewPairFromStrings("BTC", "AUD") + if err != nil { + t.Fatal(err) + } _, err = Get("Exchange", newCurrency, asset.Spot) if err == nil { t.Fatal("TestGetOrderbook retrieved non-existent orderbook using invalid second currency") @@ -270,7 +285,10 @@ func TestGetOrderbook(t *testing.T) { } func TestCreateNewOrderbook(t *testing.T) { - c := currency.NewPairFromStrings("BTC", "USD") + c, err := currency.NewPairFromStrings("BTC", "USD") + if err != nil { + t.Fatal(err) + } base := &Base{ Pair: c, Asks: []Item{{Price: 100, Amount: 10}}, @@ -279,7 +297,7 @@ func TestCreateNewOrderbook(t *testing.T) { AssetType: asset.Spot, } - err := base.Process() + err = base.Process() if err != nil { t.Fatal(err) } @@ -305,7 +323,10 @@ func TestCreateNewOrderbook(t *testing.T) { } func TestProcessOrderbook(t *testing.T) { - c := currency.NewPairFromStrings("BTC", "USD") + c, err := currency.NewPairFromStrings("BTC", "USD") + if err != nil { + t.Fatal(err) + } base := Base{ Asks: []Item{{Price: 100, Amount: 10}}, Bids: []Item{{Price: 200, Amount: 10}}, @@ -314,7 +335,7 @@ func TestProcessOrderbook(t *testing.T) { // test for empty pair base.Pair = currency.Pair{} - err := base.Process() + err = base.Process() if err == nil { t.Error("empty pair should throw an err") } @@ -341,7 +362,10 @@ func TestProcessOrderbook(t *testing.T) { } // now test for processing a pair with a different quote currency - c = currency.NewPairFromStrings("BTC", "GBP") + c, err = currency.NewPairFromStrings("BTC", "GBP") + if err != nil { + t.Fatal(err) + } base.Pair = c err = base.Process() if err != nil { @@ -356,7 +380,10 @@ func TestProcessOrderbook(t *testing.T) { } // now test for processing a pair which has a different base currency - c = currency.NewPairFromStrings("LTC", "GBP") + c, err = currency.NewPairFromStrings("LTC", "GBP") + if err != nil { + t.Fatal(err) + } base.Pair = c err = base.Process() if err != nil { @@ -491,14 +518,14 @@ func TestProcessOrderbook(t *testing.T) { } func TestSetNewData(t *testing.T) { - err := service.SetNewData(nil) + err := service.SetNewData(nil, "") if err == nil { t.Error("error cannot be nil") } } func TestGetAssociations(t *testing.T) { - _, err := service.GetAssociations(nil) + _, err := service.GetAssociations(nil, "") if err == nil { t.Error("error cannot be nil") } diff --git a/exchanges/poloniex/poloniex.go b/exchanges/poloniex/poloniex.go index f1205e96..618f31cd 100644 --- a/exchanges/poloniex/poloniex.go +++ b/exchanges/poloniex/poloniex.go @@ -16,7 +16,6 @@ import ( exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/request" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" ) const ( @@ -54,7 +53,6 @@ const ( // Poloniex is the overarching type across the poloniex package type Poloniex struct { exchange.Base - WebsocketConn *wshandler.WebsocketConnection } // GetTicker returns current ticker information diff --git a/exchanges/poloniex/poloniex_live_test.go b/exchanges/poloniex/poloniex_live_test.go index 36c5afee..8eabea2a 100644 --- a/exchanges/poloniex/poloniex_live_test.go +++ b/exchanges/poloniex/poloniex_live_test.go @@ -29,6 +29,7 @@ func TestMain(m *testing.M) { poloniexConfig.API.Credentials.Key = apiKey poloniexConfig.API.Credentials.Secret = apiSecret p.SetDefaults() + p.Websocket = sharedtestvalues.NewTestWebsocket() err = p.Setup(poloniexConfig) if err != nil { log.Fatal("Poloniex setup error", err) diff --git a/exchanges/poloniex/poloniex_mock_test.go b/exchanges/poloniex/poloniex_mock_test.go index 0df790ae..ad2cecdd 100644 --- a/exchanges/poloniex/poloniex_mock_test.go +++ b/exchanges/poloniex/poloniex_mock_test.go @@ -33,6 +33,7 @@ func TestMain(m *testing.M) { poloniexConfig.API.Credentials.Key = apiKey poloniexConfig.API.Credentials.Secret = apiSecret p.SetDefaults() + p.Websocket = sharedtestvalues.NewTestWebsocket() err = p.Setup(poloniexConfig) if err != nil { log.Fatal("Poloniex setup error", err) @@ -45,8 +46,6 @@ func TestMain(m *testing.M) { p.HTTPClient = newClient p.API.Endpoints.URL = serverDetails - p.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride() - p.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride() log.Printf(sharedtestvalues.MockTesting, p.Name, p.API.Endpoints.URL) os.Exit(m.Run()) } diff --git a/exchanges/poloniex/poloniex_test.go b/exchanges/poloniex/poloniex_test.go index 700a1ba1..25e74ee4 100644 --- a/exchanges/poloniex/poloniex_test.go +++ b/exchanges/poloniex/poloniex_test.go @@ -14,7 +14,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/kline" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) @@ -259,7 +259,7 @@ func TestSubmitOrder(t *testing.T) { var orderSubmission = &order.Submit{ Pair: currency.Pair{ - Delimiter: delimiterUnderscore, + Delimiter: currency.UnderscoreDelimiter, Base: currency.BTC, Quote: currency.LTC, }, @@ -422,22 +422,13 @@ func TestGetDepositAddress(t *testing.T) { func TestWsAuth(t *testing.T) { t.Parallel() if !p.Websocket.IsEnabled() && !p.API.AuthenticatedWebsocketSupport || !areTestAPIKeysSet() { - t.Skip(wshandler.WebsocketNotEnabled) - } - p.WebsocketConn = &wshandler.WebsocketConnection{ - ExchangeName: p.Name, - URL: p.Websocket.GetWebsocketURL(), - Verbose: p.Verbose, - ResponseMaxLimit: exchange.DefaultWebsocketResponseMaxLimit, - ResponseCheckTimeout: exchange.DefaultWebsocketResponseCheckTimeout, + t.Skip(stream.WebsocketNotEnabled) } var dialer websocket.Dialer - err := p.WebsocketConn.Dial(&dialer, http.Header{}) + err := p.Websocket.Conn.Dial(&dialer, http.Header{}) if err != nil { t.Fatal(err) } - p.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride() - p.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride() go p.wsReadData() err = p.wsSendAuthorisedCommand("subscribe") if err != nil { @@ -534,8 +525,11 @@ func TestWsHandleAccountData(t *testing.T) { } func TestGetHistoricCandles(t *testing.T) { - currencyPair := currency.NewPairFromString("BTCLTC") - _, err := p.GetHistoricCandles(currencyPair, asset.Spot, time.Unix(1588741402, 0), time.Unix(1588745003, 0), kline.FiveMin) + currencyPair, err := currency.NewPairFromString("BTCLTC") + if err != nil { + t.Fatal(err) + } + _, err = p.GetHistoricCandles(currencyPair, asset.Spot, time.Unix(1588741402, 0), time.Unix(1588745003, 0), kline.FiveMin) if err != nil { t.Fatal(err) } @@ -552,8 +546,11 @@ func TestGetHistoricCandles(t *testing.T) { } func TestGetHistoricCandlesExtended(t *testing.T) { - currencyPair := currency.NewPairFromString("BTCLTC") - _, err := p.GetHistoricCandlesExtended(currencyPair, asset.Spot, time.Unix(1588741402, 0), time.Unix(1588745003, 0), kline.FiveMin) + currencyPair, err := currency.NewPairFromString("BTCLTC") + if err != nil { + t.Fatal(err) + } + _, err = p.GetHistoricCandlesExtended(currencyPair, asset.Spot, time.Unix(1588741402, 0), time.Unix(1588745003, 0), kline.FiveMin) if err != nil { t.Fatal(err) } diff --git a/exchanges/poloniex/poloniex_websocket.go b/exchanges/poloniex/poloniex_websocket.go index d671f71f..d02a2f5e 100644 --- a/exchanges/poloniex/poloniex_websocket.go +++ b/exchanges/poloniex/poloniex_websocket.go @@ -17,9 +17,9 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream/buffer" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook" ) const ( @@ -28,7 +28,6 @@ const ( wsTickerDataID = 1002 ws24HourExchangeVolumeID = 1003 wsHeartbeat = 1010 - delimiterUnderscore = "_" ) var ( @@ -39,23 +38,26 @@ var ( // WsConnect initiates a websocket connection func (p *Poloniex) WsConnect() error { if !p.Websocket.IsEnabled() || !p.IsEnabled() { - return errors.New(wshandler.WebsocketNotEnabled) + return errors.New(stream.WebsocketNotEnabled) } var dialer websocket.Dialer - err := p.WebsocketConn.Dial(&dialer, http.Header{}) + err := p.Websocket.Conn.Dial(&dialer, http.Header{}) if err != nil { return err } - err2 := p.getCurrencyIDMap() - if err2 != nil { - return err2 + err = p.getCurrencyIDMap() + if err != nil { + return err } go p.wsReadData() - p.GenerateDefaultSubscriptions() + subs, err := p.GenerateDefaultSubscriptions() + if err != nil { + return err + } - return nil + return p.Websocket.SubscribeToChannels(subs) } func (p *Poloniex) getCurrencyIDMap() error { @@ -86,26 +88,16 @@ func checkSubscriptionSuccess(data []interface{}) bool { // wsReadData handles data from the websocket connection func (p *Poloniex) wsReadData() { p.Websocket.Wg.Add(1) - - defer func() { - p.Websocket.Wg.Done() - }() + defer p.Websocket.Wg.Done() for { - select { - case <-p.Websocket.ShutdownC: + resp := p.Websocket.Conn.ReadMessage() + if resp.Raw == nil { return - default: - resp, err := p.WebsocketConn.ReadMessage() - if err != nil { - p.Websocket.ReadMessageErrors <- err - return - } - p.Websocket.TrafficAlert <- struct{}{} - err = p.wsHandleData(resp.Raw) - if err != nil { - p.Websocket.DataHandler <- err - } + } + err := p.wsHandleData(resp.Raw) + if err != nil { + p.Websocket.DataHandler <- err } } } @@ -189,14 +181,20 @@ func (p *Poloniex) wsHandleData(respRaw []byte) error { } var currPair currency.Pair if currPairFromMap, ok := currencyIDMap[notification[1].(float64)]; ok { - currPair = currency.NewPairFromString(currPairFromMap) + currPair, err = currency.NewPairFromString(currPairFromMap) + if err != nil { + return err + } } else { // It is better to still log an order which you can recheck later, rather than error out p.Websocket.DataHandler <- fmt.Errorf(p.Name+ " - Unknown currency pair ID. "+ "Currency will appear as the pair ID: '%v'", notification[1].(float64)) - currPair = currency.NewPairFromString(strconv.FormatFloat(notification[1].(float64), 'f', -1, 64)) + currPair, err = currency.NewPairFromString(strconv.FormatFloat(notification[1].(float64), 'f', -1, 64)) + if err != nil { + return err + } } var a asset.Item a, err = p.GetPairAssetType(currPair) @@ -303,12 +301,6 @@ func (p *Poloniex) wsHandleData(respRaw []byte) error { if err != nil { return err } - - p.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{ - Exchange: p.Name, - Asset: asset.Spot, - Pair: currency.NewPairFromString(currencyPair), - } case "o": currencyPair := currencyIDMap[channelID] dataL3 := dataL2.([]interface{}) @@ -318,12 +310,6 @@ func (p *Poloniex) wsHandleData(respRaw []byte) error { if err != nil { return err } - - p.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{ - Exchange: p.Name, - Asset: asset.Spot, - Pair: currency.NewPairFromString(currencyPair), - } case "t": currencyPair := currencyIDMap[channelID] var trade WsTrade @@ -347,15 +333,20 @@ func (p *Poloniex) wsHandleData(respRaw []byte) error { } trade.Timestamp = int64(dataL3[5].(float64)) - p.Websocket.DataHandler <- wshandler.TradeData{ + pair, err := currency.NewPairFromString(currencyPair) + if err != nil { + return err + } + + p.Websocket.DataHandler <- stream.TradeData{ Timestamp: time.Unix(trade.Timestamp, 0), - CurrencyPair: currency.NewPairFromString(currencyPair), + CurrencyPair: pair, Side: side, Amount: trade.Volume, Price: trade.Price, } default: - p.Websocket.DataHandler <- wshandler.UnhandledMessageWarning{Message: p.Name + wshandler.UnhandledMessage + string(respRaw)} + p.Websocket.DataHandler <- stream.UnhandledMessageWarning{Message: p.Name + stream.UnhandledMessage + string(respRaw)} return nil } } @@ -368,14 +359,31 @@ func (p *Poloniex) wsHandleData(respRaw []byte) error { func (p *Poloniex) wsHandleTickerData(data []interface{}) error { tickerData := data[2].([]interface{}) var t WsTicker - currencyPair := currency.NewPairDelimiter(currencyIDMap[tickerData[0].(float64)], delimiterUnderscore) - if !p.GetEnabledPairs(asset.Spot).Contains(currencyPair, true) { - // Ticker subscription receives all currencies, no specific subscription - // There should be no error associated with receiving data of disabled currency ticker data + currencyPair, err := currency.NewPairDelimiter(currencyIDMap[tickerData[0].(float64)], + currency.UnderscoreDelimiter) + if err != nil { + return err + } + + enabled, err := p.GetEnabledPairs(asset.Spot) + if err != nil { + return err + } + + if !enabled.Contains(currencyPair, true) { + var avail currency.Pairs + avail, err = p.GetAvailablePairs(asset.Spot) + if err != nil { + return err + } + + if !avail.Contains(currencyPair, true) { + return fmt.Errorf("currency pair %s not found in available pair list", + currencyPair) + } return nil } - var err error t.LastPrice, err = strconv.ParseFloat(tickerData[1].(string), 64) if err != nil { return err @@ -477,7 +485,12 @@ func (p *Poloniex) WsProcessOrderbookSnapshot(ob []interface{}, symbol string) e newOrderBook.Asks = asks newOrderBook.Bids = bids newOrderBook.AssetType = asset.Spot - newOrderBook.Pair = currency.NewPairFromString(symbol) + + var err error + newOrderBook.Pair, err = currency.NewPairFromString(symbol) + if err != nil { + return err + } newOrderBook.ExchangeName = p.Name return p.Websocket.Orderbook.LoadSnapshot(&newOrderBook) @@ -485,7 +498,10 @@ func (p *Poloniex) WsProcessOrderbookSnapshot(ob []interface{}, symbol string) e // WsProcessOrderbookUpdate processes new orderbook updates func (p *Poloniex) WsProcessOrderbookUpdate(sequenceNumber int64, target []interface{}, symbol string) error { - cP := currency.NewPairFromString(symbol) + cP, err := currency.NewPairFromString(symbol) + if err != nil { + return err + } price, err := strconv.ParseFloat(target[2].(string), 64) if err != nil { return err @@ -494,7 +510,7 @@ func (p *Poloniex) WsProcessOrderbookUpdate(sequenceNumber int64, target []inter if err != nil { return err } - update := &wsorderbook.WebsocketOrderbookUpdate{ + update := &buffer.Update{ Pair: cP, Asset: asset.Spot, UpdateID: sequenceNumber, @@ -508,64 +524,114 @@ func (p *Poloniex) WsProcessOrderbookUpdate(sequenceNumber int64, target []inter } // GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() -func (p *Poloniex) GenerateDefaultSubscriptions() { - var subscriptions []wshandler.WebsocketChannelSubscription - subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{ +func (p *Poloniex) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, error) { + var subscriptions []stream.ChannelSubscription + subscriptions = append(subscriptions, stream.ChannelSubscription{ Channel: strconv.FormatInt(wsTickerDataID, 10), }) if p.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) { - subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{ + subscriptions = append(subscriptions, stream.ChannelSubscription{ Channel: strconv.FormatInt(wsAccountNotificationID, 10), }) } - enabledCurrencies := p.GetEnabledPairs(asset.Spot) + enabledCurrencies, err := p.GetEnabledPairs(asset.Spot) + if err != nil { + return nil, err + } for j := range enabledCurrencies { - enabledCurrencies[j].Delimiter = delimiterUnderscore - subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{ + enabledCurrencies[j].Delimiter = currency.UnderscoreDelimiter + subscriptions = append(subscriptions, stream.ChannelSubscription{ Channel: "orderbook", Currency: enabledCurrencies[j], + Asset: asset.Spot, }) } - p.Websocket.SubscribeToChannels(subscriptions) + return subscriptions, nil } // Subscribe sends a websocket message to receive data from the channel -func (p *Poloniex) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error { - subscriptionRequest := WsCommand{ - Command: "subscribe", +func (p *Poloniex) Subscribe(sub []stream.ChannelSubscription) error { + var errs common.Errors +channels: + for i := range sub { + subscriptionRequest := WsCommand{ + Command: "subscribe", + } + switch { + case strings.EqualFold(strconv.FormatInt(wsAccountNotificationID, 10), + sub[i].Channel): + err := p.wsSendAuthorisedCommand("subscribe") + if err != nil { + errs = append(errs, err) + continue channels + } + p.Websocket.AddSuccessfulSubscriptions(sub[i]) + continue channels + case strings.EqualFold(strconv.FormatInt(wsTickerDataID, 10), + sub[i].Channel): + subscriptionRequest.Channel = wsTickerDataID + default: + subscriptionRequest.Channel = sub[i].Currency.String() + } + + err := p.Websocket.Conn.SendJSONMessage(subscriptionRequest) + if err != nil { + errs = append(errs, err) + continue + } + + p.Websocket.AddSuccessfulSubscriptions(sub[i]) } - switch { - case strings.EqualFold(strconv.FormatInt(wsAccountNotificationID, 10), channelToSubscribe.Channel): - return p.wsSendAuthorisedCommand("subscribe") - case strings.EqualFold(strconv.FormatInt(wsTickerDataID, 10), channelToSubscribe.Channel): - subscriptionRequest.Channel = wsTickerDataID - default: - subscriptionRequest.Channel = channelToSubscribe.Currency.String() + if errs != nil { + return errs } - return p.WebsocketConn.SendJSONMessage(subscriptionRequest) + return nil } // Unsubscribe sends a websocket message to stop receiving data from the channel -func (p *Poloniex) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error { - unsubscriptionRequest := WsCommand{ - Command: "unsubscribe", +func (p *Poloniex) Unsubscribe(unsub []stream.ChannelSubscription) error { + var errs common.Errors +channels: + for i := range unsub { + unsubscriptionRequest := WsCommand{ + Command: "unsubscribe", + } + switch { + case strings.EqualFold(strconv.FormatInt(wsAccountNotificationID, 10), + unsub[i].Channel): + err := p.wsSendAuthorisedCommand("unsubscribe") + if err != nil { + errs = append(errs, err) + continue channels + } + p.Websocket.RemoveSuccessfulUnsubscriptions(unsub[i]) + continue channels + case strings.EqualFold(strconv.FormatInt(wsTickerDataID, 10), + unsub[i].Channel): + unsubscriptionRequest.Channel = wsTickerDataID + default: + unsubscriptionRequest.Channel = unsub[i].Currency.String() + } + err := p.Websocket.Conn.SendJSONMessage(unsubscriptionRequest) + if err != nil { + errs = append(errs, err) + continue + } + p.Websocket.RemoveSuccessfulUnsubscriptions(unsub[i]) } - switch { - case strings.EqualFold(strconv.FormatInt(wsAccountNotificationID, 10), channelToSubscribe.Channel): - return p.wsSendAuthorisedCommand("unsubscribe") - case strings.EqualFold(strconv.FormatInt(wsTickerDataID, 10), channelToSubscribe.Channel): - unsubscriptionRequest.Channel = wsTickerDataID - default: - unsubscriptionRequest.Channel = channelToSubscribe.Currency.String() + if errs != nil { + return errs } - return p.WebsocketConn.SendJSONMessage(unsubscriptionRequest) + return nil } func (p *Poloniex) wsSendAuthorisedCommand(command string) error { nonce := fmt.Sprintf("nonce=%v", time.Now().UnixNano()) - hmac := crypto.GetHMAC(crypto.HashSHA512, []byte(nonce), []byte(p.API.Credentials.Secret)) + hmac := crypto.GetHMAC(crypto.HashSHA512, + []byte(nonce), + []byte(p.API.Credentials.Secret)) request := WsAuthorisationRequest{ Command: command, Channel: 1000, @@ -573,5 +639,5 @@ func (p *Poloniex) wsSendAuthorisedCommand(command string) error { Key: p.API.Credentials.Key, Payload: nonce, } - return p.WebsocketConn.SendJSONMessage(request) + return p.Websocket.Conn.SendJSONMessage(request) } diff --git a/exchanges/poloniex/poloniex_wrapper.go b/exchanges/poloniex/poloniex_wrapper.go index a8b5926e..fa6a02c4 100644 --- a/exchanges/poloniex/poloniex_wrapper.go +++ b/exchanges/poloniex/poloniex_wrapper.go @@ -18,8 +18,8 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" "github.com/thrasher-corp/gocryptotrader/log" "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) @@ -55,19 +55,19 @@ func (p *Poloniex) SetDefaults() { p.API.CredentialsValidator.RequiresKey = true p.API.CredentialsValidator.RequiresSecret = true - p.CurrencyPairs = currency.PairsManager{ - AssetTypes: asset.Items{ - asset.Spot, - }, - UseGlobalFormat: true, - RequestFormat: ¤cy.PairFormat{ - Delimiter: delimiterUnderscore, - Uppercase: true, - }, - ConfigFormat: ¤cy.PairFormat{ - Delimiter: delimiterUnderscore, - Uppercase: true, - }, + requestFmt := ¤cy.PairFormat{ + Delimiter: currency.UnderscoreDelimiter, + Uppercase: true, + } + + configFmt := ¤cy.PairFormat{ + Delimiter: currency.UnderscoreDelimiter, + Uppercase: true, + } + + err := p.SetGlobalPairsManager(requestFmt, configFmt, asset.Spot) + if err != nil { + log.Errorln(log.ExchangeSys, err) } p.Features = exchange.Features{ @@ -133,7 +133,7 @@ func (p *Poloniex) SetDefaults() { p.API.Endpoints.URLDefault = poloniexAPIURL p.API.Endpoints.URL = p.API.Endpoints.URLDefault p.API.Endpoints.WebsocketURL = poloniexWebsocketAddress - p.Websocket = wshandler.New() + p.Websocket = stream.New() p.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit p.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout p.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit @@ -151,41 +151,31 @@ func (p *Poloniex) Setup(exch *config.ExchangeConfig) error { return err } - err = p.Websocket.Setup( - &wshandler.WebsocketSetup{ - Enabled: exch.Features.Enabled.Websocket, - Verbose: exch.Verbose, - AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, - WebsocketTimeout: exch.WebsocketTrafficTimeout, - DefaultURL: poloniexWebsocketAddress, - ExchangeName: exch.Name, - RunningURL: exch.API.Endpoints.WebsocketURL, - Connector: p.WsConnect, - Subscriber: p.Subscribe, - UnSubscriber: p.Unsubscribe, - Features: &p.Features.Supports.WebsocketCapabilities, - }) + err = p.Websocket.Setup(&stream.WebsocketSetup{ + Enabled: exch.Features.Enabled.Websocket, + Verbose: exch.Verbose, + AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, + WebsocketTimeout: exch.WebsocketTrafficTimeout, + DefaultURL: poloniexWebsocketAddress, + ExchangeName: exch.Name, + RunningURL: exch.API.Endpoints.WebsocketURL, + Connector: p.WsConnect, + Subscriber: p.Subscribe, + UnSubscriber: p.Unsubscribe, + GenerateSubscriptions: p.GenerateDefaultSubscriptions, + Features: &p.Features.Supports.WebsocketCapabilities, + OrderbookBufferLimit: exch.WebsocketOrderbookBufferLimit, + SortBuffer: true, + SortBufferByUpdateIDs: true, + }) if err != nil { return err } - p.WebsocketConn = &wshandler.WebsocketConnection{ - ExchangeName: p.Name, - URL: p.Websocket.GetWebsocketURL(), - ProxyURL: p.Websocket.GetProxyAddress(), - Verbose: p.Verbose, + return p.Websocket.SetupNewConnection(stream.ConnectionSetup{ ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout, ResponseMaxLimit: exch.WebsocketResponseMaxLimit, - } - - p.Websocket.Orderbook.Setup( - exch.WebsocketOrderbookBufferLimit, - false, - true, - true, - false, - exch.Name) - return nil + }) } // Start starts the Poloniex go routine @@ -200,13 +190,28 @@ func (p *Poloniex) Start(wg *sync.WaitGroup) { // Run implements the Poloniex wrapper func (p *Poloniex) Run() { if p.Verbose { - log.Debugf(log.ExchangeSys, "%s Websocket: %s (url: %s).\n", p.Name, common.IsEnabled(p.Websocket.IsEnabled()), poloniexWebsocketAddress) + log.Debugf(log.ExchangeSys, + "%s Websocket: %s (url: %s).\n", + p.Name, + common.IsEnabled(p.Websocket.IsEnabled()), + poloniexWebsocketAddress) p.PrintEnabledPairs() } forceUpdate := false - if common.StringDataCompare(p.GetAvailablePairs(asset.Spot).Strings(), "BTC_USDT") { - log.Warnf(log.ExchangeSys, "%s contains invalid pair, forcing upgrade of available currencies.\n", + + avail, err := p.GetAvailablePairs(asset.Spot) + if err != nil { + log.Errorf(log.ExchangeSys, + "%s failed to update tradable pairs. Err: %s", + p.Name, + err) + return + } + + if common.StringDataCompare(avail.Strings(), "BTC_USDT") { + log.Warnf(log.ExchangeSys, + "%s contains invalid pair, forcing upgrade of available currencies.\n", p.Name) forceUpdate = true } @@ -215,9 +220,12 @@ func (p *Poloniex) Run() { return } - err := p.UpdateTradablePairs(forceUpdate) + err = p.UpdateTradablePairs(forceUpdate) if err != nil { - log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", p.Name, err) + log.Errorf(log.ExchangeSys, + "%s failed to update tradable pairs. Err: %s", + p.Name, + err) } } @@ -243,37 +251,47 @@ func (p *Poloniex) UpdateTradablePairs(forceUpgrade bool) error { if err != nil { return err } - - return p.UpdatePairs(currency.NewPairsFromStrings(pairs), asset.Spot, false, forceUpgrade) + ps, err := currency.NewPairsFromStrings(pairs) + if err != nil { + return err + } + return p.UpdatePairs(ps, asset.Spot, false, forceUpgrade) } // UpdateTicker updates and returns the ticker for a currency pair func (p *Poloniex) UpdateTicker(currencyPair currency.Pair, assetType asset.Item) (*ticker.Price, error) { - tickerPrice := new(ticker.Price) tick, err := p.GetTicker() if err != nil { - return tickerPrice, err + return nil, err } - enabledPairs := p.GetEnabledPairs(assetType) + enabledPairs, err := p.GetEnabledPairs(assetType) + if err != nil { + return nil, err + } for i := range enabledPairs { - var tp ticker.Price - curr := p.FormatExchangeCurrency(enabledPairs[i], assetType).String() + fpair, err := p.FormatExchangeCurrency(enabledPairs[i], assetType) + if err != nil { + return nil, err + } + curr := fpair.String() if _, ok := tick[curr]; !ok { continue } - tp.Pair = enabledPairs[i] - tp.Ask = tick[curr].LowestAsk - tp.Bid = tick[curr].HighestBid - tp.High = tick[curr].High24Hr - tp.Last = tick[curr].Last - tp.Low = tick[curr].Low24Hr - tp.Volume = tick[curr].BaseVolume - tp.QuoteVolume = tick[curr].QuoteVolume - err = ticker.ProcessTicker(p.Name, &tp, assetType) + err = ticker.ProcessTicker(&ticker.Price{ + Pair: enabledPairs[i], + Ask: tick[curr].LowestAsk, + Bid: tick[curr].HighestBid, + High: tick[curr].High24Hr, + Last: tick[curr].Last, + Low: tick[curr].Low24Hr, + Volume: tick[curr].BaseVolume, + QuoteVolume: tick[curr].QuoteVolume, + ExchangeName: p.Name, + AssetType: assetType}) if err != nil { - log.Error(log.Ticker, err) + return nil, err } } return ticker.GetTicker(p.Name, currencyPair, assetType) @@ -304,9 +322,16 @@ func (p *Poloniex) UpdateOrderbook(currencyPair currency.Pair, assetType asset.I return nil, err } - enabledPairs := p.GetEnabledPairs(assetType) + enabledPairs, err := p.GetEnabledPairs(assetType) + if err != nil { + return nil, err + } for i := range enabledPairs { - data, ok := orderbookNew.Data[p.FormatExchangeCurrency(enabledPairs[i], assetType).String()] + fpair, err := p.FormatExchangeCurrency(enabledPairs[i], assetType) + if err != nil { + return nil, err + } + data, ok := orderbookNew.Data[fpair.String()] if !ok { continue } @@ -516,11 +541,6 @@ func (p *Poloniex) WithdrawFiatFundsToInternationalBank(withdrawRequest *withdra return nil, common.ErrFunctionNotSupported } -// GetWebsocket returns a pointer to the exchange websocket -func (p *Poloniex) GetWebsocket() (*wshandler.Websocket, error) { - return p.Websocket, nil -} - // GetFeeByType returns an estimate of fee based on type of transaction func (p *Poloniex) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { if (!p.AllowAuthenticatedRequest() || p.SkipAuthCheck) && // Todo check connection status @@ -537,11 +557,18 @@ func (p *Poloniex) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, return nil, err } + format, err := p.GetPairFormat(asset.Spot, false) + if err != nil { + return nil, err + } + var orders []order.Detail for key := range resp.Data { - symbol := currency.NewPairDelimiter(key, - p.GetPairFormat(asset.Spot, false).Delimiter) - + var symbol currency.Pair + symbol, err = currency.NewPairDelimiter(key, format.Delimiter) + if err != nil { + return nil, err + } for i := range resp.Data[key] { orderSide := order.Side(strings.ToUpper(resp.Data[key][i].Type)) orderDate, err := time.Parse(common.SimpleTimeFormat, resp.Data[key][i].Date) @@ -583,10 +610,18 @@ func (p *Poloniex) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, return nil, err } + format, err := p.GetPairFormat(asset.Spot, false) + if err != nil { + return nil, err + } + var orders []order.Detail for key := range resp.Data { - symbol := currency.NewPairDelimiter(key, - p.GetPairFormat(asset.Spot, false).Delimiter) + var symbol currency.Pair + symbol, err = currency.NewPairDelimiter(key, format.Delimiter) + if err != nil { + return nil, err + } for i := range resp.Data[key] { orderSide := order.Side(strings.ToUpper(resp.Data[key][i].Type)) @@ -619,30 +654,6 @@ func (p *Poloniex) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, return orders, nil } -// SubscribeToWebsocketChannels appends to ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle subscribing -func (p *Poloniex) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - p.Websocket.SubscribeToChannels(channels) - return nil -} - -// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle unsubscribing -func (p *Poloniex) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - p.Websocket.RemoveSubscribedChannels(channels) - return nil -} - -// GetSubscriptions returns a copied list of subscriptions -func (p *Poloniex) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) { - return p.Websocket.GetSubscriptions(), nil -} - -// AuthenticateWebsocket sends an authentication message to the websocket -func (p *Poloniex) AuthenticateWebsocket() error { - return common.ErrFunctionNotSupported -} - // ValidateCredentials validates current credentials used for wrapper // functionality func (p *Poloniex) ValidateCredentials() error { @@ -658,7 +669,12 @@ func (p *Poloniex) GetHistoricCandles(pair currency.Pair, a asset.Item, start, e } } - candles, err := p.GetChartData(p.FormatExchangeCurrency(pair, a).String(), + formattedPair, err := p.FormatExchangeCurrency(pair, a) + if err != nil { + return kline.Item{}, err + } + + candles, err := p.GetChartData(formattedPair.String(), start, end, p.FormatExchangeKlineInterval(interval)) if err != nil { diff --git a/exchanges/protocol/features.go b/exchanges/protocol/features.go index 821907a2..ef12bfc4 100644 --- a/exchanges/protocol/features.go +++ b/exchanges/protocol/features.go @@ -3,35 +3,38 @@ package protocol // Features holds all variables for the exchanges supported features // for a protocol (e.g REST or Websocket) type Features struct { - TickerBatching bool `json:"tickerBatching,omitempty"` - AutoPairUpdates bool `json:"autoPairUpdates,omitempty"` - AccountBalance bool `json:"accountBalance,omitempty"` - CryptoDeposit bool `json:"cryptoDeposit,omitempty"` - CryptoWithdrawal bool `json:"cryptoWithdrawal,omitempty"` - FiatWithdraw bool `json:"fiatWithdraw,omitempty"` - GetOrder bool `json:"getOrder,omitempty"` - GetOrders bool `json:"getOrders,omitempty"` - CancelOrders bool `json:"cancelOrders,omitempty"` - CancelOrder bool `json:"cancelOrder,omitempty"` - SubmitOrder bool `json:"submitOrder,omitempty"` - SubmitOrders bool `json:"submitOrders,omitempty"` - ModifyOrder bool `json:"modifyOrder,omitempty"` - DepositHistory bool `json:"depositHistory,omitempty"` - WithdrawalHistory bool `json:"withdrawalHistory,omitempty"` - TradeHistory bool `json:"tradeHistory,omitempty"` - UserTradeHistory bool `json:"userTradeHistory,omitempty"` - TradeFee bool `json:"tradeFee,omitempty"` - FiatDepositFee bool `json:"fiatDepositFee,omitempty"` - FiatWithdrawalFee bool `json:"fiatWithdrawalFee,omitempty"` - CryptoDepositFee bool `json:"cryptoDepositFee,omitempty"` - CryptoWithdrawalFee bool `json:"cryptoWithdrawalFee,omitempty"` - TickerFetching bool `json:"tickerFetching,omitempty"` - KlineFetching bool `json:"klineFetching,omitempty"` - TradeFetching bool `json:"tradeFetching,omitempty"` - OrderbookFetching bool `json:"orderbookFetching,omitempty"` - AccountInfo bool `json:"accountInfo,omitempty"` - FiatDeposit bool `json:"fiatDeposit,omitempty"` - DeadMansSwitch bool `json:"deadMansSwitch,omitempty"` + TickerBatching bool `json:"tickerBatching,omitempty"` + AutoPairUpdates bool `json:"autoPairUpdates,omitempty"` + AccountBalance bool `json:"accountBalance,omitempty"` + CryptoDeposit bool `json:"cryptoDeposit,omitempty"` + CryptoWithdrawal bool `json:"cryptoWithdrawal,omitempty"` + FiatWithdraw bool `json:"fiatWithdraw,omitempty"` + GetOrder bool `json:"getOrder,omitempty"` + GetOrders bool `json:"getOrders,omitempty"` + CancelOrders bool `json:"cancelOrders,omitempty"` + CancelOrder bool `json:"cancelOrder,omitempty"` + SubmitOrder bool `json:"submitOrder,omitempty"` + SubmitOrders bool `json:"submitOrders,omitempty"` + ModifyOrder bool `json:"modifyOrder,omitempty"` + DepositHistory bool `json:"depositHistory,omitempty"` + WithdrawalHistory bool `json:"withdrawalHistory,omitempty"` + TradeHistory bool `json:"tradeHistory,omitempty"` + UserTradeHistory bool `json:"userTradeHistory,omitempty"` + TradeFee bool `json:"tradeFee,omitempty"` + FiatDepositFee bool `json:"fiatDepositFee,omitempty"` + FiatWithdrawalFee bool `json:"fiatWithdrawalFee,omitempty"` + CryptoDepositFee bool `json:"cryptoDepositFee,omitempty"` + CryptoWithdrawalFee bool `json:"cryptoWithdrawalFee,omitempty"` + TickerFetching bool `json:"tickerFetching,omitempty"` + KlineFetching bool `json:"klineFetching,omitempty"` + TradeFetching bool `json:"tradeFetching,omitempty"` + OrderbookFetching bool `json:"orderbookFetching,omitempty"` + AccountInfo bool `json:"accountInfo,omitempty"` + FiatDeposit bool `json:"fiatDeposit,omitempty"` + DeadMansSwitch bool `json:"deadMansSwitch,omitempty"` + // FullPayloadSubscribe flushes and changes full subscription on websocket + // connection by subscribing with full default stream channel list + FullPayloadSubscribe bool `json:"fullPayloadSubscribe,omitempty"` Subscribe bool `json:"subscribe,omitempty"` Unsubscribe bool `json:"unsubscribe,omitempty"` AuthenticatedEndpoints bool `json:"authenticatedEndpoints,omitempty"` diff --git a/exchanges/sharedtestvalues/sharedtestvalues.go b/exchanges/sharedtestvalues/sharedtestvalues.go index 254e9156..3bd871a9 100644 --- a/exchanges/sharedtestvalues/sharedtestvalues.go +++ b/exchanges/sharedtestvalues/sharedtestvalues.go @@ -1,6 +1,10 @@ package sharedtestvalues -import "time" +import ( + "time" + + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" +) // This package is only to be referenced in test files const ( @@ -29,3 +33,17 @@ func GetWebsocketInterfaceChannelOverride() chan interface{} { func GetWebsocketStructChannelOverride() chan struct{} { return make(chan struct{}, WebsocketChannelOverrideCapacity) } + +// NewTestWebsocket returns a test websocket object +func NewTestWebsocket() *stream.Websocket { + return &stream.Websocket{ + Init: true, + DataHandler: make(chan interface{}, 75), + ToRoutine: make(chan interface{}, 1000), + TrafficAlert: make(chan struct{}), + ReadMessageErrors: make(chan error), + Subscribe: make(chan []stream.ChannelSubscription, 10), + Unsubscribe: make(chan []stream.ChannelSubscription, 10), + Match: stream.NewMatch(), + } +} diff --git a/exchanges/stats/stats.go b/exchanges/stats/stats.go index f9805eed..85b145dc 100644 --- a/exchanges/stats/stats.go +++ b/exchanges/stats/stats.go @@ -1,6 +1,7 @@ package stats import ( + "errors" "sort" "github.com/thrasher-corp/gocryptotrader/currency" @@ -50,40 +51,48 @@ func (b ByVolume) Swap(i, j int) { } // Add adds or updates the item stats -func Add(exchange string, p currency.Pair, assetType asset.Item, price, volume float64) { +func Add(exchange string, p currency.Pair, a asset.Item, price, volume float64) error { if exchange == "" || - assetType == "" || + a == "" || price == 0 || volume == 0 || p.Base.IsEmpty() || p.Quote.IsEmpty() { - return + return errors.New("cannot add or update, invalid params") } if p.Base == currency.XBT { - newPair := currency.NewPairFromStrings(currency.BTC.String(), p.Quote.String()) - Append(exchange, newPair, assetType, price, volume) + newPair, err := currency.NewPairFromStrings(currency.BTC.String(), + p.Quote.String()) + if err != nil { + return err + } + Append(exchange, newPair, a, price, volume) } if p.Quote == currency.USDT { - newPair := currency.NewPairFromStrings(p.Base.String(), currency.USD.String()) - Append(exchange, newPair, assetType, price, volume) + newPair, err := currency.NewPairFromStrings(p.Base.String(), currency.USD.String()) + if err != nil { + return err + } + Append(exchange, newPair, a, price, volume) } - Append(exchange, p, assetType, price, volume) + Append(exchange, p, a, price, volume) + return nil } // Append adds or updates the item stats for a specific // currency pair and asset type -func Append(exchange string, p currency.Pair, assetType asset.Item, price, volume float64) { - if AlreadyExists(exchange, p, assetType, price, volume) { +func Append(exchange string, p currency.Pair, a asset.Item, price, volume float64) { + if AlreadyExists(exchange, p, a, price, volume) { return } i := Item{ Exchange: exchange, Pair: p, - AssetType: assetType, + AssetType: a, Price: price, Volume: volume, } diff --git a/exchanges/stats/stats_test.go b/exchanges/stats/stats_test.go index 06aea628..ad8c80a2 100644 --- a/exchanges/stats/stats_test.go +++ b/exchanges/stats/stats_test.go @@ -12,7 +12,10 @@ const ( ) func TestLenByPrice(t *testing.T) { - p := currency.NewPairFromStrings("BTC", "USD") + p, err := currency.NewPairFromStrings("BTC", "USD") + if err != nil { + t.Fatal(err) + } Items = []Item{ { Exchange: testExchange, @@ -29,8 +32,10 @@ func TestLenByPrice(t *testing.T) { } func TestLessByPrice(t *testing.T) { - p := currency.NewPairFromStrings("BTC", "USD") - + p, err := currency.NewPairFromStrings("BTC", "USD") + if err != nil { + t.Fatal(err) + } Items = []Item{ { Exchange: "alphapoint", @@ -57,8 +62,10 @@ func TestLessByPrice(t *testing.T) { } func TestSwapByPrice(t *testing.T) { - p := currency.NewPairFromStrings("BTC", "USD") - + p, err := currency.NewPairFromStrings("BTC", "USD") + if err != nil { + t.Fatal(err) + } Items = []Item{ { Exchange: "bitstamp", @@ -107,27 +114,42 @@ func TestSwapByVolume(t *testing.T) { func TestAdd(t *testing.T) { Items = Items[:0] - p := currency.NewPairFromStrings("BTC", "USD") - Add(testExchange, p, asset.Spot, 1200, 42) + p, err := currency.NewPairFromStrings("BTC", "USD") + if err != nil { + t.Fatal(err) + } + err = Add(testExchange, p, asset.Spot, 1200, 42) + if err != nil { + t.Fatal(err) + } if len(Items) < 1 { t.Error("stats Add did not add exchange info.") } - Add("", p, "", 0, 0) + err = Add("", p, "", 0, 0) + if err == nil { + t.Fatal("error cannot be nil") + } if len(Items) != 1 { t.Error("stats Add did not add exchange info.") } p.Base = currency.XBT - Add(testExchange, p, asset.Spot, 1201, 43) + err = Add(testExchange, p, asset.Spot, 1201, 43) + if err != nil { + t.Fatal(err) + } if Items[1].Pair.String() != "XBTUSD" { t.Fatal("stats Add did not add exchange info.") } - p = currency.NewPairFromStrings("ETH", "USDT") + p, err = currency.NewPairFromStrings("ETH", "USDT") + if err != nil { + t.Fatal(err) + } Add(testExchange, p, asset.Spot, 300, 1000) if Items[2].Pair.String() != "ETHUSD" { @@ -136,7 +158,10 @@ func TestAdd(t *testing.T) { } func TestAppend(t *testing.T) { - p := currency.NewPairFromStrings("BTC", "USD") + p, err := currency.NewPairFromStrings("BTC", "USD") + if err != nil { + t.Fatal(err) + } Append("sillyexchange", p, asset.Spot, 1234, 45) if len(Items) < 2 { t.Error("stats Append did not add exchange values.") @@ -149,7 +174,10 @@ func TestAppend(t *testing.T) { } func TestAlreadyExists(t *testing.T) { - p := currency.NewPairFromStrings("BTC", "USD") + p, err := currency.NewPairFromStrings("BTC", "USD") + if err != nil { + t.Fatal(err) + } if !AlreadyExists(testExchange, p, asset.Spot, 1200, 42) { t.Error("stats AlreadyExists exchange does not exist.") } @@ -160,7 +188,10 @@ func TestAlreadyExists(t *testing.T) { } func TestSortExchangesByVolume(t *testing.T) { - p := currency.NewPairFromStrings("BTC", "USD") + p, err := currency.NewPairFromStrings("BTC", "USD") + if err != nil { + t.Fatal(err) + } topVolume := SortExchangesByVolume(p, asset.Spot, true) if topVolume[0].Exchange != "sillyexchange" { t.Error("stats SortExchangesByVolume incorrectly sorted values.") @@ -173,7 +204,10 @@ func TestSortExchangesByVolume(t *testing.T) { } func TestSortExchangesByPrice(t *testing.T) { - p := currency.NewPairFromStrings("BTC", "USD") + p, err := currency.NewPairFromStrings("BTC", "USD") + if err != nil { + t.Fatal(err) + } topPrice := SortExchangesByPrice(p, asset.Spot, true) if topPrice[0].Exchange != "sillyexchange" { t.Error("stats SortExchangesByPrice incorrectly sorted values.") diff --git a/exchanges/websocket/wsorderbook/wsorderbook.go b/exchanges/stream/buffer/buffer.go similarity index 81% rename from exchanges/websocket/wsorderbook/wsorderbook.go rename to exchanges/stream/buffer/buffer.go index 169eb0a2..7ef4d90e 100644 --- a/exchanges/websocket/wsorderbook/wsorderbook.go +++ b/exchanges/stream/buffer/buffer.go @@ -1,4 +1,4 @@ -package wsorderbook +package buffer import ( "errors" @@ -11,21 +11,22 @@ import ( ) // Setup sets private variables -func (w *WebsocketOrderbookLocal) Setup(obBufferLimit int, bufferEnabled, sortBuffer, sortBufferByUpdateIDs, updateEntriesByID bool, exchangeName string) { +func (w *Orderbook) Setup(obBufferLimit int, bufferEnabled, sortBuffer, sortBufferByUpdateIDs, updateEntriesByID bool, exchangeName string, dataHandler chan interface{}) { w.obBufferLimit = obBufferLimit w.bufferEnabled = bufferEnabled w.sortBuffer = sortBuffer w.sortBufferByUpdateIDs = sortBufferByUpdateIDs w.updateEntriesByID = updateEntriesByID w.exchangeName = exchangeName + w.dataHandler = dataHandler } -// Update updates a local cache using bid targets and ask targets then updates +// Update updates a local buffer using bid targets and ask targets then updates // main orderbook // Volume == 0; deletion at price target // Price target not found; append of price target // Price target found; amend volume of price target -func (w *WebsocketOrderbookLocal) Update(u *WebsocketOrderbookUpdate) error { +func (w *Orderbook) Update(u *Update) error { if (u.Bids == nil && u.Asks == nil) || (len(u.Bids) == 0 && len(u.Asks) == 0) { return fmt.Errorf("%v cannot have bids and ask targets both nil", w.exchangeName) @@ -52,19 +53,23 @@ func (w *WebsocketOrderbookLocal) Update(u *WebsocketOrderbookUpdate) error { if err != nil { return err } + if w.bufferEnabled { // Reset the buffer w.buffer[u.Pair][u.Asset] = nil } + + // Process in data handler + w.dataHandler <- obLookup return nil } -func (w *WebsocketOrderbookLocal) processBufferUpdate(o *orderbook.Base, u *WebsocketOrderbookUpdate) bool { +func (w *Orderbook) processBufferUpdate(o *orderbook.Base, u *Update) bool { if w.buffer == nil { - w.buffer = make(map[currency.Pair]map[asset.Item][]*WebsocketOrderbookUpdate) + w.buffer = make(map[currency.Pair]map[asset.Item][]*Update) } if w.buffer[u.Pair] == nil { - w.buffer[u.Pair] = make(map[asset.Item][]*WebsocketOrderbookUpdate) + w.buffer[u.Pair] = make(map[asset.Item][]*Update) } bufferLookup := w.buffer[u.Pair][u.Asset] if len(bufferLookup) <= w.obBufferLimit { @@ -93,7 +98,7 @@ func (w *WebsocketOrderbookLocal) processBufferUpdate(o *orderbook.Base, u *Webs return true } -func (w *WebsocketOrderbookLocal) processObUpdate(o *orderbook.Base, u *WebsocketOrderbookUpdate) { +func (w *Orderbook) processObUpdate(o *orderbook.Base, u *Update) { o.LastUpdateID = u.UpdateID if w.updateEntriesByID { @@ -104,7 +109,7 @@ func (w *WebsocketOrderbookLocal) processObUpdate(o *orderbook.Base, u *Websocke } } -func (w *WebsocketOrderbookLocal) updateAsksByPrice(o *orderbook.Base, u *WebsocketOrderbookUpdate) { +func (w *Orderbook) updateAsksByPrice(o *orderbook.Base, u *Update) { updates: for j := range u.Asks { for k := range o.Asks { @@ -127,7 +132,7 @@ updates: }) } -func (w *WebsocketOrderbookLocal) updateBidsByPrice(o *orderbook.Base, u *WebsocketOrderbookUpdate) { +func (w *Orderbook) updateBidsByPrice(o *orderbook.Base, u *Update) { updates: for j := range u.Bids { for k := range o.Bids { @@ -152,7 +157,7 @@ updates: // updateByIDAndAction will receive an action to execute against the orderbook // it will then match by IDs instead of price to perform the action -func (w *WebsocketOrderbookLocal) updateByIDAndAction(o *orderbook.Base, u *WebsocketOrderbookUpdate) { +func (w *Orderbook) updateByIDAndAction(o *orderbook.Base, u *Update) { switch u.Action { case "update": for x := range u.Bids { @@ -227,7 +232,7 @@ func (w *WebsocketOrderbookLocal) updateByIDAndAction(o *orderbook.Base, u *Webs // LoadSnapshot loads initial snapshot of ob data, overwrite allows full // ob to be completely rewritten because the exchange is a doing a full // update not an incremental one -func (w *WebsocketOrderbookLocal) LoadSnapshot(newOrderbook *orderbook.Base) error { +func (w *Orderbook) LoadSnapshot(newOrderbook *orderbook.Base) error { if len(newOrderbook.Asks) == 0 || len(newOrderbook.Bids) == 0 { return fmt.Errorf("%v snapshot ask and bids are nil", w.exchangeName) } @@ -254,21 +259,27 @@ func (w *WebsocketOrderbookLocal) LoadSnapshot(newOrderbook *orderbook.Base) err } w.ob[newOrderbook.Pair][newOrderbook.AssetType] = newOrderbook - return newOrderbook.Process() + err := newOrderbook.Process() + if err != nil { + return err + } + + w.dataHandler <- newOrderbook + return nil } // GetOrderbook use sparingly. Modifying anything here will ruin hash // calculation and cause problems -func (w *WebsocketOrderbookLocal) GetOrderbook(p currency.Pair, a asset.Item) *orderbook.Base { +func (w *Orderbook) GetOrderbook(p currency.Pair, a asset.Item) *orderbook.Base { w.m.Lock() ob := w.ob[p][a] w.m.Unlock() return ob } -// FlushCache flushes w.ob data to be garbage collected and refreshed when a +// FlushBuffer flushes w.ob data to be garbage collected and refreshed when a // connection is lost and reconnected -func (w *WebsocketOrderbookLocal) FlushCache() { +func (w *Orderbook) FlushBuffer() { w.m.Lock() w.ob = nil w.buffer = nil diff --git a/exchanges/websocket/wsorderbook/wsorderbook_test.go b/exchanges/stream/buffer/buffer_test.go similarity index 92% rename from exchanges/websocket/wsorderbook/wsorderbook_test.go rename to exchanges/stream/buffer/buffer_test.go index 0393712e..a62649b2 100644 --- a/exchanges/websocket/wsorderbook/wsorderbook_test.go +++ b/exchanges/stream/buffer/buffer_test.go @@ -1,4 +1,4 @@ -package wsorderbook +package buffer import ( "fmt" @@ -20,13 +20,13 @@ var itemArray = [][]orderbook.Item{ {{Price: 5000, Amount: 1, ID: 5}}, } -var cp = currency.NewPairFromString("BTCUSD") +var cp, _ = currency.NewPairFromString("BTCUSD") const ( exchangeName = "exchangeTest" ) -func createSnapshot() (obl *WebsocketOrderbookLocal, asks, bids []orderbook.Item, err error) { +func createSnapshot() (obl *Orderbook, asks, bids []orderbook.Item, err error) { var snapShot1 orderbook.Base snapShot1.ExchangeName = exchangeName asks = []orderbook.Item{ @@ -39,7 +39,7 @@ func createSnapshot() (obl *WebsocketOrderbookLocal, asks, bids []orderbook.Item snapShot1.Bids = bids snapShot1.AssetType = asset.Spot snapShot1.Pair = cp - obl = &WebsocketOrderbookLocal{exchangeName: exchangeName} + obl = &Orderbook{exchangeName: exchangeName, dataHandler: make(chan interface{}, 100)} err = obl.LoadSnapshot(&snapShot1) return } @@ -69,7 +69,7 @@ func BenchmarkUpdateBidsByPrice(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { bidAsks := bidAskGenerator() - update := &WebsocketOrderbookUpdate{ + update := &Update{ Bids: bidAsks, Asks: bidAsks, Pair: cp, @@ -88,7 +88,7 @@ func BenchmarkUpdateAsksByPrice(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { bidAsks := bidAskGenerator() - update := &WebsocketOrderbookUpdate{ + update := &Update{ Bids: bidAsks, Asks: bidAsks, Pair: cp, @@ -115,7 +115,7 @@ func BenchmarkBufferPerformance(b *testing.B) { ID: 1337, } obl.ob[cp][asset.Spot].Bids = append(obl.ob[cp][asset.Spot].Bids, dummyItem) - update := &WebsocketOrderbookUpdate{ + update := &Update{ Bids: bids, Asks: asks, Pair: cp, @@ -150,7 +150,7 @@ func BenchmarkBufferSortingPerformance(b *testing.B) { ID: 1337, } obl.ob[cp][asset.Spot].Bids = append(obl.ob[cp][asset.Spot].Bids, dummyItem) - update := &WebsocketOrderbookUpdate{ + update := &Update{ Bids: bids, Asks: asks, Pair: cp, @@ -186,7 +186,7 @@ func BenchmarkBufferSortingByIDPerformance(b *testing.B) { ID: 1337, } obl.ob[cp][asset.Spot].Bids = append(obl.ob[cp][asset.Spot].Bids, dummyItem) - update := &WebsocketOrderbookUpdate{ + update := &Update{ Bids: bids, Asks: asks, Pair: cp, @@ -220,7 +220,7 @@ func BenchmarkNoBufferPerformance(b *testing.B) { ID: 1337, } obl.ob[cp][asset.Spot].Bids = append(obl.ob[cp][asset.Spot].Bids, dummyItem) - update := &WebsocketOrderbookUpdate{ + update := &Update{ Bids: bids, Asks: asks, Pair: cp, @@ -245,7 +245,7 @@ func TestUpdates(t *testing.T) { t.Error(err) } - obl.updateAsksByPrice(obl.ob[cp][asset.Spot], &WebsocketOrderbookUpdate{ + obl.updateAsksByPrice(obl.ob[cp][asset.Spot], &Update{ Bids: itemArray[5], Asks: itemArray[5], Pair: cp, @@ -256,7 +256,7 @@ func TestUpdates(t *testing.T) { t.Error(err) } - obl.updateAsksByPrice(obl.ob[cp][asset.Spot], &WebsocketOrderbookUpdate{ + obl.updateAsksByPrice(obl.ob[cp][asset.Spot], &Update{ Bids: itemArray[0], Asks: itemArray[0], Pair: cp, @@ -283,7 +283,7 @@ func TestHittingTheBuffer(t *testing.T) { for i := range itemArray { asks := itemArray[i] bids := itemArray[i] - err = obl.Update(&WebsocketOrderbookUpdate{ + err = obl.Update(&Update{ Bids: bids, Asks: asks, Pair: cp, @@ -317,7 +317,7 @@ func TestInsertWithIDs(t *testing.T) { for i := range itemArray { asks := itemArray[i] bids := itemArray[i] - err = obl.Update(&WebsocketOrderbookUpdate{ + err = obl.Update(&Update{ Bids: bids, Asks: asks, Pair: cp, @@ -352,7 +352,7 @@ func TestSortIDs(t *testing.T) { for i := range itemArray { asks := itemArray[i] bids := itemArray[i] - err = obl.Update(&WebsocketOrderbookUpdate{ + err = obl.Update(&Update{ Bids: bids, Asks: asks, Pair: cp, @@ -397,7 +397,7 @@ func TestDeleteWithIDs(t *testing.T) { for i := range itemArray { asks := itemArray[i] bids := itemArray[i] - err = obl.Update(&WebsocketOrderbookUpdate{ + err = obl.Update(&Update{ Bids: bids, Asks: asks, Pair: cp, @@ -430,7 +430,7 @@ func TestUpdateWithIDs(t *testing.T) { for i := range itemArray { asks := itemArray[i] bids := itemArray[i] - err = obl.Update(&WebsocketOrderbookUpdate{ + err = obl.Update(&Update{ Bids: bids, Asks: asks, Pair: cp, @@ -469,7 +469,7 @@ func TestOutOfOrderIDs(t *testing.T) { obl.obBufferLimit = 5 for i := range itemArray { asks := itemArray[i] - err = obl.Update(&WebsocketOrderbookUpdate{ + err = obl.Update(&Update{ Asks: asks, Pair: cp, UpdateID: outOFOrderIDs[i], @@ -498,7 +498,7 @@ func TestOrderbookLastUpdateID(t *testing.T) { for i := range itemArray { asks := itemArray[i] - err = obl.Update(&WebsocketOrderbookUpdate{ + err = obl.Update(&Update{ Asks: asks, Pair: cp, UpdateID: int64(i) + 1, @@ -517,7 +517,7 @@ func TestOrderbookLastUpdateID(t *testing.T) { // TestRunUpdateWithoutSnapshot logic test func TestRunUpdateWithoutSnapshot(t *testing.T) { - var obl WebsocketOrderbookLocal + var obl Orderbook var snapShot1 orderbook.Base asks := []orderbook.Item{ {Price: 4000, Amount: 1, ID: 8}, @@ -531,7 +531,7 @@ func TestRunUpdateWithoutSnapshot(t *testing.T) { snapShot1.AssetType = asset.Spot snapShot1.Pair = cp obl.exchangeName = exchangeName - err := obl.Update(&WebsocketOrderbookUpdate{ + err := obl.Update(&Update{ Bids: bids, Asks: asks, Pair: cp, @@ -548,14 +548,14 @@ func TestRunUpdateWithoutSnapshot(t *testing.T) { // TestRunUpdateWithoutAnyUpdates logic test func TestRunUpdateWithoutAnyUpdates(t *testing.T) { - var obl WebsocketOrderbookLocal + var obl Orderbook var snapShot1 orderbook.Base snapShot1.Asks = []orderbook.Item{} snapShot1.Bids = []orderbook.Item{} snapShot1.AssetType = asset.Spot snapShot1.Pair = cp obl.exchangeName = exchangeName - err := obl.Update(&WebsocketOrderbookUpdate{ + err := obl.Update(&Update{ Bids: snapShot1.Asks, Asks: snapShot1.Bids, Pair: cp, @@ -573,7 +573,7 @@ func TestRunUpdateWithoutAnyUpdates(t *testing.T) { // TestRunSnapshotWithNoData logic test func TestRunSnapshotWithNoData(t *testing.T) { - var obl WebsocketOrderbookLocal + var obl Orderbook var snapShot1 orderbook.Base snapShot1.Asks = []orderbook.Item{} snapShot1.Bids = []orderbook.Item{} @@ -592,7 +592,8 @@ func TestRunSnapshotWithNoData(t *testing.T) { // TestLoadSnapshot logic test func TestLoadSnapshot(t *testing.T) { - var obl WebsocketOrderbookLocal + var obl Orderbook + obl.dataHandler = make(chan interface{}, 100) var snapShot1 orderbook.Base snapShot1.ExchangeName = "SnapshotWithOverride" asks := []orderbook.Item{ @@ -611,8 +612,8 @@ func TestLoadSnapshot(t *testing.T) { } } -// TestFlushCache logic test -func TestFlushCache(t *testing.T) { +// TestFlushbuffer logic test +func TestFlushbuffer(t *testing.T) { obl, _, _, err := createSnapshot() if err != nil { t.Fatal(err) @@ -620,7 +621,7 @@ func TestFlushCache(t *testing.T) { if obl.ob[cp][asset.Spot] == nil { t.Error("expected ob to have ask entries") } - obl.FlushCache() + obl.FlushBuffer() if obl.ob[cp][asset.Spot] != nil { t.Error("expected ob be flushed") } @@ -628,7 +629,8 @@ func TestFlushCache(t *testing.T) { // TestInsertingSnapShots logic test func TestInsertingSnapShots(t *testing.T) { - var obl WebsocketOrderbookLocal + var obl Orderbook + obl.dataHandler = make(chan interface{}, 100) var snapShot1 orderbook.Base snapShot1.ExchangeName = "WSORDERBOOKTEST1" asks := []orderbook.Item{ @@ -700,7 +702,10 @@ func TestInsertingSnapShots(t *testing.T) { snapShot2.Asks = asks snapShot2.Bids = bids snapShot2.AssetType = asset.Spot - snapShot2.Pair = currency.NewPairFromString("LTCUSD") + snapShot2.Pair, err = currency.NewPairFromString("LTCUSD") + if err != nil { + t.Fatal(err) + } err = obl.LoadSnapshot(&snapShot2) if err != nil { t.Fatal(err) @@ -738,7 +743,10 @@ func TestInsertingSnapShots(t *testing.T) { snapShot3.Asks = asks snapShot3.Bids = bids snapShot3.AssetType = "FUTURES" - snapShot3.Pair = currency.NewPairFromString("LTCUSD") + snapShot3.Pair, err = currency.NewPairFromString("LTCUSD") + if err != nil { + t.Fatal(err) + } err = obl.LoadSnapshot(&snapShot3) if err != nil { t.Fatal(err) @@ -772,8 +780,8 @@ func TestGetOrderbook(t *testing.T) { } func TestSetup(t *testing.T) { - w := WebsocketOrderbookLocal{} - w.Setup(1, true, true, true, true, "hi") + w := Orderbook{} + w.Setup(1, true, true, true, true, "hi", make(chan interface{})) if w.obBufferLimit != 1 || !w.bufferEnabled || !w.sortBuffer || @@ -791,7 +799,7 @@ func TestEnsureMultipleUpdatesViaPrice(t *testing.T) { } asks := bidAskGenerator() - obl.updateAsksByPrice(obl.ob[cp][asset.Spot], &WebsocketOrderbookUpdate{ + obl.updateAsksByPrice(obl.ob[cp][asset.Spot], &Update{ Bids: asks, Asks: asks, Pair: cp, diff --git a/exchanges/websocket/wsorderbook/wsorderbook_types.go b/exchanges/stream/buffer/buffer_types.go similarity index 65% rename from exchanges/websocket/wsorderbook/wsorderbook_types.go rename to exchanges/stream/buffer/buffer_types.go index 2ae77a29..93020b85 100644 --- a/exchanges/websocket/wsorderbook/wsorderbook_types.go +++ b/exchanges/stream/buffer/buffer_types.go @@ -1,4 +1,4 @@ -package wsorderbook +package buffer import ( "sync" @@ -9,22 +9,23 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" ) -// WebsocketOrderbookLocal defines a local cache of orderbooks for amending, -// appending and deleting changes and updates the main store in wsorderbook.go -type WebsocketOrderbookLocal struct { +// Orderbook defines a local cache of orderbooks for amending, appending +// and deleting changes and updates the main store for a stream +type Orderbook struct { ob map[currency.Pair]map[asset.Item]*orderbook.Base - buffer map[currency.Pair]map[asset.Item][]*WebsocketOrderbookUpdate + buffer map[currency.Pair]map[asset.Item][]*Update obBufferLimit int bufferEnabled bool sortBuffer bool sortBufferByUpdateIDs bool // When timestamps aren't provided, an id can help sort updateEntriesByID bool // Use the update IDs to match ob entries exchangeName string + dataHandler chan interface{} m sync.Mutex } -// WebsocketOrderbookUpdate stores orderbook updates and dictates what features to use when processing -type WebsocketOrderbookUpdate struct { +// Update stores orderbook updates and dictates what features to use when processing +type Update struct { UpdateID int64 // Used when no time is provided UpdateTime time.Time Asset asset.Item diff --git a/exchanges/stream/stream_match.go b/exchanges/stream/stream_match.go new file mode 100644 index 00000000..ecf30583 --- /dev/null +++ b/exchanges/stream/stream_match.go @@ -0,0 +1,81 @@ +package stream + +import ( + "errors" + "sync" +) + +// NewMatch returns a new matcher +func NewMatch() *Match { + return &Match{ + m: make(map[interface{}]chan []byte), + } +} + +// Match is a distributed subtype that handles the matching of requests and +// responses in a timely manner, reducing the need to differentiate between +// connections. Stream systems fan in all incoming payloads to one routine for +// processing. +type Match struct { + m map[interface{}]chan []byte + sync.Mutex +} + +// Incoming matches with request, disregarding the returned payload +func (m *Match) Incoming(signature interface{}) bool { + return m.IncomingWithData(signature, nil) +} + +// IncomingWithData matches with requests and takes in the returned payload, to +// be processed outside of a stream processing routine +func (m *Match) IncomingWithData(signature interface{}, data []byte) bool { + m.Lock() + defer m.Unlock() + ch, ok := m.m[signature] + if ok { + select { + case ch <- data: + default: + // this shouldn't occur but if it does continue to process as normal + return false + } + return true + } + return false +} + +// Sets the signature response channel for incoming data +func (m *Match) set(signature interface{}) (matcher, error) { + var ch chan []byte + m.Lock() + _, ok := m.m[signature] + if ok { + m.Unlock() + return matcher{}, errors.New("signature collision") + } + // This is buffered so we don't need to wait for receiver. + ch = make(chan []byte, 1) + m.m[signature] = ch + m.Unlock() + + return matcher{ + C: ch, + sig: signature, + m: m, + }, nil +} + +// matcher defines a payload matching return mechanism +type matcher struct { + C chan []byte + sig interface{} + m *Match +} + +// Cleanup closes underlying channel and deletes signature from map +func (m *matcher) Cleanup() { + m.m.Lock() + close(m.C) + delete(m.m.m, m.sig) + m.m.Unlock() +} diff --git a/exchanges/stream/stream_match_test.go b/exchanges/stream/stream_match_test.go new file mode 100644 index 00000000..743a2d94 --- /dev/null +++ b/exchanges/stream/stream_match_test.go @@ -0,0 +1,50 @@ +package stream + +import ( + "fmt" + "testing" +) + +func TestMatch(t *testing.T) { + bm := &Match{} + if bm.Incoming("wow") { + t.Fatal("Should not have matched") + } + + nm := NewMatch() + // try to match with unset signature + if nm.Incoming("hello") { + t.Fatal("should not be able to match") + } + + m, err := nm.set("hello") + if err != nil { + t.Fatal(err) + } + + _, err = nm.set("hello") + if err == nil { + t.Fatal("error cannot be nil as this collision cannot occur") + } + + if m.sig != "hello" { + t.Fatal(err) + } + + // try and match with initial payload + if !nm.Incoming("hello") { + t.Fatal("should of matched") + } + + // put in secondary payload with conflicting signature + if nm.Incoming("hello") { + fmt.Println("should not have been able to match") + } + + data := <-m.C + if data != nil { + t.Fatal("wow") + } + + m.Cleanup() +} diff --git a/exchanges/stream/stream_types.go b/exchanges/stream/stream_types.go new file mode 100644 index 00000000..311d26a6 --- /dev/null +++ b/exchanges/stream/stream_types.go @@ -0,0 +1,111 @@ +package stream + +import ( + "net/http" + "time" + + "github.com/gorilla/websocket" + "github.com/thrasher-corp/gocryptotrader/currency" + "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" +) + +// Connection defines a streaming services connection +type Connection interface { + Dial(*websocket.Dialer, http.Header) error + ReadMessage() Response + SendJSONMessage(interface{}) error + SetupPingHandler(PingHandler) + GenerateMessageID(highPrecision bool) int64 + SendMessageReturnResponse(signature interface{}, request interface{}) ([]byte, error) + SendRawMessage(messageType int, message []byte) error + SetURL(string) + SetProxy(string) + GetURL() string + Shutdown() error +} + +// Response defines generalised data from the stream connection +type Response struct { + Type int + Raw []byte +} + +// ChannelSubscription container for streaming subscriptions +type ChannelSubscription struct { + Channel string + Currency currency.Pair + Asset asset.Item + Params map[string]interface{} +} + +// ConnectionSetup defines variables for an individual stream connection +type ConnectionSetup struct { + ResponseCheckTimeout time.Duration + ResponseMaxLimit time.Duration + RateLimit int64 + URL string + Authenticated bool +} + +// PingHandler container for ping handler settings +type PingHandler struct { + Websocket bool + UseGorillaHandler bool + MessageType int + Message []byte + Delay time.Duration +} + +// TradeData defines trade data +type TradeData struct { + Timestamp time.Time + CurrencyPair currency.Pair + AssetType asset.Item + Exchange string + EventType order.Type + Price float64 + Amount float64 + Side order.Side +} + +// FundingData defines funding data +type FundingData struct { + Timestamp time.Time + CurrencyPair currency.Pair + AssetType asset.Item + Exchange string + Amount float64 + Rate float64 + Period int64 + Side order.Side +} + +// KlineData defines kline feed +type KlineData struct { + Timestamp time.Time + Pair currency.Pair + AssetType asset.Item + Exchange string + StartTime time.Time + CloseTime time.Time + Interval string + OpenPrice float64 + ClosePrice float64 + HighPrice float64 + LowPrice float64 + Volume float64 +} + +// WebsocketPositionUpdated reflects a change in orders/contracts on an exchange +type WebsocketPositionUpdated struct { + Timestamp time.Time + Pair currency.Pair + AssetType asset.Item + Exchange string +} + +// UnhandledMessageWarning defines a container for unhandled message warnings +type UnhandledMessageWarning struct { + Message string +} diff --git a/exchanges/stream/websocket.go b/exchanges/stream/websocket.go new file mode 100644 index 00000000..6ee1e0c7 --- /dev/null +++ b/exchanges/stream/websocket.go @@ -0,0 +1,935 @@ +package stream + +import ( + "errors" + "fmt" + "net" + "net/url" + "strings" + "sync" + "time" + + "github.com/gorilla/websocket" + "github.com/thrasher-corp/gocryptotrader/config" + "github.com/thrasher-corp/gocryptotrader/log" +) + +const ( + defaultJobBuffer = 1000 + // defaultTrafficPeriod defines a period of pause for the traffic monitor, + // as there are periods with large incoming traffic alerts which requires a + // timer reset, this limits work on this routine to a more effective rate + // of check. + defaultTrafficPeriod = time.Second +) + +// New initialises the websocket struct +func New() *Websocket { + return &Websocket{ + Init: true, + DataHandler: make(chan interface{}), + ToRoutine: make(chan interface{}, defaultJobBuffer), + TrafficAlert: make(chan struct{}), + ReadMessageErrors: make(chan error), + Subscribe: make(chan []ChannelSubscription), + Unsubscribe: make(chan []ChannelSubscription), + Match: NewMatch(), + } +} + +// Setup sets main variables for websocket connection +func (w *Websocket) Setup(s *WebsocketSetup) error { + if w == nil { + return errors.New("websocket is nil") + } + + if !w.Init { + return fmt.Errorf("%s Websocket already initialised", + s.ExchangeName) + } + + w.verbose = s.Verbose + if s.Features == nil { + return errors.New("websocket features is unset") + } + + w.features = s.Features + + if w.features.Subscribe && s.Subscriber == nil { + return errors.New("features have been set yet channel subscriber is not set") + } + w.Subscriber = s.Subscriber + + if w.features.Unsubscribe && s.UnSubscriber == nil { + return errors.New("features have been set yet channel unsubscriber is not set") + } + w.Unsubscriber = s.UnSubscriber + + w.GenerateSubs = s.GenerateSubscriptions + + w.enabled = s.Enabled + if s.DefaultURL == "" { + return errors.New("default url is empty") + } + w.defaultURL = s.DefaultURL + if s.RunningURL == "" { + return errors.New("running URL cannot be nil") + } + err := w.SetWebsocketURL(s.RunningURL, false, false) + if err != nil { + return err + } + + if s.RunningURLAuth != "" { + err = w.SetWebsocketURL(s.RunningURLAuth, true, false) + if err != nil { + return err + } + } + + w.connector = s.Connector + if s.ExchangeName == "" { + return errors.New("exchange name unset") + } + w.exchangeName = s.ExchangeName + + if s.WebsocketTimeout < time.Second { + return fmt.Errorf("traffic timeout cannot be less than %s", time.Second) + } + + w.trafficTimeout = s.WebsocketTimeout + if s.Features == nil { + return errors.New("feature set is nil") + } + + w.ShutdownC = make(chan struct{}) + w.Wg = new(sync.WaitGroup) + + w.SetCanUseAuthenticatedEndpoints(s.AuthenticatedWebsocketAPISupport) + err = w.Initialise() + if err != nil { + return err + } + + w.Orderbook.Setup(s.OrderbookBufferLimit, + s.BufferEnabled, + s.SortBuffer, + s.SortBufferByUpdateIDs, + s.UpdateEntriesByID, + w.exchangeName, + w.DataHandler) + return nil +} + +// SetupNewConnection sets up an auth or unauth streaming connection +func (w *Websocket) SetupNewConnection(c ConnectionSetup) error { + if w == nil { + return errors.New("setting up new connection error: websocket is nil") + } + if c == (ConnectionSetup{}) { + return errors.New("setting up new connection error: websocket connection configuration empty") + } + + if w.exchangeName == "" { + return errors.New("setting up new connection error: exchange name not set, please call setup first") + } + + if w.TrafficAlert == nil { + return errors.New("setting up new connection error: traffic alert is nil, please call setup first") + } + + if w.ReadMessageErrors == nil { + return errors.New("setting up new connection error: read message errors is nil, please call setup first") + } + + connectionURL := w.GetWebsocketURL() + if c.URL != "" { + connectionURL = c.URL + } + + newConn := &WebsocketConnection{ + ExchangeName: w.exchangeName, + URL: connectionURL, + ProxyURL: w.GetProxyAddress(), + Verbose: w.verbose, + ResponseMaxLimit: c.ResponseMaxLimit, + Traffic: w.TrafficAlert, + readMessageErrors: w.ReadMessageErrors, + ShutdownC: w.ShutdownC, + Wg: w.Wg, + Match: w.Match, + RateLimit: c.RateLimit, + } + + if c.Authenticated { + w.AuthConn = newConn + } else { + w.Conn = newConn + } + + return nil +} + +// Connect initiates a websocket connection by using a package defined connection +// function +func (w *Websocket) Connect() error { + if w.connector == nil { + return errors.New("websocket connect function not set, cannot continue") + } + w.m.Lock() + defer w.m.Unlock() + + if !w.IsEnabled() { + return errors.New(WebsocketNotEnabled) + } + if w.IsConnecting() { + return fmt.Errorf("%v Websocket already attempting to connect", + w.exchangeName) + } + if w.IsConnected() { + return fmt.Errorf("%v Websocket already connected", + w.exchangeName) + } + w.setConnectingStatus(true) + + w.dataMonitor() + + err := w.trafficMonitor() + if err != nil { + return err + } + + // flush any subscriptions from last connection if needed + w.subscriptionMutex.Lock() + w.subscriptions = nil + w.subscriptionMutex.Unlock() + + err = w.connector() + if err != nil { + w.setConnectingStatus(false) + return fmt.Errorf("%v Error connecting %s", + w.exchangeName, err) + } + + w.setConnectedStatus(true) + w.setConnectingStatus(false) + w.setInit(true) + + if !w.IsConnectionMonitorRunning() { + go w.connectionMonitor() + } + + return nil +} + +// Disable disables the exchange websocket protocol +func (w *Websocket) Disable() error { + if !w.IsConnected() || !w.IsEnabled() { + return fmt.Errorf("websocket is already disabled for exchange %s", + w.exchangeName) + } + + w.setEnabled(false) + return nil +} + +// Enable enables the exchange websocket protocol +func (w *Websocket) Enable() error { + if w.IsConnected() || w.IsEnabled() { + return fmt.Errorf("websocket is already enabled for exchange %s", + w.exchangeName) + } + + w.setEnabled(true) + return w.Connect() +} + +// dataMonitor monitors job throughput and logs if there is a back log of data +func (w *Websocket) dataMonitor() { + if w.IsDataMonitorRunning() { + return + } + w.setDataMonitorRunning(true) + w.Wg.Add(1) + + go func() { + defer func() { + for { + // Bleeds data from the websocket connection if needed + select { + case <-w.DataHandler: + default: + w.setDataMonitorRunning(false) + w.Wg.Done() + return + } + } + }() + + for { + select { + case <-w.ShutdownC: + return + case d := <-w.DataHandler: + select { + case w.ToRoutine <- d: + case <-w.ShutdownC: + return + default: + log.Warnf(log.WebsocketMgr, + "%s exchange backlog in websocket processing detected", + w.exchangeName) + select { + case w.ToRoutine <- d: + case <-w.ShutdownC: + return + } + } + } + } + }() +} + +// connectionMonitor ensures that the WS keeps connecting +func (w *Websocket) connectionMonitor() { + if w.IsConnectionMonitorRunning() { + return + } + w.setConnectionMonitorRunning(true) + timer := time.NewTimer(connectionMonitorDelay) + + for { + if w.verbose { + log.Debugf(log.WebsocketMgr, + "%v websocket: running connection monitor cycle\n", + w.exchangeName) + } + if !w.IsEnabled() { + if w.verbose { + log.Debugf(log.WebsocketMgr, + "%v websocket: connectionMonitor - websocket disabled, shutting down\n", + w.exchangeName) + } + if w.IsConnected() { + err := w.Shutdown() + if err != nil { + log.Error(log.WebsocketMgr, err) + } + } + if w.verbose { + log.Debugf(log.WebsocketMgr, + "%v websocket: connection monitor exiting\n", + w.exchangeName) + } + timer.Stop() + w.setConnectionMonitorRunning(false) + return + } + select { + case err := <-w.ReadMessageErrors: + // check if this error is a disconnection error + if isDisconnectionError(err) { + w.setInit(false) + log.Warnf(log.WebsocketMgr, + "%v websocket has been disconnected. Reason: %v", + w.exchangeName, err) + w.setConnectedStatus(false) + } else { + // pass off non disconnect errors to datahandler to manage + w.DataHandler <- err + } + case <-timer.C: + if !w.IsConnecting() && !w.IsConnected() { + err := w.Connect() + if err != nil { + log.Error(log.WebsocketMgr, err) + } + } + if !timer.Stop() { + select { + case <-timer.C: + default: + } + } + timer.Reset(connectionMonitorDelay) + } + } +} + +// Shutdown attempts to shut down a websocket connection and associated routines +// by using a package defined shutdown function +func (w *Websocket) Shutdown() error { + w.m.Lock() + defer w.m.Unlock() + + if !w.IsConnected() { + return fmt.Errorf("%v websocket: cannot shutdown a disconnected websocket", + w.exchangeName) + } + + if w.IsConnecting() { + return fmt.Errorf("%v websocket: cannot shutdown, in the process of reconnection", + w.exchangeName) + } + + if w.verbose { + log.Debugf(log.WebsocketMgr, + "%v websocket: shutting down websocket\n", + w.exchangeName) + } + + defer w.Orderbook.FlushBuffer() + + if w.Conn != nil { + if err := w.Conn.Shutdown(); err != nil { + return err + } + } + + if w.AuthConn != nil { + if err := w.AuthConn.Shutdown(); err != nil { + return err + } + } + + // flush any subscriptions from last connection if needed + w.subscriptionMutex.Lock() + w.subscriptions = nil + w.subscriptionMutex.Unlock() + + close(w.ShutdownC) + w.Wg.Wait() + w.ShutdownC = make(chan struct{}) + w.setConnectedStatus(false) + w.setConnectingStatus(false) + if w.verbose { + log.Debugf(log.WebsocketMgr, + "%v websocket: completed websocket shutdown\n", + w.exchangeName) + } + return nil +} + +// FlushChannels flushes channel subscriptions when there is a pair/asset change +func (w *Websocket) FlushChannels() error { + if !w.IsEnabled() { + return fmt.Errorf("%s websocket: service not enabled", w.exchangeName) + } + + if !w.IsConnected() { + return fmt.Errorf("%s websocket: service not connected", w.exchangeName) + } + + if w.features.Subscribe { + newsubs, err := w.GenerateSubs() + if err != nil { + return err + } + + subs, unsubs := w.GetChannelDifference(newsubs) + if w.features.Unsubscribe { + if len(unsubs) != 0 { + err := w.UnsubscribeChannels(unsubs) + if err != nil { + return err + } + } + + if len(subs) != 0 { + return w.SubscribeToChannels(subs) + } + + return nil + } else if len(unsubs) == 0 { + if len(subs) == 0 { + return nil + } + return w.SubscribeToChannels(subs) + } + // FullPayloadSubscribe means that the endpoint requires all + // subscriptions to be sent via the websocket connection e.g. if you are + // subscribed to ticker and orderbook but require trades as well, you + // would need to send ticker, orderbook and trades channel subscription + // messages. + } else if w.features.FullPayloadSubscribe { + newsubs, err := w.GenerateSubs() + if err != nil { + return err + } + + if len(newsubs) != 0 { + return w.SubscribeToChannels(newsubs) + } + return nil + } + + err := w.Shutdown() + if err != nil { + return err + } + return w.Connect() +} + +// trafficMonitor uses a timer of WebsocketTrafficLimitTime and once it expires, +// it will reconnect if the TrafficAlert channel has not received any data. The +// trafficTimer will reset on each traffic alert +func (w *Websocket) trafficMonitor() error { + if w.IsTrafficMonitorRunning() { + return errors.New("traffic monitor already running") + } + w.setTrafficMonitorRunning(true) + w.Wg.Add(1) + + go func() { + var trafficTimer = time.NewTimer(w.trafficTimeout) + pause := make(chan struct{}) + for { + select { + case <-w.ShutdownC: + if w.verbose { + log.Debugf(log.WebsocketMgr, + "%v websocket: trafficMonitor shutdown message received\n", + w.exchangeName) + } + trafficTimer.Stop() + w.setTrafficMonitorRunning(false) + w.Wg.Done() + return + case <-w.TrafficAlert: + if !trafficTimer.Stop() { + select { + case <-trafficTimer.C: + default: + } + } + w.setConnectedStatus(true) + trafficTimer.Reset(w.trafficTimeout) + case <-trafficTimer.C: // Falls through when timer runs out + if w.verbose { + log.Warnf(log.WebsocketMgr, + "%v websocket: has not received a traffic alert in %v. Reconnecting", + w.exchangeName, + w.trafficTimeout) + } + trafficTimer.Stop() + w.Wg.Done() + err := w.Shutdown() + if err != nil { + log.Errorf(log.WebsocketMgr, + "%v websocket: trafficMonitor shutdown err: %s", + w.exchangeName, err) + } + w.setTrafficMonitorRunning(false) + return + } + + // Routine pausing mechanism + go func(p chan struct{}) { + time.Sleep(defaultTrafficPeriod) + p <- struct{}{} + }(pause) + select { + case <-w.ShutdownC: + trafficTimer.Stop() + w.setTrafficMonitorRunning(false) + w.Wg.Done() + return + case <-pause: + } + } + }() + return nil +} + +func (w *Websocket) setConnectedStatus(b bool) { + w.connectionMutex.Lock() + w.connected = b + w.connectionMutex.Unlock() +} + +// IsConnected returns status of connection +func (w *Websocket) IsConnected() bool { + w.connectionMutex.RLock() + defer w.connectionMutex.RUnlock() + return w.connected +} + +func (w *Websocket) setConnectingStatus(b bool) { + w.connectionMutex.Lock() + w.connecting = b + w.connectionMutex.Unlock() +} + +// IsConnecting returns status of connecting +func (w *Websocket) IsConnecting() bool { + w.connectionMutex.RLock() + defer w.connectionMutex.RUnlock() + return w.connecting +} + +func (w *Websocket) setEnabled(b bool) { + w.connectionMutex.Lock() + w.enabled = b + w.connectionMutex.Unlock() +} + +// IsEnabled returns status of enabled +func (w *Websocket) IsEnabled() bool { + w.connectionMutex.RLock() + defer w.connectionMutex.RUnlock() + return w.enabled +} + +func (w *Websocket) setInit(b bool) { + w.connectionMutex.Lock() + w.Init = b + w.connectionMutex.Unlock() +} + +// IsInit returns status of init +func (w *Websocket) IsInit() bool { + w.connectionMutex.RLock() + defer w.connectionMutex.RUnlock() + return w.Init +} + +func (w *Websocket) setTrafficMonitorRunning(b bool) { + w.connectionMutex.Lock() + w.trafficMonitorRunning = b + w.connectionMutex.Unlock() +} + +// IsTrafficMonitorRunning returns status of the traffic monitor +func (w *Websocket) IsTrafficMonitorRunning() bool { + w.connectionMutex.RLock() + defer w.connectionMutex.RUnlock() + return w.trafficMonitorRunning +} + +func (w *Websocket) setConnectionMonitorRunning(b bool) { + w.connectionMutex.Lock() + w.connectionMonitorRunning = b + w.connectionMutex.Unlock() +} + +// IsConnectionMonitorRunning returns status of connection monitor +func (w *Websocket) IsConnectionMonitorRunning() bool { + w.connectionMutex.RLock() + defer w.connectionMutex.RUnlock() + return w.connectionMonitorRunning +} + +func (w *Websocket) setDataMonitorRunning(b bool) { + w.connectionMutex.Lock() + w.dataMonitorRunning = b + w.connectionMutex.Unlock() +} + +// IsDataMonitorRunning returns status of data monitor +func (w *Websocket) IsDataMonitorRunning() bool { + w.connectionMutex.RLock() + defer w.connectionMutex.RUnlock() + return w.dataMonitorRunning +} + +// CanUseAuthenticatedWebsocketForWrapper Handles a common check to +// verify whether a wrapper can use an authenticated websocket endpoint +func (w *Websocket) CanUseAuthenticatedWebsocketForWrapper() bool { + if w.IsConnected() && w.CanUseAuthenticatedEndpoints() { + return true + } else if w.IsConnected() && !w.CanUseAuthenticatedEndpoints() { + log.Infof(log.WebsocketMgr, + WebsocketNotAuthenticatedUsingRest, + w.exchangeName) + } + return false +} + +// SetWebsocketURL sets websocket URL and can refresh underlying connections +func (w *Websocket) SetWebsocketURL(url string, auth, reconnect bool) error { + defaultVals := url == "" || url == config.WebsocketURLNonDefaultMessage + if auth { + if defaultVals { + url = w.defaultURLAuth + } + + err := checkWebsocketURL(url) + if err != nil { + return err + } + w.runningURLAuth = url + + if w.verbose { + log.Debugf(log.WebsocketMgr, + "%s websocket: setting authenticated websocket URL: %s\n", + w.exchangeName, + url) + } + + if w.AuthConn != nil { + w.AuthConn.SetURL(url) + } + } else { + if defaultVals { + url = w.defaultURL + } + err := checkWebsocketURL(url) + if err != nil { + return err + } + w.runningURL = url + + if w.verbose { + log.Debugf(log.WebsocketMgr, + "%s websocket: setting unauthenticated websocket URL: %s\n", + w.exchangeName, + url) + } + + if w.Conn != nil { + w.Conn.SetURL(url) + } + } + + if w.IsConnected() && reconnect { + log.Debugf(log.WebsocketMgr, + "%s websocket: flushing websocket connection to %s\n", + w.exchangeName, + url) + return w.Shutdown() + } + return nil +} + +// GetWebsocketURL returns the running websocket URL +func (w *Websocket) GetWebsocketURL() string { + return w.runningURL +} + +// Initialise verifies status and connects +func (w *Websocket) Initialise() error { + if w.IsEnabled() { + if w.IsInit() { + return nil + } + return fmt.Errorf("%v websocket: already initialised", w.exchangeName) + } + w.setEnabled(w.enabled) + return nil +} + +// SetProxyAddress sets websocket proxy address +func (w *Websocket) SetProxyAddress(proxyAddr string) error { + if proxyAddr != "" { + _, err := url.ParseRequestURI(proxyAddr) + if err != nil { + return fmt.Errorf("%v websocket: cannot set proxy address error '%v'", + w.exchangeName, + err) + } + + if w.proxyAddr == proxyAddr { + return fmt.Errorf("%v websocket: cannot set proxy address to the same address '%v'", + w.exchangeName, + w.proxyAddr) + } + + log.Debugf(log.ExchangeSys, + "%s websocket: setting websocket proxy: %s\n", + w.exchangeName, + proxyAddr) + } else { + log.Debugf(log.ExchangeSys, + "%s websocket: removing websocket proxy\n", + w.exchangeName) + } + + if w.Conn != nil { + w.Conn.SetProxy(proxyAddr) + } + if w.AuthConn != nil { + w.AuthConn.SetProxy(proxyAddr) + } + + w.proxyAddr = proxyAddr + if w.IsInit() && w.IsEnabled() { + if w.IsConnected() { + err := w.Shutdown() + if err != nil { + return err + } + } + return w.Connect() + } + return nil +} + +// GetProxyAddress returns the current websocket proxy +func (w *Websocket) GetProxyAddress() string { + return w.proxyAddr +} + +// GetName returns exchange name +func (w *Websocket) GetName() string { + return w.exchangeName +} + +// GetChannelDifference finds the difference between the subscribed channels +// and the new subscription list when pairs are disabled or enabled. +func (w *Websocket) GetChannelDifference(genSubs []ChannelSubscription) (sub, unsub []ChannelSubscription) { + w.subscriptionMutex.Lock() + defer w.subscriptionMutex.Unlock() + +oldsubs: + for x := range w.subscriptions { + for y := range genSubs { + if w.subscriptions[x].Equal(&genSubs[y]) { + continue oldsubs + } + } + unsub = append(unsub, w.subscriptions[x]) + } + +newsubs: + for x := range genSubs { + for y := range w.subscriptions { + if genSubs[x].Equal(&w.subscriptions[y]) { + continue newsubs + } + } + sub = append(sub, genSubs[x]) + } + return +} + +// UnsubscribeChannels unsubscribes from a websocket channel +func (w *Websocket) UnsubscribeChannels(channels []ChannelSubscription) error { + if len(channels) == 0 { + return fmt.Errorf("%s websocket: channels not populated cannot remove", + w.exchangeName) + } + w.subscriptionMutex.Lock() + defer w.subscriptionMutex.Unlock() + +channels: + for x := range channels { + for y := range w.subscriptions { + if channels[x].Equal(&w.subscriptions[y]) { + continue channels + } + } + return fmt.Errorf("%s websocket: subscription not found in list: %+v", + w.exchangeName, + channels[x]) + } + return w.Unsubscriber(channels) +} + +// ResubscribeToChannel resubscribes to channel +func (w *Websocket) ResubscribeToChannel(subscribedChannel *ChannelSubscription) error { + err := w.UnsubscribeChannels([]ChannelSubscription{*subscribedChannel}) + if err != nil { + return err + } + return w.SubscribeToChannels([]ChannelSubscription{*subscribedChannel}) +} + +// SubscribeToChannels appends supplied channels to channelsToSubscribe +func (w *Websocket) SubscribeToChannels(channels []ChannelSubscription) error { + if len(channels) == 0 { + return fmt.Errorf("%s websocket: cannot subscribe no channels supplied", + w.exchangeName) + } + w.subscriptionMutex.Lock() + defer w.subscriptionMutex.Unlock() + for x := range channels { + for y := range w.subscriptions { + if channels[x].Equal(&w.subscriptions[y]) { + return fmt.Errorf("%s websocket: %v already subscribed", + w.exchangeName, + channels[x]) + } + } + } + return w.Subscriber(channels) +} + +// AddSuccessfulSubscriptions adds subscriptions to the subscription lists that +// has been successfully subscribed +func (w *Websocket) AddSuccessfulSubscriptions(channels ...ChannelSubscription) { + w.subscriptions = append(w.subscriptions, channels...) +} + +// RemoveSuccessfulUnsubscriptions removes subscriptions from the subscription +// list that has been successfulling unsubscribed +func (w *Websocket) RemoveSuccessfulUnsubscriptions(channels ...ChannelSubscription) { + for x := range channels { + for y := range w.subscriptions { + if channels[x].Equal(&w.subscriptions[y]) { + w.subscriptions[y] = w.subscriptions[len(w.subscriptions)-1] + w.subscriptions[len(w.subscriptions)-1] = ChannelSubscription{} + w.subscriptions = w.subscriptions[:len(w.subscriptions)-1] + break + } + } + } +} + +// Equal two WebsocketChannelSubscription to determine equality +func (w *ChannelSubscription) Equal(s *ChannelSubscription) bool { + return strings.EqualFold(w.Channel, s.Channel) && + w.Currency.Equal(s.Currency) +} + +// GetSubscriptions returns a copied list of subscriptions +// subscriptions is a private member and cannot be manipulated +func (w *Websocket) GetSubscriptions() []ChannelSubscription { + w.subscriptionMutex.Lock() + defer w.subscriptionMutex.Unlock() + return append(w.subscriptions[:0:0], w.subscriptions...) +} + +// SetCanUseAuthenticatedEndpoints sets canUseAuthenticatedEndpoints val in +// a thread safe manner +func (w *Websocket) SetCanUseAuthenticatedEndpoints(val bool) { + w.subscriptionMutex.Lock() + defer w.subscriptionMutex.Unlock() + w.canUseAuthenticatedEndpoints = val +} + +// CanUseAuthenticatedEndpoints gets canUseAuthenticatedEndpoints val in +// a thread safe manner +func (w *Websocket) CanUseAuthenticatedEndpoints() bool { + w.subscriptionMutex.Lock() + defer w.subscriptionMutex.Unlock() + return w.canUseAuthenticatedEndpoints +} + +// isDisconnectionError Determines if the error sent over chan ReadMessageErrors is a disconnection error +func isDisconnectionError(err error) bool { + if websocket.IsUnexpectedCloseError(err) { + return true + } + switch e := err.(type) { + case *websocket.CloseError: + return true + case *net.OpError: + if e.Err.Error() == "use of closed network connection" { + return false + } + return true + } + return false +} + +// checkWebsocketURL checks for a valid websocket url +func checkWebsocketURL(s string) error { + u, err := url.Parse(s) + if err != nil { + return err + } + if u.Scheme != "ws" && u.Scheme != "wss" { + return fmt.Errorf("cannot set invalid websocket URL %s", s) + } + return nil +} diff --git a/exchanges/stream/websocket_connection.go b/exchanges/stream/websocket_connection.go new file mode 100644 index 00000000..8e1179ee --- /dev/null +++ b/exchanges/stream/websocket_connection.go @@ -0,0 +1,314 @@ +package stream + +import ( + "bytes" + "compress/flate" + "compress/gzip" + "crypto/rand" + "fmt" + "io/ioutil" + "math/big" + "net" + "net/http" + "net/url" + "sync/atomic" + "time" + + "github.com/gorilla/websocket" + "github.com/thrasher-corp/gocryptotrader/log" +) + +// SendMessageReturnResponse will send a WS message to the connection and wait +// for response +func (w *WebsocketConnection) SendMessageReturnResponse(signature, request interface{}) ([]byte, error) { + m, err := w.Match.set(signature) + if err != nil { + return nil, err + } + defer m.Cleanup() + + err = w.SendJSONMessage(request) + if err != nil { + return nil, err + } + + timer := time.NewTimer(w.ResponseMaxLimit) + + select { + case payload := <-m.C: + return payload, nil + case <-timer.C: + timer.Stop() + return nil, fmt.Errorf("%s websocket connection: timeout waiting for response with signature: %v", + w.ExchangeName, + signature) + } +} + +// Dial sets proxy urls and then connects to the websocket +func (w *WebsocketConnection) Dial(dialer *websocket.Dialer, headers http.Header) error { + if w.ProxyURL != "" { + proxy, err := url.Parse(w.ProxyURL) + if err != nil { + return err + } + dialer.Proxy = http.ProxyURL(proxy) + } + + var err error + var conStatus *http.Response + + w.Connection, conStatus, err = dialer.Dial(w.URL, headers) + if err != nil { + if conStatus != nil { + return fmt.Errorf("%s websocket connection: %v %v %v Error: %v", + w.ExchangeName, + w.URL, + conStatus, + conStatus.StatusCode, + err) + } + return fmt.Errorf("%s websocket connection: %v Error: %v", + w.ExchangeName, + w.URL, + err) + } + defer conStatus.Body.Close() + + if w.Verbose { + log.Infof(log.WebsocketMgr, + "%v Websocket connected to %s\n", + w.ExchangeName, + w.URL) + } + select { + case w.Traffic <- struct{}{}: + default: + } + w.setConnectedStatus(true) + return nil +} + +// SendJSONMessage sends a JSON encoded message over the connection +func (w *WebsocketConnection) SendJSONMessage(data interface{}) error { + if !w.IsConnected() { + return fmt.Errorf("%s websocket connection: cannot send message to a disconnected websocket", + w.ExchangeName) + } + + w.writeControl.Lock() + defer w.writeControl.Unlock() + + if w.Verbose { + log.Debugf(log.WebsocketMgr, + "%s websocket connection: sending message to websocket %+v\n", + w.ExchangeName, + data) + } + + if w.RateLimit > 0 { + time.Sleep(time.Duration(w.RateLimit) * time.Millisecond) + if !w.IsConnected() { + return fmt.Errorf("%v websocket connection: cannot send message to a disconnected websocket", + w.ExchangeName) + } + } + return w.Connection.WriteJSON(data) +} + +// SendRawMessage sends a message over the connection without JSON encoding it +func (w *WebsocketConnection) SendRawMessage(messageType int, message []byte) error { + if !w.IsConnected() { + return fmt.Errorf("%v websocket connection: cannot send message to a disconnected websocket", + w.ExchangeName) + } + + w.writeControl.Lock() + defer w.writeControl.Unlock() + + if w.Verbose { + log.Debugf(log.WebsocketMgr, + "%v websocket connection: sending message [%s]\n", + w.ExchangeName, + message) + } + if w.RateLimit > 0 { + time.Sleep(time.Duration(w.RateLimit) * time.Millisecond) + if !w.IsConnected() { + return fmt.Errorf("%v websocket connection: cannot send message to a disconnected websocket", + w.ExchangeName) + } + } + if !w.IsConnected() { + return fmt.Errorf("%v websocket connection: cannot send message to a disconnected websocket", + w.ExchangeName) + } + return w.Connection.WriteMessage(messageType, message) +} + +// SetupPingHandler will automatically send ping or pong messages based on +// WebsocketPingHandler configuration +func (w *WebsocketConnection) SetupPingHandler(handler PingHandler) { + if handler.UseGorillaHandler { + h := func(msg string) error { + err := w.Connection.WriteControl(handler.MessageType, + []byte(msg), + time.Now().Add(handler.Delay)) + if err == websocket.ErrCloseSent { + return nil + } else if e, ok := err.(net.Error); ok && e.Temporary() { + return nil + } + return err + } + w.Connection.SetPingHandler(h) + return + } + w.Wg.Add(1) + defer w.Wg.Done() + go func() { + ticker := time.NewTicker(handler.Delay) + for { + select { + case <-w.ShutdownC: + ticker.Stop() + return + case <-ticker.C: + err := w.SendRawMessage(handler.MessageType, handler.Message) + if err != nil { + log.Errorf(log.WebsocketMgr, + "%v websocket connection: ping handler failed to send message [%s]", + w.ExchangeName, + handler.Message) + return + } + } + } + }() +} + +func (w *WebsocketConnection) setConnectedStatus(b bool) { + if b { + atomic.StoreInt32(&w.connected, 1) + return + } + atomic.StoreInt32(&w.connected, 0) +} + +// IsConnected exposes websocket connection status +func (w *WebsocketConnection) IsConnected() bool { + return atomic.LoadInt32(&w.connected) == 1 +} + +// ReadMessage reads messages, can handle text, gzip and binary +func (w *WebsocketConnection) ReadMessage() Response { + mType, resp, err := w.Connection.ReadMessage() + if err != nil { + if isDisconnectionError(err) { + w.setConnectedStatus(false) + w.readMessageErrors <- err + } + return Response{} + } + + select { + case w.Traffic <- struct{}{}: + default: // causes contention, just bypass if there is no receiver. + } + + var standardMessage []byte + switch mType { + case websocket.TextMessage: + standardMessage = resp + case websocket.BinaryMessage: + standardMessage, err = w.parseBinaryResponse(resp) + if err != nil { + log.Errorf(log.WebsocketMgr, + "%v websocket connection: parseBinaryResponse error: %v", + w.ExchangeName, + err) + return Response{} + } + } + if w.Verbose { + log.Debugf(log.WebsocketMgr, + "%v websocket connection: message received: %v", + w.ExchangeName, + string(standardMessage)) + } + return Response{Raw: standardMessage, Type: mType} +} + +// parseBinaryResponse parses a websocket binary response into a usable byte array +func (w *WebsocketConnection) parseBinaryResponse(resp []byte) ([]byte, error) { + var standardMessage []byte + var err error + // Detect GZIP + if resp[0] == 31 && resp[1] == 139 { + b := bytes.NewReader(resp) + var gReader *gzip.Reader + gReader, err = gzip.NewReader(b) + if err != nil { + return standardMessage, err + } + standardMessage, err = ioutil.ReadAll(gReader) + if err != nil { + return standardMessage, err + } + err = gReader.Close() + if err != nil { + return standardMessage, err + } + } else { + reader := flate.NewReader(bytes.NewReader(resp)) + standardMessage, err = ioutil.ReadAll(reader) + if err != nil { + return standardMessage, err + } + err = reader.Close() + if err != nil { + return standardMessage, err + } + } + return standardMessage, nil +} + +// GenerateMessageID Creates a messageID to checkout +func (w *WebsocketConnection) GenerateMessageID(highPrec bool) int64 { + var min int64 = 1e8 + var max int64 = 2e8 + if highPrec { + max = 2e12 + min = 1e12 + } + // utlization of hard coded positive numbers and default crypto/rand + // io.reader will panic on error instead of returning + randomNumber, err := rand.Int(rand.Reader, big.NewInt(max-min+1)) + if err != nil { + panic(err) + } + return randomNumber.Int64() + min +} + +// Shutdown shuts down and closes specific connection +func (w *WebsocketConnection) Shutdown() error { + if w == nil || w.Connection == nil { + return nil + } + return w.Connection.UnderlyingConn().Close() +} + +// SetURL sets connection URL +func (w *WebsocketConnection) SetURL(url string) { + w.URL = url +} + +// SetProxy sets connection proxy +func (w *WebsocketConnection) SetProxy(proxy string) { + w.ProxyURL = proxy +} + +// GetURL returns the connection URL +func (w *WebsocketConnection) GetURL() string { + return w.URL +} diff --git a/exchanges/stream/websocket_test.go b/exchanges/stream/websocket_test.go new file mode 100644 index 00000000..995115e7 --- /dev/null +++ b/exchanges/stream/websocket_test.go @@ -0,0 +1,957 @@ +package stream + +import ( + "bytes" + "compress/flate" + "compress/gzip" + "encoding/json" + "errors" + "fmt" + "net" + "net/http" + "strconv" + "strings" + "sync" + "testing" + "time" + + "github.com/gorilla/websocket" + "github.com/thrasher-corp/gocryptotrader/currency" + "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" +) + +const ( + websocketTestURL = "wss://www.bitmex.com/realtime" + useProxyTests = false // Disabled by default. Freely available proxy servers that work all the time are difficult to find + proxyURL = "http://212.186.171.4:80" // Replace with a usable proxy server +) + +var dialer websocket.Dialer + +type testStruct struct { + Error error + WC WebsocketConnection +} + +type testRequest struct { + Event string `json:"event"` + RequestID int64 `json:"reqid,omitempty"` + Pairs []string `json:"pair"` + Subscription testRequestData `json:"subscription,omitempty"` +} + +// testRequestData contains details on WS channel +type testRequestData struct { + Name string `json:"name,omitempty"` + Interval int64 `json:"interval,omitempty"` + Depth int64 `json:"depth,omitempty"` +} + +type testResponse struct { + RequestID int64 `json:"reqid,omitempty"` +} + +var defaultSetup = &WebsocketSetup{ + Enabled: true, + AuthenticatedWebsocketAPISupport: true, + WebsocketTimeout: time.Second * 5, + DefaultURL: "testDefaultURL", + ExchangeName: "exchangeName", + RunningURL: "wss://testRunningURL", + Connector: func() error { return nil }, + Subscriber: func(_ []ChannelSubscription) error { return nil }, + UnSubscriber: func(_ []ChannelSubscription) error { return nil }, + GenerateSubscriptions: func() ([]ChannelSubscription, error) { + return []ChannelSubscription{ + {Channel: "TestSub"}, + {Channel: "TestSub2"}, + {Channel: "TestSub3"}, + {Channel: "TestSub4"}, + }, nil + }, + Features: &protocol.Features{Subscribe: true, Unsubscribe: true}, +} + +func TestTrafficMonitorTimeout(t *testing.T) { + ws := *New() + err := ws.Setup(defaultSetup) + if err != nil { + t.Fatal(err) + } + ws.trafficTimeout = time.Second + ws.ShutdownC = make(chan struct{}) + err = ws.trafficMonitor() + if err != nil { + t.Fatal(err) + } + // try to add another traffic monitor + err = ws.trafficMonitor() + if err == nil { + t.Fatal("expected not allowed") + } + // Deploy traffic alert + ws.TrafficAlert <- struct{}{} + time.Sleep(time.Second * 2) + ws.Wg.Wait() + if ws.IsTrafficMonitorRunning() { + t.Error("should be ded") + } +} + +func TestIsDisconnectionError(t *testing.T) { + isADisconnectionError := isDisconnectionError(errors.New("errorText")) + if isADisconnectionError { + t.Error("Its not") + } + isADisconnectionError = isDisconnectionError(&websocket.CloseError{ + Code: 1006, + Text: "errorText", + }) + if !isADisconnectionError { + t.Error("It is") + } + + isADisconnectionError = isDisconnectionError(&net.OpError{ + Op: "", + Net: "", + Source: nil, + Addr: nil, + Err: errors.New("errorText"), + }) + if !isADisconnectionError { + t.Error("It is") + } +} + +func TestConnectionMessageErrors(t *testing.T) { + ws := *New() + err := ws.Setup(defaultSetup) + if err != nil { + t.Fatal(err) + } + ws.trafficTimeout = time.Minute + ws.connector = func() error { return nil } + + err = ws.Connect() + if err != nil { + t.Fatal(err) + } + + ws.TrafficAlert <- struct{}{} + + timer := time.NewTimer(900 * time.Millisecond) + ws.ReadMessageErrors <- errors.New("errorText") + select { + case err := <-ws.ToRoutine: + if err.(error).Error() != "errorText" { + t.Errorf("Expected 'errorText', received %v", err) + } + case <-timer.C: + t.Error("Timeout waiting for datahandler to receive error") + } + ws.ReadMessageErrors <- &websocket.CloseError{ + Code: 1006, + Text: "errorText", + } +outer: + for { + select { + case <-ws.ToRoutine: + t.Fatal("Error is a disconnection error") + case <-timer.C: + break outer + } + } +} + +func TestWebsocket(t *testing.T) { + ws := Websocket{} + err := ws.Setup(&WebsocketSetup{ + ExchangeName: "test", + Enabled: true, + }) + if err != nil && err.Error() != "test Websocket already initialised" { + t.Errorf("Expected 'test Websocket already initialised', received %v", err) + } + + ws = *New() + err = ws.SetProxyAddress("garbagio") + if err == nil { + t.Error("error cannot be nil") + } + err = ws.SetProxyAddress("https://192.168.0.1:1337") + if err != nil { + t.Error("SetProxyAddress", err) + } + // removing proxy + err = ws.SetProxyAddress("") + if err != nil { + t.Error(err) + } + // reinstate proxy + err = ws.SetProxyAddress("http://localhost:1337") + if err != nil { + t.Error(err) + } + // conflict proxy + err = ws.SetProxyAddress("http://localhost:1337") + if err == nil { + t.Error("error cannot be nil") + } + err = ws.Setup(defaultSetup) + if err != nil { + t.Fatal(err) + } + if ws.GetName() != "exchangeName" { + t.Error("WebsocketSetup") + } + + if !ws.IsEnabled() { + t.Error("WebsocketSetup") + } + + ws.setEnabled(false) + if ws.IsEnabled() { + t.Error("WebsocketSetup") + } + ws.setEnabled(true) + if !ws.IsEnabled() { + t.Error("WebsocketSetup") + } + + if ws.GetProxyAddress() != "http://localhost:1337" { + t.Error("WebsocketSetup") + } + + if ws.GetWebsocketURL() != "wss://testRunningURL" { + t.Error("WebsocketSetup") + } + if ws.trafficTimeout != time.Second*5 { + t.Error("WebsocketSetup") + } + // -- Not connected shutdown + err = ws.Shutdown() + if err == nil { + t.Fatal("should not be connected to able to shut down") + } + // -- Normal connect + err = ws.Connect() + if err != nil { + t.Fatal("WebsocketSetup", err) + } + err = ws.SetWebsocketURL("ws://demos.kaazing.com/echo", false, false) + if err != nil { + t.Fatal(err) + } + err = ws.SetWebsocketURL("ws://demos.kaazing.com/echo", true, false) + if err != nil { + t.Fatal(err) + } + // -- Already connected connect + err = ws.Connect() + if err == nil { + t.Fatal("should not connect, already connected") + } + // -- Normal shutdown + err = ws.Shutdown() + if err != nil { + t.Fatal("WebsocketSetup", err) + } + ws.Wg.Wait() +} + +// TestSubscribe logic test +func TestSubscribeUnsubscribe(t *testing.T) { + ws := *New() + err := ws.Setup(defaultSetup) + if err != nil { + t.Fatal(err) + } + + fnSub := func(subs []ChannelSubscription) error { + ws.AddSuccessfulSubscriptions(subs...) + return nil + } + fnUnsub := func(unsubs []ChannelSubscription) error { + ws.RemoveSuccessfulUnsubscriptions(unsubs...) + return nil + } + ws.Subscriber = fnSub + ws.Unsubscriber = fnUnsub + + err = ws.UnsubscribeChannels(nil) + if err == nil { + t.Fatal("error cannot be nil") + } + + // Generate test sub + subs, err := ws.GenerateSubs() + if err != nil { + t.Fatal(err) + } + + // unsub when no subscribed channel + err = ws.UnsubscribeChannels(subs) + if err == nil { + t.Fatal("error cannot be nil") + } + + err = ws.SubscribeToChannels(subs) + if err != nil { + t.Fatal(err) + } + + // subscribe when already subscribed + err = ws.SubscribeToChannels(subs) + if err == nil { + t.Fatal("error cannot be nil") + } + + err = ws.UnsubscribeChannels(subs) + if err != nil { + t.Fatal(err) + } +} + +func TestResubscribe(t *testing.T) { + ws := *New() + err := ws.Setup(defaultSetup) + if err != nil { + t.Fatal(err) + } + + fnSub := func(subs []ChannelSubscription) error { + ws.AddSuccessfulSubscriptions(subs...) + return nil + } + fnUnsub := func(unsubs []ChannelSubscription) error { + ws.RemoveSuccessfulUnsubscriptions(unsubs...) + return nil + } + ws.Subscriber = fnSub + ws.Unsubscriber = fnUnsub + + channel := []ChannelSubscription{{Channel: "resubTest"}} + err = ws.ResubscribeToChannel(&channel[0]) + if err == nil { + t.Fatal("error cannot be nil") + } + + err = ws.SubscribeToChannels(channel) + if err != nil { + t.Fatal(err) + } + + err = ws.ResubscribeToChannel(&channel[0]) + if err != nil { + t.Fatal("error cannot be nil") + } +} + +// TestConnectionMonitorNoConnection logic test +func TestConnectionMonitorNoConnection(t *testing.T) { + ws := *New() + ws.DataHandler = make(chan interface{}, 1) + ws.ShutdownC = make(chan struct{}, 1) + ws.exchangeName = "hello" + ws.trafficTimeout = 1 + go ws.connectionMonitor() + time.Sleep(time.Second) + if ws.IsConnectionMonitorRunning() { + t.Fatal("Should have exited") + } +} + +// TestSliceCopyDoesntImpactBoth logic test +func TestGetSubscriptions(t *testing.T) { + w := Websocket{ + subscriptions: []ChannelSubscription{ + { + Channel: "hello3", + }, + }, + } + if !strings.EqualFold("hello3", w.GetSubscriptions()[0].Channel) { + t.Error("Subscriptions was not copied properly") + } +} + +// TestSetCanUseAuthenticatedEndpoints logic test +func TestSetCanUseAuthenticatedEndpoints(t *testing.T) { + ws := *New() + result := ws.CanUseAuthenticatedEndpoints() + if result { + t.Error("expected `canUseAuthenticatedEndpoints` to be false") + } + ws.SetCanUseAuthenticatedEndpoints(true) + result = ws.CanUseAuthenticatedEndpoints() + if !result { + t.Error("expected `canUseAuthenticatedEndpoints` to be true") + } +} + +// TestDial logic test +func TestDial(t *testing.T) { + var testCases = []testStruct{ + {Error: nil, + WC: WebsocketConnection{ + ExchangeName: "test1", + Verbose: true, + URL: websocketTestURL, + RateLimit: 10, + ResponseMaxLimit: 7000000000, + }, + }, + {Error: errors.New(" Error: malformed ws or wss URL"), + WC: WebsocketConnection{ + ExchangeName: "test2", + Verbose: true, + URL: "", + ResponseMaxLimit: 7000000000, + }, + }, + {Error: nil, + WC: WebsocketConnection{ + ExchangeName: "test3", + Verbose: true, + URL: websocketTestURL, + ProxyURL: proxyURL, + ResponseMaxLimit: 7000000000, + }, + }, + } + for i := range testCases { + testData := &testCases[i] + t.Run(testData.WC.ExchangeName, func(t *testing.T) { + if testData.WC.ProxyURL != "" && !useProxyTests { + t.Skip("Proxy testing not enabled, skipping") + } + err := testData.WC.Dial(&dialer, http.Header{}) + if err != nil { + if testData.Error != nil && strings.Contains(err.Error(), testData.Error.Error()) { + return + } + t.Fatal(err) + } + }) + } +} + +// TestSendMessage logic test +func TestSendMessage(t *testing.T) { + var testCases = []testStruct{ + {Error: nil, WC: WebsocketConnection{ + ExchangeName: "test1", + Verbose: true, + URL: websocketTestURL, + RateLimit: 10, + ResponseMaxLimit: 7000000000, + }, + }, + {Error: errors.New(" Error: malformed ws or wss URL"), + WC: WebsocketConnection{ + ExchangeName: "test2", + Verbose: true, + URL: "", + ResponseMaxLimit: 7000000000, + }, + }, + {Error: nil, + WC: WebsocketConnection{ + ExchangeName: "test3", + Verbose: true, + URL: websocketTestURL, + ProxyURL: proxyURL, + ResponseMaxLimit: 7000000000, + }, + }, + } + for i := range testCases { + testData := &testCases[i] + t.Run(testData.WC.ExchangeName, func(t *testing.T) { + if testData.WC.ProxyURL != "" && !useProxyTests { + t.Skip("Proxy testing not enabled, skipping") + } + err := testData.WC.Dial(&dialer, http.Header{}) + if err != nil { + if testData.Error != nil && strings.Contains(err.Error(), testData.Error.Error()) { + return + } + t.Fatal(err) + } + err = testData.WC.SendJSONMessage(Ping) + if err != nil { + t.Error(err) + } + err = testData.WC.SendRawMessage(websocket.TextMessage, []byte(Ping)) + if err != nil { + t.Error(err) + } + }) + } +} + +// TestSendMessageWithResponse logic test +func TestSendMessageWithResponse(t *testing.T) { + wc := &WebsocketConnection{ + Verbose: true, + URL: "wss://echo.websocket.org", + ResponseMaxLimit: time.Second * 5, + Match: NewMatch(), + } + if wc.ProxyURL != "" && !useProxyTests { + t.Skip("Proxy testing not enabled, skipping") + } + + err := wc.Dial(&dialer, http.Header{}) + if err != nil { + t.Fatal(err) + } + + go readMessages(wc, t) + + request := testRequest{ + Event: "subscribe", + Pairs: []string{currency.NewPairWithDelimiter("XBT", "USD", "/").String()}, + Subscription: testRequestData{ + Name: "ticker", + }, + RequestID: wc.GenerateMessageID(false), + } + + _, err = wc.SendMessageReturnResponse(request.RequestID, request) + if err != nil { + t.Error(err) + } +} + +// readMessages helper func +func readMessages(wc *WebsocketConnection, t *testing.T) { + timer := time.NewTimer(20 * time.Second) + for { + select { + case <-timer.C: + return + default: + resp := wc.ReadMessage() + if resp.Raw == nil { + t.Error("connection has closed") + return + } + var incoming testResponse + err := json.Unmarshal(resp.Raw, &incoming) + if err != nil { + t.Error(err) + return + } + if incoming.RequestID > 0 { + wc.Match.IncomingWithData(incoming.RequestID, resp.Raw) + return + } + } + } +} + +// TestSetupPingHandler logic test +func TestSetupPingHandler(t *testing.T) { + wc := &WebsocketConnection{ + URL: "wss://echo.websocket.org", + ResponseMaxLimit: time.Second * 5, + Match: NewMatch(), + Wg: &sync.WaitGroup{}, + } + + if wc.ProxyURL != "" && !useProxyTests { + t.Skip("Proxy testing not enabled, skipping") + } + wc.ShutdownC = make(chan struct{}) + err := wc.Dial(&dialer, http.Header{}) + if err != nil { + t.Fatal(err) + } + + wc.SetupPingHandler(PingHandler{ + UseGorillaHandler: true, + MessageType: websocket.PingMessage, + Delay: 1000, + }) + + err = wc.Connection.Close() + if err != nil { + t.Error(err) + } + + err = wc.Dial(&dialer, http.Header{}) + if err != nil { + t.Fatal(err) + } + wc.SetupPingHandler(PingHandler{ + MessageType: websocket.TextMessage, + Message: []byte(Ping), + Delay: 200, + }) + time.Sleep(time.Millisecond * 500) + close(wc.ShutdownC) + wc.Wg.Wait() +} + +// TestParseBinaryResponse logic test +func TestParseBinaryResponse(t *testing.T) { + wc := &WebsocketConnection{ + URL: "wss://echo.websocket.org", + ResponseMaxLimit: time.Second * 5, + Match: NewMatch(), + } + + var b bytes.Buffer + w := gzip.NewWriter(&b) + _, err := w.Write([]byte("hello")) + if err != nil { + t.Error(err) + } + err = w.Close() + if err != nil { + t.Error(err) + } + var resp []byte + resp, err = wc.parseBinaryResponse(b.Bytes()) + if err != nil { + t.Error(err) + } + if !strings.EqualFold(string(resp), "hello") { + t.Errorf("GZip conversion failed. Received: '%v', Expected: 'hello'", string(resp)) + } + + var b2 bytes.Buffer + w2, err2 := flate.NewWriter(&b2, 1) + if err2 != nil { + t.Error(err2) + } + _, err2 = w2.Write([]byte("hello")) + if err2 != nil { + t.Error(err) + } + err2 = w2.Close() + if err2 != nil { + t.Error(err) + } + resp2, err3 := wc.parseBinaryResponse(b2.Bytes()) + if err3 != nil { + t.Error(err3) + } + if !strings.EqualFold(string(resp2), "hello") { + t.Errorf("GZip conversion failed. Received: '%v', Expected: 'hello'", string(resp2)) + } +} + +// TestCanUseAuthenticatedWebsocketForWrapper logic test +func TestCanUseAuthenticatedWebsocketForWrapper(t *testing.T) { + ws := &Websocket{} + resp := ws.CanUseAuthenticatedWebsocketForWrapper() + if resp { + t.Error("Expected false, `connected` is false") + } + ws.setConnectedStatus(true) + resp = ws.CanUseAuthenticatedWebsocketForWrapper() + if resp { + t.Error("Expected false, `connected` is true and `CanUseAuthenticatedEndpoints` is false") + } + ws.canUseAuthenticatedEndpoints = true + resp = ws.CanUseAuthenticatedWebsocketForWrapper() + if !resp { + t.Error("Expected true, `connected` and `CanUseAuthenticatedEndpoints` is true") + } +} + +func TestGenerateMessageID(t *testing.T) { + wc := WebsocketConnection{} + var id int64 + for i := 0; i < 10; i++ { + newID := wc.GenerateMessageID(true) + if id == newID { + t.Fatal("ID generation is not unique") + } + id = newID + } +} + +// BenchmarkGenerateMessageID-8 2850018 408 ns/op 56 B/op 4 allocs/op +func BenchmarkGenerateMessageID_High(b *testing.B) { + wc := WebsocketConnection{} + for i := 0; i < b.N; i++ { + _ = wc.GenerateMessageID(true) + } +} + +// BenchmarkGenerateMessageID_Low-8 2591596 447 ns/op 56 B/op 4 allocs/op +func BenchmarkGenerateMessageID_Low(b *testing.B) { + wc := WebsocketConnection{} + for i := 0; i < b.N; i++ { + _ = wc.GenerateMessageID(false) + } +} + +func TestCheckWebsocketURL(t *testing.T) { + err := checkWebsocketURL("") + if err == nil { + t.Fatal("error cannot be nil") + } + + err = checkWebsocketURL("wowowow:wowowowo") + if err == nil { + t.Fatal("error cannot be nil") + } + + err = checkWebsocketURL("://") + if err == nil { + t.Fatal("error cannot be nil") + } + + err = checkWebsocketURL("http://www.google.com") + if err == nil { + t.Fatal("error cannot be nil") + } + + err = checkWebsocketURL("wss://websocketconnection.place") + if err != nil { + t.Fatal(err) + } + + err = checkWebsocketURL("ws://websocketconnection.place") + if err != nil { + t.Fatal(err) + } +} + +func TestGetChannelDifference(t *testing.T) { + web := Websocket{} + + newChans := []ChannelSubscription{ + { + Channel: "Test1", + }, + { + Channel: "Test2", + }, + { + Channel: "Test3", + }, + } + subs, unsubs := web.GetChannelDifference(newChans) + if len(subs) != 3 { + t.Fatal("error mismatch") + } + + if len(unsubs) != 0 { + t.Fatal("error mismatch") + } + + web.subscriptions = subs + + flushedSubs := []ChannelSubscription{ + { + Channel: "Test2", + }, + } + + subs, unsubs = web.GetChannelDifference(flushedSubs) + if len(subs) != 0 { + t.Fatal("error mismatch") + } + if len(unsubs) != 2 { + t.Fatal("error mismatch") + } + + flushedSubs = []ChannelSubscription{ + { + Channel: "Test2", + }, + { + Channel: "Test4", + }, + } + + subs, unsubs = web.GetChannelDifference(flushedSubs) + if len(subs) != 1 { + t.Fatal("error mismatch") + } + if len(unsubs) != 2 { + t.Fatal("error mismatch") + } +} + +// GenSubs defines a theoretical exchange with pair management +type GenSubs struct { + EnabledPairs currency.Pairs + subscribos []ChannelSubscription + unsubscribos []ChannelSubscription +} + +// generateSubs default subs created from the enabled pairs list +func (g *GenSubs) generateSubs() ([]ChannelSubscription, error) { + var superduperchannelsubs []ChannelSubscription + for i := range g.EnabledPairs { + superduperchannelsubs = append(superduperchannelsubs, ChannelSubscription{ + Channel: "TEST:" + strconv.FormatInt(int64(i), 10), + Currency: g.EnabledPairs[i], + }) + } + return superduperchannelsubs, nil +} + +func (g *GenSubs) SUBME(subs []ChannelSubscription) error { + if len(subs) == 0 { + return errors.New("WOW") + } + g.subscribos = subs + return nil +} + +func (g *GenSubs) UNSUBME(unsubs []ChannelSubscription) error { + if len(unsubs) == 0 { + return errors.New("WOW") + } + g.unsubscribos = unsubs + return nil +} + +// sneaky connect func +func connect() error { return nil } + +func TestFlushChannels(t *testing.T) { + // Enabled pairs/setup system + newgen := GenSubs{EnabledPairs: []currency.Pair{ + currency.NewPair(currency.BTC, currency.AUD), + currency.NewPair(currency.BTC, currency.USDT), + }} + web := Websocket{enabled: true, + connected: true, + connector: connect, + ShutdownC: make(chan struct{}), + Subscriber: newgen.SUBME, + Unsubscriber: newgen.UNSUBME, + Wg: new(sync.WaitGroup), + features: &protocol.Features{ + // No features + }} + web.GenerateSubs = newgen.generateSubs + subs, err := web.GenerateSubs() + if err != nil { + t.Fatal(err) + } + web.subscriptions = subs + // Disable pair and flush system + newgen.EnabledPairs = []currency.Pair{ + currency.NewPair(currency.BTC, currency.AUD)} + err = web.FlushChannels() + if err != nil { + t.Fatal(err) + } + + web.features.FullPayloadSubscribe = true + err = web.FlushChannels() + if err != nil { + t.Fatal(err) + } + web.features.FullPayloadSubscribe = false + web.features.Subscribe = true + err = web.FlushChannels() + if err != nil { + t.Fatal(err) + } + web.setConnectedStatus(true) + web.features.Unsubscribe = true + err = web.FlushChannels() + if err != nil { + t.Fatal(err) + } +} + +func TestDisable(t *testing.T) { + web := Websocket{ + enabled: true, + connected: true, + ShutdownC: make(chan struct{}), + } + err := web.Disable() + if err != nil { + t.Fatal(err) + } + err = web.Disable() + if err == nil { + t.Fatal("should already be disabled") + } +} + +func TestEnable(t *testing.T) { + web := Websocket{ + connector: connect, + Wg: new(sync.WaitGroup), + ShutdownC: make(chan struct{}), + } + err := web.Enable() + if err != nil { + t.Fatal(err) + } + + err = web.Enable() + if err == nil { + t.Fatal("should already be enabled") + } + + fmt.Print() +} + +func TestSetupNewConnection(t *testing.T) { + web := Websocket{ + connector: connect, + Wg: new(sync.WaitGroup), + ShutdownC: make(chan struct{}), + Init: true, + TrafficAlert: make(chan struct{}), + ReadMessageErrors: make(chan error), + } + + err := web.Setup(defaultSetup) + if err != nil { + t.Fatal(err) + } + err = web.SetupNewConnection(ConnectionSetup{}) + if err == nil { + t.Fatal("error cannot be nil") + } + err = web.SetupNewConnection(ConnectionSetup{URL: "urlstring"}) + if err != nil { + t.Fatal(err) + } + err = web.SetupNewConnection(ConnectionSetup{URL: "urlstring", + Authenticated: true}) + if err != nil { + t.Fatal(err) + } +} + +func TestWebsocketConnectionShutdown(t *testing.T) { + wc := WebsocketConnection{} + err := wc.Shutdown() + if err != nil { + t.Fatal(err) + } + + err = wc.Dial(&websocket.Dialer{}, nil) + if err == nil { + t.Fatal("error cannot be nil") + } + + wc.URL = "wss://echo.websocket.org" + + err = wc.Dial(&websocket.Dialer{}, nil) + if err != nil { + t.Fatal(err) + } + + err = wc.Shutdown() + if err != nil { + t.Fatal(err) + } +} diff --git a/exchanges/stream/websocket_types.go b/exchanges/stream/websocket_types.go new file mode 100644 index 00000000..06f08518 --- /dev/null +++ b/exchanges/stream/websocket_types.go @@ -0,0 +1,132 @@ +package stream + +import ( + "sync" + "time" + + "github.com/gorilla/websocket" + "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream/buffer" +) + +// Websocket functionality list and state consts +const ( + // WebsocketNotEnabled alerts of a disabled websocket + WebsocketNotEnabled = "exchange_websocket_not_enabled" + // connection monitor time delays and limits + connectionMonitorDelay = 2 * time.Second + WebsocketNotAuthenticatedUsingRest = "%v - Websocket not authenticated, using REST\n" + Ping = "ping" + Pong = "pong" + UnhandledMessage = " - Unhandled websocket message: " +) + +// Websocket defines a return type for websocket connections via the interface +// wrapper for routine processing in routines.go +type Websocket struct { + canUseAuthenticatedEndpoints bool + enabled bool + Init bool + connected bool + connecting bool + verbose bool + connectionMonitorRunning bool + trafficMonitorRunning bool + dataMonitorRunning bool + trafficTimeout time.Duration + proxyAddr string + defaultURL string + defaultURLAuth string + runningURL string + runningURLAuth string + exchangeName string + m sync.Mutex + connectionMutex sync.RWMutex + connector func() error + + subscriptionMutex sync.Mutex + subscriptions []ChannelSubscription + Subscribe chan []ChannelSubscription + Unsubscribe chan []ChannelSubscription + + // Subscriber function for package defined websocket subscriber + // functionality + Subscriber func([]ChannelSubscription) error + // Unsubscriber function for packaged defined websocket unsubscriber + // functionality + Unsubscriber func([]ChannelSubscription) error + // GenerateSubs function for package defined websocket generate + // subscriptions functionality + GenerateSubs func() ([]ChannelSubscription, error) + + DataHandler chan interface{} + ToRoutine chan interface{} + + Match *Match + + // shutdown synchronises shutdown event across routines + ShutdownC chan struct{} + Wg *sync.WaitGroup + + // Orderbook is a local buffer of orderbooks + Orderbook buffer.Orderbook + + // trafficAlert monitors if there is a halt in traffic throughput + TrafficAlert chan struct{} + // ReadMessageErrors will received all errors from ws.ReadMessage() and + // verify if its a disconnection + ReadMessageErrors chan error + features *protocol.Features + + // Standard stream connection + Conn Connection + // Authenticated stream connection + AuthConn Connection +} + +// WebsocketSetup defines variables for setting up a websocket connection +type WebsocketSetup struct { + Enabled bool + Verbose bool + AuthenticatedWebsocketAPISupport bool + WebsocketTimeout time.Duration + DefaultURL string + ExchangeName string + RunningURL string + RunningURLAuth string + Connector func() error + Subscriber func([]ChannelSubscription) error + UnSubscriber func([]ChannelSubscription) error + GenerateSubscriptions func() ([]ChannelSubscription, error) + Features *protocol.Features + // Local orderbook buffer config values + OrderbookBufferLimit int + BufferEnabled bool + SortBuffer bool + SortBufferByUpdateIDs bool + UpdateEntriesByID bool +} + +// WebsocketConnection contains all the data needed to send a message to a WS +// connection +type WebsocketConnection struct { + Verbose bool + connected int32 + + // Gorilla websocket does not allow more than one goroutine to utilise + // writes methods + writeControl sync.Mutex + + RateLimit int64 + ExchangeName string + URL string + ProxyURL string + Wg *sync.WaitGroup + Connection *websocket.Conn + ShutdownC chan struct{} + + Match *Match + ResponseMaxLimit time.Duration + Traffic chan struct{} + readMessageErrors chan error +} diff --git a/exchanges/ticker/ticker.go b/exchanges/ticker/ticker.go index ea418b84..7f7947cc 100644 --- a/exchanges/ticker/ticker.go +++ b/exchanges/ticker/ticker.go @@ -33,7 +33,6 @@ func SubscribeTicker(exchange string, p currency.Pair, a asset.Item) (dispatch.P p, a) } - return service.mux.Subscribe(tick.Main) } @@ -80,25 +79,22 @@ func GetTicker(exchange string, p currency.Pair, tickerType asset.Item) (*Price, // ProcessTicker processes incoming tickers, creating or updating the Tickers // list -func ProcessTicker(exchangeName string, tickerNew *Price, assetType asset.Item) error { - if exchangeName == "" { +func ProcessTicker(tickerNew *Price) error { + if tickerNew.ExchangeName == "" { return fmt.Errorf(errExchangeNameUnset) } - tickerNew.ExchangeName = strings.ToLower(exchangeName) - if tickerNew.Pair.IsEmpty() { - return fmt.Errorf("%s %s", exchangeName, errPairNotSet) + return fmt.Errorf("%s %s", tickerNew.ExchangeName, errPairNotSet) } - if assetType == "" { - return fmt.Errorf("%s %s %s", exchangeName, + if tickerNew.AssetType == "" { + return fmt.Errorf("%s %s %s", + tickerNew.ExchangeName, tickerNew.Pair, errAssetTypeNotSet) } - tickerNew.AssetType = assetType - if tickerNew.LastUpdated.IsZero() { tickerNew.LastUpdated = time.Now() } @@ -108,46 +104,11 @@ func ProcessTicker(exchangeName string, tickerNew *Price, assetType asset.Item) // Update updates ticker price func (s *Service) Update(p *Price) error { - var ids []uuid.UUID - + name := strings.ToLower(p.ExchangeName) s.Lock() - switch { - case s.Tickers[p.ExchangeName] == nil: - s.Tickers[p.ExchangeName] = make(map[*currency.Item]map[*currency.Item]map[asset.Item]*Ticker) - s.Tickers[p.ExchangeName][p.Pair.Base.Item] = make(map[*currency.Item]map[asset.Item]*Ticker) - s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item] = make(map[asset.Item]*Ticker) - err := s.SetItemID(p) - if err != nil { - s.Unlock() - return err - } - case s.Tickers[p.ExchangeName][p.Pair.Base.Item] == nil: - s.Tickers[p.ExchangeName][p.Pair.Base.Item] = make(map[*currency.Item]map[asset.Item]*Ticker) - s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item] = make(map[asset.Item]*Ticker) - err := s.SetItemID(p) - if err != nil { - s.Unlock() - return err - } - - case s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item] == nil: - s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item] = make(map[asset.Item]*Ticker) - err := s.SetItemID(p) - if err != nil { - s.Unlock() - return err - } - - case s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item][p.AssetType] == nil: - err := s.SetItemID(p) - if err != nil { - s.Unlock() - return err - } - - default: - ticker := s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item][p.AssetType] + ticker, ok := s.Tickers[name][p.Pair.Base.Item][p.Pair.Quote.Item][p.AssetType] + if ok { ticker.Last = p.Last ticker.High = p.High ticker.Low = p.Low @@ -159,20 +120,39 @@ func (s *Service) Update(p *Price) error { ticker.Open = p.Open ticker.Close = p.Close ticker.LastUpdated = p.LastUpdated - ids = ticker.Assoc - ids = append(ids, ticker.Main) + ids := append(ticker.Assoc, ticker.Main) + s.Unlock() + return s.mux.Publish(ids, p) } + + switch { + case s.Tickers[name] == nil: + s.Tickers[name] = make(map[*currency.Item]map[*currency.Item]map[asset.Item]*Ticker) + fallthrough + case s.Tickers[name][p.Pair.Base.Item] == nil: + s.Tickers[name][p.Pair.Base.Item] = make(map[*currency.Item]map[asset.Item]*Ticker) + fallthrough + case s.Tickers[name][p.Pair.Base.Item][p.Pair.Quote.Item] == nil: + s.Tickers[name][p.Pair.Base.Item][p.Pair.Quote.Item] = make(map[asset.Item]*Ticker) + } + + err := s.SetItemID(p, name) + if err != nil { + s.Unlock() + return err + } + s.Unlock() - return s.mux.Publish(ids, p) + return nil } // SetItemID retrieves and sets dispatch mux publish IDs -func (s *Service) SetItemID(p *Price) error { +func (s *Service) SetItemID(p *Price, fmtName string) error { if p == nil { return errors.New(errTickerPriceIsNil) } - ids, err := s.GetAssociations(p) + ids, err := s.GetAssociations(p, fmtName) if err != nil { return err } @@ -181,26 +161,26 @@ func (s *Service) SetItemID(p *Price) error { return err } - s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item][p.AssetType] = &Ticker{Price: *p, + s.Tickers[fmtName][p.Pair.Base.Item][p.Pair.Quote.Item][p.AssetType] = &Ticker{Price: *p, Main: singleID, Assoc: ids} return nil } // GetAssociations links a singular book with it's dispatch associations -func (s *Service) GetAssociations(p *Price) ([]uuid.UUID, error) { +func (s *Service) GetAssociations(p *Price, fmtName string) ([]uuid.UUID, error) { if p == nil || *p == (Price{}) { return nil, errors.New(errTickerPriceIsNil) } var ids []uuid.UUID - exchangeID, ok := s.Exchange[p.ExchangeName] + exchangeID, ok := s.Exchange[fmtName] if !ok { var err error exchangeID, err = s.mux.GetID() if err != nil { return nil, err } - s.Exchange[p.ExchangeName] = exchangeID + s.Exchange[fmtName] = exchangeID } ids = append(ids, exchangeID) diff --git a/exchanges/ticker/ticker_test.go b/exchanges/ticker/ticker_test.go index a947b33e..feff25d7 100644 --- a/exchanges/ticker/ticker_test.go +++ b/exchanges/ticker/ticker_test.go @@ -37,32 +37,48 @@ func TestSubscribeTicker(t *testing.T) { // force error service.mux = nil - err = ProcessTicker("subscribetest", &Price{Pair: p}, asset.Spot) + err = ProcessTicker(&Price{ + Pair: p, + ExchangeName: "subscribetest", + AssetType: asset.Spot}) if err == nil { t.Error("error cannot be nil") } sillyP := p sillyP.Base = currency.GALA_NEO - err = ProcessTicker("subscribetest", &Price{Pair: sillyP}, asset.Spot) + err = ProcessTicker(&Price{ + Pair: sillyP, + ExchangeName: "subscribetest", + AssetType: asset.Spot}) if err == nil { t.Error("error cannot be nil") } sillyP.Quote = currency.AAA - err = ProcessTicker("subscribetest", &Price{Pair: sillyP}, asset.Spot) + err = ProcessTicker(&Price{ + Pair: sillyP, + ExchangeName: "subscribetest", + AssetType: asset.Spot}) if err == nil { t.Error("error cannot be nil") } - err = ProcessTicker("subscribetest", &Price{Pair: sillyP}, "silly") + err = ProcessTicker(&Price{ + Pair: sillyP, + ExchangeName: "subscribetest", + AssetType: "silly", + }) if err == nil { t.Error("error cannot be nil") } // reinstate mux service.mux = cpyMux - err = ProcessTicker("subscribetest", &Price{Pair: p}, asset.Spot) + err = ProcessTicker(&Price{ + Pair: p, + ExchangeName: "subscribetest", + AssetType: asset.Spot}) if err != nil { t.Error("error cannot be nil") } @@ -81,7 +97,10 @@ func TestSubscribeToExchangeTickers(t *testing.T) { p := currency.NewPair(currency.BTC, currency.USD) - err = ProcessTicker("subscribeExchangeTest", &Price{Pair: p}, asset.Spot) + err = ProcessTicker(&Price{ + Pair: p, + ExchangeName: "subscribeExchangeTest", + AssetType: asset.Spot}) if err != nil { t.Error(err) } @@ -93,19 +112,24 @@ func TestSubscribeToExchangeTickers(t *testing.T) { } func TestGetTicker(t *testing.T) { - newPair := currency.NewPairFromStrings("BTC", "USD") + newPair, err := currency.NewPairFromStrings("BTC", "USD") + if err != nil { + t.Fatal(err) + } priceStruct := Price{ - Pair: newPair, - Last: 1200, - High: 1298, - Low: 1148, - Bid: 1195, - Ask: 1220, - Volume: 5, - PriceATH: 1337, + Pair: newPair, + Last: 1200, + High: 1298, + Low: 1148, + Bid: 1195, + Ask: 1220, + Volume: 5, + PriceATH: 1337, + ExchangeName: "bitfinex", + AssetType: asset.Spot, } - err := ProcessTicker("bitfinex", &priceStruct, asset.Spot) + err = ProcessTicker(&priceStruct) if err != nil { t.Fatal("ProcessTicker error", err) } @@ -129,7 +153,11 @@ func TestGetTicker(t *testing.T) { t.Fatal("TestGetTicker returned ticker for invalid first currency") } - btcltcPair := currency.NewPairFromStrings("BTC", "LTC") + btcltcPair, err := currency.NewPairFromStrings("BTC", "LTC") + if err != nil { + t.Fatal(err) + } + _, err = GetTicker("bitfinex", btcltcPair, asset.Spot) if err == nil { t.Fatal("TestGetTicker returned ticker for invalid second currency") @@ -137,7 +165,8 @@ func TestGetTicker(t *testing.T) { priceStruct.PriceATH = 9001 priceStruct.Pair.Base = currency.ETH - err = ProcessTicker("bitfinex", &priceStruct, "futures_3m") + priceStruct.AssetType = "futures_3m" + err = ProcessTicker(&priceStruct) if err != nil { t.Fatal("ProcessTicker error", err) } @@ -156,13 +185,14 @@ func TestGetTicker(t *testing.T) { t.Error("Ticker GetTicker error cannot be nil") } - err = ProcessTicker("bitfinex", &priceStruct, "meowCats") + priceStruct.AssetType = "meowCats" + err = ProcessTicker(&priceStruct) if err != nil { t.Fatal("ProcessTicker error", err) } // process update again - err = ProcessTicker("bitfinex", &priceStruct, "meowCats") + err = ProcessTicker(&priceStruct) if err != nil { t.Fatal("ProcessTicker error", err) } @@ -170,7 +200,11 @@ func TestGetTicker(t *testing.T) { func TestProcessTicker(t *testing.T) { // non-appending function to tickers exchName := "bitstamp" - newPair := currency.NewPairFromStrings("BTC", "USD") + newPair, err := currency.NewPairFromStrings("BTC", "USD") + if err != nil { + t.Fatal(err) + } + priceStruct := Price{ Last: 1200, High: 1298, @@ -181,26 +215,28 @@ func TestProcessTicker(t *testing.T) { // non-appending function to tickers PriceATH: 1337, } - err := ProcessTicker("", &priceStruct, asset.Spot) + err = ProcessTicker(&priceStruct) if err == nil { t.Fatal("empty exchange should throw an err") } + priceStruct.ExchangeName = exchName + // test for empty pair - err = ProcessTicker(exchName, &priceStruct, asset.Spot) + err = ProcessTicker(&priceStruct) if err == nil { t.Fatal("empty pair should throw an err") } // test for empty asset type priceStruct.Pair = newPair - err = ProcessTicker(exchName, &priceStruct, "") + err = ProcessTicker(&priceStruct) if err == nil { t.Fatal("ProcessTicker error cannot be nil") } - + priceStruct.AssetType = asset.Spot // now process a valid ticker - err = ProcessTicker(exchName, &priceStruct, asset.Spot) + err = ProcessTicker(&priceStruct) if err != nil { t.Fatal("ProcessTicker error", err) } @@ -213,9 +249,13 @@ func TestProcessTicker(t *testing.T) { // non-appending function to tickers } // now test for processing a pair with a different quote currency - newPair = currency.NewPairFromStrings("BTC", "AUD") + newPair, err = currency.NewPairFromStrings("BTC", "AUD") + if err != nil { + t.Fatal(err) + } + priceStruct.Pair = newPair - err = ProcessTicker(exchName, &priceStruct, asset.Spot) + err = ProcessTicker(&priceStruct) if err != nil { t.Fatal("ProcessTicker error", err) } @@ -229,9 +269,13 @@ func TestProcessTicker(t *testing.T) { // non-appending function to tickers } // now test for processing a pair which has a different base currency - newPair = currency.NewPairFromStrings("LTC", "AUD") + newPair, err = currency.NewPairFromStrings("LTC", "AUD") + if err != nil { + t.Fatal(err) + } + priceStruct.Pair = newPair - err = ProcessTicker(exchName, &priceStruct, asset.Spot) + err = ProcessTicker(&priceStruct) if err != nil { t.Fatal("ProcessTicker error", err) } @@ -266,16 +310,21 @@ func TestProcessTicker(t *testing.T) { // non-appending function to tickers wg.Add(1) go func() { newName := "Exchange" + strconv.FormatInt(rand.Int63(), 10) - newPairs := currency.NewPairFromStrings("BTC"+strconv.FormatInt(rand.Int63(), 10), + newPairs, err := currency.NewPairFromStrings("BTC"+strconv.FormatInt(rand.Int63(), 10), "USD"+strconv.FormatInt(rand.Int63(), 10)) + if err != nil { + log.Fatal(err) + } tp := Price{ - Pair: newPairs, - Last: rand.Float64(), + Pair: newPairs, + Last: rand.Float64(), + ExchangeName: newName, + AssetType: asset.Spot, } sm.Lock() - err = ProcessTicker(newName, &tp, asset.Spot) + err = ProcessTicker(&tp) if err != nil { t.Error(err) catastrophicFailure = true @@ -319,12 +368,12 @@ func TestProcessTicker(t *testing.T) { // non-appending function to tickers } func TestSetItemID(t *testing.T) { - err := service.SetItemID(nil) + err := service.SetItemID(nil, "") if err == nil { t.Error("error cannot be nil") } - err = service.SetItemID(&Price{}) + err = service.SetItemID(&Price{}, "") if err == nil { t.Error("error cannot be nil") } @@ -332,7 +381,7 @@ func TestSetItemID(t *testing.T) { p := currency.NewPair(currency.CYC, currency.CYG) service.mux = nil - err = service.SetItemID(&Price{Pair: p, ExchangeName: "SetItemID"}) + err = service.SetItemID(&Price{Pair: p, ExchangeName: "SetItemID"}, "setitemid") if err == nil { t.Error("error cannot be nil") } @@ -341,7 +390,7 @@ func TestSetItemID(t *testing.T) { } func TestGetAssociation(t *testing.T) { - _, err := service.GetAssociations(nil) + _, err := service.GetAssociations(nil, "") if err == nil { t.Error("error cannot be nil") } @@ -350,7 +399,7 @@ func TestGetAssociation(t *testing.T) { service.mux = nil - _, err = service.GetAssociations(&Price{Pair: p, ExchangeName: "GetAssociation"}) + _, err = service.GetAssociations(&Price{Pair: p, ExchangeName: "GetAssociation"}, "getassociation") if err == nil { t.Error("error cannot be nil") } diff --git a/exchanges/websocket/wshandler/wshandler.go b/exchanges/websocket/wshandler/wshandler.go deleted file mode 100644 index c29e4a29..00000000 --- a/exchanges/websocket/wshandler/wshandler.go +++ /dev/null @@ -1,898 +0,0 @@ -package wshandler - -import ( - "bytes" - "compress/flate" - "compress/gzip" - "errors" - "fmt" - "io/ioutil" - "net" - "net/http" - "net/url" - "strings" - "sync" - "time" - - "github.com/gorilla/websocket" - "github.com/thrasher-corp/gocryptotrader/config" - "github.com/thrasher-corp/gocryptotrader/log" -) - -// New initialises the websocket struct -func New() *Websocket { - return &Websocket{ - defaultURL: "", - enabled: false, - proxyAddr: "", - runningURL: "", - init: true, - } -} - -// Setup sets main variables for websocket connection -func (w *Websocket) Setup(setupData *WebsocketSetup) error { - w.DataHandler = make(chan interface{}, 1) - w.TrafficAlert = make(chan struct{}, 1) - w.verbose = setupData.Verbose - w.SetChannelSubscriber(setupData.Subscriber) - w.SetChannelUnsubscriber(setupData.UnSubscriber) - w.enabled = setupData.Enabled - w.SetDefaultURL(setupData.DefaultURL) - w.SetConnector(setupData.Connector) - w.SetWebsocketURL(setupData.RunningURL) - w.SetExchangeName(setupData.ExchangeName) - w.SetCanUseAuthenticatedEndpoints(setupData.AuthenticatedWebsocketAPISupport) - w.trafficTimeout = setupData.WebsocketTimeout - w.features = setupData.Features - err := w.Initialise() - if err != nil { - return err - } - - return nil -} - -// Connect initiates a websocket connection by using a package defined connection -// function -func (w *Websocket) Connect() error { - w.m.Lock() - defer w.m.Unlock() - - if !w.IsEnabled() { - return errors.New(WebsocketNotEnabled) - } - if w.IsConnecting() { - return fmt.Errorf("%v Websocket already attempting to connect", - w.exchangeName) - } - if w.IsConnected() { - return fmt.Errorf("%v Websocket already connected", - w.exchangeName) - } - w.setConnectingStatus(true) - w.ShutdownC = make(chan struct{}, 1) - w.ReadMessageErrors = make(chan error, 1) - err := w.connector() - if err != nil { - w.setConnectingStatus(false) - return fmt.Errorf("%v Error connecting %s", - w.exchangeName, err) - } - - w.setConnectedStatus(true) - w.setConnectingStatus(false) - w.setInit(true) - - var anotherWG sync.WaitGroup - anotherWG.Add(1) - go w.trafficMonitor(&anotherWG) - anotherWG.Wait() - if !w.IsConnectionMonitorRunning() { - go w.connectionMonitor() - } - if w.features.Subscribe || w.features.Unsubscribe { - w.Wg.Add(1) - go w.manageSubscriptions() - } - - return nil -} - -// connectionMonitor ensures that the WS keeps connecting -func (w *Websocket) connectionMonitor() { - if w.IsConnectionMonitorRunning() { - return - } - w.setConnectionMonitorRunning(true) - timer := time.NewTimer(connectionMonitorDelay) - - defer func() { - if !timer.Stop() { - select { - case <-timer.C: - default: - } - } - w.setConnectionMonitorRunning(false) - if w.verbose { - log.Debugf(log.WebsocketMgr, "%v websocket connection monitor exiting", - w.exchangeName) - } - }() - - for { - if w.verbose { - log.Debugf(log.WebsocketMgr, "%v running connection monitor cycle", - w.exchangeName) - } - if !w.IsEnabled() { - if w.verbose { - log.Debugf(log.WebsocketMgr, "%v connectionMonitor: websocket disabled, shutting down", w.exchangeName) - } - if w.IsConnected() { - err := w.Shutdown() - if err != nil { - log.Error(log.WebsocketMgr, err) - } - } - if w.verbose { - log.Debugf(log.WebsocketMgr, "%v websocket connection monitor exiting", - w.exchangeName) - } - return - } - select { - case err := <-w.ReadMessageErrors: - // check if this error is a disconnection error - if isDisconnectionError(err) { - w.setConnectedStatus(false) - w.setConnectingStatus(false) - w.setInit(false) - if w.verbose { - log.Debugf(log.WebsocketMgr, "%v websocket has been disconnected. Reason: %v", - w.exchangeName, err) - } - err = w.Connect() - if err != nil { - log.Error(log.WebsocketMgr, err) - } - } else { - // pass off non disconnect errors to datahandler to manage - w.DataHandler <- err - } - case <-timer.C: - if !w.IsConnecting() && !w.IsConnected() { - err := w.Connect() - if err != nil { - log.Error(log.WebsocketMgr, err) - } - } - if !timer.Stop() { - select { - case <-timer.C: - default: - } - } - timer.Reset(connectionMonitorDelay) - } - } -} - -// Shutdown attempts to shut down a websocket connection and associated routines -// by using a package defined shutdown function -func (w *Websocket) Shutdown() error { - w.m.Lock() - defer func() { - w.Orderbook.FlushCache() - w.m.Unlock() - }() - if !w.IsConnected() { - return fmt.Errorf("%v cannot shutdown a disconnected websocket", w.exchangeName) - } - if w.verbose { - log.Debugf(log.WebsocketMgr, "%v shutting down websocket channels", w.exchangeName) - } - close(w.ShutdownC) - w.Wg.Wait() - w.setConnectedStatus(false) - w.setConnectingStatus(false) - if w.verbose { - log.Debugf(log.WebsocketMgr, "%v completed websocket channel shutdown", w.exchangeName) - } - return nil -} - -// trafficMonitor uses a timer of WebsocketTrafficLimitTime and once it expires -// Will reconnect if the TrafficAlert channel has not received any data -// The trafficTimer will reset on each traffic alert -func (w *Websocket) trafficMonitor(wg *sync.WaitGroup) { - w.Wg.Add(1) - wg.Done() - trafficTimer := time.NewTimer(w.trafficTimeout) - defer func() { - if !trafficTimer.Stop() { - select { - case <-trafficTimer.C: - default: - } - } - w.setTrafficMonitorRunning(false) - w.Wg.Done() - }() - if w.IsTrafficMonitorRunning() { - return - } - w.setTrafficMonitorRunning(true) - for { - select { - case <-w.ShutdownC: - if w.verbose { - log.Debugf(log.WebsocketMgr, "%v trafficMonitor shutdown message received", w.exchangeName) - } - return - case <-w.TrafficAlert: - if !trafficTimer.Stop() { - select { - case <-trafficTimer.C: - default: - } - } - trafficTimer.Reset(w.trafficTimeout) - case <-trafficTimer.C: // Falls through when timer runs out - if w.verbose { - log.Warnf(log.WebsocketMgr, "%v has not received a traffic alert in %v. Reconnecting", w.exchangeName, w.trafficTimeout) - } - go w.Shutdown() - } - } -} - -func (w *Websocket) setConnectedStatus(b bool) { - w.connectionMutex.Lock() - w.connected = b - w.connectionMutex.Unlock() -} - -// IsConnected returns status of connection -func (w *Websocket) IsConnected() bool { - w.connectionMutex.RLock() - isConnected := w.connected - w.connectionMutex.RUnlock() - return isConnected -} - -func (w *Websocket) setConnectingStatus(b bool) { - w.connectionMutex.Lock() - w.connecting = b - w.connectionMutex.Unlock() -} - -// IsConnecting returns status of connecting -func (w *Websocket) IsConnecting() bool { - w.connectionMutex.RLock() - isConnecting := w.connecting - w.connectionMutex.RUnlock() - return isConnecting -} - -func (w *Websocket) setEnabled(b bool) { - w.connectionMutex.Lock() - w.enabled = b - w.connectionMutex.Unlock() -} - -// IsEnabled returns status of enabled -func (w *Websocket) IsEnabled() bool { - w.connectionMutex.RLock() - isEnabled := w.enabled - w.connectionMutex.RUnlock() - return isEnabled -} - -func (w *Websocket) setInit(b bool) { - w.connectionMutex.Lock() - w.init = b - w.connectionMutex.Unlock() -} - -// IsInit returns status of init -func (w *Websocket) IsInit() bool { - w.connectionMutex.RLock() - isInit := w.init - w.connectionMutex.RUnlock() - return isInit -} - -func (w *Websocket) setTrafficMonitorRunning(b bool) { - w.connectionMutex.Lock() - w.trafficMonitorRunning = b - w.connectionMutex.Unlock() -} - -// IsTrafficMonitorRunning returns status of the traffic monitor -func (w *Websocket) IsTrafficMonitorRunning() bool { - w.connectionMutex.RLock() - trafficMonRunning := w.trafficMonitorRunning - w.connectionMutex.RUnlock() - return trafficMonRunning -} - -func (w *Websocket) setConnectionMonitorRunning(b bool) { - w.connectionMutex.Lock() - w.connectionMonitorRunning = b - w.connectionMutex.Unlock() -} - -// IsConnectionMonitorRunning returns status of connection monitor -func (w *Websocket) IsConnectionMonitorRunning() bool { - w.connectionMutex.RLock() - isConnMonRunning := w.connectionMonitorRunning - w.connectionMutex.RUnlock() - return isConnMonRunning -} - -// CanUseAuthenticatedWebsocketForWrapper Handles a common check to -// verify whether a wrapper can use an authenticated websocket endpoint -func (w *Websocket) CanUseAuthenticatedWebsocketForWrapper() bool { - if w.IsConnected() && w.CanUseAuthenticatedEndpoints() { - return true - } else if w.IsConnected() && !w.CanUseAuthenticatedEndpoints() { - log.Infof(log.WebsocketMgr, WebsocketNotAuthenticatedUsingRest, w.exchangeName) - } - return false -} - -// SetWebsocketURL sets websocket URL -func (w *Websocket) SetWebsocketURL(websocketURL string) { - if websocketURL == "" || websocketURL == config.WebsocketURLNonDefaultMessage { - w.runningURL = w.defaultURL - return - } - w.runningURL = websocketURL -} - -// GetWebsocketURL returns the running websocket URL -func (w *Websocket) GetWebsocketURL() string { - return w.runningURL -} - -// Initialise verifies status and connects -func (w *Websocket) Initialise() error { - if w.IsEnabled() { - if w.IsInit() { - return nil - } - return fmt.Errorf("%v Websocket already initialised", - w.exchangeName) - } - w.setEnabled(w.enabled) - return nil -} - -// SetProxyAddress sets websocket proxy address -func (w *Websocket) SetProxyAddress(proxyAddr string) error { - if w.proxyAddr == proxyAddr { - return fmt.Errorf("%v Cannot set proxy address to the same address '%v'", w.exchangeName, w.proxyAddr) - } - - w.proxyAddr = proxyAddr - if !w.IsInit() && w.IsEnabled() { - if w.IsConnected() { - err := w.Shutdown() - if err != nil { - return err - } - } - return w.Connect() - } - return nil -} - -// GetProxyAddress returns the current websocket proxy -func (w *Websocket) GetProxyAddress() string { - return w.proxyAddr -} - -// SetDefaultURL sets default websocket URL -func (w *Websocket) SetDefaultURL(defaultURL string) { - w.defaultURL = defaultURL -} - -// GetDefaultURL returns the default websocket URL -func (w *Websocket) GetDefaultURL() string { - return w.defaultURL -} - -// SetConnector sets connection function -func (w *Websocket) SetConnector(connector func() error) { - w.connector = connector -} - -// SetExchangeName sets exchange name -func (w *Websocket) SetExchangeName(exchName string) { - w.exchangeName = exchName -} - -// GetName returns exchange name -func (w *Websocket) GetName() string { - return w.exchangeName -} - -// SetChannelSubscriber sets the function to use the base subscribe func -func (w *Websocket) SetChannelSubscriber(subscriber func(channelToSubscribe WebsocketChannelSubscription) error) { - w.channelSubscriber = subscriber -} - -// SetChannelUnsubscriber sets the function to use the base unsubscribe func -func (w *Websocket) SetChannelUnsubscriber(unsubscriber func(channelToUnsubscribe WebsocketChannelSubscription) error) { - w.channelUnsubscriber = unsubscriber -} - -// ManageSubscriptions ensures the subscriptions specified continue to be subscribed to -func (w *Websocket) manageSubscriptions() { - if !w.features.Subscribe && !w.features.Unsubscribe { - w.DataHandler <- fmt.Errorf("%v does not support channel subscriptions, exiting ManageSubscriptions()", w.exchangeName) - return - } - defer func() { - if w.verbose { - log.Debugf(log.WebsocketMgr, "%v ManageSubscriptions exiting", w.exchangeName) - } - w.Wg.Done() - }() - for { - select { - case <-w.ShutdownC: - w.subscriptionMutex.Lock() - w.subscribedChannels = []WebsocketChannelSubscription{} - w.subscriptionMutex.Unlock() - if w.verbose { - log.Debugf(log.WebsocketMgr, "%v shutdown manageSubscriptions", w.exchangeName) - } - return - default: - time.Sleep(manageSubscriptionsDelay) - if !w.IsConnected() { - w.subscriptionMutex.Lock() - w.subscribedChannels = []WebsocketChannelSubscription{} - w.subscriptionMutex.Unlock() - - continue - } - if w.verbose { - log.Debugf(log.WebsocketMgr, "%v checking subscriptions", w.exchangeName) - } - // Subscribe to channels Pending a subscription - if w.features.Subscribe { - err := w.appendSubscribedChannels() - if err != nil { - w.DataHandler <- err - } - } - if w.features.Unsubscribe { - err := w.unsubscribeToChannels() - if err != nil { - w.DataHandler <- err - } - } - } - } -} - -// appendSubscribedChannels compares channelsToSubscribe to subscribedChannels -// and subscribes to any channels not present in subscribedChannels -func (w *Websocket) appendSubscribedChannels() error { - w.subscriptionMutex.Lock() - defer w.subscriptionMutex.Unlock() - for i := range w.channelsToSubscribe { - channelIsSubscribed := false - for j := 0; j < len(w.subscribedChannels); j++ { - if w.subscribedChannels[j].Equal(&w.channelsToSubscribe[i]) { - channelIsSubscribed = true - break - } - } - if !channelIsSubscribed { - if w.verbose { - log.Debugf(log.WebsocketMgr, "%v Subscribing to %v %v", w.exchangeName, w.channelsToSubscribe[i].Channel, w.channelsToSubscribe[i].Currency.String()) - } - err := w.channelSubscriber(w.channelsToSubscribe[i]) - if err != nil { - return err - } - w.subscribedChannels = append(w.subscribedChannels, w.channelsToSubscribe[i]) - } - } - return nil -} - -// unsubscribeToChannels compares subscribedChannels to channelsToSubscribe -// and unsubscribes to any channels not present in channelsToSubscribe -func (w *Websocket) unsubscribeToChannels() error { - w.subscriptionMutex.Lock() - defer w.subscriptionMutex.Unlock() - for i := range w.subscribedChannels { - subscriptionFound := false - for j := 0; j < len(w.channelsToSubscribe); j++ { - if w.channelsToSubscribe[j].Equal(&w.subscribedChannels[i]) { - subscriptionFound = true - break - } - } - if !subscriptionFound { - err := w.channelUnsubscriber(w.subscribedChannels[i]) - if err != nil { - return err - } - } - } - // Now that the slices should match, assign rather than looping and appending the differences - w.subscribedChannels = append(w.channelsToSubscribe[:0:0], w.channelsToSubscribe...) //nolint:gocritic - - return nil -} - -// RemoveSubscribedChannels removes supplied channels from channelsToSubscribe -func (w *Websocket) RemoveSubscribedChannels(channels []WebsocketChannelSubscription) { - for i := range channels { - w.removeChannelToSubscribe(channels[i]) - } -} - -// removeChannelToSubscribe removes an entry from w.channelsToSubscribe -// so an unsubscribe event can be triggered -func (w *Websocket) removeChannelToSubscribe(subscribedChannel WebsocketChannelSubscription) { - w.subscriptionMutex.Lock() - defer w.subscriptionMutex.Unlock() - channelLength := len(w.channelsToSubscribe) - i := 0 - for j := 0; j < len(w.channelsToSubscribe); j++ { - if !w.channelsToSubscribe[j].Equal(&subscribedChannel) { - w.channelsToSubscribe[i] = w.channelsToSubscribe[j] - i++ - } - } - w.channelsToSubscribe = w.channelsToSubscribe[:i] - if channelLength == len(w.channelsToSubscribe) { - w.DataHandler <- fmt.Errorf("%v removeChannelToSubscribe() Channel %v Currency %v could not be removed because it was not found", - w.exchangeName, - subscribedChannel.Channel, - subscribedChannel.Currency) - } -} - -// ResubscribeToChannel calls unsubscribe func and -// removes it from subscribedChannels to trigger a subscribe event -func (w *Websocket) ResubscribeToChannel(subscribedChannel WebsocketChannelSubscription) { - w.subscriptionMutex.Lock() - defer w.subscriptionMutex.Unlock() - err := w.channelUnsubscriber(subscribedChannel) - if err != nil { - w.DataHandler <- err - } - // Remove the channel from the list of subscribed channels - // ManageSubscriptions will automatically resubscribe - i := 0 - for j := 0; j < len(w.subscribedChannels); j++ { - if !w.subscribedChannels[j].Equal(&subscribedChannel) { - w.subscribedChannels[i] = w.subscribedChannels[j] - i++ - } - } - w.subscribedChannels = w.subscribedChannels[:i] -} - -// SubscribeToChannels appends supplied channels to channelsToSubscribe -func (w *Websocket) SubscribeToChannels(channels []WebsocketChannelSubscription) { - for i := range channels { - channelFound := false - for j := range w.channelsToSubscribe { - if w.channelsToSubscribe[j].Equal(&channels[i]) { - channelFound = true - } - } - if !channelFound { - w.channelsToSubscribe = append(w.channelsToSubscribe, channels[i]) - } - } -} - -// Equal two WebsocketChannelSubscription to determine equality -func (w *WebsocketChannelSubscription) Equal(subscribedChannel *WebsocketChannelSubscription) bool { - return strings.EqualFold(w.Channel, subscribedChannel.Channel) && - strings.EqualFold(w.Currency.String(), subscribedChannel.Currency.String()) -} - -// GetSubscriptions returns a copied list of subscriptions -// subscriptions is a private member and cannot be manipulated -func (w *Websocket) GetSubscriptions() []WebsocketChannelSubscription { - return append(w.subscribedChannels[:0:0], w.subscribedChannels...) -} - -// SetCanUseAuthenticatedEndpoints sets canUseAuthenticatedEndpoints val in -// a thread safe manner -func (w *Websocket) SetCanUseAuthenticatedEndpoints(val bool) { - w.subscriptionMutex.Lock() - defer w.subscriptionMutex.Unlock() - w.canUseAuthenticatedEndpoints = val -} - -// CanUseAuthenticatedEndpoints gets canUseAuthenticatedEndpoints val in -// a thread safe manner -func (w *Websocket) CanUseAuthenticatedEndpoints() bool { - w.subscriptionMutex.Lock() - canUseAuthEndpoints := w.canUseAuthenticatedEndpoints - w.subscriptionMutex.Unlock() - return canUseAuthEndpoints -} - -// SetResponseIDAndData adds data to IDResponses with locks and a nil check -func (w *WebsocketConnection) SetResponseIDAndData(id int64, data []byte) { - w.Lock() - defer w.Unlock() - if w.IDResponses == nil { - w.IDResponses = make(map[int64][]byte) - } - w.IDResponses[id] = data -} - -// Dial sets proxy urls and then connects to the websocket -func (w *WebsocketConnection) Dial(dialer *websocket.Dialer, headers http.Header) error { - if w.ProxyURL != "" { - proxy, err := url.Parse(w.ProxyURL) - if err != nil { - return err - } - dialer.Proxy = http.ProxyURL(proxy) - } - var err error - var conStatus *http.Response - w.Connection, conStatus, err = dialer.Dial(w.URL, headers) - if conStatus != nil { - conStatus.Body.Close() - } - if err != nil { - if conStatus != nil { - return fmt.Errorf("%v %v %v Error: %v", w.URL, conStatus, conStatus.StatusCode, err) - } - return fmt.Errorf("%v Error: %v", w.URL, err) - } - if w.Verbose { - log.Infof(log.WebsocketMgr, "%v Websocket connected to %s", w.ExchangeName, w.URL) - } - w.setConnectedStatus(true) - return nil -} - -// SendJSONMessage sends a JSON encoded message over the connection -func (w *WebsocketConnection) SendJSONMessage(data interface{}) error { - w.Lock() - defer w.Unlock() - if !w.IsConnected() { - return fmt.Errorf("%v cannot send message to a disconnected websocket", w.ExchangeName) - } - if w.Verbose { - log.Debugf(log.WebsocketMgr, - "%v sending message to websocket %+v", w.ExchangeName, data) - } - if w.RateLimit > 0 { - time.Sleep(time.Duration(w.RateLimit) * time.Millisecond) - } - return w.Connection.WriteJSON(data) -} - -// SendRawMessage sends a message over the connection without JSON encoding it -func (w *WebsocketConnection) SendRawMessage(messageType int, message []byte) error { - w.Lock() - defer w.Unlock() - if !w.IsConnected() { - return fmt.Errorf("%v cannot send message to a disconnected websocket", w.ExchangeName) - } - if w.Verbose { - log.Debugf(log.WebsocketMgr, - "%v sending message to websocket %s", w.ExchangeName, message) - } - if w.RateLimit > 0 { - time.Sleep(time.Duration(w.RateLimit) * time.Millisecond) - } - return w.Connection.WriteMessage(messageType, message) -} - -// SetupPingHandler will automatically send ping or pong messages based on -// WebsocketPingHandler configuration -func (w *WebsocketConnection) SetupPingHandler(handler WebsocketPingHandler) { - if handler.UseGorillaHandler { - h := func(msg string) error { - err := w.Connection.WriteControl(handler.MessageType, []byte(msg), time.Now().Add(handler.Delay)) - if err == websocket.ErrCloseSent { - return nil - } else if e, ok := err.(net.Error); ok && e.Temporary() { - return nil - } - return err - } - w.Connection.SetPingHandler(h) - return - } - w.Wg.Add(1) - defer w.Wg.Done() - go func() { - ticker := time.NewTicker(handler.Delay) - for { - select { - case <-w.Shutdown: - ticker.Stop() - return - case <-ticker.C: - err := w.SendRawMessage(handler.MessageType, handler.Message) - if err != nil { - log.Errorf(log.WebsocketMgr, - "%v failed to send message to websocket %s", w.ExchangeName, handler.Message) - return - } - } - } - }() -} - -// SendMessageReturnResponse will send a WS message to the connection -// It will then run a goroutine to await a JSON response -// If there is no response it will return an error -func (w *WebsocketConnection) SendMessageReturnResponse(id int64, request interface{}) ([]byte, error) { - err := w.SendJSONMessage(request) - if err != nil { - return nil, err - } - w.SetResponseIDAndData(id, nil) - var wg sync.WaitGroup - wg.Add(1) - go w.WaitForResult(id, &wg) - defer func() { - delete(w.IDResponses, id) - }() - wg.Wait() - if _, ok := w.IDResponses[id]; !ok { - return nil, fmt.Errorf("timeout waiting for response with ID %v", id) - } - - return w.IDResponses[id], nil -} - -// IsIDWaitingForResponse will verify whether the websocket is awaiting -// a response with a correlating ID. If true, the datahandler won't process -// the data, and instead will be processed by the wrapper function -func (w *WebsocketConnection) IsIDWaitingForResponse(id int64) bool { - w.Lock() - defer w.Unlock() - for k := range w.IDResponses { - if k == id && w.IDResponses[k] == nil { - return true - } - } - return false -} - -// WaitForResult will keep checking w.IDResponses for a response ID -// If the timer expires, it will return without -func (w *WebsocketConnection) WaitForResult(id int64, wg *sync.WaitGroup) { - defer wg.Done() - timer := time.NewTimer(w.ResponseMaxLimit) - for { - select { - case <-timer.C: - return - default: - w.Lock() - for k := range w.IDResponses { - if k == id && w.IDResponses[k] != nil { - w.Unlock() - if !timer.Stop() { - select { - case <-timer.C: - default: - } - } - return - } - } - w.Unlock() - time.Sleep(w.ResponseCheckTimeout) - } - } -} - -func (w *WebsocketConnection) setConnectedStatus(b bool) { - w.connectionMutex.Lock() - w.connected = b - w.connectionMutex.Unlock() -} - -// IsConnected exposes websocket connection status -func (w *WebsocketConnection) IsConnected() bool { - w.connectionMutex.RLock() - isConnected := w.connected - w.connectionMutex.RUnlock() - return isConnected -} - -// ReadMessage reads messages, can handle text, gzip and binary -func (w *WebsocketConnection) ReadMessage() (WebsocketResponse, error) { - mType, resp, err := w.Connection.ReadMessage() - if err != nil { - if isDisconnectionError(err) { - w.setConnectedStatus(false) - } - return WebsocketResponse{}, err - } - var standardMessage []byte - switch mType { - case websocket.TextMessage: - standardMessage = resp - case websocket.BinaryMessage: - standardMessage, err = w.parseBinaryResponse(resp) - if err != nil { - return WebsocketResponse{}, err - } - } - if w.Verbose { - log.Debugf(log.WebsocketMgr, "%v Websocket message received: %v", - w.ExchangeName, - string(standardMessage)) - } - return WebsocketResponse{Raw: standardMessage, Type: mType}, nil -} - -// parseBinaryResponse parses a websocket binary response into a usable byte array -func (w *WebsocketConnection) parseBinaryResponse(resp []byte) ([]byte, error) { - var standardMessage []byte - var err error - // Detect GZIP - if resp[0] == 31 && resp[1] == 139 { - b := bytes.NewReader(resp) - var gReader *gzip.Reader - gReader, err = gzip.NewReader(b) - if err != nil { - return standardMessage, err - } - standardMessage, err = ioutil.ReadAll(gReader) - if err != nil { - return standardMessage, err - } - err = gReader.Close() - if err != nil { - return standardMessage, err - } - } else { - reader := flate.NewReader(bytes.NewReader(resp)) - standardMessage, err = ioutil.ReadAll(reader) - if err != nil { - return standardMessage, err - } - err = reader.Close() - if err != nil { - return standardMessage, err - } - } - return standardMessage, nil -} - -// GenerateMessageID Creates a messageID to checkout -func (w *WebsocketConnection) GenerateMessageID(useNano bool) int64 { - if useNano { - return time.Now().UnixNano() - } - return time.Now().Unix() -} - -// isDisconnectionError Determines if the error sent over chan ReadMessageErrors is a disconnection error -func isDisconnectionError(err error) bool { - if websocket.IsUnexpectedCloseError(err) { - return true - } - switch err.(type) { - case *websocket.CloseError, *net.OpError: - return true - } - return false -} diff --git a/exchanges/websocket/wshandler/wshandler_test.go b/exchanges/websocket/wshandler/wshandler_test.go deleted file mode 100644 index d59c55e8..00000000 --- a/exchanges/websocket/wshandler/wshandler_test.go +++ /dev/null @@ -1,771 +0,0 @@ -package wshandler - -import ( - "bytes" - "compress/flate" - "compress/gzip" - "encoding/json" - "errors" - "net" - "net/http" - "os" - "strings" - "sync" - "testing" - "time" - - "github.com/gorilla/websocket" - "github.com/thrasher-corp/gocryptotrader/currency" - "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" -) - -func TestTrafficMonitorTimeout(t *testing.T) { - ws := New() - err := ws.Setup( - &WebsocketSetup{ - Enabled: true, - AuthenticatedWebsocketAPISupport: true, - WebsocketTimeout: 10000, - DefaultURL: "testDefaultURL", - ExchangeName: "exchangeName", - RunningURL: "testRunningURL", - Connector: func() error { return nil }, - Subscriber: func(test WebsocketChannelSubscription) error { return nil }, - UnSubscriber: func(test WebsocketChannelSubscription) error { return nil }, - }) - if err != nil { - t.Error(err) - } - ws.setConnectedStatus(true) - ws.TrafficAlert = make(chan struct{}, 2) - ws.ShutdownC = make(chan struct{}) - var anotherWG sync.WaitGroup - anotherWG.Add(1) - go ws.trafficMonitor(&anotherWG) - anotherWG.Wait() - ws.TrafficAlert <- struct{}{} - trafficTimer := time.NewTimer(5 * time.Second) - select { - case <-trafficTimer.C: - t.Error("should be exiting") - default: - ws.Wg.Wait() - } -} - -func TestIsDisconnectionError(t *testing.T) { - isADisconnectionError := isDisconnectionError(errors.New("errorText")) - if isADisconnectionError { - t.Error("Its not") - } - isADisconnectionError = isDisconnectionError(&websocket.CloseError{ - Code: 1006, - Text: "errorText", - }) - if !isADisconnectionError { - t.Error("It is") - } - - isADisconnectionError = isDisconnectionError(&net.OpError{ - Op: "", - Net: "", - Source: nil, - Addr: nil, - Err: errors.New("errorText"), - }) - if !isADisconnectionError { - t.Error("It is") - } -} - -func TestConnectionMessageErrors(t *testing.T) { - ws := New() - ws.connected = true - ws.enabled = true - ws.ReadMessageErrors = make(chan error) - ws.DataHandler = make(chan interface{}) - ws.ShutdownC = make(chan struct{}) - ws.connector = func() error { return nil } - ws.features = &protocol.Features{} - go ws.connectionMonitor() - timer := time.NewTimer(900 * time.Millisecond) - ws.ReadMessageErrors <- errors.New("errorText") - select { - case err := <-ws.DataHandler: - if err.(error).Error() != "errorText" { - t.Errorf("Expected 'errorText', received %v", err) - } - case <-timer.C: - t.Error("Timeout waiting for datahandler to receive error") - } - timer = time.NewTimer(900 * time.Millisecond) - ws.ReadMessageErrors <- &websocket.CloseError{ - Code: 1006, - Text: "errorText", - } -outer: - for { - select { - case <-ws.DataHandler: - t.Fatal("Error is a disconnection error") - case <-timer.C: - break outer - } - } -} - -func TestWebsocket(t *testing.T) { - ws := Websocket{} - ws.setInit(true) - err := ws.Setup(&WebsocketSetup{ - ExchangeName: "test", - Enabled: true, - }) - if err != nil && err.Error() != "test Websocket already initialised" { - t.Errorf("Expected 'test Websocket already initialised', received %v", err) - } - - ws = *New() - err = ws.SetProxyAddress("testProxy") - if err != nil { - t.Error("SetProxyAddress", err) - } - - err = ws.Setup( - &WebsocketSetup{ - Enabled: true, - AuthenticatedWebsocketAPISupport: true, - WebsocketTimeout: 2, - DefaultURL: "testDefaultURL", - ExchangeName: "exchangeName", - RunningURL: "testRunningURL", - Connector: func() error { return nil }, - Subscriber: func(test WebsocketChannelSubscription) error { return nil }, - UnSubscriber: func(test WebsocketChannelSubscription) error { return nil }, - Features: &protocol.Features{}, - }) - if err != nil { - t.Error(err) - } - - if ws.GetName() != "exchangeName" { - t.Error("WebsocketSetup") - } - - if !ws.IsEnabled() { - t.Error("WebsocketSetup") - } - - ws.setEnabled(false) - if ws.IsEnabled() { - t.Error("WebsocketSetup") - } - - ws.setEnabled(true) - if !ws.IsEnabled() { - t.Error("WebsocketSetup") - } - - if ws.GetProxyAddress() != "testProxy" { - t.Error("WebsocketSetup") - } - - if ws.GetDefaultURL() != "testDefaultURL" { - t.Error("WebsocketSetup") - } - - if ws.GetWebsocketURL() != "testRunningURL" { - t.Error("WebsocketSetup") - } - - if ws.trafficTimeout != time.Duration(2) { - t.Error("WebsocketSetup") - } - // -- Not connected shutdown - err = ws.Shutdown() - if err == nil { - t.Fatal("should not be connected to able to shut down") - } - ws.Wg.Wait() - // -- Normal connect - err = ws.Connect() - if err != nil { - t.Fatal("WebsocketSetup", err) - } - ws.SetWebsocketURL("ws://demos.kaazing.com/echo") - // -- Already connected connect - err = ws.Connect() - if err == nil { - t.Fatal("should not connect, already connected") - } - // -- Normal shutdown - err = ws.Shutdown() - if err != nil { - t.Fatal("WebsocketSetup", err) - } - ws.Wg.Wait() -} - -// placeholderSubscriber basic function to test subscriptions -func placeholderSubscriber(channelToSubscribe WebsocketChannelSubscription) error { - return nil -} - -// TestSubscribe logic test -func TestSubscribe(t *testing.T) { - w := Websocket{ - channelsToSubscribe: []WebsocketChannelSubscription{ - { - Channel: "hello", - }, - }, - subscribedChannels: []WebsocketChannelSubscription{}, - } - w.SetChannelSubscriber(placeholderSubscriber) - err := w.appendSubscribedChannels() - if err != nil { - t.Error(err) - } - if len(w.subscribedChannels) != 1 { - t.Errorf("Subscription did not occur") - } -} - -// TestSubscribe logic test -func TestSubscribeToChannels(t *testing.T) { - w := Websocket{ - channelsToSubscribe: []WebsocketChannelSubscription{ - { - Channel: "hello", - }, - }, - subscribedChannels: []WebsocketChannelSubscription{}, - } - w.SetChannelSubscriber(placeholderSubscriber) - w.SubscribeToChannels([]WebsocketChannelSubscription{{Channel: "hello"}, {Channel: "hello2"}}) - if len(w.channelsToSubscribe) != 2 { - t.Errorf("Subscription did not occur") - } -} - -// TestUnsubscribe logic test -func TestUnsubscribe(t *testing.T) { - w := Websocket{ - channelsToSubscribe: []WebsocketChannelSubscription{}, - subscribedChannels: []WebsocketChannelSubscription{ - { - Channel: "hello", - }, - }, - } - w.SetChannelUnsubscriber(placeholderSubscriber) - err := w.unsubscribeToChannels() - if err != nil { - t.Error(err) - } - if len(w.subscribedChannels) != 0 { - t.Errorf("Unsubscription did not occur") - } -} - -// TestSubscriptionWithExistingEntry logic test -func TestSubscriptionWithExistingEntry(t *testing.T) { - w := Websocket{ - channelsToSubscribe: []WebsocketChannelSubscription{ - { - Channel: "hello", - }, - }, - subscribedChannels: []WebsocketChannelSubscription{ - { - Channel: "hello", - }, - }, - } - w.SetChannelSubscriber(placeholderSubscriber) - err := w.appendSubscribedChannels() - if err != nil { - t.Error(err) - } - if len(w.subscribedChannels) != 1 { - t.Errorf("Subscription should not have occurred") - } -} - -// TestUnsubscriptionWithExistingEntry logic test -func TestUnsubscriptionWithExistingEntry(t *testing.T) { - w := Websocket{ - channelsToSubscribe: []WebsocketChannelSubscription{ - { - Channel: "hello", - }, - }, - subscribedChannels: []WebsocketChannelSubscription{ - { - Channel: "hello", - }, - }, - } - w.SetChannelUnsubscriber(placeholderSubscriber) - err := w.unsubscribeToChannels() - if err != nil { - t.Error(err) - } - if len(w.subscribedChannels) != 1 { - t.Errorf("Unsubscription should not have occurred") - } -} - -// TestManageSubscriptionsStartStop logic test -func TestManageSubscriptionsStartStop(t *testing.T) { - w := Websocket{ - ShutdownC: make(chan struct{}), - features: &protocol.Features{Subscribe: true, Unsubscribe: true}, - } - w.Wg.Add(1) - go w.manageSubscriptions() - close(w.ShutdownC) - w.Wg.Wait() -} - -// TestManageSubscriptions logic test -func TestManageSubscriptions(t *testing.T) { - w := Websocket{ - ShutdownC: make(chan struct{}), - features: &protocol.Features{Subscribe: true, Unsubscribe: true}, - subscribedChannels: []WebsocketChannelSubscription{ - { - Channel: "hello", - }, - }, - } - w.SetChannelUnsubscriber(placeholderSubscriber) - w.SetChannelSubscriber(placeholderSubscriber) - w.setConnectedStatus(true) - go w.manageSubscriptions() - time.Sleep(8 * time.Second) - w.setConnectedStatus(false) - time.Sleep(manageSubscriptionsDelay) - w.subscriptionMutex.Lock() - if len(w.subscribedChannels) > 0 { - t.Error("Expected empty subscribed channels") - } - w.subscriptionMutex.Unlock() -} - -// TestConnectionMonitorNoConnection logic test -func TestConnectionMonitorNoConnection(t *testing.T) { - ws := New() - ws.DataHandler = make(chan interface{}, 1) - ws.ShutdownC = make(chan struct{}, 1) - ws.exchangeName = "hello" - ws.trafficTimeout = 1 - go ws.connectionMonitor() - if ws.IsConnectionMonitorRunning() { - t.Fatal("Should have exited") - } -} - -// TestRemoveChannelToSubscribe logic test -func TestRemoveChannelToSubscribe(t *testing.T) { - subscription := WebsocketChannelSubscription{ - Channel: "hello", - } - w := Websocket{ - channelsToSubscribe: []WebsocketChannelSubscription{ - subscription, - }, - } - w.SetChannelUnsubscriber(placeholderSubscriber) - w.removeChannelToSubscribe(subscription) - if len(w.subscribedChannels) != 0 { - t.Errorf("Unsubscription did not occur") - } -} - -// TestRemoveChannelToSubscribeWithNoSubscription logic test -func TestRemoveChannelToSubscribeWithNoSubscription(t *testing.T) { - subscription := WebsocketChannelSubscription{ - Channel: "hello", - } - w := Websocket{ - channelsToSubscribe: []WebsocketChannelSubscription{}, - } - w.DataHandler = make(chan interface{}, 1) - w.SetChannelUnsubscriber(placeholderSubscriber) - go w.removeChannelToSubscribe(subscription) - err := <-w.DataHandler - if !strings.Contains(err.(error).Error(), "could not be removed because it was not found") { - t.Error("Expected not found error") - } -} - -// TestResubscribeToChannel logic test -func TestResubscribeToChannel(t *testing.T) { - subscription := WebsocketChannelSubscription{ - Channel: "hello", - } - w := Websocket{ - channelsToSubscribe: []WebsocketChannelSubscription{}, - } - w.DataHandler = make(chan interface{}, 1) - w.SetChannelUnsubscriber(placeholderSubscriber) - w.SetChannelSubscriber(placeholderSubscriber) - w.ResubscribeToChannel(subscription) -} - -// TestSliceCopyDoesntImpactBoth logic test -func TestSliceCopyDoesntImpactBoth(t *testing.T) { - w := Websocket{ - channelsToSubscribe: []WebsocketChannelSubscription{ - { - Channel: "hello1", - }, - { - Channel: "hello2", - }, - }, - subscribedChannels: []WebsocketChannelSubscription{ - { - Channel: "hello3", - }, - }, - } - w.SetChannelUnsubscriber(placeholderSubscriber) - err := w.unsubscribeToChannels() - if err != nil { - t.Error(err) - } - if len(w.subscribedChannels) != 2 { - t.Errorf("Unsubscription did not occur") - } - w.subscribedChannels[0].Channel = "test" - if strings.EqualFold(w.subscribedChannels[0].Channel, w.channelsToSubscribe[0].Channel) { - t.Errorf("Slice has not been copied appropriately") - } -} - -// TestSliceCopyDoesntImpactBoth logic test -func TestGetSubscriptions(t *testing.T) { - w := Websocket{ - subscribedChannels: []WebsocketChannelSubscription{ - { - Channel: "hello3", - }, - }, - } - - subs := w.GetSubscriptions() - subs[0].Channel = "noHELLO" - if strings.EqualFold(w.subscribedChannels[0].Channel, subs[0].Channel) { - t.Error("Subscriptions was not copied properly") - } -} - -// TestSetCanUseAuthenticatedEndpoints logic test -func TestSetCanUseAuthenticatedEndpoints(t *testing.T) { - ws := New() - result := ws.CanUseAuthenticatedEndpoints() - if result { - t.Error("expected `canUseAuthenticatedEndpoints` to be false") - } - ws.SetCanUseAuthenticatedEndpoints(true) - result = ws.CanUseAuthenticatedEndpoints() - if !result { - t.Error("expected `canUseAuthenticatedEndpoints` to be true") - } -} - -func TestRemoveSubscribedChannels(t *testing.T) { - w := Websocket{ - channelsToSubscribe: []WebsocketChannelSubscription{ - { - Channel: "hello3", - }, - }, - } - - w.RemoveSubscribedChannels([]WebsocketChannelSubscription{{Channel: "hello3"}}) - if len(w.channelsToSubscribe) == 1 { - t.Error("Did not remove subscription") - } -} - -const ( - websocketTestURL = "wss://www.bitmex.com/realtime" - returnResponseURL = "wss://ws.kraken.com" - useProxyTests = false // Disabled by default. Freely available proxy servers that work all the time are difficult to find - proxyURL = "http://212.186.171.4:80" // Replace with a usable proxy server -) - -var wc *WebsocketConnection -var dialer websocket.Dialer - -type testStruct struct { - Error error - WC WebsocketConnection -} - -type testRequest struct { - Event string `json:"event"` - RequestID int64 `json:"reqid,omitempty"` - Pairs []string `json:"pair"` - Subscription testRequestData `json:"subscription,omitempty"` -} - -// testRequestData contains details on WS channel -type testRequestData struct { - Name string `json:"name,omitempty"` - Interval int64 `json:"interval,omitempty"` - Depth int64 `json:"depth,omitempty"` -} - -type testResponse struct { - RequestID int64 `json:"reqid,omitempty"` -} - -// TestMain setup test -func TestMain(m *testing.M) { - wc = &WebsocketConnection{ - ExchangeName: "test", - URL: returnResponseURL, - ResponseMaxLimit: 7000000000, - ResponseCheckTimeout: 30000000, - } - os.Exit(m.Run()) -} - -// TestDial logic test -func TestDial(t *testing.T) { - var testCases = []testStruct{ - {Error: nil, WC: WebsocketConnection{ExchangeName: "test1", Verbose: true, URL: websocketTestURL, RateLimit: 10, ResponseCheckTimeout: 30000000, ResponseMaxLimit: 7000000000}}, - {Error: errors.New(" Error: malformed ws or wss URL"), WC: WebsocketConnection{ExchangeName: "test2", Verbose: true, URL: "", ResponseCheckTimeout: 30000000, ResponseMaxLimit: 7000000000}}, - {Error: nil, WC: WebsocketConnection{ExchangeName: "test3", Verbose: true, URL: websocketTestURL, ProxyURL: proxyURL, ResponseCheckTimeout: 30000000, ResponseMaxLimit: 7000000000}}, - } - for i := range testCases { - testData := &testCases[i] - t.Run(testData.WC.ExchangeName, func(t *testing.T) { - if testData.WC.ProxyURL != "" && !useProxyTests { - t.Skip("Proxy testing not enabled, skipping") - } - err := testData.WC.Dial(&dialer, http.Header{}) - if err != nil { - if testData.Error != nil && err.Error() == testData.Error.Error() { - return - } - t.Fatal(err) - } - }) - } -} - -// TestSendMessage logic test -func TestSendMessage(t *testing.T) { - var testCases = []testStruct{ - {Error: nil, WC: WebsocketConnection{ExchangeName: "test1", Verbose: true, URL: websocketTestURL, RateLimit: 10, ResponseCheckTimeout: 30000000, ResponseMaxLimit: 7000000000}}, - {Error: errors.New(" Error: malformed ws or wss URL"), WC: WebsocketConnection{ExchangeName: "test2", Verbose: true, URL: "", ResponseCheckTimeout: 30000000, ResponseMaxLimit: 7000000000}}, - {Error: nil, WC: WebsocketConnection{ExchangeName: "test3", Verbose: true, URL: websocketTestURL, ProxyURL: proxyURL, ResponseCheckTimeout: 30000000, ResponseMaxLimit: 7000000000}}, - } - for i := range testCases { - testData := &testCases[i] - t.Run(testData.WC.ExchangeName, func(t *testing.T) { - if testData.WC.ProxyURL != "" && !useProxyTests { - t.Skip("Proxy testing not enabled, skipping") - } - err := testData.WC.Dial(&dialer, http.Header{}) - if err != nil { - if testData.Error != nil && err.Error() == testData.Error.Error() { - return - } - t.Fatal(err) - } - err = testData.WC.SendJSONMessage(Ping) - if err != nil { - t.Error(err) - } - err = testData.WC.SendRawMessage(websocket.TextMessage, []byte(Ping)) - if err != nil { - t.Error(err) - } - }) - } -} - -// TestSendMessageWithResponse logic test -func TestSendMessageWithResponse(t *testing.T) { - if wc.ProxyURL != "" && !useProxyTests { - t.Skip("Proxy testing not enabled, skipping") - } - err := wc.Dial(&dialer, http.Header{}) - if err != nil { - t.Fatal(err) - } - go readMessages(wc, t) - - request := testRequest{ - Event: "subscribe", - Pairs: []string{currency.NewPairWithDelimiter("XBT", "USD", "/").String()}, - Subscription: testRequestData{ - Name: "ticker", - }, - RequestID: wc.GenerateMessageID(false), - } - _, err = wc.SendMessageReturnResponse(request.RequestID, request) - if err != nil { - t.Error(err) - } -} - -// TestSetupPingHandler logic test -func TestSetupPingHandler(t *testing.T) { - if wc.ProxyURL != "" && !useProxyTests { - t.Skip("Proxy testing not enabled, skipping") - } - wc.Shutdown = make(chan struct{}) - err := wc.Dial(&dialer, http.Header{}) - if err != nil { - t.Fatal(err) - } - - wc.SetupPingHandler(WebsocketPingHandler{ - UseGorillaHandler: true, - MessageType: websocket.PingMessage, - Delay: 1000, - }) - - err = wc.Connection.Close() - if err != nil { - t.Error(err) - } - - err = wc.Dial(&dialer, http.Header{}) - if err != nil { - t.Fatal(err) - } - wc.SetupPingHandler(WebsocketPingHandler{ - MessageType: websocket.TextMessage, - Message: []byte(Ping), - Delay: 200, - }) - time.Sleep(time.Millisecond * 500) - close(wc.Shutdown) - wc.Wg.Wait() -} - -// TestParseBinaryResponse logic test -func TestParseBinaryResponse(t *testing.T) { - var b bytes.Buffer - w := gzip.NewWriter(&b) - _, err := w.Write([]byte("hello")) - if err != nil { - t.Error(err) - } - err = w.Close() - if err != nil { - t.Error(err) - } - var resp []byte - resp, err = wc.parseBinaryResponse(b.Bytes()) - if err != nil { - t.Error(err) - } - if !strings.EqualFold(string(resp), "hello") { - t.Errorf("GZip conversion failed. Received: '%v', Expected: 'hello'", string(resp)) - } - - var b2 bytes.Buffer - w2, err2 := flate.NewWriter(&b2, 1) - if err2 != nil { - t.Error(err2) - } - _, err2 = w2.Write([]byte("hello")) - if err2 != nil { - t.Error(err) - } - err2 = w2.Close() - if err2 != nil { - t.Error(err) - } - resp2, err3 := wc.parseBinaryResponse(b2.Bytes()) - if err3 != nil { - t.Error(err3) - } - if !strings.EqualFold(string(resp2), "hello") { - t.Errorf("GZip conversion failed. Received: '%v', Expected: 'hello'", string(resp2)) - } -} - -// TestSetResponseIDAndData logic test -func TestSetResponseIDAndData(t *testing.T) { - wc.IDResponses = nil - wc.SetResponseIDAndData(0, nil) - wc.SetResponseIDAndData(1, []byte("hi")) - if len(wc.IDResponses) != 2 { - t.Error("Expected 2 entries") - } -} - -// TestIsIDWaitingForResponse logic test -func TestIsIDWaitingForResponse(t *testing.T) { - wc.IDResponses = nil - wc.SetResponseIDAndData(0, nil) - wc.SetResponseIDAndData(1, []byte("hi")) - if len(wc.IDResponses) != 2 { - t.Error("Expected 2 entries") - } - if !wc.IsIDWaitingForResponse(0) { - t.Error("Expected true") - } - if wc.IsIDWaitingForResponse(2) { - t.Error("Expected false") - } - if wc.IsIDWaitingForResponse(1337) { - t.Error("Expected false") - } -} - -// readMessages helper func -func readMessages(wc *WebsocketConnection, t *testing.T) { - timer := time.NewTimer(20 * time.Second) - for { - select { - case <-timer.C: - return - default: - resp, err := wc.ReadMessage() - if err != nil { - t.Error(err) - return - } - var incoming testResponse - err = json.Unmarshal(resp.Raw, &incoming) - if err != nil { - t.Error(err) - return - } - if incoming.RequestID > 0 { - wc.SetResponseIDAndData(incoming.RequestID, resp.Raw) - return - } - } - } -} - -// TestCanUseAuthenticatedWebsocketForWrapper logic test -func TestCanUseAuthenticatedWebsocketForWrapper(t *testing.T) { - ws := &Websocket{} - resp := ws.CanUseAuthenticatedWebsocketForWrapper() - if resp { - t.Error("Expected false, `connected` is false") - } - ws.setConnectedStatus(true) - resp = ws.CanUseAuthenticatedWebsocketForWrapper() - if resp { - t.Error("Expected false, `connected` is true and `CanUseAuthenticatedEndpoints` is false") - } - ws.canUseAuthenticatedEndpoints = true - resp = ws.CanUseAuthenticatedWebsocketForWrapper() - if !resp { - t.Error("Expected true, `connected` and `CanUseAuthenticatedEndpoints` is true") - } -} diff --git a/exchanges/websocket/wshandler/wshandler_types.go b/exchanges/websocket/wshandler/wshandler_types.go deleted file mode 100644 index b9180413..00000000 --- a/exchanges/websocket/wshandler/wshandler_types.go +++ /dev/null @@ -1,183 +0,0 @@ -package wshandler - -import ( - "sync" - "time" - - "github.com/gorilla/websocket" - "github.com/thrasher-corp/gocryptotrader/currency" - "github.com/thrasher-corp/gocryptotrader/exchanges/asset" - "github.com/thrasher-corp/gocryptotrader/exchanges/order" - "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wsorderbook" -) - -// Websocket functionality list and state consts -const ( - // WebsocketNotEnabled alerts of a disabled websocket - WebsocketNotEnabled = "exchange_websocket_not_enabled" - manageSubscriptionsDelay = 5 * time.Second - // connection monitor time delays and limits - connectionMonitorDelay = 2 * time.Second - WebsocketNotAuthenticatedUsingRest = "%v - Websocket not authenticated, using REST" - Ping = "ping" - Pong = "pong" - UnhandledMessage = " - Unhandled websocket message: " -) - -// Websocket defines a return type for websocket connections via the interface -// wrapper for routine processing in routines.go -type Websocket struct { - canUseAuthenticatedEndpoints bool - enabled bool - init bool - connected bool - connecting bool - trafficMonitorRunning bool - verbose bool - connectionMonitorRunning bool - trafficTimeout time.Duration - proxyAddr string - defaultURL string - runningURL string - exchangeName string - m sync.Mutex - subscriptionMutex sync.Mutex - connectionMutex sync.RWMutex - connector func() error - subscribedChannels []WebsocketChannelSubscription - channelsToSubscribe []WebsocketChannelSubscription - channelSubscriber func(channelToSubscribe WebsocketChannelSubscription) error - channelUnsubscriber func(channelToUnsubscribe WebsocketChannelSubscription) error - DataHandler chan interface{} - // ShutdownC is the main shutdown channel which controls all websocket go funcs - ShutdownC chan struct{} - // Orderbook is a local cache of orderbooks - Orderbook wsorderbook.WebsocketOrderbookLocal - // Wg defines a wait group for websocket routines for cleanly shutting down - // routines - Wg sync.WaitGroup - // TrafficAlert monitors if there is a halt in traffic throughput - TrafficAlert chan struct{} - // ReadMessageErrors will received all errors from ws.ReadMessage() and verify if its a disconnection - ReadMessageErrors chan error - features *protocol.Features -} - -// WebsocketSetup defines variables for setting up a websocket connection -type WebsocketSetup struct { - Enabled bool - Verbose bool - AuthenticatedWebsocketAPISupport bool - WebsocketTimeout time.Duration - DefaultURL string - ExchangeName string - RunningURL string - Connector func() error - Subscriber func(channelToSubscribe WebsocketChannelSubscription) error - UnSubscriber func(channelToUnsubscribe WebsocketChannelSubscription) error - Features *protocol.Features -} - -// WebsocketChannelSubscription container for websocket subscriptions -// Currently only a one at a time thing to avoid complexity -type WebsocketChannelSubscription struct { - Channel string - Currency currency.Pair - Params map[string]interface{} -} - -// WebsocketResponse defines generalised data from the websocket connection -type WebsocketResponse struct { - Type int - Raw []byte -} - -// WebsocketOrderbookUpdate defines a websocket event in which the orderbook -// has been updated in the orderbook package -type WebsocketOrderbookUpdate struct { - Pair currency.Pair - Asset asset.Item - Exchange string -} - -// TradeData defines trade data -type TradeData struct { - Timestamp time.Time - CurrencyPair currency.Pair - AssetType asset.Item - Exchange string - EventType order.Type - Price float64 - Amount float64 - Side order.Side -} - -// FundingData defines funding data -type FundingData struct { - Timestamp time.Time - CurrencyPair currency.Pair - AssetType asset.Item - Exchange string - Amount float64 - Rate float64 - Period int64 - Side order.Side -} - -// KlineData defines kline feed -type KlineData struct { - Timestamp time.Time - Pair currency.Pair - AssetType asset.Item - Exchange string - StartTime time.Time - CloseTime time.Time - Interval string - OpenPrice float64 - ClosePrice float64 - HighPrice float64 - LowPrice float64 - Volume float64 -} - -// WebsocketPositionUpdated reflects a change in orders/contracts on an exchange -type WebsocketPositionUpdated struct { - Timestamp time.Time - Pair currency.Pair - AssetType asset.Item - Exchange string -} - -// WebsocketConnection contains all the data needed to send a message to a WS -type WebsocketConnection struct { - sync.Mutex - Verbose bool - connected bool - connectionMutex sync.RWMutex - RateLimit float64 - ExchangeName string - URL string - ProxyURL string - Wg sync.WaitGroup - Connection *websocket.Conn - Shutdown chan struct{} - // These are the request IDs and the corresponding response JSON - IDResponses map[int64][]byte - ResponseCheckTimeout time.Duration - ResponseMaxLimit time.Duration - TrafficTimeout time.Duration -} - -// WebsocketPingHandler container for ping handler settings -type WebsocketPingHandler struct { - UseGorillaHandler bool - MessageType int - Message []byte - Delay time.Duration -} - -// UnhandledMessageWarning is used for unhandled websocket messages -type UnhandledMessageWarning struct { - Message string -} diff --git a/exchanges/yobit/yobit_wrapper.go b/exchanges/yobit/yobit_wrapper.go index 76a91a47..354644f3 100644 --- a/exchanges/yobit/yobit_wrapper.go +++ b/exchanges/yobit/yobit_wrapper.go @@ -20,7 +20,6 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" "github.com/thrasher-corp/gocryptotrader/log" "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) @@ -56,20 +55,11 @@ func (y *Yobit) SetDefaults() { y.API.CredentialsValidator.RequiresKey = true y.API.CredentialsValidator.RequiresSecret = true - y.CurrencyPairs = currency.PairsManager{ - AssetTypes: asset.Items{ - asset.Spot, - }, - UseGlobalFormat: true, - RequestFormat: ¤cy.PairFormat{ - Delimiter: "_", - Uppercase: false, - Separator: "-", - }, - ConfigFormat: ¤cy.PairFormat{ - Delimiter: "_", - Uppercase: true, - }, + requestFmt := ¤cy.PairFormat{Delimiter: currency.UnderscoreDelimiter, Separator: currency.DashDelimiter} + configFmt := ¤cy.PairFormat{Delimiter: currency.UnderscoreDelimiter, Uppercase: true} + err := y.SetGlobalPairsManager(requestFmt, configFmt, asset.Spot) + if err != nil { + log.Errorln(log.ExchangeSys, err) } y.Features = exchange.Features{ @@ -119,7 +109,6 @@ func (y *Yobit) Setup(exch *config.ExchangeConfig) error { y.SetEnabled(false) return nil } - return y.SetupDefaults(exch) } @@ -173,13 +162,19 @@ func (y *Yobit) UpdateTradablePairs(forceUpdate bool) error { if err != nil { return err } - - return y.UpdatePairs(currency.NewPairsFromStrings(pairs), asset.Spot, false, forceUpdate) + p, err := currency.NewPairsFromStrings(pairs) + if err != nil { + return err + } + return y.UpdatePairs(p, asset.Spot, false, forceUpdate) } // UpdateTicker updates and returns the ticker for a currency pair func (y *Yobit) UpdateTicker(p currency.Pair, assetType asset.Item) (*ticker.Price, error) { - enabledPairs := y.GetEnabledPairs(assetType) + enabledPairs, err := y.GetEnabledPairs(assetType) + if err != nil { + return nil, err + } pairsCollated, err := y.FormatExchangeCurrencies(enabledPairs, assetType) if err != nil { return nil, err @@ -191,24 +186,29 @@ func (y *Yobit) UpdateTicker(p currency.Pair, assetType asset.Item) (*ticker.Pri } for i := range enabledPairs { - curr := y.FormatExchangeCurrency(enabledPairs[i], assetType).Lower().String() + fpair, err := y.FormatExchangeCurrency(enabledPairs[i], assetType) + if err != nil { + return nil, err + } + curr := fpair.Lower().String() if _, ok := result[curr]; !ok { continue } - resultCurr := result[curr] - tickerPrice := new(ticker.Price) - tickerPrice.Pair = enabledPairs[i] - tickerPrice.Last = resultCurr.Last - tickerPrice.Ask = resultCurr.Sell - tickerPrice.Bid = resultCurr.Buy - tickerPrice.Last = resultCurr.Last - tickerPrice.Low = resultCurr.Low - tickerPrice.QuoteVolume = resultCurr.VolumeCurrent - tickerPrice.Volume = resultCurr.Vol - err = ticker.ProcessTicker(y.Name, tickerPrice, assetType) + resultCurr := result[curr] + err = ticker.ProcessTicker(&ticker.Price{ + Pair: enabledPairs[i], + Last: resultCurr.Last, + Ask: resultCurr.Sell, + Bid: resultCurr.Buy, + Low: resultCurr.Low, + QuoteVolume: resultCurr.VolumeCurrent, + Volume: resultCurr.Vol, + ExchangeName: y.Name, + AssetType: assetType, + }) if err != nil { - log.Error(log.Ticker, err) + return nil, err } } return ticker.GetTicker(y.Name, p, assetType) @@ -235,7 +235,11 @@ func (y *Yobit) FetchOrderbook(p currency.Pair, assetType asset.Item) (*orderboo // UpdateOrderbook updates and returns the orderbook for a currency pair func (y *Yobit) UpdateOrderbook(p currency.Pair, assetType asset.Item) (*orderbook.Base, error) { orderBook := new(orderbook.Base) - orderbookNew, err := y.GetDepth(y.FormatExchangeCurrency(p, assetType).String()) + fpair, err := y.FormatExchangeCurrency(p, assetType) + if err != nil { + return nil, err + } + orderbookNew, err := y.GetDepth(fpair.String()) if err != nil { return orderBook, err } @@ -376,10 +380,16 @@ func (y *Yobit) CancelAllOrders(_ *order.Cancel) (order.CancelAllResponse, error } var allActiveOrders []map[string]ActiveOrders - enabledPairs := y.GetEnabledPairs(asset.Spot) + enabledPairs, err := y.GetEnabledPairs(asset.Spot) + if err != nil { + return cancelAllOrdersResponse, err + } for i := range enabledPairs { - fCurr := y.FormatExchangeCurrency(enabledPairs[i], asset.Spot).String() - activeOrdersForPair, err := y.GetOpenOrders(fCurr) + fCurr, err := y.FormatExchangeCurrency(enabledPairs[i], asset.Spot) + if err != nil { + return cancelAllOrdersResponse, err + } + activeOrdersForPair, err := y.GetOpenOrders(fCurr.String()) if err != nil { return cancelAllOrdersResponse, err } @@ -446,11 +456,6 @@ func (y *Yobit) WithdrawFiatFundsToInternationalBank(withdrawRequest *withdraw.R return nil, common.ErrFunctionNotSupported } -// GetWebsocket returns a pointer to the exchange websocket -func (y *Yobit) GetWebsocket() (*wshandler.Websocket, error) { - return nil, common.ErrFunctionNotSupported -} - // GetFeeByType returns an estimate of fee based on type of transaction func (y *Yobit) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { if !y.AllowAuthenticatedRequest() && // Todo check connection status @@ -463,16 +468,28 @@ func (y *Yobit) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { // GetActiveOrders retrieves any orders that are active/open func (y *Yobit) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, error) { var orders []order.Detail + + format, err := y.GetPairFormat(asset.Spot, false) + if err != nil { + return nil, err + } + for x := range req.Pairs { - fCurr := y.FormatExchangeCurrency(req.Pairs[x], asset.Spot).String() - resp, err := y.GetOpenOrders(fCurr) + fCurr, err := y.FormatExchangeCurrency(req.Pairs[x], asset.Spot) + if err != nil { + return nil, err + } + resp, err := y.GetOpenOrders(fCurr.String()) if err != nil { return nil, err } for id := range resp { - symbol := currency.NewPairDelimiter(resp[id].Pair, - y.GetPairFormat(asset.Spot, false).Delimiter) + var symbol currency.Pair + symbol, err = currency.NewPairDelimiter(resp[id].Pair, format.Delimiter) + if err != nil { + return nil, err + } orderDate := time.Unix(int64(resp[id].TimestampCreated), 0) side := order.Side(strings.ToUpper(resp[id].Type)) orders = append(orders, order.Detail{ @@ -497,13 +514,17 @@ func (y *Yobit) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, er func (y *Yobit) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, error) { var allOrders []TradeHistory for x := range req.Pairs { + fpair, err := y.FormatExchangeCurrency(req.Pairs[x], asset.Spot) + if err != nil { + return nil, err + } resp, err := y.GetTradeHistory(0, 10000, math.MaxInt64, req.StartTicks.Unix(), req.EndTicks.Unix(), "DESC", - y.FormatExchangeCurrency(req.Pairs[x], asset.Spot).String()) + fpair.String()) if err != nil { return nil, err } @@ -513,10 +534,18 @@ func (y *Yobit) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, er } } + format, err := y.GetPairFormat(asset.Spot, false) + if err != nil { + return nil, err + } + var orders []order.Detail for i := range allOrders { - symbol := currency.NewPairDelimiter(allOrders[i].Pair, - y.GetPairFormat(asset.Spot, false).Delimiter) + var symbol currency.Pair + symbol, err = currency.NewPairDelimiter(allOrders[i].Pair, format.Delimiter) + if err != nil { + return nil, err + } orderDate := time.Unix(int64(allOrders[i].Timestamp), 0) side := order.Side(strings.ToUpper(allOrders[i].Type)) orders = append(orders, order.Detail{ @@ -535,28 +564,6 @@ func (y *Yobit) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, er return orders, nil } -// SubscribeToWebsocketChannels appends to ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle subscribing -func (y *Yobit) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - return common.ErrFunctionNotSupported -} - -// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle unsubscribing -func (y *Yobit) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - return common.ErrFunctionNotSupported -} - -// GetSubscriptions returns a copied list of subscriptions -func (y *Yobit) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) { - return nil, common.ErrFunctionNotSupported -} - -// AuthenticateWebsocket sends an authentication message to the websocket -func (y *Yobit) AuthenticateWebsocket() error { - return common.ErrFunctionNotSupported -} - // ValidateCredentials validates current credentials used for wrapper // functionality func (y *Yobit) ValidateCredentials() error { diff --git a/exchanges/zb/zb.go b/exchanges/zb/zb.go index e4a7080d..55cc7ede 100644 --- a/exchanges/zb/zb.go +++ b/exchanges/zb/zb.go @@ -16,7 +16,6 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/request" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" ) const ( @@ -42,7 +41,6 @@ const ( // 47.91.169.147 api.zb.com // 47.52.55.212 trade.zb.com type ZB struct { - WebsocketConn *wshandler.WebsocketConnection exchange.Base } diff --git a/exchanges/zb/zb_test.go b/exchanges/zb/zb_test.go index d7ad8c46..e0667bc6 100644 --- a/exchanges/zb/zb_test.go +++ b/exchanges/zb/zb_test.go @@ -20,7 +20,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/kline" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) @@ -49,12 +49,11 @@ func TestMain(m *testing.M) { zbConfig.API.AuthenticatedWebsocketSupport = true zbConfig.API.Credentials.Key = apiKey zbConfig.API.Credentials.Secret = apiSecret + z.Websocket = sharedtestvalues.NewTestWebsocket() err = z.Setup(zbConfig) if err != nil { log.Fatal("ZB setup error", err) } - z.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride() - z.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride() os.Exit(m.Run()) } @@ -63,22 +62,13 @@ func setupWsAuth(t *testing.T) { return } if !z.Websocket.IsEnabled() && !z.API.AuthenticatedWebsocketSupport || !z.ValidateAPICredentials() || !canManipulateRealOrders { - t.Skip(wshandler.WebsocketNotEnabled) - } - z.WebsocketConn = &wshandler.WebsocketConnection{ - ExchangeName: z.Name, - URL: zbWebsocketAPI, - Verbose: z.Verbose, - ResponseMaxLimit: exchange.DefaultWebsocketResponseMaxLimit, - ResponseCheckTimeout: exchange.DefaultWebsocketResponseCheckTimeout, + t.Skip(stream.WebsocketNotEnabled) } var dialer websocket.Dialer - err := z.WebsocketConn.Dial(&dialer, http.Header{}) + err := z.Websocket.Conn.Dial(&dialer, http.Header{}) if err != nil { t.Fatal(err) } - z.Websocket.DataHandler = make(chan interface{}, 11) - z.Websocket.TrafficAlert = make(chan struct{}, 11) go z.wsReadData() wsSetupRan = true } @@ -841,9 +831,12 @@ func TestWsCreateSubUserResponse(t *testing.T) { } func TestGetHistoricCandles(t *testing.T) { - currencyPair := currency.NewPairFromString("btc_usdt") + currencyPair, err := currency.NewPairFromString("btc_usdt") + if err != nil { + t.Fatal(err) + } startTime := time.Now().Add(-time.Hour * 1) - _, err := z.GetHistoricCandles(currencyPair, asset.Spot, startTime, time.Now(), kline.OneHour) + _, err = z.GetHistoricCandles(currencyPair, asset.Spot, startTime, time.Now(), kline.OneHour) if err != nil { t.Fatal(err) } @@ -855,10 +848,13 @@ func TestGetHistoricCandles(t *testing.T) { } func TestGetHistoricCandlesExtended(t *testing.T) { - currencyPair := currency.NewPairFromString("btc_usdt") + currencyPair, err := currency.NewPairFromString("btc_usdt") + if err != nil { + t.Fatal(err) + } start := time.Now().AddDate(0, -2, 0) end := time.Now() - _, err := z.GetHistoricCandlesExtended(currencyPair, asset.Spot, start, end, kline.OneHour) + _, err = z.GetHistoricCandlesExtended(currencyPair, asset.Spot, start, end, kline.OneHour) if err != nil { t.Fatal(err) } diff --git a/exchanges/zb/zb_websocket.go b/exchanges/zb/zb_websocket.go index 8ad8fb45..bc453faf 100644 --- a/exchanges/zb/zb_websocket.go +++ b/exchanges/zb/zb_websocket.go @@ -11,14 +11,15 @@ import ( "time" "github.com/gorilla/websocket" + "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/crypto" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" "github.com/thrasher-corp/gocryptotrader/log" ) @@ -31,43 +32,36 @@ const ( // WsConnect initiates a websocket connection func (z *ZB) WsConnect() error { if !z.Websocket.IsEnabled() || !z.IsEnabled() { - return errors.New(wshandler.WebsocketNotEnabled) + return errors.New(stream.WebsocketNotEnabled) } var dialer websocket.Dialer - err := z.WebsocketConn.Dial(&dialer, http.Header{}) + err := z.Websocket.Conn.Dial(&dialer, http.Header{}) if err != nil { return err } + subs, err := z.GenerateDefaultSubscriptions() + if err != nil { + return err + } go z.wsReadData() - z.GenerateDefaultSubscriptions() - - return nil + return z.Websocket.SubscribeToChannels(subs) } // wsReadData handles all the websocket data coming from the websocket // connection func (z *ZB) wsReadData() { z.Websocket.Wg.Add(1) - defer func() { - z.Websocket.Wg.Done() - }() + defer z.Websocket.Wg.Done() for { - select { - case <-z.Websocket.ShutdownC: + resp := z.Websocket.Conn.ReadMessage() + if resp.Raw == nil { return - default: - resp, err := z.WebsocketConn.ReadMessage() - if err != nil { - z.Websocket.ReadMessageErrors <- err - return - } - z.Websocket.TrafficAlert <- struct{}{} - err = z.wsHandleData(resp.Raw) - if err != nil { - z.Websocket.DataHandler <- err - } + } + err := z.wsHandleData(resp.Raw) + if err != nil { + z.Websocket.DataHandler <- err } } } @@ -80,14 +74,17 @@ func (z *ZB) wsHandleData(respRaw []byte) error { return err } if result.No > 0 { - if z.WebsocketConn.IsIDWaitingForResponse(result.No) { - z.WebsocketConn.SetResponseIDAndData(result.No, fixedJSON) + if z.Websocket.Match.IncomingWithData(result.No, fixedJSON) { return nil } } if result.Code > 0 && result.Code != 1000 { - return fmt.Errorf("%v request failed, message: %v, error code: %v", z.Name, result.Message, wsErrCodes[result.Code]) + return fmt.Errorf("%v request failed, message: %v, error code: %v", + z.Name, + result.Message, + wsErrCodes[result.Code]) } + switch { case strings.Contains(result.Channel, "markets"): var markets Markets @@ -95,7 +92,6 @@ func (z *ZB) wsHandleData(respRaw []byte) error { if err != nil { return err } - case strings.Contains(result.Channel, "ticker"): cPair := strings.Split(result.Channel, "_") var wsTicker WsTicker @@ -104,6 +100,11 @@ func (z *ZB) wsHandleData(respRaw []byte) error { return err } + p, err := currency.NewPairFromString(cPair[0]) + if err != nil { + return err + } + z.Websocket.DataHandler <- &ticker.Price{ ExchangeName: z.Name, Close: wsTicker.Data.Last, @@ -115,9 +116,8 @@ func (z *ZB) wsHandleData(respRaw []byte) error { Ask: wsTicker.Data.Sell, LastUpdated: time.Unix(0, wsTicker.Date*int64(time.Millisecond)), AssetType: asset.Spot, - Pair: currency.NewPairFromString(cPair[0]), + Pair: p, } - case strings.Contains(result.Channel, "depth"): var depth WsDepth err := json.Unmarshal(fixedJSON, &depth) @@ -142,7 +142,11 @@ func (z *ZB) wsHandleData(respRaw []byte) error { } channelInfo := strings.Split(result.Channel, "_") - cPair := currency.NewPairFromString(channelInfo[0]) + cPair, err := currency.NewPairFromString(channelInfo[0]) + if err != nil { + return err + } + var newOrderBook orderbook.Base newOrderBook.Asks = asks newOrderBook.Bids = bids @@ -154,12 +158,6 @@ func (z *ZB) wsHandleData(respRaw []byte) error { if err != nil { return err } - - z.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{ - Pair: cPair, - Asset: asset.Spot, - Exchange: z.Name, - } case strings.Contains(result.Channel, "_order"): cPair := strings.Split(result.Channel, "_") var o WsSubmitOrderResponse @@ -168,9 +166,17 @@ func (z *ZB) wsHandleData(respRaw []byte) error { return err } if !o.Success { - return fmt.Errorf("%s - Order %v failed to be placed. %s", z.Name, o.Data.EntrustID, respRaw) + return fmt.Errorf("%s - Order %v failed to be placed. %s", + z.Name, + o.Data.EntrustID, + respRaw) } - p := currency.NewPairFromString(cPair[0]) + + p, err := currency.NewPairFromString(cPair[0]) + if err != nil { + return err + } + var a asset.Item a, err = z.GetPairAssetType(p) if err != nil { @@ -190,12 +196,21 @@ func (z *ZB) wsHandleData(respRaw []byte) error { return err } if !o.Success { - return fmt.Errorf("%s - Order %v failed to be cancelled. %s", z.Name, o.Data.EntrustID, respRaw) + return fmt.Errorf("%s - Order %v failed to be cancelled. %s", + z.Name, + o.Data.EntrustID, + respRaw) } + + p, err := currency.NewPairFromString(cPair[0]) + if err != nil { + return err + } + z.Websocket.DataHandler <- &order.Modify{ Exchange: z.Name, ID: strconv.FormatInt(o.Data.EntrustID, 10), - Pair: currency.NewPairFromString(cPair[0]), + Pair: p, Status: order.Cancelled, } case strings.Contains(result.Channel, "trades"): @@ -204,65 +219,86 @@ func (z *ZB) wsHandleData(respRaw []byte) error { if err != nil { return err } - // Most up to date trade - if len(trades.Data) == 0 { - return errors.New(z.Name + " - Empty websocket trade data received: " + string(fixedJSON)) - } - t := trades.Data[len(trades.Data)-1] - channelInfo := strings.Split(result.Channel, "_") - cPair := currency.NewPairFromString(channelInfo[0]) - tSide, err := order.StringToOrderSide(t.TradeType) - if err != nil { - z.Websocket.DataHandler <- order.ClassificationError{ - Exchange: z.Name, - Err: err, + for i := range trades.Data { + channelInfo := strings.Split(result.Channel, "_") + cPair, err := currency.NewPairFromString(channelInfo[0]) + if err != nil { + return err + } + + tSide, err := order.StringToOrderSide(trades.Data[i].TradeType) + if err != nil { + z.Websocket.DataHandler <- order.ClassificationError{ + Exchange: z.Name, + Err: err, + } + } + + z.Websocket.DataHandler <- stream.TradeData{ + Timestamp: time.Unix(trades.Data[i].Date, 0), + CurrencyPair: cPair, + AssetType: asset.Spot, + Exchange: z.Name, + Price: trades.Data[i].Price, + Amount: trades.Data[i].Amount, + Side: tSide, } } - z.Websocket.DataHandler <- wshandler.TradeData{ - Timestamp: time.Unix(t.Date, 0), - CurrencyPair: cPair, - AssetType: asset.Spot, - Exchange: z.Name, - Price: t.Price, - Amount: t.Amount, - Side: tSide, - } default: - z.Websocket.DataHandler <- wshandler.UnhandledMessageWarning{Message: z.Name + wshandler.UnhandledMessage + string(respRaw)} - return nil + z.Websocket.DataHandler <- stream.UnhandledMessageWarning{ + Message: z.Name + + stream.UnhandledMessage + + string(respRaw)} } return nil } // GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() -func (z *ZB) GenerateDefaultSubscriptions() { - var subscriptions []wshandler.WebsocketChannelSubscription - // Tickerdata is its own channel - subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{ +func (z *ZB) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, error) { + var subscriptions []stream.ChannelSubscription + // market configuration is its own channel + subscriptions = append(subscriptions, stream.ChannelSubscription{ Channel: "markets", }) channels := []string{"%s_ticker", "%s_depth", "%s_trades"} - enabledCurrencies := z.GetEnabledPairs(asset.Spot) + enabledCurrencies, err := z.GetEnabledPairs(asset.Spot) + if err != nil { + return nil, err + } + for i := range channels { for j := range enabledCurrencies { enabledCurrencies[j].Delimiter = "" - subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{ + subscriptions = append(subscriptions, stream.ChannelSubscription{ Channel: fmt.Sprintf(channels[i], enabledCurrencies[j].Lower().String()), Currency: enabledCurrencies[j].Lower(), + Asset: asset.Spot, }) } } - z.Websocket.SubscribeToChannels(subscriptions) + return subscriptions, nil } // Subscribe sends a websocket message to receive data from the channel -func (z *ZB) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error { - subscriptionRequest := Subscription{ - Event: zWebsocketAddChannel, - Channel: channelToSubscribe.Channel, +func (z *ZB) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error { + var errs common.Errors + for i := range channelsToSubscribe { + subscriptionRequest := Subscription{ + Event: zWebsocketAddChannel, + Channel: channelsToSubscribe[i].Channel, + } + err := z.Websocket.Conn.SendJSONMessage(subscriptionRequest) + if err != nil { + errs = append(errs, err) + continue + } + z.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe[i]) } - return z.WebsocketConn.SendJSONMessage(subscriptionRequest) + if errs != nil { + return errs + } + return nil } func (z *ZB) wsGenerateSignature(request interface{}) string { @@ -303,9 +339,9 @@ func (z *ZB) wsAddSubUser(username, password string) (*WsGetSubUserListResponse, request.Channel = "addSubUser" request.Event = zWebsocketAddChannel request.Accesskey = z.API.Credentials.Key - request.No = z.WebsocketConn.GenerateMessageID(true) + request.No = z.Websocket.Conn.GenerateMessageID(true) request.Sign = z.wsGenerateSignature(request) - resp, err := z.WebsocketConn.SendMessageReturnResponse(request.No, request) + resp, err := z.Websocket.Conn.SendMessageReturnResponse(request.No, request) if err != nil { return nil, err } @@ -315,7 +351,11 @@ func (z *ZB) wsAddSubUser(username, password string) (*WsGetSubUserListResponse, return nil, err } if genericResponse.Code > 0 && genericResponse.Code != 1000 { - return nil, fmt.Errorf("%v request failed, message: %v, error code: %v", z.Name, genericResponse.Message, wsErrCodes[genericResponse.Code]) + return nil, + fmt.Errorf("%v request failed, message: %v, error code: %v", + z.Name, + genericResponse.Message, + wsErrCodes[genericResponse.Code]) } var response WsGetSubUserListResponse err = json.Unmarshal(resp, &response) @@ -327,16 +367,17 @@ func (z *ZB) wsAddSubUser(username, password string) (*WsGetSubUserListResponse, func (z *ZB) wsGetSubUserList() (*WsGetSubUserListResponse, error) { if !z.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) { - return nil, fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", z.Name) + return nil, + fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", z.Name) } request := WsAuthenticatedRequest{} request.Channel = "getSubUserList" request.Event = zWebsocketAddChannel request.Accesskey = z.API.Credentials.Key - request.No = z.WebsocketConn.GenerateMessageID(true) + request.No = z.Websocket.Conn.GenerateMessageID(true) request.Sign = z.wsGenerateSignature(request) - resp, err := z.WebsocketConn.SendMessageReturnResponse(request.No, request) + resp, err := z.Websocket.Conn.SendMessageReturnResponse(request.No, request) if err != nil { return nil, err } @@ -346,28 +387,33 @@ func (z *ZB) wsGetSubUserList() (*WsGetSubUserListResponse, error) { return nil, err } if response.Code > 0 && response.Code != 1000 { - return &response, fmt.Errorf("%v request failed, message: %v, error code: %v", z.Name, response.Message, wsErrCodes[response.Code]) + return &response, + fmt.Errorf("%v request failed, message: %v, error code: %v", + z.Name, + response.Message, + wsErrCodes[response.Code]) } return &response, nil } func (z *ZB) wsDoTransferFunds(pair currency.Code, amount float64, fromUserName, toUserName string) (*WsRequestResponse, error) { if !z.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) { - return nil, fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", z.Name) + return nil, + fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", z.Name) } request := WsDoTransferFundsRequest{ Amount: amount, Currency: pair, FromUserName: fromUserName, ToUserName: toUserName, - No: z.WebsocketConn.GenerateMessageID(true), + No: z.Websocket.Conn.GenerateMessageID(true), } request.Channel = "doTransferFunds" request.Event = zWebsocketAddChannel request.Accesskey = z.API.Credentials.Key request.Sign = z.wsGenerateSignature(request) - resp, err := z.WebsocketConn.SendMessageReturnResponse(request.No, request) + resp, err := z.Websocket.Conn.SendMessageReturnResponse(request.No, request) if err != nil { return nil, err } @@ -377,14 +423,19 @@ func (z *ZB) wsDoTransferFunds(pair currency.Code, amount float64, fromUserName, return nil, err } if response.Code > 0 && response.Code != 1000 { - return &response, fmt.Errorf("%v request failed, message: %v, error code: %v", z.Name, response.Message, wsErrCodes[response.Code]) + return &response, + fmt.Errorf("%v request failed, message: %v, error code: %v", + z.Name, + response.Message, + wsErrCodes[response.Code]) } return &response, nil } func (z *ZB) wsCreateSubUserKey(assetPerm, entrustPerm, leverPerm, moneyPerm bool, keyName, toUserID string) (*WsRequestResponse, error) { if !z.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) { - return nil, fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", z.Name) + return nil, + fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", z.Name) } request := WsCreateSubUserKeyRequest{ AssetPerm: assetPerm, @@ -392,7 +443,7 @@ func (z *ZB) wsCreateSubUserKey(assetPerm, entrustPerm, leverPerm, moneyPerm boo KeyName: keyName, LeverPerm: leverPerm, MoneyPerm: moneyPerm, - No: z.WebsocketConn.GenerateMessageID(true), + No: z.Websocket.Conn.GenerateMessageID(true), ToUserID: toUserID, } request.Channel = "createSubUserKey" @@ -400,7 +451,7 @@ func (z *ZB) wsCreateSubUserKey(assetPerm, entrustPerm, leverPerm, moneyPerm boo request.Accesskey = z.API.Credentials.Key request.Sign = z.wsGenerateSignature(request) - resp, err := z.WebsocketConn.SendMessageReturnResponse(request.No, request) + resp, err := z.Websocket.Conn.SendMessageReturnResponse(request.No, request) if err != nil { return nil, err } @@ -410,27 +461,32 @@ func (z *ZB) wsCreateSubUserKey(assetPerm, entrustPerm, leverPerm, moneyPerm boo return nil, err } if response.Code > 0 && response.Code != 1000 { - return &response, fmt.Errorf("%v request failed, message: %v, error code: %v", z.Name, response.Message, wsErrCodes[response.Code]) + return &response, + fmt.Errorf("%v request failed, message: %v, error code: %v", + z.Name, + response.Message, + wsErrCodes[response.Code]) } return &response, nil } func (z *ZB) wsSubmitOrder(pair currency.Pair, amount, price float64, tradeType int64) (*WsSubmitOrderResponse, error) { if !z.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) { - return nil, fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", z.Name) + return nil, + fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", z.Name) } request := WsSubmitOrderRequest{ Amount: amount, Price: price, TradeType: tradeType, - No: z.WebsocketConn.GenerateMessageID(true), + No: z.Websocket.Conn.GenerateMessageID(true), } request.Channel = pair.String() + "_order" request.Event = zWebsocketAddChannel request.Accesskey = z.API.Credentials.Key request.Sign = z.wsGenerateSignature(request) - resp, err := z.WebsocketConn.SendMessageReturnResponse(request.No, request) + resp, err := z.Websocket.Conn.SendMessageReturnResponse(request.No, request) if err != nil { return nil, err } @@ -440,25 +496,30 @@ func (z *ZB) wsSubmitOrder(pair currency.Pair, amount, price float64, tradeType return nil, err } if response.Code > 0 && response.Code != 1000 { - return &response, fmt.Errorf("%v request failed, message: %v, error code: %v", z.Name, response.Message, wsErrCodes[response.Code]) + return &response, + fmt.Errorf("%v request failed, message: %v, error code: %v", + z.Name, + response.Message, + wsErrCodes[response.Code]) } return &response, nil } func (z *ZB) wsCancelOrder(pair currency.Pair, orderID int64) (*WsCancelOrderResponse, error) { if !z.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) { - return nil, fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", z.Name) + return nil, + fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", z.Name) } request := WsCancelOrderRequest{ ID: orderID, - No: z.WebsocketConn.GenerateMessageID(true), + No: z.Websocket.Conn.GenerateMessageID(true), } request.Channel = pair.String() + "_cancelorder" request.Event = zWebsocketAddChannel request.Accesskey = z.API.Credentials.Key request.Sign = z.wsGenerateSignature(request) - resp, err := z.WebsocketConn.SendMessageReturnResponse(request.No, request) + resp, err := z.Websocket.Conn.SendMessageReturnResponse(request.No, request) if err != nil { return nil, err } @@ -468,25 +529,30 @@ func (z *ZB) wsCancelOrder(pair currency.Pair, orderID int64) (*WsCancelOrderRes return nil, err } if response.Code > 0 && response.Code != 1000 { - return &response, fmt.Errorf("%v request failed, message: %v, error code: %v", z.Name, response.Message, wsErrCodes[response.Code]) + return &response, + fmt.Errorf("%v request failed, message: %v, error code: %v", + z.Name, + response.Message, + wsErrCodes[response.Code]) } return &response, nil } func (z *ZB) wsGetOrder(pair currency.Pair, orderID int64) (*WsGetOrderResponse, error) { if !z.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) { - return nil, fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", z.Name) + return nil, + fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", z.Name) } request := WsGetOrderRequest{ ID: orderID, - No: z.WebsocketConn.GenerateMessageID(true), + No: z.Websocket.Conn.GenerateMessageID(true), } request.Channel = pair.String() + "_getorder" request.Event = zWebsocketAddChannel request.Accesskey = z.API.Credentials.Key request.Sign = z.wsGenerateSignature(request) - resp, err := z.WebsocketConn.SendMessageReturnResponse(request.No, request) + resp, err := z.Websocket.Conn.SendMessageReturnResponse(request.No, request) if err != nil { return nil, err } @@ -496,25 +562,30 @@ func (z *ZB) wsGetOrder(pair currency.Pair, orderID int64) (*WsGetOrderResponse, return nil, err } if response.Code > 0 && response.Code != 1000 { - return &response, fmt.Errorf("%v request failed, message: %v, error code: %v", z.Name, response.Message, wsErrCodes[response.Code]) + return &response, + fmt.Errorf("%v request failed, message: %v, error code: %v", + z.Name, + response.Message, + wsErrCodes[response.Code]) } return &response, nil } func (z *ZB) wsGetOrders(pair currency.Pair, pageIndex, tradeType int64) (*WsGetOrdersResponse, error) { if !z.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) { - return nil, fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", z.Name) + return nil, + fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", z.Name) } request := WsGetOrdersRequest{ PageIndex: pageIndex, TradeType: tradeType, - No: z.WebsocketConn.GenerateMessageID(true), + No: z.Websocket.Conn.GenerateMessageID(true), } request.Channel = pair.String() + "_getorders" request.Event = zWebsocketAddChannel request.Accesskey = z.API.Credentials.Key request.Sign = z.wsGenerateSignature(request) - resp, err := z.WebsocketConn.SendMessageReturnResponse(request.No, request) + resp, err := z.Websocket.Conn.SendMessageReturnResponse(request.No, request) if err != nil { return nil, err } @@ -524,26 +595,31 @@ func (z *ZB) wsGetOrders(pair currency.Pair, pageIndex, tradeType int64) (*WsGet return nil, err } if response.Code > 0 && response.Code != 1000 { - return &response, fmt.Errorf("%v request failed, message: %v, error code: %v", z.Name, response.Message, wsErrCodes[response.Code]) + return &response, + fmt.Errorf("%v request failed, message: %v, error code: %v", + z.Name, + response.Message, + wsErrCodes[response.Code]) } return &response, nil } func (z *ZB) wsGetOrdersIgnoreTradeType(pair currency.Pair, pageIndex, pageSize int64) (*WsGetOrdersIgnoreTradeTypeResponse, error) { if !z.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) { - return nil, fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", z.Name) + return nil, + fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", z.Name) } request := WsGetOrdersIgnoreTradeTypeRequest{ PageIndex: pageIndex, PageSize: pageSize, - No: z.WebsocketConn.GenerateMessageID(true), + No: z.Websocket.Conn.GenerateMessageID(true), } request.Channel = pair.String() + "_getordersignoretradetype" request.Event = zWebsocketAddChannel request.Accesskey = z.API.Credentials.Key request.Sign = z.wsGenerateSignature(request) - resp, err := z.WebsocketConn.SendMessageReturnResponse(request.No, request) + resp, err := z.Websocket.Conn.SendMessageReturnResponse(request.No, request) if err != nil { return nil, err } @@ -553,24 +629,29 @@ func (z *ZB) wsGetOrdersIgnoreTradeType(pair currency.Pair, pageIndex, pageSize return nil, err } if response.Code > 0 && response.Code != 1000 { - return &response, fmt.Errorf("%v request failed, message: %v, error code: %v", z.Name, response.Message, wsErrCodes[response.Code]) + return &response, + fmt.Errorf("%v request failed, message: %v, error code: %v", + z.Name, + response.Message, + wsErrCodes[response.Code]) } return &response, nil } func (z *ZB) wsGetAccountInfoRequest() (*WsGetAccountInfoResponse, error) { if !z.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) { - return nil, fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", z.Name) + return nil, + fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", z.Name) } request := WsAuthenticatedRequest{ Channel: "getaccountinfo", Event: zWebsocketAddChannel, Accesskey: z.API.Credentials.Key, - No: z.WebsocketConn.GenerateMessageID(true), + No: z.Websocket.Conn.GenerateMessageID(true), } request.Sign = z.wsGenerateSignature(request) - resp, err := z.WebsocketConn.SendMessageReturnResponse(request.No, request) + resp, err := z.Websocket.Conn.SendMessageReturnResponse(request.No, request) if err != nil { return nil, err } @@ -580,7 +661,11 @@ func (z *ZB) wsGetAccountInfoRequest() (*WsGetAccountInfoResponse, error) { return nil, err } if response.Code > 0 && response.Code != 1000 { - return &response, fmt.Errorf("%v request failed, message: %v, error code: %v", z.Name, response.Message, wsErrCodes[response.Code]) + return &response, + fmt.Errorf("%v request failed, message: %v, error code: %v", + z.Name, + response.Message, + wsErrCodes[response.Code]) } return &response, nil } diff --git a/exchanges/zb/zb_wrapper.go b/exchanges/zb/zb_wrapper.go index c5a003d6..f4778160 100644 --- a/exchanges/zb/zb_wrapper.go +++ b/exchanges/zb/zb_wrapper.go @@ -19,8 +19,8 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" - "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" "github.com/thrasher-corp/gocryptotrader/log" "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) @@ -56,19 +56,11 @@ func (z *ZB) SetDefaults() { z.API.CredentialsValidator.RequiresKey = true z.API.CredentialsValidator.RequiresSecret = true - z.CurrencyPairs = currency.PairsManager{ - AssetTypes: asset.Items{ - asset.Spot, - }, - - UseGlobalFormat: true, - RequestFormat: ¤cy.PairFormat{ - Delimiter: "_", - }, - ConfigFormat: ¤cy.PairFormat{ - Delimiter: "_", - Uppercase: true, - }, + requestFmt := ¤cy.PairFormat{Delimiter: currency.UnderscoreDelimiter} + configFmt := ¤cy.PairFormat{Delimiter: currency.UnderscoreDelimiter, Uppercase: true} + err := z.SetGlobalPairsManager(requestFmt, configFmt, asset.Spot) + if err != nil { + log.Errorln(log.ExchangeSys, err) } z.Features = exchange.Features{ @@ -142,7 +134,7 @@ func (z *ZB) SetDefaults() { z.API.Endpoints.URLSecondaryDefault = zbMarketURL z.API.Endpoints.URLSecondary = z.API.Endpoints.URLSecondaryDefault z.API.Endpoints.WebsocketURL = zbWebsocketAPI - z.Websocket = wshandler.New() + z.Websocket = stream.New() z.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit z.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout } @@ -159,33 +151,30 @@ func (z *ZB) Setup(exch *config.ExchangeConfig) error { return err } - err = z.Websocket.Setup( - &wshandler.WebsocketSetup{ - Enabled: exch.Features.Enabled.Websocket, - Verbose: exch.Verbose, - AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, - WebsocketTimeout: exch.WebsocketTrafficTimeout, - DefaultURL: zbWebsocketAPI, - ExchangeName: exch.Name, - RunningURL: exch.API.Endpoints.WebsocketURL, - Connector: z.WsConnect, - Subscriber: z.Subscribe, - Features: &z.Features.Supports.WebsocketCapabilities, - }) + err = z.Websocket.Setup(&stream.WebsocketSetup{ + Enabled: exch.Features.Enabled.Websocket, + Verbose: exch.Verbose, + AuthenticatedWebsocketAPISupport: exch.API.AuthenticatedWebsocketSupport, + WebsocketTimeout: exch.WebsocketTrafficTimeout, + DefaultURL: zbWebsocketAPI, + ExchangeName: exch.Name, + RunningURL: exch.API.Endpoints.WebsocketURL, + Connector: z.WsConnect, + GenerateSubscriptions: z.GenerateDefaultSubscriptions, + Subscriber: z.Subscribe, + Features: &z.Features.Supports.WebsocketCapabilities, + OrderbookBufferLimit: exch.WebsocketOrderbookBufferLimit, + }) if err != nil { return err } - z.WebsocketConn = &wshandler.WebsocketConnection{ - ExchangeName: z.Name, + return z.Websocket.SetupNewConnection(stream.ConnectionSetup{ URL: z.Websocket.GetWebsocketURL(), - ProxyURL: z.Websocket.GetProxyAddress(), - Verbose: z.Verbose, RateLimit: zbWebsocketRateLimit, ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout, ResponseMaxLimit: exch.WebsocketResponseMaxLimit, - } - return nil + }) } // Start starts the OKEX go routine @@ -235,19 +224,24 @@ func (z *ZB) UpdateTradablePairs(forceUpdate bool) error { if err != nil { return err } - return z.UpdatePairs(currency.NewPairsFromStrings(pairs), asset.Spot, false, forceUpdate) + p, err := currency.NewPairsFromStrings(pairs) + if err != nil { + return err + } + return z.UpdatePairs(p, asset.Spot, false, forceUpdate) } // UpdateTicker updates and returns the ticker for a currency pair func (z *ZB) UpdateTicker(p currency.Pair, assetType asset.Item) (*ticker.Price, error) { - tickerPrice := new(ticker.Price) - result, err := z.GetTickers() if err != nil { - return tickerPrice, err + return nil, err } - enabledPairs := z.GetEnabledPairs(assetType) + enabledPairs, err := z.GetEnabledPairs(assetType) + if err != nil { + return nil, err + } for x := range enabledPairs { // We can't use either pair format here, so format it to lower- // case and without any delimiter @@ -255,18 +249,19 @@ func (z *ZB) UpdateTicker(p currency.Pair, assetType asset.Item) (*ticker.Price, if _, ok := result[curr]; !ok { continue } - var tp ticker.Price - tp.Pair = enabledPairs[x] - tp.High = result[curr].High - tp.Last = result[curr].Last - tp.Ask = result[curr].Sell - tp.Bid = result[curr].Buy - tp.Low = result[curr].Low - tp.Volume = result[curr].Volume - err = ticker.ProcessTicker(z.Name, &tp, assetType) + err = ticker.ProcessTicker(&ticker.Price{ + Pair: enabledPairs[x], + High: result[curr].High, + Last: result[curr].Last, + Ask: result[curr].Sell, + Bid: result[curr].Buy, + Low: result[curr].Low, + Volume: result[curr].Volume, + ExchangeName: z.Name, + AssetType: assetType}) if err != nil { - log.Error(log.Ticker, err) + return nil, err } } @@ -294,9 +289,12 @@ func (z *ZB) FetchOrderbook(p currency.Pair, assetType asset.Item) (*orderbook.B // UpdateOrderbook updates and returns the orderbook for a currency pair func (z *ZB) UpdateOrderbook(p currency.Pair, assetType asset.Item) (*orderbook.Base, error) { orderBook := new(orderbook.Base) - curr := z.FormatExchangeCurrency(p, assetType).String() + currFormat, err := z.FormatExchangeCurrency(p, assetType) + if err != nil { + return nil, err + } - orderbookNew, err := z.GetOrderbook(curr) + orderbookNew, err := z.GetOrderbook(currFormat.String()) if err != nil { return orderBook, err } @@ -473,8 +471,11 @@ func (z *ZB) CancelOrder(o *order.Cancel) error { } return nil } - return z.CancelExistingOrder(orderIDInt, z.FormatExchangeCurrency(o.Pair, - o.AssetType).String()) + fpair, err := z.FormatExchangeCurrency(o.Pair, o.AssetType) + if err != nil { + return err + } + return z.CancelExistingOrder(orderIDInt, fpair.String()) } // CancelAllOrders cancels all orders associated with a currency pair @@ -483,11 +484,18 @@ func (z *ZB) CancelAllOrders(_ *order.Cancel) (order.CancelAllResponse, error) { Status: make(map[string]string), } var allOpenOrders []Order - enabledPairs := z.GetEnabledPairs(asset.Spot) + enabledPairs, err := z.GetEnabledPairs(asset.Spot) + if err != nil { + return cancelAllOrdersResponse, err + } + for x := range enabledPairs { - fPair := z.FormatExchangeCurrency(enabledPairs[x], asset.Spot).String() + fPair, err := z.FormatExchangeCurrency(enabledPairs[x], asset.Spot) + if err != nil { + return cancelAllOrdersResponse, err + } for y := int64(1); ; y++ { - openOrders, err := z.GetUnfinishedOrdersIgnoreTradeType(fPair, y, 10) + openOrders, err := z.GetUnfinishedOrdersIgnoreTradeType(fPair.String(), y, 10) if err != nil { if strings.Contains(err.Error(), "3001") { break @@ -508,9 +516,15 @@ func (z *ZB) CancelAllOrders(_ *order.Cancel) (order.CancelAllResponse, error) { } for i := range allOpenOrders { - err := z.CancelOrder(&order.Cancel{ + p, err := currency.NewPairFromString(allOpenOrders[i].Currency) + if err != nil { + cancelAllOrdersResponse.Status[strconv.FormatInt(allOpenOrders[i].ID, 10)] = err.Error() + continue + } + + err = z.CancelOrder(&order.Cancel{ ID: strconv.FormatInt(allOpenOrders[i].ID, 10), - Pair: currency.NewPairFromString(allOpenOrders[i].Currency), + Pair: p, }) if err != nil { cancelAllOrdersResponse.Status[strconv.FormatInt(allOpenOrders[i].ID, 10)] = err.Error() @@ -560,11 +574,6 @@ func (z *ZB) WithdrawFiatFundsToInternationalBank(withdrawRequest *withdraw.Requ return nil, common.ErrFunctionNotSupported } -// GetWebsocket returns a pointer to the exchange websocket -func (z *ZB) GetWebsocket() (*wshandler.Websocket, error) { - return z.Websocket, nil -} - // GetFeeByType returns an estimate of fee based on type of transaction func (z *ZB) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { if !z.AllowAuthenticatedRequest() && // Todo check connection status @@ -580,8 +589,11 @@ func (z *ZB) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, error var allOrders []Order for x := range req.Pairs { for i := int64(1); ; i++ { - fPair := z.FormatExchangeCurrency(req.Pairs[x], asset.Spot).String() - resp, err := z.GetUnfinishedOrdersIgnoreTradeType(fPair, i, 10) + fPair, err := z.FormatExchangeCurrency(req.Pairs[x], asset.Spot) + if err != nil { + return nil, err + } + resp, err := z.GetUnfinishedOrdersIgnoreTradeType(fPair.String(), i, 10) if err != nil { if strings.Contains(err.Error(), "3001") { break @@ -601,10 +613,19 @@ func (z *ZB) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, error } } + format, err := z.GetPairFormat(asset.Spot, false) + if err != nil { + return nil, err + } + var orders []order.Detail for i := range allOrders { - symbol := currency.NewPairDelimiter(allOrders[i].Currency, - z.GetPairFormat(asset.Spot, false).Delimiter) + var symbol currency.Pair + symbol, err = currency.NewPairDelimiter(allOrders[i].Currency, + format.Delimiter) + if err != nil { + return nil, err + } orderDate := time.Unix(int64(allOrders[i].TradeDate), 0) orderSide := orderSideMap[allOrders[i].Type] orders = append(orders, order.Detail{ @@ -653,8 +674,11 @@ func (z *ZB) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, error } for x := range req.Pairs { for y := int64(1); ; y++ { - fPair := z.FormatExchangeCurrency(req.Pairs[x], asset.Spot).String() - resp, err := z.GetOrders(fPair, y, side) + fPair, err := z.FormatExchangeCurrency(req.Pairs[x], asset.Spot) + if err != nil { + return nil, err + } + resp, err := z.GetOrders(fPair.String(), y, side) if err != nil { return nil, err } @@ -669,9 +693,18 @@ func (z *ZB) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, error } } + format, err := z.GetPairFormat(asset.Spot, false) + if err != nil { + return nil, err + } + for i := range allOrders { - symbol := currency.NewPairDelimiter(allOrders[i].Currency, - z.GetPairFormat(asset.Spot, false).Delimiter) + var symbol currency.Pair + symbol, err = currency.NewPairDelimiter(allOrders[i].Currency, + format.Delimiter) + if err != nil { + return nil, err + } orderDate := time.Unix(int64(allOrders[i].TradeDate), 0) orderSide := orderSideMap[allOrders[i].Type] orders = append(orders, order.Detail{ @@ -689,29 +722,6 @@ func (z *ZB) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, error return orders, nil } -// SubscribeToWebsocketChannels appends to ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle subscribing -func (z *ZB) SubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - z.Websocket.SubscribeToChannels(channels) - return nil -} - -// UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe -// which lets websocket.manageSubscriptions handle unsubscribing -func (z *ZB) UnsubscribeToWebsocketChannels(channels []wshandler.WebsocketChannelSubscription) error { - return common.ErrFunctionNotSupported -} - -// GetSubscriptions returns a copied list of subscriptions -func (z *ZB) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) { - return z.Websocket.GetSubscriptions(), nil -} - -// AuthenticateWebsocket sends an authentication message to the websocket -func (z *ZB) AuthenticateWebsocket() error { - return common.ErrFunctionNotSupported -} - // ValidateCredentials validates current credentials used for wrapper // functionality func (z *ZB) ValidateCredentials() error { @@ -745,9 +755,14 @@ func (z *ZB) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end tim } } + formattedPair, err := z.FormatExchangeCurrency(pair, a) + if err != nil { + return kline.Item{}, err + } + klineParams := KlinesRequestParams{ Type: z.FormatExchangeKlineInterval(interval), - Symbol: z.FormatExchangeCurrency(pair, a).String(), + Symbol: formattedPair.String(), } candles, err := z.GetSpotKline(klineParams) diff --git a/gctrpc/rpc.pb.go b/gctrpc/rpc.pb.go index 8a2594f1..d926f207 100644 --- a/gctrpc/rpc.pb.go +++ b/gctrpc/rpc.pb.go @@ -1,79 +1,63 @@ // Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.22.0-devel -// protoc v3.12.3 // source: rpc.proto package gctrpc import ( context "context" + fmt "fmt" proto "github.com/golang/protobuf/proto" timestamp "github.com/golang/protobuf/ptypes/timestamp" _ "google.golang.org/genproto/googleapis/api/annotations" grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - reflect "reflect" - sync "sync" + math "math" ) -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf -// This is a compile-time assertion that a sufficiently up-to-date version -// of the legacy proto package is being used. -const _ = proto.ProtoPackageIsVersion4 +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package type GetInfoRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GetInfoRequest) Reset() { - *x = GetInfoRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetInfoRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetInfoRequest) ProtoMessage() {} - -func (x *GetInfoRequest) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetInfoRequest.ProtoReflect.Descriptor instead. +func (m *GetInfoRequest) Reset() { *m = GetInfoRequest{} } +func (m *GetInfoRequest) String() string { return proto.CompactTextString(m) } +func (*GetInfoRequest) ProtoMessage() {} func (*GetInfoRequest) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{0} + return fileDescriptor_77a6da22d6a3feb1, []int{0} } +func (m *GetInfoRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetInfoRequest.Unmarshal(m, b) +} +func (m *GetInfoRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetInfoRequest.Marshal(b, m, deterministic) +} +func (m *GetInfoRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetInfoRequest.Merge(m, src) +} +func (m *GetInfoRequest) XXX_Size() int { + return xxx_messageInfo_GetInfoRequest.Size(m) +} +func (m *GetInfoRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetInfoRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetInfoRequest proto.InternalMessageInfo + type GetInfoResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - Uptime string `protobuf:"bytes,1,opt,name=uptime,proto3" json:"uptime,omitempty"` AvailableExchanges int64 `protobuf:"varint,2,opt,name=available_exchanges,json=availableExchanges,proto3" json:"available_exchanges,omitempty"` EnabledExchanges int64 `protobuf:"varint,3,opt,name=enabled_exchanges,json=enabledExchanges,proto3" json:"enabled_exchanges,omitempty"` @@ -81,10860 +65,7559 @@ type GetInfoResponse struct { DefaultFiatCurrency string `protobuf:"bytes,5,opt,name=default_fiat_currency,json=defaultFiatCurrency,proto3" json:"default_fiat_currency,omitempty"` SubsystemStatus map[string]bool `protobuf:"bytes,6,rep,name=subsystem_status,json=subsystemStatus,proto3" json:"subsystem_status,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"` RpcEndpoints map[string]*RPCEndpoint `protobuf:"bytes,7,rep,name=rpc_endpoints,json=rpcEndpoints,proto3" json:"rpc_endpoints,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GetInfoResponse) Reset() { - *x = GetInfoResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetInfoResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetInfoResponse) ProtoMessage() {} - -func (x *GetInfoResponse) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetInfoResponse.ProtoReflect.Descriptor instead. +func (m *GetInfoResponse) Reset() { *m = GetInfoResponse{} } +func (m *GetInfoResponse) String() string { return proto.CompactTextString(m) } +func (*GetInfoResponse) ProtoMessage() {} func (*GetInfoResponse) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{1} + return fileDescriptor_77a6da22d6a3feb1, []int{1} } -func (x *GetInfoResponse) GetUptime() string { - if x != nil { - return x.Uptime +func (m *GetInfoResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetInfoResponse.Unmarshal(m, b) +} +func (m *GetInfoResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetInfoResponse.Marshal(b, m, deterministic) +} +func (m *GetInfoResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetInfoResponse.Merge(m, src) +} +func (m *GetInfoResponse) XXX_Size() int { + return xxx_messageInfo_GetInfoResponse.Size(m) +} +func (m *GetInfoResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetInfoResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetInfoResponse proto.InternalMessageInfo + +func (m *GetInfoResponse) GetUptime() string { + if m != nil { + return m.Uptime } return "" } -func (x *GetInfoResponse) GetAvailableExchanges() int64 { - if x != nil { - return x.AvailableExchanges +func (m *GetInfoResponse) GetAvailableExchanges() int64 { + if m != nil { + return m.AvailableExchanges } return 0 } -func (x *GetInfoResponse) GetEnabledExchanges() int64 { - if x != nil { - return x.EnabledExchanges +func (m *GetInfoResponse) GetEnabledExchanges() int64 { + if m != nil { + return m.EnabledExchanges } return 0 } -func (x *GetInfoResponse) GetDefaultForexProvider() string { - if x != nil { - return x.DefaultForexProvider +func (m *GetInfoResponse) GetDefaultForexProvider() string { + if m != nil { + return m.DefaultForexProvider } return "" } -func (x *GetInfoResponse) GetDefaultFiatCurrency() string { - if x != nil { - return x.DefaultFiatCurrency +func (m *GetInfoResponse) GetDefaultFiatCurrency() string { + if m != nil { + return m.DefaultFiatCurrency } return "" } -func (x *GetInfoResponse) GetSubsystemStatus() map[string]bool { - if x != nil { - return x.SubsystemStatus +func (m *GetInfoResponse) GetSubsystemStatus() map[string]bool { + if m != nil { + return m.SubsystemStatus } return nil } -func (x *GetInfoResponse) GetRpcEndpoints() map[string]*RPCEndpoint { - if x != nil { - return x.RpcEndpoints +func (m *GetInfoResponse) GetRpcEndpoints() map[string]*RPCEndpoint { + if m != nil { + return m.RpcEndpoints } return nil } type GetCommunicationRelayersRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GetCommunicationRelayersRequest) Reset() { - *x = GetCommunicationRelayersRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetCommunicationRelayersRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetCommunicationRelayersRequest) ProtoMessage() {} - -func (x *GetCommunicationRelayersRequest) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetCommunicationRelayersRequest.ProtoReflect.Descriptor instead. +func (m *GetCommunicationRelayersRequest) Reset() { *m = GetCommunicationRelayersRequest{} } +func (m *GetCommunicationRelayersRequest) String() string { return proto.CompactTextString(m) } +func (*GetCommunicationRelayersRequest) ProtoMessage() {} func (*GetCommunicationRelayersRequest) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{2} + return fileDescriptor_77a6da22d6a3feb1, []int{2} } +func (m *GetCommunicationRelayersRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetCommunicationRelayersRequest.Unmarshal(m, b) +} +func (m *GetCommunicationRelayersRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetCommunicationRelayersRequest.Marshal(b, m, deterministic) +} +func (m *GetCommunicationRelayersRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetCommunicationRelayersRequest.Merge(m, src) +} +func (m *GetCommunicationRelayersRequest) XXX_Size() int { + return xxx_messageInfo_GetCommunicationRelayersRequest.Size(m) +} +func (m *GetCommunicationRelayersRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetCommunicationRelayersRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetCommunicationRelayersRequest proto.InternalMessageInfo + type CommunicationRelayer struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"` - Connected bool `protobuf:"varint,2,opt,name=connected,proto3" json:"connected,omitempty"` + Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"` + Connected bool `protobuf:"varint,2,opt,name=connected,proto3" json:"connected,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *CommunicationRelayer) Reset() { - *x = CommunicationRelayer{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *CommunicationRelayer) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*CommunicationRelayer) ProtoMessage() {} - -func (x *CommunicationRelayer) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[3] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use CommunicationRelayer.ProtoReflect.Descriptor instead. +func (m *CommunicationRelayer) Reset() { *m = CommunicationRelayer{} } +func (m *CommunicationRelayer) String() string { return proto.CompactTextString(m) } +func (*CommunicationRelayer) ProtoMessage() {} func (*CommunicationRelayer) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{3} + return fileDescriptor_77a6da22d6a3feb1, []int{3} } -func (x *CommunicationRelayer) GetEnabled() bool { - if x != nil { - return x.Enabled +func (m *CommunicationRelayer) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_CommunicationRelayer.Unmarshal(m, b) +} +func (m *CommunicationRelayer) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_CommunicationRelayer.Marshal(b, m, deterministic) +} +func (m *CommunicationRelayer) XXX_Merge(src proto.Message) { + xxx_messageInfo_CommunicationRelayer.Merge(m, src) +} +func (m *CommunicationRelayer) XXX_Size() int { + return xxx_messageInfo_CommunicationRelayer.Size(m) +} +func (m *CommunicationRelayer) XXX_DiscardUnknown() { + xxx_messageInfo_CommunicationRelayer.DiscardUnknown(m) +} + +var xxx_messageInfo_CommunicationRelayer proto.InternalMessageInfo + +func (m *CommunicationRelayer) GetEnabled() bool { + if m != nil { + return m.Enabled } return false } -func (x *CommunicationRelayer) GetConnected() bool { - if x != nil { - return x.Connected +func (m *CommunicationRelayer) GetConnected() bool { + if m != nil { + return m.Connected } return false } type GetCommunicationRelayersResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - CommunicationRelayers map[string]*CommunicationRelayer `protobuf:"bytes,1,rep,name=communication_relayers,json=communicationRelayers,proto3" json:"communication_relayers,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GetCommunicationRelayersResponse) Reset() { - *x = GetCommunicationRelayersResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetCommunicationRelayersResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetCommunicationRelayersResponse) ProtoMessage() {} - -func (x *GetCommunicationRelayersResponse) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[4] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetCommunicationRelayersResponse.ProtoReflect.Descriptor instead. +func (m *GetCommunicationRelayersResponse) Reset() { *m = GetCommunicationRelayersResponse{} } +func (m *GetCommunicationRelayersResponse) String() string { return proto.CompactTextString(m) } +func (*GetCommunicationRelayersResponse) ProtoMessage() {} func (*GetCommunicationRelayersResponse) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{4} + return fileDescriptor_77a6da22d6a3feb1, []int{4} } -func (x *GetCommunicationRelayersResponse) GetCommunicationRelayers() map[string]*CommunicationRelayer { - if x != nil { - return x.CommunicationRelayers +func (m *GetCommunicationRelayersResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetCommunicationRelayersResponse.Unmarshal(m, b) +} +func (m *GetCommunicationRelayersResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetCommunicationRelayersResponse.Marshal(b, m, deterministic) +} +func (m *GetCommunicationRelayersResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetCommunicationRelayersResponse.Merge(m, src) +} +func (m *GetCommunicationRelayersResponse) XXX_Size() int { + return xxx_messageInfo_GetCommunicationRelayersResponse.Size(m) +} +func (m *GetCommunicationRelayersResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetCommunicationRelayersResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetCommunicationRelayersResponse proto.InternalMessageInfo + +func (m *GetCommunicationRelayersResponse) GetCommunicationRelayers() map[string]*CommunicationRelayer { + if m != nil { + return m.CommunicationRelayers } return nil } type GenericSubsystemRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Subsystem string `protobuf:"bytes,1,opt,name=subsystem,proto3" json:"subsystem,omitempty"` + Subsystem string `protobuf:"bytes,1,opt,name=subsystem,proto3" json:"subsystem,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GenericSubsystemRequest) Reset() { - *x = GenericSubsystemRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GenericSubsystemRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GenericSubsystemRequest) ProtoMessage() {} - -func (x *GenericSubsystemRequest) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[5] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GenericSubsystemRequest.ProtoReflect.Descriptor instead. +func (m *GenericSubsystemRequest) Reset() { *m = GenericSubsystemRequest{} } +func (m *GenericSubsystemRequest) String() string { return proto.CompactTextString(m) } +func (*GenericSubsystemRequest) ProtoMessage() {} func (*GenericSubsystemRequest) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{5} + return fileDescriptor_77a6da22d6a3feb1, []int{5} } -func (x *GenericSubsystemRequest) GetSubsystem() string { - if x != nil { - return x.Subsystem +func (m *GenericSubsystemRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GenericSubsystemRequest.Unmarshal(m, b) +} +func (m *GenericSubsystemRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GenericSubsystemRequest.Marshal(b, m, deterministic) +} +func (m *GenericSubsystemRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GenericSubsystemRequest.Merge(m, src) +} +func (m *GenericSubsystemRequest) XXX_Size() int { + return xxx_messageInfo_GenericSubsystemRequest.Size(m) +} +func (m *GenericSubsystemRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GenericSubsystemRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GenericSubsystemRequest proto.InternalMessageInfo + +func (m *GenericSubsystemRequest) GetSubsystem() string { + if m != nil { + return m.Subsystem } return "" } -type GenericSubsystemResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields -} - -func (x *GenericSubsystemResponse) Reset() { - *x = GenericSubsystemResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GenericSubsystemResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GenericSubsystemResponse) ProtoMessage() {} - -func (x *GenericSubsystemResponse) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[6] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GenericSubsystemResponse.ProtoReflect.Descriptor instead. -func (*GenericSubsystemResponse) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{6} -} - type GetSubsystemsRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GetSubsystemsRequest) Reset() { - *x = GetSubsystemsRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetSubsystemsRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetSubsystemsRequest) ProtoMessage() {} - -func (x *GetSubsystemsRequest) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[7] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetSubsystemsRequest.ProtoReflect.Descriptor instead. +func (m *GetSubsystemsRequest) Reset() { *m = GetSubsystemsRequest{} } +func (m *GetSubsystemsRequest) String() string { return proto.CompactTextString(m) } +func (*GetSubsystemsRequest) ProtoMessage() {} func (*GetSubsystemsRequest) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{7} + return fileDescriptor_77a6da22d6a3feb1, []int{6} } +func (m *GetSubsystemsRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetSubsystemsRequest.Unmarshal(m, b) +} +func (m *GetSubsystemsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetSubsystemsRequest.Marshal(b, m, deterministic) +} +func (m *GetSubsystemsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetSubsystemsRequest.Merge(m, src) +} +func (m *GetSubsystemsRequest) XXX_Size() int { + return xxx_messageInfo_GetSubsystemsRequest.Size(m) +} +func (m *GetSubsystemsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetSubsystemsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetSubsystemsRequest proto.InternalMessageInfo + type GetSusbsytemsResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - SubsystemsStatus map[string]bool `protobuf:"bytes,1,rep,name=subsystems_status,json=subsystemsStatus,proto3" json:"subsystems_status,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"` + SubsystemsStatus map[string]bool `protobuf:"bytes,1,rep,name=subsystems_status,json=subsystemsStatus,proto3" json:"subsystems_status,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GetSusbsytemsResponse) Reset() { - *x = GetSusbsytemsResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[8] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetSusbsytemsResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetSusbsytemsResponse) ProtoMessage() {} - -func (x *GetSusbsytemsResponse) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[8] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetSusbsytemsResponse.ProtoReflect.Descriptor instead. +func (m *GetSusbsytemsResponse) Reset() { *m = GetSusbsytemsResponse{} } +func (m *GetSusbsytemsResponse) String() string { return proto.CompactTextString(m) } +func (*GetSusbsytemsResponse) ProtoMessage() {} func (*GetSusbsytemsResponse) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{8} + return fileDescriptor_77a6da22d6a3feb1, []int{7} } -func (x *GetSusbsytemsResponse) GetSubsystemsStatus() map[string]bool { - if x != nil { - return x.SubsystemsStatus +func (m *GetSusbsytemsResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetSusbsytemsResponse.Unmarshal(m, b) +} +func (m *GetSusbsytemsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetSusbsytemsResponse.Marshal(b, m, deterministic) +} +func (m *GetSusbsytemsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetSusbsytemsResponse.Merge(m, src) +} +func (m *GetSusbsytemsResponse) XXX_Size() int { + return xxx_messageInfo_GetSusbsytemsResponse.Size(m) +} +func (m *GetSusbsytemsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetSusbsytemsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetSusbsytemsResponse proto.InternalMessageInfo + +func (m *GetSusbsytemsResponse) GetSubsystemsStatus() map[string]bool { + if m != nil { + return m.SubsystemsStatus } return nil } type GetRPCEndpointsRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GetRPCEndpointsRequest) Reset() { - *x = GetRPCEndpointsRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[9] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetRPCEndpointsRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetRPCEndpointsRequest) ProtoMessage() {} - -func (x *GetRPCEndpointsRequest) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[9] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetRPCEndpointsRequest.ProtoReflect.Descriptor instead. +func (m *GetRPCEndpointsRequest) Reset() { *m = GetRPCEndpointsRequest{} } +func (m *GetRPCEndpointsRequest) String() string { return proto.CompactTextString(m) } +func (*GetRPCEndpointsRequest) ProtoMessage() {} func (*GetRPCEndpointsRequest) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{9} + return fileDescriptor_77a6da22d6a3feb1, []int{8} } +func (m *GetRPCEndpointsRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetRPCEndpointsRequest.Unmarshal(m, b) +} +func (m *GetRPCEndpointsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetRPCEndpointsRequest.Marshal(b, m, deterministic) +} +func (m *GetRPCEndpointsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetRPCEndpointsRequest.Merge(m, src) +} +func (m *GetRPCEndpointsRequest) XXX_Size() int { + return xxx_messageInfo_GetRPCEndpointsRequest.Size(m) +} +func (m *GetRPCEndpointsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetRPCEndpointsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetRPCEndpointsRequest proto.InternalMessageInfo + type RPCEndpoint struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Started bool `protobuf:"varint,1,opt,name=started,proto3" json:"started,omitempty"` - ListenAddress string `protobuf:"bytes,2,opt,name=listen_address,json=listenAddress,proto3" json:"listen_address,omitempty"` + Started bool `protobuf:"varint,1,opt,name=started,proto3" json:"started,omitempty"` + ListenAddress string `protobuf:"bytes,2,opt,name=listen_address,json=listenAddress,proto3" json:"listen_address,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *RPCEndpoint) Reset() { - *x = RPCEndpoint{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[10] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *RPCEndpoint) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*RPCEndpoint) ProtoMessage() {} - -func (x *RPCEndpoint) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[10] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use RPCEndpoint.ProtoReflect.Descriptor instead. +func (m *RPCEndpoint) Reset() { *m = RPCEndpoint{} } +func (m *RPCEndpoint) String() string { return proto.CompactTextString(m) } +func (*RPCEndpoint) ProtoMessage() {} func (*RPCEndpoint) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{10} + return fileDescriptor_77a6da22d6a3feb1, []int{9} } -func (x *RPCEndpoint) GetStarted() bool { - if x != nil { - return x.Started +func (m *RPCEndpoint) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_RPCEndpoint.Unmarshal(m, b) +} +func (m *RPCEndpoint) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_RPCEndpoint.Marshal(b, m, deterministic) +} +func (m *RPCEndpoint) XXX_Merge(src proto.Message) { + xxx_messageInfo_RPCEndpoint.Merge(m, src) +} +func (m *RPCEndpoint) XXX_Size() int { + return xxx_messageInfo_RPCEndpoint.Size(m) +} +func (m *RPCEndpoint) XXX_DiscardUnknown() { + xxx_messageInfo_RPCEndpoint.DiscardUnknown(m) +} + +var xxx_messageInfo_RPCEndpoint proto.InternalMessageInfo + +func (m *RPCEndpoint) GetStarted() bool { + if m != nil { + return m.Started } return false } -func (x *RPCEndpoint) GetListenAddress() string { - if x != nil { - return x.ListenAddress +func (m *RPCEndpoint) GetListenAddress() string { + if m != nil { + return m.ListenAddress } return "" } type GetRPCEndpointsResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Endpoints map[string]*RPCEndpoint `protobuf:"bytes,1,rep,name=endpoints,proto3" json:"endpoints,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + Endpoints map[string]*RPCEndpoint `protobuf:"bytes,1,rep,name=endpoints,proto3" json:"endpoints,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GetRPCEndpointsResponse) Reset() { - *x = GetRPCEndpointsResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[11] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetRPCEndpointsResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetRPCEndpointsResponse) ProtoMessage() {} - -func (x *GetRPCEndpointsResponse) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[11] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetRPCEndpointsResponse.ProtoReflect.Descriptor instead. +func (m *GetRPCEndpointsResponse) Reset() { *m = GetRPCEndpointsResponse{} } +func (m *GetRPCEndpointsResponse) String() string { return proto.CompactTextString(m) } +func (*GetRPCEndpointsResponse) ProtoMessage() {} func (*GetRPCEndpointsResponse) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{11} + return fileDescriptor_77a6da22d6a3feb1, []int{10} } -func (x *GetRPCEndpointsResponse) GetEndpoints() map[string]*RPCEndpoint { - if x != nil { - return x.Endpoints +func (m *GetRPCEndpointsResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetRPCEndpointsResponse.Unmarshal(m, b) +} +func (m *GetRPCEndpointsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetRPCEndpointsResponse.Marshal(b, m, deterministic) +} +func (m *GetRPCEndpointsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetRPCEndpointsResponse.Merge(m, src) +} +func (m *GetRPCEndpointsResponse) XXX_Size() int { + return xxx_messageInfo_GetRPCEndpointsResponse.Size(m) +} +func (m *GetRPCEndpointsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetRPCEndpointsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetRPCEndpointsResponse proto.InternalMessageInfo + +func (m *GetRPCEndpointsResponse) GetEndpoints() map[string]*RPCEndpoint { + if m != nil { + return m.Endpoints } return nil } type GenericExchangeNameRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GenericExchangeNameRequest) Reset() { - *x = GenericExchangeNameRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[12] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GenericExchangeNameRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GenericExchangeNameRequest) ProtoMessage() {} - -func (x *GenericExchangeNameRequest) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[12] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GenericExchangeNameRequest.ProtoReflect.Descriptor instead. +func (m *GenericExchangeNameRequest) Reset() { *m = GenericExchangeNameRequest{} } +func (m *GenericExchangeNameRequest) String() string { return proto.CompactTextString(m) } +func (*GenericExchangeNameRequest) ProtoMessage() {} func (*GenericExchangeNameRequest) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{12} + return fileDescriptor_77a6da22d6a3feb1, []int{11} } -func (x *GenericExchangeNameRequest) GetExchange() string { - if x != nil { - return x.Exchange +func (m *GenericExchangeNameRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GenericExchangeNameRequest.Unmarshal(m, b) +} +func (m *GenericExchangeNameRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GenericExchangeNameRequest.Marshal(b, m, deterministic) +} +func (m *GenericExchangeNameRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GenericExchangeNameRequest.Merge(m, src) +} +func (m *GenericExchangeNameRequest) XXX_Size() int { + return xxx_messageInfo_GenericExchangeNameRequest.Size(m) +} +func (m *GenericExchangeNameRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GenericExchangeNameRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GenericExchangeNameRequest proto.InternalMessageInfo + +func (m *GenericExchangeNameRequest) GetExchange() string { + if m != nil { + return m.Exchange } return "" } -type GenericExchangeNameResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields -} - -func (x *GenericExchangeNameResponse) Reset() { - *x = GenericExchangeNameResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[13] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GenericExchangeNameResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GenericExchangeNameResponse) ProtoMessage() {} - -func (x *GenericExchangeNameResponse) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[13] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GenericExchangeNameResponse.ProtoReflect.Descriptor instead. -func (*GenericExchangeNameResponse) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{13} -} - type GetExchangesRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"` + Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GetExchangesRequest) Reset() { - *x = GetExchangesRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[14] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetExchangesRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetExchangesRequest) ProtoMessage() {} - -func (x *GetExchangesRequest) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[14] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetExchangesRequest.ProtoReflect.Descriptor instead. +func (m *GetExchangesRequest) Reset() { *m = GetExchangesRequest{} } +func (m *GetExchangesRequest) String() string { return proto.CompactTextString(m) } +func (*GetExchangesRequest) ProtoMessage() {} func (*GetExchangesRequest) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{14} + return fileDescriptor_77a6da22d6a3feb1, []int{12} } -func (x *GetExchangesRequest) GetEnabled() bool { - if x != nil { - return x.Enabled +func (m *GetExchangesRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetExchangesRequest.Unmarshal(m, b) +} +func (m *GetExchangesRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetExchangesRequest.Marshal(b, m, deterministic) +} +func (m *GetExchangesRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetExchangesRequest.Merge(m, src) +} +func (m *GetExchangesRequest) XXX_Size() int { + return xxx_messageInfo_GetExchangesRequest.Size(m) +} +func (m *GetExchangesRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetExchangesRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetExchangesRequest proto.InternalMessageInfo + +func (m *GetExchangesRequest) GetEnabled() bool { + if m != nil { + return m.Enabled } return false } type GetExchangesResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Exchanges string `protobuf:"bytes,1,opt,name=exchanges,proto3" json:"exchanges,omitempty"` + Exchanges string `protobuf:"bytes,1,opt,name=exchanges,proto3" json:"exchanges,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GetExchangesResponse) Reset() { - *x = GetExchangesResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[15] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetExchangesResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetExchangesResponse) ProtoMessage() {} - -func (x *GetExchangesResponse) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[15] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetExchangesResponse.ProtoReflect.Descriptor instead. +func (m *GetExchangesResponse) Reset() { *m = GetExchangesResponse{} } +func (m *GetExchangesResponse) String() string { return proto.CompactTextString(m) } +func (*GetExchangesResponse) ProtoMessage() {} func (*GetExchangesResponse) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{15} + return fileDescriptor_77a6da22d6a3feb1, []int{13} } -func (x *GetExchangesResponse) GetExchanges() string { - if x != nil { - return x.Exchanges +func (m *GetExchangesResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetExchangesResponse.Unmarshal(m, b) +} +func (m *GetExchangesResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetExchangesResponse.Marshal(b, m, deterministic) +} +func (m *GetExchangesResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetExchangesResponse.Merge(m, src) +} +func (m *GetExchangesResponse) XXX_Size() int { + return xxx_messageInfo_GetExchangesResponse.Size(m) +} +func (m *GetExchangesResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetExchangesResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetExchangesResponse proto.InternalMessageInfo + +func (m *GetExchangesResponse) GetExchanges() string { + if m != nil { + return m.Exchanges } return "" } type GetExchangeOTPReponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - OtpCode string `protobuf:"bytes,1,opt,name=otp_code,json=otpCode,proto3" json:"otp_code,omitempty"` + OtpCode string `protobuf:"bytes,1,opt,name=otp_code,json=otpCode,proto3" json:"otp_code,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GetExchangeOTPReponse) Reset() { - *x = GetExchangeOTPReponse{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[16] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetExchangeOTPReponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetExchangeOTPReponse) ProtoMessage() {} - -func (x *GetExchangeOTPReponse) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[16] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetExchangeOTPReponse.ProtoReflect.Descriptor instead. +func (m *GetExchangeOTPReponse) Reset() { *m = GetExchangeOTPReponse{} } +func (m *GetExchangeOTPReponse) String() string { return proto.CompactTextString(m) } +func (*GetExchangeOTPReponse) ProtoMessage() {} func (*GetExchangeOTPReponse) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{16} + return fileDescriptor_77a6da22d6a3feb1, []int{14} } -func (x *GetExchangeOTPReponse) GetOtpCode() string { - if x != nil { - return x.OtpCode +func (m *GetExchangeOTPReponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetExchangeOTPReponse.Unmarshal(m, b) +} +func (m *GetExchangeOTPReponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetExchangeOTPReponse.Marshal(b, m, deterministic) +} +func (m *GetExchangeOTPReponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetExchangeOTPReponse.Merge(m, src) +} +func (m *GetExchangeOTPReponse) XXX_Size() int { + return xxx_messageInfo_GetExchangeOTPReponse.Size(m) +} +func (m *GetExchangeOTPReponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetExchangeOTPReponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetExchangeOTPReponse proto.InternalMessageInfo + +func (m *GetExchangeOTPReponse) GetOtpCode() string { + if m != nil { + return m.OtpCode } return "" } type GetExchangeOTPsRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GetExchangeOTPsRequest) Reset() { - *x = GetExchangeOTPsRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[17] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetExchangeOTPsRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetExchangeOTPsRequest) ProtoMessage() {} - -func (x *GetExchangeOTPsRequest) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[17] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetExchangeOTPsRequest.ProtoReflect.Descriptor instead. +func (m *GetExchangeOTPsRequest) Reset() { *m = GetExchangeOTPsRequest{} } +func (m *GetExchangeOTPsRequest) String() string { return proto.CompactTextString(m) } +func (*GetExchangeOTPsRequest) ProtoMessage() {} func (*GetExchangeOTPsRequest) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{17} + return fileDescriptor_77a6da22d6a3feb1, []int{15} } +func (m *GetExchangeOTPsRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetExchangeOTPsRequest.Unmarshal(m, b) +} +func (m *GetExchangeOTPsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetExchangeOTPsRequest.Marshal(b, m, deterministic) +} +func (m *GetExchangeOTPsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetExchangeOTPsRequest.Merge(m, src) +} +func (m *GetExchangeOTPsRequest) XXX_Size() int { + return xxx_messageInfo_GetExchangeOTPsRequest.Size(m) +} +func (m *GetExchangeOTPsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetExchangeOTPsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetExchangeOTPsRequest proto.InternalMessageInfo + type GetExchangeOTPsResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - OtpCodes map[string]string `protobuf:"bytes,1,rep,name=otp_codes,json=otpCodes,proto3" json:"otp_codes,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + OtpCodes map[string]string `protobuf:"bytes,1,rep,name=otp_codes,json=otpCodes,proto3" json:"otp_codes,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GetExchangeOTPsResponse) Reset() { - *x = GetExchangeOTPsResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[18] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetExchangeOTPsResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetExchangeOTPsResponse) ProtoMessage() {} - -func (x *GetExchangeOTPsResponse) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[18] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetExchangeOTPsResponse.ProtoReflect.Descriptor instead. +func (m *GetExchangeOTPsResponse) Reset() { *m = GetExchangeOTPsResponse{} } +func (m *GetExchangeOTPsResponse) String() string { return proto.CompactTextString(m) } +func (*GetExchangeOTPsResponse) ProtoMessage() {} func (*GetExchangeOTPsResponse) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{18} + return fileDescriptor_77a6da22d6a3feb1, []int{16} } -func (x *GetExchangeOTPsResponse) GetOtpCodes() map[string]string { - if x != nil { - return x.OtpCodes +func (m *GetExchangeOTPsResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetExchangeOTPsResponse.Unmarshal(m, b) +} +func (m *GetExchangeOTPsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetExchangeOTPsResponse.Marshal(b, m, deterministic) +} +func (m *GetExchangeOTPsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetExchangeOTPsResponse.Merge(m, src) +} +func (m *GetExchangeOTPsResponse) XXX_Size() int { + return xxx_messageInfo_GetExchangeOTPsResponse.Size(m) +} +func (m *GetExchangeOTPsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetExchangeOTPsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetExchangeOTPsResponse proto.InternalMessageInfo + +func (m *GetExchangeOTPsResponse) GetOtpCodes() map[string]string { + if m != nil { + return m.OtpCodes } return nil } type DisableExchangeRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *DisableExchangeRequest) Reset() { - *x = DisableExchangeRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[19] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *DisableExchangeRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*DisableExchangeRequest) ProtoMessage() {} - -func (x *DisableExchangeRequest) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[19] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use DisableExchangeRequest.ProtoReflect.Descriptor instead. +func (m *DisableExchangeRequest) Reset() { *m = DisableExchangeRequest{} } +func (m *DisableExchangeRequest) String() string { return proto.CompactTextString(m) } +func (*DisableExchangeRequest) ProtoMessage() {} func (*DisableExchangeRequest) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{19} + return fileDescriptor_77a6da22d6a3feb1, []int{17} } -func (x *DisableExchangeRequest) GetExchange() string { - if x != nil { - return x.Exchange +func (m *DisableExchangeRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_DisableExchangeRequest.Unmarshal(m, b) +} +func (m *DisableExchangeRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_DisableExchangeRequest.Marshal(b, m, deterministic) +} +func (m *DisableExchangeRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_DisableExchangeRequest.Merge(m, src) +} +func (m *DisableExchangeRequest) XXX_Size() int { + return xxx_messageInfo_DisableExchangeRequest.Size(m) +} +func (m *DisableExchangeRequest) XXX_DiscardUnknown() { + xxx_messageInfo_DisableExchangeRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_DisableExchangeRequest proto.InternalMessageInfo + +func (m *DisableExchangeRequest) GetExchange() string { + if m != nil { + return m.Exchange } return "" } type PairsSupported struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - AvailablePairs string `protobuf:"bytes,1,opt,name=available_pairs,json=availablePairs,proto3" json:"available_pairs,omitempty"` - EnabledPairs string `protobuf:"bytes,2,opt,name=enabled_pairs,json=enabledPairs,proto3" json:"enabled_pairs,omitempty"` + AvailablePairs string `protobuf:"bytes,1,opt,name=available_pairs,json=availablePairs,proto3" json:"available_pairs,omitempty"` + EnabledPairs string `protobuf:"bytes,2,opt,name=enabled_pairs,json=enabledPairs,proto3" json:"enabled_pairs,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *PairsSupported) Reset() { - *x = PairsSupported{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[20] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *PairsSupported) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*PairsSupported) ProtoMessage() {} - -func (x *PairsSupported) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[20] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use PairsSupported.ProtoReflect.Descriptor instead. +func (m *PairsSupported) Reset() { *m = PairsSupported{} } +func (m *PairsSupported) String() string { return proto.CompactTextString(m) } +func (*PairsSupported) ProtoMessage() {} func (*PairsSupported) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{20} + return fileDescriptor_77a6da22d6a3feb1, []int{18} } -func (x *PairsSupported) GetAvailablePairs() string { - if x != nil { - return x.AvailablePairs +func (m *PairsSupported) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_PairsSupported.Unmarshal(m, b) +} +func (m *PairsSupported) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_PairsSupported.Marshal(b, m, deterministic) +} +func (m *PairsSupported) XXX_Merge(src proto.Message) { + xxx_messageInfo_PairsSupported.Merge(m, src) +} +func (m *PairsSupported) XXX_Size() int { + return xxx_messageInfo_PairsSupported.Size(m) +} +func (m *PairsSupported) XXX_DiscardUnknown() { + xxx_messageInfo_PairsSupported.DiscardUnknown(m) +} + +var xxx_messageInfo_PairsSupported proto.InternalMessageInfo + +func (m *PairsSupported) GetAvailablePairs() string { + if m != nil { + return m.AvailablePairs } return "" } -func (x *PairsSupported) GetEnabledPairs() string { - if x != nil { - return x.EnabledPairs +func (m *PairsSupported) GetEnabledPairs() string { + if m != nil { + return m.EnabledPairs } return "" } type GetExchangeInfoResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - Enabled bool `protobuf:"varint,2,opt,name=enabled,proto3" json:"enabled,omitempty"` - Verbose bool `protobuf:"varint,3,opt,name=verbose,proto3" json:"verbose,omitempty"` - UsingSandbox bool `protobuf:"varint,4,opt,name=using_sandbox,json=usingSandbox,proto3" json:"using_sandbox,omitempty"` - HttpTimeout string `protobuf:"bytes,5,opt,name=http_timeout,json=httpTimeout,proto3" json:"http_timeout,omitempty"` - HttpUseragent string `protobuf:"bytes,6,opt,name=http_useragent,json=httpUseragent,proto3" json:"http_useragent,omitempty"` - HttpProxy string `protobuf:"bytes,7,opt,name=http_proxy,json=httpProxy,proto3" json:"http_proxy,omitempty"` - BaseCurrencies string `protobuf:"bytes,8,opt,name=base_currencies,json=baseCurrencies,proto3" json:"base_currencies,omitempty"` - SupportedAssets map[string]*PairsSupported `protobuf:"bytes,9,rep,name=supported_assets,json=supportedAssets,proto3" json:"supported_assets,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` - AuthenticatedApi bool `protobuf:"varint,10,opt,name=authenticated_api,json=authenticatedApi,proto3" json:"authenticated_api,omitempty"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Enabled bool `protobuf:"varint,2,opt,name=enabled,proto3" json:"enabled,omitempty"` + Verbose bool `protobuf:"varint,3,opt,name=verbose,proto3" json:"verbose,omitempty"` + UsingSandbox bool `protobuf:"varint,4,opt,name=using_sandbox,json=usingSandbox,proto3" json:"using_sandbox,omitempty"` + HttpTimeout string `protobuf:"bytes,5,opt,name=http_timeout,json=httpTimeout,proto3" json:"http_timeout,omitempty"` + HttpUseragent string `protobuf:"bytes,6,opt,name=http_useragent,json=httpUseragent,proto3" json:"http_useragent,omitempty"` + HttpProxy string `protobuf:"bytes,7,opt,name=http_proxy,json=httpProxy,proto3" json:"http_proxy,omitempty"` + BaseCurrencies string `protobuf:"bytes,8,opt,name=base_currencies,json=baseCurrencies,proto3" json:"base_currencies,omitempty"` + SupportedAssets map[string]*PairsSupported `protobuf:"bytes,9,rep,name=supported_assets,json=supportedAssets,proto3" json:"supported_assets,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + AuthenticatedApi bool `protobuf:"varint,10,opt,name=authenticated_api,json=authenticatedApi,proto3" json:"authenticated_api,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GetExchangeInfoResponse) Reset() { - *x = GetExchangeInfoResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[21] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetExchangeInfoResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetExchangeInfoResponse) ProtoMessage() {} - -func (x *GetExchangeInfoResponse) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[21] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetExchangeInfoResponse.ProtoReflect.Descriptor instead. +func (m *GetExchangeInfoResponse) Reset() { *m = GetExchangeInfoResponse{} } +func (m *GetExchangeInfoResponse) String() string { return proto.CompactTextString(m) } +func (*GetExchangeInfoResponse) ProtoMessage() {} func (*GetExchangeInfoResponse) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{21} + return fileDescriptor_77a6da22d6a3feb1, []int{19} } -func (x *GetExchangeInfoResponse) GetName() string { - if x != nil { - return x.Name +func (m *GetExchangeInfoResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetExchangeInfoResponse.Unmarshal(m, b) +} +func (m *GetExchangeInfoResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetExchangeInfoResponse.Marshal(b, m, deterministic) +} +func (m *GetExchangeInfoResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetExchangeInfoResponse.Merge(m, src) +} +func (m *GetExchangeInfoResponse) XXX_Size() int { + return xxx_messageInfo_GetExchangeInfoResponse.Size(m) +} +func (m *GetExchangeInfoResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetExchangeInfoResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetExchangeInfoResponse proto.InternalMessageInfo + +func (m *GetExchangeInfoResponse) GetName() string { + if m != nil { + return m.Name } return "" } -func (x *GetExchangeInfoResponse) GetEnabled() bool { - if x != nil { - return x.Enabled +func (m *GetExchangeInfoResponse) GetEnabled() bool { + if m != nil { + return m.Enabled } return false } -func (x *GetExchangeInfoResponse) GetVerbose() bool { - if x != nil { - return x.Verbose +func (m *GetExchangeInfoResponse) GetVerbose() bool { + if m != nil { + return m.Verbose } return false } -func (x *GetExchangeInfoResponse) GetUsingSandbox() bool { - if x != nil { - return x.UsingSandbox +func (m *GetExchangeInfoResponse) GetUsingSandbox() bool { + if m != nil { + return m.UsingSandbox } return false } -func (x *GetExchangeInfoResponse) GetHttpTimeout() string { - if x != nil { - return x.HttpTimeout +func (m *GetExchangeInfoResponse) GetHttpTimeout() string { + if m != nil { + return m.HttpTimeout } return "" } -func (x *GetExchangeInfoResponse) GetHttpUseragent() string { - if x != nil { - return x.HttpUseragent +func (m *GetExchangeInfoResponse) GetHttpUseragent() string { + if m != nil { + return m.HttpUseragent } return "" } -func (x *GetExchangeInfoResponse) GetHttpProxy() string { - if x != nil { - return x.HttpProxy +func (m *GetExchangeInfoResponse) GetHttpProxy() string { + if m != nil { + return m.HttpProxy } return "" } -func (x *GetExchangeInfoResponse) GetBaseCurrencies() string { - if x != nil { - return x.BaseCurrencies +func (m *GetExchangeInfoResponse) GetBaseCurrencies() string { + if m != nil { + return m.BaseCurrencies } return "" } -func (x *GetExchangeInfoResponse) GetSupportedAssets() map[string]*PairsSupported { - if x != nil { - return x.SupportedAssets +func (m *GetExchangeInfoResponse) GetSupportedAssets() map[string]*PairsSupported { + if m != nil { + return m.SupportedAssets } return nil } -func (x *GetExchangeInfoResponse) GetAuthenticatedApi() bool { - if x != nil { - return x.AuthenticatedApi +func (m *GetExchangeInfoResponse) GetAuthenticatedApi() bool { + if m != nil { + return m.AuthenticatedApi } return false } type GetTickerRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` - Pair *CurrencyPair `protobuf:"bytes,2,opt,name=pair,proto3" json:"pair,omitempty"` - AssetType string `protobuf:"bytes,3,opt,name=asset_type,json=assetType,proto3" json:"asset_type,omitempty"` + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + Pair *CurrencyPair `protobuf:"bytes,2,opt,name=pair,proto3" json:"pair,omitempty"` + AssetType string `protobuf:"bytes,3,opt,name=asset_type,json=assetType,proto3" json:"asset_type,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GetTickerRequest) Reset() { - *x = GetTickerRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[22] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetTickerRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetTickerRequest) ProtoMessage() {} - -func (x *GetTickerRequest) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[22] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetTickerRequest.ProtoReflect.Descriptor instead. +func (m *GetTickerRequest) Reset() { *m = GetTickerRequest{} } +func (m *GetTickerRequest) String() string { return proto.CompactTextString(m) } +func (*GetTickerRequest) ProtoMessage() {} func (*GetTickerRequest) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{22} + return fileDescriptor_77a6da22d6a3feb1, []int{20} } -func (x *GetTickerRequest) GetExchange() string { - if x != nil { - return x.Exchange +func (m *GetTickerRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetTickerRequest.Unmarshal(m, b) +} +func (m *GetTickerRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetTickerRequest.Marshal(b, m, deterministic) +} +func (m *GetTickerRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetTickerRequest.Merge(m, src) +} +func (m *GetTickerRequest) XXX_Size() int { + return xxx_messageInfo_GetTickerRequest.Size(m) +} +func (m *GetTickerRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetTickerRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetTickerRequest proto.InternalMessageInfo + +func (m *GetTickerRequest) GetExchange() string { + if m != nil { + return m.Exchange } return "" } -func (x *GetTickerRequest) GetPair() *CurrencyPair { - if x != nil { - return x.Pair +func (m *GetTickerRequest) GetPair() *CurrencyPair { + if m != nil { + return m.Pair } return nil } -func (x *GetTickerRequest) GetAssetType() string { - if x != nil { - return x.AssetType +func (m *GetTickerRequest) GetAssetType() string { + if m != nil { + return m.AssetType } return "" } type CurrencyPair struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Delimiter string `protobuf:"bytes,1,opt,name=delimiter,proto3" json:"delimiter,omitempty"` - Base string `protobuf:"bytes,2,opt,name=base,proto3" json:"base,omitempty"` - Quote string `protobuf:"bytes,3,opt,name=quote,proto3" json:"quote,omitempty"` + Delimiter string `protobuf:"bytes,1,opt,name=delimiter,proto3" json:"delimiter,omitempty"` + Base string `protobuf:"bytes,2,opt,name=base,proto3" json:"base,omitempty"` + Quote string `protobuf:"bytes,3,opt,name=quote,proto3" json:"quote,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *CurrencyPair) Reset() { - *x = CurrencyPair{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[23] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *CurrencyPair) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*CurrencyPair) ProtoMessage() {} - -func (x *CurrencyPair) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[23] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use CurrencyPair.ProtoReflect.Descriptor instead. +func (m *CurrencyPair) Reset() { *m = CurrencyPair{} } +func (m *CurrencyPair) String() string { return proto.CompactTextString(m) } +func (*CurrencyPair) ProtoMessage() {} func (*CurrencyPair) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{23} + return fileDescriptor_77a6da22d6a3feb1, []int{21} } -func (x *CurrencyPair) GetDelimiter() string { - if x != nil { - return x.Delimiter +func (m *CurrencyPair) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_CurrencyPair.Unmarshal(m, b) +} +func (m *CurrencyPair) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_CurrencyPair.Marshal(b, m, deterministic) +} +func (m *CurrencyPair) XXX_Merge(src proto.Message) { + xxx_messageInfo_CurrencyPair.Merge(m, src) +} +func (m *CurrencyPair) XXX_Size() int { + return xxx_messageInfo_CurrencyPair.Size(m) +} +func (m *CurrencyPair) XXX_DiscardUnknown() { + xxx_messageInfo_CurrencyPair.DiscardUnknown(m) +} + +var xxx_messageInfo_CurrencyPair proto.InternalMessageInfo + +func (m *CurrencyPair) GetDelimiter() string { + if m != nil { + return m.Delimiter } return "" } -func (x *CurrencyPair) GetBase() string { - if x != nil { - return x.Base +func (m *CurrencyPair) GetBase() string { + if m != nil { + return m.Base } return "" } -func (x *CurrencyPair) GetQuote() string { - if x != nil { - return x.Quote +func (m *CurrencyPair) GetQuote() string { + if m != nil { + return m.Quote } return "" } type TickerResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Pair *CurrencyPair `protobuf:"bytes,1,opt,name=pair,proto3" json:"pair,omitempty"` - LastUpdated int64 `protobuf:"varint,2,opt,name=last_updated,json=lastUpdated,proto3" json:"last_updated,omitempty"` - CurrencyPair string `protobuf:"bytes,3,opt,name=currency_pair,json=currencyPair,proto3" json:"currency_pair,omitempty"` - Last float64 `protobuf:"fixed64,4,opt,name=last,proto3" json:"last,omitempty"` - High float64 `protobuf:"fixed64,5,opt,name=high,proto3" json:"high,omitempty"` - Low float64 `protobuf:"fixed64,6,opt,name=low,proto3" json:"low,omitempty"` - Bid float64 `protobuf:"fixed64,7,opt,name=bid,proto3" json:"bid,omitempty"` - Ask float64 `protobuf:"fixed64,8,opt,name=ask,proto3" json:"ask,omitempty"` - Volume float64 `protobuf:"fixed64,9,opt,name=volume,proto3" json:"volume,omitempty"` - PriceAth float64 `protobuf:"fixed64,10,opt,name=price_ath,json=priceAth,proto3" json:"price_ath,omitempty"` + Pair *CurrencyPair `protobuf:"bytes,1,opt,name=pair,proto3" json:"pair,omitempty"` + LastUpdated int64 `protobuf:"varint,2,opt,name=last_updated,json=lastUpdated,proto3" json:"last_updated,omitempty"` + CurrencyPair string `protobuf:"bytes,3,opt,name=currency_pair,json=currencyPair,proto3" json:"currency_pair,omitempty"` + Last float64 `protobuf:"fixed64,4,opt,name=last,proto3" json:"last,omitempty"` + High float64 `protobuf:"fixed64,5,opt,name=high,proto3" json:"high,omitempty"` + Low float64 `protobuf:"fixed64,6,opt,name=low,proto3" json:"low,omitempty"` + Bid float64 `protobuf:"fixed64,7,opt,name=bid,proto3" json:"bid,omitempty"` + Ask float64 `protobuf:"fixed64,8,opt,name=ask,proto3" json:"ask,omitempty"` + Volume float64 `protobuf:"fixed64,9,opt,name=volume,proto3" json:"volume,omitempty"` + PriceAth float64 `protobuf:"fixed64,10,opt,name=price_ath,json=priceAth,proto3" json:"price_ath,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *TickerResponse) Reset() { - *x = TickerResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[24] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *TickerResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*TickerResponse) ProtoMessage() {} - -func (x *TickerResponse) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[24] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use TickerResponse.ProtoReflect.Descriptor instead. +func (m *TickerResponse) Reset() { *m = TickerResponse{} } +func (m *TickerResponse) String() string { return proto.CompactTextString(m) } +func (*TickerResponse) ProtoMessage() {} func (*TickerResponse) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{24} + return fileDescriptor_77a6da22d6a3feb1, []int{22} } -func (x *TickerResponse) GetPair() *CurrencyPair { - if x != nil { - return x.Pair +func (m *TickerResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_TickerResponse.Unmarshal(m, b) +} +func (m *TickerResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_TickerResponse.Marshal(b, m, deterministic) +} +func (m *TickerResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_TickerResponse.Merge(m, src) +} +func (m *TickerResponse) XXX_Size() int { + return xxx_messageInfo_TickerResponse.Size(m) +} +func (m *TickerResponse) XXX_DiscardUnknown() { + xxx_messageInfo_TickerResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_TickerResponse proto.InternalMessageInfo + +func (m *TickerResponse) GetPair() *CurrencyPair { + if m != nil { + return m.Pair } return nil } -func (x *TickerResponse) GetLastUpdated() int64 { - if x != nil { - return x.LastUpdated +func (m *TickerResponse) GetLastUpdated() int64 { + if m != nil { + return m.LastUpdated } return 0 } -func (x *TickerResponse) GetCurrencyPair() string { - if x != nil { - return x.CurrencyPair +func (m *TickerResponse) GetCurrencyPair() string { + if m != nil { + return m.CurrencyPair } return "" } -func (x *TickerResponse) GetLast() float64 { - if x != nil { - return x.Last +func (m *TickerResponse) GetLast() float64 { + if m != nil { + return m.Last } return 0 } -func (x *TickerResponse) GetHigh() float64 { - if x != nil { - return x.High +func (m *TickerResponse) GetHigh() float64 { + if m != nil { + return m.High } return 0 } -func (x *TickerResponse) GetLow() float64 { - if x != nil { - return x.Low +func (m *TickerResponse) GetLow() float64 { + if m != nil { + return m.Low } return 0 } -func (x *TickerResponse) GetBid() float64 { - if x != nil { - return x.Bid +func (m *TickerResponse) GetBid() float64 { + if m != nil { + return m.Bid } return 0 } -func (x *TickerResponse) GetAsk() float64 { - if x != nil { - return x.Ask +func (m *TickerResponse) GetAsk() float64 { + if m != nil { + return m.Ask } return 0 } -func (x *TickerResponse) GetVolume() float64 { - if x != nil { - return x.Volume +func (m *TickerResponse) GetVolume() float64 { + if m != nil { + return m.Volume } return 0 } -func (x *TickerResponse) GetPriceAth() float64 { - if x != nil { - return x.PriceAth +func (m *TickerResponse) GetPriceAth() float64 { + if m != nil { + return m.PriceAth } return 0 } type GetTickersRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GetTickersRequest) Reset() { - *x = GetTickersRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[25] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetTickersRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetTickersRequest) ProtoMessage() {} - -func (x *GetTickersRequest) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[25] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetTickersRequest.ProtoReflect.Descriptor instead. +func (m *GetTickersRequest) Reset() { *m = GetTickersRequest{} } +func (m *GetTickersRequest) String() string { return proto.CompactTextString(m) } +func (*GetTickersRequest) ProtoMessage() {} func (*GetTickersRequest) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{25} + return fileDescriptor_77a6da22d6a3feb1, []int{23} } +func (m *GetTickersRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetTickersRequest.Unmarshal(m, b) +} +func (m *GetTickersRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetTickersRequest.Marshal(b, m, deterministic) +} +func (m *GetTickersRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetTickersRequest.Merge(m, src) +} +func (m *GetTickersRequest) XXX_Size() int { + return xxx_messageInfo_GetTickersRequest.Size(m) +} +func (m *GetTickersRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetTickersRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetTickersRequest proto.InternalMessageInfo + type Tickers struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` - Tickers []*TickerResponse `protobuf:"bytes,2,rep,name=tickers,proto3" json:"tickers,omitempty"` + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + Tickers []*TickerResponse `protobuf:"bytes,2,rep,name=tickers,proto3" json:"tickers,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *Tickers) Reset() { - *x = Tickers{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[26] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Tickers) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Tickers) ProtoMessage() {} - -func (x *Tickers) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[26] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Tickers.ProtoReflect.Descriptor instead. +func (m *Tickers) Reset() { *m = Tickers{} } +func (m *Tickers) String() string { return proto.CompactTextString(m) } +func (*Tickers) ProtoMessage() {} func (*Tickers) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{26} + return fileDescriptor_77a6da22d6a3feb1, []int{24} } -func (x *Tickers) GetExchange() string { - if x != nil { - return x.Exchange +func (m *Tickers) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Tickers.Unmarshal(m, b) +} +func (m *Tickers) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Tickers.Marshal(b, m, deterministic) +} +func (m *Tickers) XXX_Merge(src proto.Message) { + xxx_messageInfo_Tickers.Merge(m, src) +} +func (m *Tickers) XXX_Size() int { + return xxx_messageInfo_Tickers.Size(m) +} +func (m *Tickers) XXX_DiscardUnknown() { + xxx_messageInfo_Tickers.DiscardUnknown(m) +} + +var xxx_messageInfo_Tickers proto.InternalMessageInfo + +func (m *Tickers) GetExchange() string { + if m != nil { + return m.Exchange } return "" } -func (x *Tickers) GetTickers() []*TickerResponse { - if x != nil { - return x.Tickers +func (m *Tickers) GetTickers() []*TickerResponse { + if m != nil { + return m.Tickers } return nil } type GetTickersResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Tickers []*Tickers `protobuf:"bytes,1,rep,name=tickers,proto3" json:"tickers,omitempty"` + Tickers []*Tickers `protobuf:"bytes,1,rep,name=tickers,proto3" json:"tickers,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GetTickersResponse) Reset() { - *x = GetTickersResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[27] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetTickersResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetTickersResponse) ProtoMessage() {} - -func (x *GetTickersResponse) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[27] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetTickersResponse.ProtoReflect.Descriptor instead. +func (m *GetTickersResponse) Reset() { *m = GetTickersResponse{} } +func (m *GetTickersResponse) String() string { return proto.CompactTextString(m) } +func (*GetTickersResponse) ProtoMessage() {} func (*GetTickersResponse) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{27} + return fileDescriptor_77a6da22d6a3feb1, []int{25} } -func (x *GetTickersResponse) GetTickers() []*Tickers { - if x != nil { - return x.Tickers +func (m *GetTickersResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetTickersResponse.Unmarshal(m, b) +} +func (m *GetTickersResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetTickersResponse.Marshal(b, m, deterministic) +} +func (m *GetTickersResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetTickersResponse.Merge(m, src) +} +func (m *GetTickersResponse) XXX_Size() int { + return xxx_messageInfo_GetTickersResponse.Size(m) +} +func (m *GetTickersResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetTickersResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetTickersResponse proto.InternalMessageInfo + +func (m *GetTickersResponse) GetTickers() []*Tickers { + if m != nil { + return m.Tickers } return nil } type GetOrderbookRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` - Pair *CurrencyPair `protobuf:"bytes,2,opt,name=pair,proto3" json:"pair,omitempty"` - AssetType string `protobuf:"bytes,3,opt,name=asset_type,json=assetType,proto3" json:"asset_type,omitempty"` + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + Pair *CurrencyPair `protobuf:"bytes,2,opt,name=pair,proto3" json:"pair,omitempty"` + AssetType string `protobuf:"bytes,3,opt,name=asset_type,json=assetType,proto3" json:"asset_type,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GetOrderbookRequest) Reset() { - *x = GetOrderbookRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[28] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetOrderbookRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetOrderbookRequest) ProtoMessage() {} - -func (x *GetOrderbookRequest) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[28] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetOrderbookRequest.ProtoReflect.Descriptor instead. +func (m *GetOrderbookRequest) Reset() { *m = GetOrderbookRequest{} } +func (m *GetOrderbookRequest) String() string { return proto.CompactTextString(m) } +func (*GetOrderbookRequest) ProtoMessage() {} func (*GetOrderbookRequest) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{28} + return fileDescriptor_77a6da22d6a3feb1, []int{26} } -func (x *GetOrderbookRequest) GetExchange() string { - if x != nil { - return x.Exchange +func (m *GetOrderbookRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetOrderbookRequest.Unmarshal(m, b) +} +func (m *GetOrderbookRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetOrderbookRequest.Marshal(b, m, deterministic) +} +func (m *GetOrderbookRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetOrderbookRequest.Merge(m, src) +} +func (m *GetOrderbookRequest) XXX_Size() int { + return xxx_messageInfo_GetOrderbookRequest.Size(m) +} +func (m *GetOrderbookRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetOrderbookRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetOrderbookRequest proto.InternalMessageInfo + +func (m *GetOrderbookRequest) GetExchange() string { + if m != nil { + return m.Exchange } return "" } -func (x *GetOrderbookRequest) GetPair() *CurrencyPair { - if x != nil { - return x.Pair +func (m *GetOrderbookRequest) GetPair() *CurrencyPair { + if m != nil { + return m.Pair } return nil } -func (x *GetOrderbookRequest) GetAssetType() string { - if x != nil { - return x.AssetType +func (m *GetOrderbookRequest) GetAssetType() string { + if m != nil { + return m.AssetType } return "" } type OrderbookItem struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Amount float64 `protobuf:"fixed64,1,opt,name=amount,proto3" json:"amount,omitempty"` - Price float64 `protobuf:"fixed64,2,opt,name=price,proto3" json:"price,omitempty"` - Id int64 `protobuf:"varint,3,opt,name=id,proto3" json:"id,omitempty"` + Amount float64 `protobuf:"fixed64,1,opt,name=amount,proto3" json:"amount,omitempty"` + Price float64 `protobuf:"fixed64,2,opt,name=price,proto3" json:"price,omitempty"` + Id int64 `protobuf:"varint,3,opt,name=id,proto3" json:"id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *OrderbookItem) Reset() { - *x = OrderbookItem{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[29] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *OrderbookItem) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*OrderbookItem) ProtoMessage() {} - -func (x *OrderbookItem) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[29] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use OrderbookItem.ProtoReflect.Descriptor instead. +func (m *OrderbookItem) Reset() { *m = OrderbookItem{} } +func (m *OrderbookItem) String() string { return proto.CompactTextString(m) } +func (*OrderbookItem) ProtoMessage() {} func (*OrderbookItem) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{29} + return fileDescriptor_77a6da22d6a3feb1, []int{27} } -func (x *OrderbookItem) GetAmount() float64 { - if x != nil { - return x.Amount +func (m *OrderbookItem) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_OrderbookItem.Unmarshal(m, b) +} +func (m *OrderbookItem) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_OrderbookItem.Marshal(b, m, deterministic) +} +func (m *OrderbookItem) XXX_Merge(src proto.Message) { + xxx_messageInfo_OrderbookItem.Merge(m, src) +} +func (m *OrderbookItem) XXX_Size() int { + return xxx_messageInfo_OrderbookItem.Size(m) +} +func (m *OrderbookItem) XXX_DiscardUnknown() { + xxx_messageInfo_OrderbookItem.DiscardUnknown(m) +} + +var xxx_messageInfo_OrderbookItem proto.InternalMessageInfo + +func (m *OrderbookItem) GetAmount() float64 { + if m != nil { + return m.Amount } return 0 } -func (x *OrderbookItem) GetPrice() float64 { - if x != nil { - return x.Price +func (m *OrderbookItem) GetPrice() float64 { + if m != nil { + return m.Price } return 0 } -func (x *OrderbookItem) GetId() int64 { - if x != nil { - return x.Id +func (m *OrderbookItem) GetId() int64 { + if m != nil { + return m.Id } return 0 } type OrderbookResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Pair *CurrencyPair `protobuf:"bytes,1,opt,name=pair,proto3" json:"pair,omitempty"` - CurrencyPair string `protobuf:"bytes,2,opt,name=currency_pair,json=currencyPair,proto3" json:"currency_pair,omitempty"` - Bids []*OrderbookItem `protobuf:"bytes,3,rep,name=bids,proto3" json:"bids,omitempty"` - Asks []*OrderbookItem `protobuf:"bytes,4,rep,name=asks,proto3" json:"asks,omitempty"` - LastUpdated int64 `protobuf:"varint,5,opt,name=last_updated,json=lastUpdated,proto3" json:"last_updated,omitempty"` - AssetType string `protobuf:"bytes,6,opt,name=asset_type,json=assetType,proto3" json:"asset_type,omitempty"` + Pair *CurrencyPair `protobuf:"bytes,1,opt,name=pair,proto3" json:"pair,omitempty"` + CurrencyPair string `protobuf:"bytes,2,opt,name=currency_pair,json=currencyPair,proto3" json:"currency_pair,omitempty"` + Bids []*OrderbookItem `protobuf:"bytes,3,rep,name=bids,proto3" json:"bids,omitempty"` + Asks []*OrderbookItem `protobuf:"bytes,4,rep,name=asks,proto3" json:"asks,omitempty"` + LastUpdated int64 `protobuf:"varint,5,opt,name=last_updated,json=lastUpdated,proto3" json:"last_updated,omitempty"` + AssetType string `protobuf:"bytes,6,opt,name=asset_type,json=assetType,proto3" json:"asset_type,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *OrderbookResponse) Reset() { - *x = OrderbookResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[30] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *OrderbookResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*OrderbookResponse) ProtoMessage() {} - -func (x *OrderbookResponse) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[30] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use OrderbookResponse.ProtoReflect.Descriptor instead. +func (m *OrderbookResponse) Reset() { *m = OrderbookResponse{} } +func (m *OrderbookResponse) String() string { return proto.CompactTextString(m) } +func (*OrderbookResponse) ProtoMessage() {} func (*OrderbookResponse) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{30} + return fileDescriptor_77a6da22d6a3feb1, []int{28} } -func (x *OrderbookResponse) GetPair() *CurrencyPair { - if x != nil { - return x.Pair +func (m *OrderbookResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_OrderbookResponse.Unmarshal(m, b) +} +func (m *OrderbookResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_OrderbookResponse.Marshal(b, m, deterministic) +} +func (m *OrderbookResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_OrderbookResponse.Merge(m, src) +} +func (m *OrderbookResponse) XXX_Size() int { + return xxx_messageInfo_OrderbookResponse.Size(m) +} +func (m *OrderbookResponse) XXX_DiscardUnknown() { + xxx_messageInfo_OrderbookResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_OrderbookResponse proto.InternalMessageInfo + +func (m *OrderbookResponse) GetPair() *CurrencyPair { + if m != nil { + return m.Pair } return nil } -func (x *OrderbookResponse) GetCurrencyPair() string { - if x != nil { - return x.CurrencyPair +func (m *OrderbookResponse) GetCurrencyPair() string { + if m != nil { + return m.CurrencyPair } return "" } -func (x *OrderbookResponse) GetBids() []*OrderbookItem { - if x != nil { - return x.Bids +func (m *OrderbookResponse) GetBids() []*OrderbookItem { + if m != nil { + return m.Bids } return nil } -func (x *OrderbookResponse) GetAsks() []*OrderbookItem { - if x != nil { - return x.Asks +func (m *OrderbookResponse) GetAsks() []*OrderbookItem { + if m != nil { + return m.Asks } return nil } -func (x *OrderbookResponse) GetLastUpdated() int64 { - if x != nil { - return x.LastUpdated +func (m *OrderbookResponse) GetLastUpdated() int64 { + if m != nil { + return m.LastUpdated } return 0 } -func (x *OrderbookResponse) GetAssetType() string { - if x != nil { - return x.AssetType +func (m *OrderbookResponse) GetAssetType() string { + if m != nil { + return m.AssetType } return "" } type GetOrderbooksRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GetOrderbooksRequest) Reset() { - *x = GetOrderbooksRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[31] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetOrderbooksRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetOrderbooksRequest) ProtoMessage() {} - -func (x *GetOrderbooksRequest) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[31] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetOrderbooksRequest.ProtoReflect.Descriptor instead. +func (m *GetOrderbooksRequest) Reset() { *m = GetOrderbooksRequest{} } +func (m *GetOrderbooksRequest) String() string { return proto.CompactTextString(m) } +func (*GetOrderbooksRequest) ProtoMessage() {} func (*GetOrderbooksRequest) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{31} + return fileDescriptor_77a6da22d6a3feb1, []int{29} } +func (m *GetOrderbooksRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetOrderbooksRequest.Unmarshal(m, b) +} +func (m *GetOrderbooksRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetOrderbooksRequest.Marshal(b, m, deterministic) +} +func (m *GetOrderbooksRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetOrderbooksRequest.Merge(m, src) +} +func (m *GetOrderbooksRequest) XXX_Size() int { + return xxx_messageInfo_GetOrderbooksRequest.Size(m) +} +func (m *GetOrderbooksRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetOrderbooksRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetOrderbooksRequest proto.InternalMessageInfo + type Orderbooks struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` - Orderbooks []*OrderbookResponse `protobuf:"bytes,2,rep,name=orderbooks,proto3" json:"orderbooks,omitempty"` + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + Orderbooks []*OrderbookResponse `protobuf:"bytes,2,rep,name=orderbooks,proto3" json:"orderbooks,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *Orderbooks) Reset() { - *x = Orderbooks{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[32] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Orderbooks) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Orderbooks) ProtoMessage() {} - -func (x *Orderbooks) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[32] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Orderbooks.ProtoReflect.Descriptor instead. +func (m *Orderbooks) Reset() { *m = Orderbooks{} } +func (m *Orderbooks) String() string { return proto.CompactTextString(m) } +func (*Orderbooks) ProtoMessage() {} func (*Orderbooks) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{32} + return fileDescriptor_77a6da22d6a3feb1, []int{30} } -func (x *Orderbooks) GetExchange() string { - if x != nil { - return x.Exchange +func (m *Orderbooks) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Orderbooks.Unmarshal(m, b) +} +func (m *Orderbooks) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Orderbooks.Marshal(b, m, deterministic) +} +func (m *Orderbooks) XXX_Merge(src proto.Message) { + xxx_messageInfo_Orderbooks.Merge(m, src) +} +func (m *Orderbooks) XXX_Size() int { + return xxx_messageInfo_Orderbooks.Size(m) +} +func (m *Orderbooks) XXX_DiscardUnknown() { + xxx_messageInfo_Orderbooks.DiscardUnknown(m) +} + +var xxx_messageInfo_Orderbooks proto.InternalMessageInfo + +func (m *Orderbooks) GetExchange() string { + if m != nil { + return m.Exchange } return "" } -func (x *Orderbooks) GetOrderbooks() []*OrderbookResponse { - if x != nil { - return x.Orderbooks +func (m *Orderbooks) GetOrderbooks() []*OrderbookResponse { + if m != nil { + return m.Orderbooks } return nil } type GetOrderbooksResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Orderbooks []*Orderbooks `protobuf:"bytes,1,rep,name=orderbooks,proto3" json:"orderbooks,omitempty"` + Orderbooks []*Orderbooks `protobuf:"bytes,1,rep,name=orderbooks,proto3" json:"orderbooks,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GetOrderbooksResponse) Reset() { - *x = GetOrderbooksResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[33] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetOrderbooksResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetOrderbooksResponse) ProtoMessage() {} - -func (x *GetOrderbooksResponse) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[33] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetOrderbooksResponse.ProtoReflect.Descriptor instead. +func (m *GetOrderbooksResponse) Reset() { *m = GetOrderbooksResponse{} } +func (m *GetOrderbooksResponse) String() string { return proto.CompactTextString(m) } +func (*GetOrderbooksResponse) ProtoMessage() {} func (*GetOrderbooksResponse) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{33} + return fileDescriptor_77a6da22d6a3feb1, []int{31} } -func (x *GetOrderbooksResponse) GetOrderbooks() []*Orderbooks { - if x != nil { - return x.Orderbooks +func (m *GetOrderbooksResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetOrderbooksResponse.Unmarshal(m, b) +} +func (m *GetOrderbooksResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetOrderbooksResponse.Marshal(b, m, deterministic) +} +func (m *GetOrderbooksResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetOrderbooksResponse.Merge(m, src) +} +func (m *GetOrderbooksResponse) XXX_Size() int { + return xxx_messageInfo_GetOrderbooksResponse.Size(m) +} +func (m *GetOrderbooksResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetOrderbooksResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetOrderbooksResponse proto.InternalMessageInfo + +func (m *GetOrderbooksResponse) GetOrderbooks() []*Orderbooks { + if m != nil { + return m.Orderbooks } return nil } type GetAccountInfoRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GetAccountInfoRequest) Reset() { - *x = GetAccountInfoRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[34] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetAccountInfoRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetAccountInfoRequest) ProtoMessage() {} - -func (x *GetAccountInfoRequest) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[34] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetAccountInfoRequest.ProtoReflect.Descriptor instead. +func (m *GetAccountInfoRequest) Reset() { *m = GetAccountInfoRequest{} } +func (m *GetAccountInfoRequest) String() string { return proto.CompactTextString(m) } +func (*GetAccountInfoRequest) ProtoMessage() {} func (*GetAccountInfoRequest) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{34} + return fileDescriptor_77a6da22d6a3feb1, []int{32} } -func (x *GetAccountInfoRequest) GetExchange() string { - if x != nil { - return x.Exchange +func (m *GetAccountInfoRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetAccountInfoRequest.Unmarshal(m, b) +} +func (m *GetAccountInfoRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetAccountInfoRequest.Marshal(b, m, deterministic) +} +func (m *GetAccountInfoRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetAccountInfoRequest.Merge(m, src) +} +func (m *GetAccountInfoRequest) XXX_Size() int { + return xxx_messageInfo_GetAccountInfoRequest.Size(m) +} +func (m *GetAccountInfoRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetAccountInfoRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetAccountInfoRequest proto.InternalMessageInfo + +func (m *GetAccountInfoRequest) GetExchange() string { + if m != nil { + return m.Exchange } return "" } type Account struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - Currencies []*AccountCurrencyInfo `protobuf:"bytes,2,rep,name=currencies,proto3" json:"currencies,omitempty"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Currencies []*AccountCurrencyInfo `protobuf:"bytes,2,rep,name=currencies,proto3" json:"currencies,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *Account) Reset() { - *x = Account{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[35] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Account) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Account) ProtoMessage() {} - -func (x *Account) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[35] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Account.ProtoReflect.Descriptor instead. +func (m *Account) Reset() { *m = Account{} } +func (m *Account) String() string { return proto.CompactTextString(m) } +func (*Account) ProtoMessage() {} func (*Account) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{35} + return fileDescriptor_77a6da22d6a3feb1, []int{33} } -func (x *Account) GetId() string { - if x != nil { - return x.Id +func (m *Account) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Account.Unmarshal(m, b) +} +func (m *Account) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Account.Marshal(b, m, deterministic) +} +func (m *Account) XXX_Merge(src proto.Message) { + xxx_messageInfo_Account.Merge(m, src) +} +func (m *Account) XXX_Size() int { + return xxx_messageInfo_Account.Size(m) +} +func (m *Account) XXX_DiscardUnknown() { + xxx_messageInfo_Account.DiscardUnknown(m) +} + +var xxx_messageInfo_Account proto.InternalMessageInfo + +func (m *Account) GetId() string { + if m != nil { + return m.Id } return "" } -func (x *Account) GetCurrencies() []*AccountCurrencyInfo { - if x != nil { - return x.Currencies +func (m *Account) GetCurrencies() []*AccountCurrencyInfo { + if m != nil { + return m.Currencies } return nil } type AccountCurrencyInfo struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Currency string `protobuf:"bytes,1,opt,name=currency,proto3" json:"currency,omitempty"` - TotalValue float64 `protobuf:"fixed64,2,opt,name=total_value,json=totalValue,proto3" json:"total_value,omitempty"` - Hold float64 `protobuf:"fixed64,3,opt,name=hold,proto3" json:"hold,omitempty"` + Currency string `protobuf:"bytes,1,opt,name=currency,proto3" json:"currency,omitempty"` + TotalValue float64 `protobuf:"fixed64,2,opt,name=total_value,json=totalValue,proto3" json:"total_value,omitempty"` + Hold float64 `protobuf:"fixed64,3,opt,name=hold,proto3" json:"hold,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *AccountCurrencyInfo) Reset() { - *x = AccountCurrencyInfo{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[36] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *AccountCurrencyInfo) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*AccountCurrencyInfo) ProtoMessage() {} - -func (x *AccountCurrencyInfo) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[36] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use AccountCurrencyInfo.ProtoReflect.Descriptor instead. +func (m *AccountCurrencyInfo) Reset() { *m = AccountCurrencyInfo{} } +func (m *AccountCurrencyInfo) String() string { return proto.CompactTextString(m) } +func (*AccountCurrencyInfo) ProtoMessage() {} func (*AccountCurrencyInfo) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{36} + return fileDescriptor_77a6da22d6a3feb1, []int{34} } -func (x *AccountCurrencyInfo) GetCurrency() string { - if x != nil { - return x.Currency +func (m *AccountCurrencyInfo) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_AccountCurrencyInfo.Unmarshal(m, b) +} +func (m *AccountCurrencyInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_AccountCurrencyInfo.Marshal(b, m, deterministic) +} +func (m *AccountCurrencyInfo) XXX_Merge(src proto.Message) { + xxx_messageInfo_AccountCurrencyInfo.Merge(m, src) +} +func (m *AccountCurrencyInfo) XXX_Size() int { + return xxx_messageInfo_AccountCurrencyInfo.Size(m) +} +func (m *AccountCurrencyInfo) XXX_DiscardUnknown() { + xxx_messageInfo_AccountCurrencyInfo.DiscardUnknown(m) +} + +var xxx_messageInfo_AccountCurrencyInfo proto.InternalMessageInfo + +func (m *AccountCurrencyInfo) GetCurrency() string { + if m != nil { + return m.Currency } return "" } -func (x *AccountCurrencyInfo) GetTotalValue() float64 { - if x != nil { - return x.TotalValue +func (m *AccountCurrencyInfo) GetTotalValue() float64 { + if m != nil { + return m.TotalValue } return 0 } -func (x *AccountCurrencyInfo) GetHold() float64 { - if x != nil { - return x.Hold +func (m *AccountCurrencyInfo) GetHold() float64 { + if m != nil { + return m.Hold } return 0 } type GetAccountInfoResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` - Accounts []*Account `protobuf:"bytes,2,rep,name=accounts,proto3" json:"accounts,omitempty"` + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + Accounts []*Account `protobuf:"bytes,2,rep,name=accounts,proto3" json:"accounts,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GetAccountInfoResponse) Reset() { - *x = GetAccountInfoResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[37] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetAccountInfoResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetAccountInfoResponse) ProtoMessage() {} - -func (x *GetAccountInfoResponse) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[37] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetAccountInfoResponse.ProtoReflect.Descriptor instead. +func (m *GetAccountInfoResponse) Reset() { *m = GetAccountInfoResponse{} } +func (m *GetAccountInfoResponse) String() string { return proto.CompactTextString(m) } +func (*GetAccountInfoResponse) ProtoMessage() {} func (*GetAccountInfoResponse) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{37} + return fileDescriptor_77a6da22d6a3feb1, []int{35} } -func (x *GetAccountInfoResponse) GetExchange() string { - if x != nil { - return x.Exchange +func (m *GetAccountInfoResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetAccountInfoResponse.Unmarshal(m, b) +} +func (m *GetAccountInfoResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetAccountInfoResponse.Marshal(b, m, deterministic) +} +func (m *GetAccountInfoResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetAccountInfoResponse.Merge(m, src) +} +func (m *GetAccountInfoResponse) XXX_Size() int { + return xxx_messageInfo_GetAccountInfoResponse.Size(m) +} +func (m *GetAccountInfoResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetAccountInfoResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetAccountInfoResponse proto.InternalMessageInfo + +func (m *GetAccountInfoResponse) GetExchange() string { + if m != nil { + return m.Exchange } return "" } -func (x *GetAccountInfoResponse) GetAccounts() []*Account { - if x != nil { - return x.Accounts +func (m *GetAccountInfoResponse) GetAccounts() []*Account { + if m != nil { + return m.Accounts } return nil } type GetConfigRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GetConfigRequest) Reset() { - *x = GetConfigRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[38] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetConfigRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetConfigRequest) ProtoMessage() {} - -func (x *GetConfigRequest) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[38] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetConfigRequest.ProtoReflect.Descriptor instead. +func (m *GetConfigRequest) Reset() { *m = GetConfigRequest{} } +func (m *GetConfigRequest) String() string { return proto.CompactTextString(m) } +func (*GetConfigRequest) ProtoMessage() {} func (*GetConfigRequest) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{38} + return fileDescriptor_77a6da22d6a3feb1, []int{36} } +func (m *GetConfigRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetConfigRequest.Unmarshal(m, b) +} +func (m *GetConfigRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetConfigRequest.Marshal(b, m, deterministic) +} +func (m *GetConfigRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetConfigRequest.Merge(m, src) +} +func (m *GetConfigRequest) XXX_Size() int { + return xxx_messageInfo_GetConfigRequest.Size(m) +} +func (m *GetConfigRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetConfigRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetConfigRequest proto.InternalMessageInfo + type GetConfigResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` + Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GetConfigResponse) Reset() { - *x = GetConfigResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[39] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetConfigResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetConfigResponse) ProtoMessage() {} - -func (x *GetConfigResponse) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[39] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetConfigResponse.ProtoReflect.Descriptor instead. +func (m *GetConfigResponse) Reset() { *m = GetConfigResponse{} } +func (m *GetConfigResponse) String() string { return proto.CompactTextString(m) } +func (*GetConfigResponse) ProtoMessage() {} func (*GetConfigResponse) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{39} + return fileDescriptor_77a6da22d6a3feb1, []int{37} } -func (x *GetConfigResponse) GetData() []byte { - if x != nil { - return x.Data +func (m *GetConfigResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetConfigResponse.Unmarshal(m, b) +} +func (m *GetConfigResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetConfigResponse.Marshal(b, m, deterministic) +} +func (m *GetConfigResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetConfigResponse.Merge(m, src) +} +func (m *GetConfigResponse) XXX_Size() int { + return xxx_messageInfo_GetConfigResponse.Size(m) +} +func (m *GetConfigResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetConfigResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetConfigResponse proto.InternalMessageInfo + +func (m *GetConfigResponse) GetData() []byte { + if m != nil { + return m.Data } return nil } type PortfolioAddress struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` - CoinType string `protobuf:"bytes,2,opt,name=coin_type,json=coinType,proto3" json:"coin_type,omitempty"` - Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"` - Balance float64 `protobuf:"fixed64,4,opt,name=balance,proto3" json:"balance,omitempty"` + Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + CoinType string `protobuf:"bytes,2,opt,name=coin_type,json=coinType,proto3" json:"coin_type,omitempty"` + Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"` + Balance float64 `protobuf:"fixed64,4,opt,name=balance,proto3" json:"balance,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *PortfolioAddress) Reset() { - *x = PortfolioAddress{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[40] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *PortfolioAddress) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*PortfolioAddress) ProtoMessage() {} - -func (x *PortfolioAddress) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[40] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use PortfolioAddress.ProtoReflect.Descriptor instead. +func (m *PortfolioAddress) Reset() { *m = PortfolioAddress{} } +func (m *PortfolioAddress) String() string { return proto.CompactTextString(m) } +func (*PortfolioAddress) ProtoMessage() {} func (*PortfolioAddress) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{40} + return fileDescriptor_77a6da22d6a3feb1, []int{38} } -func (x *PortfolioAddress) GetAddress() string { - if x != nil { - return x.Address +func (m *PortfolioAddress) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_PortfolioAddress.Unmarshal(m, b) +} +func (m *PortfolioAddress) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_PortfolioAddress.Marshal(b, m, deterministic) +} +func (m *PortfolioAddress) XXX_Merge(src proto.Message) { + xxx_messageInfo_PortfolioAddress.Merge(m, src) +} +func (m *PortfolioAddress) XXX_Size() int { + return xxx_messageInfo_PortfolioAddress.Size(m) +} +func (m *PortfolioAddress) XXX_DiscardUnknown() { + xxx_messageInfo_PortfolioAddress.DiscardUnknown(m) +} + +var xxx_messageInfo_PortfolioAddress proto.InternalMessageInfo + +func (m *PortfolioAddress) GetAddress() string { + if m != nil { + return m.Address } return "" } -func (x *PortfolioAddress) GetCoinType() string { - if x != nil { - return x.CoinType +func (m *PortfolioAddress) GetCoinType() string { + if m != nil { + return m.CoinType } return "" } -func (x *PortfolioAddress) GetDescription() string { - if x != nil { - return x.Description +func (m *PortfolioAddress) GetDescription() string { + if m != nil { + return m.Description } return "" } -func (x *PortfolioAddress) GetBalance() float64 { - if x != nil { - return x.Balance +func (m *PortfolioAddress) GetBalance() float64 { + if m != nil { + return m.Balance } return 0 } type GetPortfolioRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GetPortfolioRequest) Reset() { - *x = GetPortfolioRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[41] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetPortfolioRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetPortfolioRequest) ProtoMessage() {} - -func (x *GetPortfolioRequest) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[41] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetPortfolioRequest.ProtoReflect.Descriptor instead. +func (m *GetPortfolioRequest) Reset() { *m = GetPortfolioRequest{} } +func (m *GetPortfolioRequest) String() string { return proto.CompactTextString(m) } +func (*GetPortfolioRequest) ProtoMessage() {} func (*GetPortfolioRequest) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{41} + return fileDescriptor_77a6da22d6a3feb1, []int{39} } +func (m *GetPortfolioRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetPortfolioRequest.Unmarshal(m, b) +} +func (m *GetPortfolioRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetPortfolioRequest.Marshal(b, m, deterministic) +} +func (m *GetPortfolioRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetPortfolioRequest.Merge(m, src) +} +func (m *GetPortfolioRequest) XXX_Size() int { + return xxx_messageInfo_GetPortfolioRequest.Size(m) +} +func (m *GetPortfolioRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetPortfolioRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetPortfolioRequest proto.InternalMessageInfo + type GetPortfolioResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Portfolio []*PortfolioAddress `protobuf:"bytes,1,rep,name=portfolio,proto3" json:"portfolio,omitempty"` + Portfolio []*PortfolioAddress `protobuf:"bytes,1,rep,name=portfolio,proto3" json:"portfolio,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GetPortfolioResponse) Reset() { - *x = GetPortfolioResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[42] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetPortfolioResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetPortfolioResponse) ProtoMessage() {} - -func (x *GetPortfolioResponse) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[42] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetPortfolioResponse.ProtoReflect.Descriptor instead. +func (m *GetPortfolioResponse) Reset() { *m = GetPortfolioResponse{} } +func (m *GetPortfolioResponse) String() string { return proto.CompactTextString(m) } +func (*GetPortfolioResponse) ProtoMessage() {} func (*GetPortfolioResponse) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{42} + return fileDescriptor_77a6da22d6a3feb1, []int{40} } -func (x *GetPortfolioResponse) GetPortfolio() []*PortfolioAddress { - if x != nil { - return x.Portfolio +func (m *GetPortfolioResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetPortfolioResponse.Unmarshal(m, b) +} +func (m *GetPortfolioResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetPortfolioResponse.Marshal(b, m, deterministic) +} +func (m *GetPortfolioResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetPortfolioResponse.Merge(m, src) +} +func (m *GetPortfolioResponse) XXX_Size() int { + return xxx_messageInfo_GetPortfolioResponse.Size(m) +} +func (m *GetPortfolioResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetPortfolioResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetPortfolioResponse proto.InternalMessageInfo + +func (m *GetPortfolioResponse) GetPortfolio() []*PortfolioAddress { + if m != nil { + return m.Portfolio } return nil } type GetPortfolioSummaryRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GetPortfolioSummaryRequest) Reset() { - *x = GetPortfolioSummaryRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[43] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetPortfolioSummaryRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetPortfolioSummaryRequest) ProtoMessage() {} - -func (x *GetPortfolioSummaryRequest) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[43] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetPortfolioSummaryRequest.ProtoReflect.Descriptor instead. +func (m *GetPortfolioSummaryRequest) Reset() { *m = GetPortfolioSummaryRequest{} } +func (m *GetPortfolioSummaryRequest) String() string { return proto.CompactTextString(m) } +func (*GetPortfolioSummaryRequest) ProtoMessage() {} func (*GetPortfolioSummaryRequest) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{43} + return fileDescriptor_77a6da22d6a3feb1, []int{41} } +func (m *GetPortfolioSummaryRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetPortfolioSummaryRequest.Unmarshal(m, b) +} +func (m *GetPortfolioSummaryRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetPortfolioSummaryRequest.Marshal(b, m, deterministic) +} +func (m *GetPortfolioSummaryRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetPortfolioSummaryRequest.Merge(m, src) +} +func (m *GetPortfolioSummaryRequest) XXX_Size() int { + return xxx_messageInfo_GetPortfolioSummaryRequest.Size(m) +} +func (m *GetPortfolioSummaryRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetPortfolioSummaryRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetPortfolioSummaryRequest proto.InternalMessageInfo + type Coin struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Coin string `protobuf:"bytes,1,opt,name=coin,proto3" json:"coin,omitempty"` - Balance float64 `protobuf:"fixed64,2,opt,name=balance,proto3" json:"balance,omitempty"` - Address string `protobuf:"bytes,3,opt,name=address,proto3" json:"address,omitempty"` - Percentage float64 `protobuf:"fixed64,4,opt,name=percentage,proto3" json:"percentage,omitempty"` + Coin string `protobuf:"bytes,1,opt,name=coin,proto3" json:"coin,omitempty"` + Balance float64 `protobuf:"fixed64,2,opt,name=balance,proto3" json:"balance,omitempty"` + Address string `protobuf:"bytes,3,opt,name=address,proto3" json:"address,omitempty"` + Percentage float64 `protobuf:"fixed64,4,opt,name=percentage,proto3" json:"percentage,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *Coin) Reset() { - *x = Coin{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[44] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Coin) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Coin) ProtoMessage() {} - -func (x *Coin) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[44] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Coin.ProtoReflect.Descriptor instead. +func (m *Coin) Reset() { *m = Coin{} } +func (m *Coin) String() string { return proto.CompactTextString(m) } +func (*Coin) ProtoMessage() {} func (*Coin) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{44} + return fileDescriptor_77a6da22d6a3feb1, []int{42} } -func (x *Coin) GetCoin() string { - if x != nil { - return x.Coin +func (m *Coin) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Coin.Unmarshal(m, b) +} +func (m *Coin) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Coin.Marshal(b, m, deterministic) +} +func (m *Coin) XXX_Merge(src proto.Message) { + xxx_messageInfo_Coin.Merge(m, src) +} +func (m *Coin) XXX_Size() int { + return xxx_messageInfo_Coin.Size(m) +} +func (m *Coin) XXX_DiscardUnknown() { + xxx_messageInfo_Coin.DiscardUnknown(m) +} + +var xxx_messageInfo_Coin proto.InternalMessageInfo + +func (m *Coin) GetCoin() string { + if m != nil { + return m.Coin } return "" } -func (x *Coin) GetBalance() float64 { - if x != nil { - return x.Balance +func (m *Coin) GetBalance() float64 { + if m != nil { + return m.Balance } return 0 } -func (x *Coin) GetAddress() string { - if x != nil { - return x.Address +func (m *Coin) GetAddress() string { + if m != nil { + return m.Address } return "" } -func (x *Coin) GetPercentage() float64 { - if x != nil { - return x.Percentage +func (m *Coin) GetPercentage() float64 { + if m != nil { + return m.Percentage } return 0 } type OfflineCoinSummary struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` - Balance float64 `protobuf:"fixed64,2,opt,name=balance,proto3" json:"balance,omitempty"` - Percentage float64 `protobuf:"fixed64,3,opt,name=percentage,proto3" json:"percentage,omitempty"` + Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + Balance float64 `protobuf:"fixed64,2,opt,name=balance,proto3" json:"balance,omitempty"` + Percentage float64 `protobuf:"fixed64,3,opt,name=percentage,proto3" json:"percentage,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *OfflineCoinSummary) Reset() { - *x = OfflineCoinSummary{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[45] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *OfflineCoinSummary) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*OfflineCoinSummary) ProtoMessage() {} - -func (x *OfflineCoinSummary) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[45] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use OfflineCoinSummary.ProtoReflect.Descriptor instead. +func (m *OfflineCoinSummary) Reset() { *m = OfflineCoinSummary{} } +func (m *OfflineCoinSummary) String() string { return proto.CompactTextString(m) } +func (*OfflineCoinSummary) ProtoMessage() {} func (*OfflineCoinSummary) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{45} + return fileDescriptor_77a6da22d6a3feb1, []int{43} } -func (x *OfflineCoinSummary) GetAddress() string { - if x != nil { - return x.Address +func (m *OfflineCoinSummary) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_OfflineCoinSummary.Unmarshal(m, b) +} +func (m *OfflineCoinSummary) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_OfflineCoinSummary.Marshal(b, m, deterministic) +} +func (m *OfflineCoinSummary) XXX_Merge(src proto.Message) { + xxx_messageInfo_OfflineCoinSummary.Merge(m, src) +} +func (m *OfflineCoinSummary) XXX_Size() int { + return xxx_messageInfo_OfflineCoinSummary.Size(m) +} +func (m *OfflineCoinSummary) XXX_DiscardUnknown() { + xxx_messageInfo_OfflineCoinSummary.DiscardUnknown(m) +} + +var xxx_messageInfo_OfflineCoinSummary proto.InternalMessageInfo + +func (m *OfflineCoinSummary) GetAddress() string { + if m != nil { + return m.Address } return "" } -func (x *OfflineCoinSummary) GetBalance() float64 { - if x != nil { - return x.Balance +func (m *OfflineCoinSummary) GetBalance() float64 { + if m != nil { + return m.Balance } return 0 } -func (x *OfflineCoinSummary) GetPercentage() float64 { - if x != nil { - return x.Percentage +func (m *OfflineCoinSummary) GetPercentage() float64 { + if m != nil { + return m.Percentage } return 0 } type OnlineCoinSummary struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Balance float64 `protobuf:"fixed64,1,opt,name=balance,proto3" json:"balance,omitempty"` - Percentage float64 `protobuf:"fixed64,2,opt,name=percentage,proto3" json:"percentage,omitempty"` + Balance float64 `protobuf:"fixed64,1,opt,name=balance,proto3" json:"balance,omitempty"` + Percentage float64 `protobuf:"fixed64,2,opt,name=percentage,proto3" json:"percentage,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *OnlineCoinSummary) Reset() { - *x = OnlineCoinSummary{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[46] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *OnlineCoinSummary) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*OnlineCoinSummary) ProtoMessage() {} - -func (x *OnlineCoinSummary) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[46] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use OnlineCoinSummary.ProtoReflect.Descriptor instead. +func (m *OnlineCoinSummary) Reset() { *m = OnlineCoinSummary{} } +func (m *OnlineCoinSummary) String() string { return proto.CompactTextString(m) } +func (*OnlineCoinSummary) ProtoMessage() {} func (*OnlineCoinSummary) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{46} + return fileDescriptor_77a6da22d6a3feb1, []int{44} } -func (x *OnlineCoinSummary) GetBalance() float64 { - if x != nil { - return x.Balance +func (m *OnlineCoinSummary) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_OnlineCoinSummary.Unmarshal(m, b) +} +func (m *OnlineCoinSummary) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_OnlineCoinSummary.Marshal(b, m, deterministic) +} +func (m *OnlineCoinSummary) XXX_Merge(src proto.Message) { + xxx_messageInfo_OnlineCoinSummary.Merge(m, src) +} +func (m *OnlineCoinSummary) XXX_Size() int { + return xxx_messageInfo_OnlineCoinSummary.Size(m) +} +func (m *OnlineCoinSummary) XXX_DiscardUnknown() { + xxx_messageInfo_OnlineCoinSummary.DiscardUnknown(m) +} + +var xxx_messageInfo_OnlineCoinSummary proto.InternalMessageInfo + +func (m *OnlineCoinSummary) GetBalance() float64 { + if m != nil { + return m.Balance } return 0 } -func (x *OnlineCoinSummary) GetPercentage() float64 { - if x != nil { - return x.Percentage +func (m *OnlineCoinSummary) GetPercentage() float64 { + if m != nil { + return m.Percentage } return 0 } type OfflineCoins struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Addresses []*OfflineCoinSummary `protobuf:"bytes,1,rep,name=addresses,proto3" json:"addresses,omitempty"` + Addresses []*OfflineCoinSummary `protobuf:"bytes,1,rep,name=addresses,proto3" json:"addresses,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *OfflineCoins) Reset() { - *x = OfflineCoins{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[47] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *OfflineCoins) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*OfflineCoins) ProtoMessage() {} - -func (x *OfflineCoins) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[47] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use OfflineCoins.ProtoReflect.Descriptor instead. +func (m *OfflineCoins) Reset() { *m = OfflineCoins{} } +func (m *OfflineCoins) String() string { return proto.CompactTextString(m) } +func (*OfflineCoins) ProtoMessage() {} func (*OfflineCoins) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{47} + return fileDescriptor_77a6da22d6a3feb1, []int{45} } -func (x *OfflineCoins) GetAddresses() []*OfflineCoinSummary { - if x != nil { - return x.Addresses +func (m *OfflineCoins) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_OfflineCoins.Unmarshal(m, b) +} +func (m *OfflineCoins) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_OfflineCoins.Marshal(b, m, deterministic) +} +func (m *OfflineCoins) XXX_Merge(src proto.Message) { + xxx_messageInfo_OfflineCoins.Merge(m, src) +} +func (m *OfflineCoins) XXX_Size() int { + return xxx_messageInfo_OfflineCoins.Size(m) +} +func (m *OfflineCoins) XXX_DiscardUnknown() { + xxx_messageInfo_OfflineCoins.DiscardUnknown(m) +} + +var xxx_messageInfo_OfflineCoins proto.InternalMessageInfo + +func (m *OfflineCoins) GetAddresses() []*OfflineCoinSummary { + if m != nil { + return m.Addresses } return nil } type OnlineCoins struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Coins map[string]*OnlineCoinSummary `protobuf:"bytes,1,rep,name=coins,proto3" json:"coins,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + Coins map[string]*OnlineCoinSummary `protobuf:"bytes,1,rep,name=coins,proto3" json:"coins,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *OnlineCoins) Reset() { - *x = OnlineCoins{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[48] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *OnlineCoins) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*OnlineCoins) ProtoMessage() {} - -func (x *OnlineCoins) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[48] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use OnlineCoins.ProtoReflect.Descriptor instead. +func (m *OnlineCoins) Reset() { *m = OnlineCoins{} } +func (m *OnlineCoins) String() string { return proto.CompactTextString(m) } +func (*OnlineCoins) ProtoMessage() {} func (*OnlineCoins) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{48} + return fileDescriptor_77a6da22d6a3feb1, []int{46} } -func (x *OnlineCoins) GetCoins() map[string]*OnlineCoinSummary { - if x != nil { - return x.Coins +func (m *OnlineCoins) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_OnlineCoins.Unmarshal(m, b) +} +func (m *OnlineCoins) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_OnlineCoins.Marshal(b, m, deterministic) +} +func (m *OnlineCoins) XXX_Merge(src proto.Message) { + xxx_messageInfo_OnlineCoins.Merge(m, src) +} +func (m *OnlineCoins) XXX_Size() int { + return xxx_messageInfo_OnlineCoins.Size(m) +} +func (m *OnlineCoins) XXX_DiscardUnknown() { + xxx_messageInfo_OnlineCoins.DiscardUnknown(m) +} + +var xxx_messageInfo_OnlineCoins proto.InternalMessageInfo + +func (m *OnlineCoins) GetCoins() map[string]*OnlineCoinSummary { + if m != nil { + return m.Coins } return nil } type GetPortfolioSummaryResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - CoinTotals []*Coin `protobuf:"bytes,1,rep,name=coin_totals,json=coinTotals,proto3" json:"coin_totals,omitempty"` - CoinsOffline []*Coin `protobuf:"bytes,2,rep,name=coins_offline,json=coinsOffline,proto3" json:"coins_offline,omitempty"` - CoinsOfflineSummary map[string]*OfflineCoins `protobuf:"bytes,3,rep,name=coins_offline_summary,json=coinsOfflineSummary,proto3" json:"coins_offline_summary,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` - CoinsOnline []*Coin `protobuf:"bytes,4,rep,name=coins_online,json=coinsOnline,proto3" json:"coins_online,omitempty"` - CoinsOnlineSummary map[string]*OnlineCoins `protobuf:"bytes,5,rep,name=coins_online_summary,json=coinsOnlineSummary,proto3" json:"coins_online_summary,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + CoinTotals []*Coin `protobuf:"bytes,1,rep,name=coin_totals,json=coinTotals,proto3" json:"coin_totals,omitempty"` + CoinsOffline []*Coin `protobuf:"bytes,2,rep,name=coins_offline,json=coinsOffline,proto3" json:"coins_offline,omitempty"` + CoinsOfflineSummary map[string]*OfflineCoins `protobuf:"bytes,3,rep,name=coins_offline_summary,json=coinsOfflineSummary,proto3" json:"coins_offline_summary,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + CoinsOnline []*Coin `protobuf:"bytes,4,rep,name=coins_online,json=coinsOnline,proto3" json:"coins_online,omitempty"` + CoinsOnlineSummary map[string]*OnlineCoins `protobuf:"bytes,5,rep,name=coins_online_summary,json=coinsOnlineSummary,proto3" json:"coins_online_summary,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GetPortfolioSummaryResponse) Reset() { - *x = GetPortfolioSummaryResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[49] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetPortfolioSummaryResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetPortfolioSummaryResponse) ProtoMessage() {} - -func (x *GetPortfolioSummaryResponse) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[49] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetPortfolioSummaryResponse.ProtoReflect.Descriptor instead. +func (m *GetPortfolioSummaryResponse) Reset() { *m = GetPortfolioSummaryResponse{} } +func (m *GetPortfolioSummaryResponse) String() string { return proto.CompactTextString(m) } +func (*GetPortfolioSummaryResponse) ProtoMessage() {} func (*GetPortfolioSummaryResponse) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{49} + return fileDescriptor_77a6da22d6a3feb1, []int{47} } -func (x *GetPortfolioSummaryResponse) GetCoinTotals() []*Coin { - if x != nil { - return x.CoinTotals +func (m *GetPortfolioSummaryResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetPortfolioSummaryResponse.Unmarshal(m, b) +} +func (m *GetPortfolioSummaryResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetPortfolioSummaryResponse.Marshal(b, m, deterministic) +} +func (m *GetPortfolioSummaryResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetPortfolioSummaryResponse.Merge(m, src) +} +func (m *GetPortfolioSummaryResponse) XXX_Size() int { + return xxx_messageInfo_GetPortfolioSummaryResponse.Size(m) +} +func (m *GetPortfolioSummaryResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetPortfolioSummaryResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetPortfolioSummaryResponse proto.InternalMessageInfo + +func (m *GetPortfolioSummaryResponse) GetCoinTotals() []*Coin { + if m != nil { + return m.CoinTotals } return nil } -func (x *GetPortfolioSummaryResponse) GetCoinsOffline() []*Coin { - if x != nil { - return x.CoinsOffline +func (m *GetPortfolioSummaryResponse) GetCoinsOffline() []*Coin { + if m != nil { + return m.CoinsOffline } return nil } -func (x *GetPortfolioSummaryResponse) GetCoinsOfflineSummary() map[string]*OfflineCoins { - if x != nil { - return x.CoinsOfflineSummary +func (m *GetPortfolioSummaryResponse) GetCoinsOfflineSummary() map[string]*OfflineCoins { + if m != nil { + return m.CoinsOfflineSummary } return nil } -func (x *GetPortfolioSummaryResponse) GetCoinsOnline() []*Coin { - if x != nil { - return x.CoinsOnline +func (m *GetPortfolioSummaryResponse) GetCoinsOnline() []*Coin { + if m != nil { + return m.CoinsOnline } return nil } -func (x *GetPortfolioSummaryResponse) GetCoinsOnlineSummary() map[string]*OnlineCoins { - if x != nil { - return x.CoinsOnlineSummary +func (m *GetPortfolioSummaryResponse) GetCoinsOnlineSummary() map[string]*OnlineCoins { + if m != nil { + return m.CoinsOnlineSummary } return nil } type AddPortfolioAddressRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` - CoinType string `protobuf:"bytes,2,opt,name=coin_type,json=coinType,proto3" json:"coin_type,omitempty"` - Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"` - Balance float64 `protobuf:"fixed64,4,opt,name=balance,proto3" json:"balance,omitempty"` - SupportedExchanges string `protobuf:"bytes,5,opt,name=supported_exchanges,json=supportedExchanges,proto3" json:"supported_exchanges,omitempty"` - ColdStorage bool `protobuf:"varint,6,opt,name=cold_storage,json=coldStorage,proto3" json:"cold_storage,omitempty"` + Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + CoinType string `protobuf:"bytes,2,opt,name=coin_type,json=coinType,proto3" json:"coin_type,omitempty"` + Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"` + Balance float64 `protobuf:"fixed64,4,opt,name=balance,proto3" json:"balance,omitempty"` + SupportedExchanges string `protobuf:"bytes,5,opt,name=supported_exchanges,json=supportedExchanges,proto3" json:"supported_exchanges,omitempty"` + ColdStorage bool `protobuf:"varint,6,opt,name=cold_storage,json=coldStorage,proto3" json:"cold_storage,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *AddPortfolioAddressRequest) Reset() { - *x = AddPortfolioAddressRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[50] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *AddPortfolioAddressRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*AddPortfolioAddressRequest) ProtoMessage() {} - -func (x *AddPortfolioAddressRequest) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[50] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use AddPortfolioAddressRequest.ProtoReflect.Descriptor instead. +func (m *AddPortfolioAddressRequest) Reset() { *m = AddPortfolioAddressRequest{} } +func (m *AddPortfolioAddressRequest) String() string { return proto.CompactTextString(m) } +func (*AddPortfolioAddressRequest) ProtoMessage() {} func (*AddPortfolioAddressRequest) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{50} + return fileDescriptor_77a6da22d6a3feb1, []int{48} } -func (x *AddPortfolioAddressRequest) GetAddress() string { - if x != nil { - return x.Address +func (m *AddPortfolioAddressRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_AddPortfolioAddressRequest.Unmarshal(m, b) +} +func (m *AddPortfolioAddressRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_AddPortfolioAddressRequest.Marshal(b, m, deterministic) +} +func (m *AddPortfolioAddressRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_AddPortfolioAddressRequest.Merge(m, src) +} +func (m *AddPortfolioAddressRequest) XXX_Size() int { + return xxx_messageInfo_AddPortfolioAddressRequest.Size(m) +} +func (m *AddPortfolioAddressRequest) XXX_DiscardUnknown() { + xxx_messageInfo_AddPortfolioAddressRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_AddPortfolioAddressRequest proto.InternalMessageInfo + +func (m *AddPortfolioAddressRequest) GetAddress() string { + if m != nil { + return m.Address } return "" } -func (x *AddPortfolioAddressRequest) GetCoinType() string { - if x != nil { - return x.CoinType +func (m *AddPortfolioAddressRequest) GetCoinType() string { + if m != nil { + return m.CoinType } return "" } -func (x *AddPortfolioAddressRequest) GetDescription() string { - if x != nil { - return x.Description +func (m *AddPortfolioAddressRequest) GetDescription() string { + if m != nil { + return m.Description } return "" } -func (x *AddPortfolioAddressRequest) GetBalance() float64 { - if x != nil { - return x.Balance +func (m *AddPortfolioAddressRequest) GetBalance() float64 { + if m != nil { + return m.Balance } return 0 } -func (x *AddPortfolioAddressRequest) GetSupportedExchanges() string { - if x != nil { - return x.SupportedExchanges +func (m *AddPortfolioAddressRequest) GetSupportedExchanges() string { + if m != nil { + return m.SupportedExchanges } return "" } -func (x *AddPortfolioAddressRequest) GetColdStorage() bool { - if x != nil { - return x.ColdStorage +func (m *AddPortfolioAddressRequest) GetColdStorage() bool { + if m != nil { + return m.ColdStorage } return false } -type AddPortfolioAddressResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields -} - -func (x *AddPortfolioAddressResponse) Reset() { - *x = AddPortfolioAddressResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[51] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *AddPortfolioAddressResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*AddPortfolioAddressResponse) ProtoMessage() {} - -func (x *AddPortfolioAddressResponse) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[51] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use AddPortfolioAddressResponse.ProtoReflect.Descriptor instead. -func (*AddPortfolioAddressResponse) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{51} -} - type RemovePortfolioAddressRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` - CoinType string `protobuf:"bytes,2,opt,name=coin_type,json=coinType,proto3" json:"coin_type,omitempty"` - Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"` + Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + CoinType string `protobuf:"bytes,2,opt,name=coin_type,json=coinType,proto3" json:"coin_type,omitempty"` + Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *RemovePortfolioAddressRequest) Reset() { - *x = RemovePortfolioAddressRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[52] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *RemovePortfolioAddressRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*RemovePortfolioAddressRequest) ProtoMessage() {} - -func (x *RemovePortfolioAddressRequest) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[52] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use RemovePortfolioAddressRequest.ProtoReflect.Descriptor instead. +func (m *RemovePortfolioAddressRequest) Reset() { *m = RemovePortfolioAddressRequest{} } +func (m *RemovePortfolioAddressRequest) String() string { return proto.CompactTextString(m) } +func (*RemovePortfolioAddressRequest) ProtoMessage() {} func (*RemovePortfolioAddressRequest) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{52} + return fileDescriptor_77a6da22d6a3feb1, []int{49} } -func (x *RemovePortfolioAddressRequest) GetAddress() string { - if x != nil { - return x.Address +func (m *RemovePortfolioAddressRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_RemovePortfolioAddressRequest.Unmarshal(m, b) +} +func (m *RemovePortfolioAddressRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_RemovePortfolioAddressRequest.Marshal(b, m, deterministic) +} +func (m *RemovePortfolioAddressRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_RemovePortfolioAddressRequest.Merge(m, src) +} +func (m *RemovePortfolioAddressRequest) XXX_Size() int { + return xxx_messageInfo_RemovePortfolioAddressRequest.Size(m) +} +func (m *RemovePortfolioAddressRequest) XXX_DiscardUnknown() { + xxx_messageInfo_RemovePortfolioAddressRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_RemovePortfolioAddressRequest proto.InternalMessageInfo + +func (m *RemovePortfolioAddressRequest) GetAddress() string { + if m != nil { + return m.Address } return "" } -func (x *RemovePortfolioAddressRequest) GetCoinType() string { - if x != nil { - return x.CoinType +func (m *RemovePortfolioAddressRequest) GetCoinType() string { + if m != nil { + return m.CoinType } return "" } -func (x *RemovePortfolioAddressRequest) GetDescription() string { - if x != nil { - return x.Description +func (m *RemovePortfolioAddressRequest) GetDescription() string { + if m != nil { + return m.Description } return "" } -type RemovePortfolioAddressResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields -} - -func (x *RemovePortfolioAddressResponse) Reset() { - *x = RemovePortfolioAddressResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[53] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *RemovePortfolioAddressResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*RemovePortfolioAddressResponse) ProtoMessage() {} - -func (x *RemovePortfolioAddressResponse) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[53] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use RemovePortfolioAddressResponse.ProtoReflect.Descriptor instead. -func (*RemovePortfolioAddressResponse) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{53} -} - type GetForexProvidersRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GetForexProvidersRequest) Reset() { - *x = GetForexProvidersRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[54] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetForexProvidersRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetForexProvidersRequest) ProtoMessage() {} - -func (x *GetForexProvidersRequest) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[54] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetForexProvidersRequest.ProtoReflect.Descriptor instead. +func (m *GetForexProvidersRequest) Reset() { *m = GetForexProvidersRequest{} } +func (m *GetForexProvidersRequest) String() string { return proto.CompactTextString(m) } +func (*GetForexProvidersRequest) ProtoMessage() {} func (*GetForexProvidersRequest) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{54} + return fileDescriptor_77a6da22d6a3feb1, []int{50} } +func (m *GetForexProvidersRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetForexProvidersRequest.Unmarshal(m, b) +} +func (m *GetForexProvidersRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetForexProvidersRequest.Marshal(b, m, deterministic) +} +func (m *GetForexProvidersRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetForexProvidersRequest.Merge(m, src) +} +func (m *GetForexProvidersRequest) XXX_Size() int { + return xxx_messageInfo_GetForexProvidersRequest.Size(m) +} +func (m *GetForexProvidersRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetForexProvidersRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetForexProvidersRequest proto.InternalMessageInfo + type ForexProvider struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - Enabled bool `protobuf:"varint,2,opt,name=enabled,proto3" json:"enabled,omitempty"` - Verbose bool `protobuf:"varint,3,opt,name=verbose,proto3" json:"verbose,omitempty"` - RestPollingDelay string `protobuf:"bytes,4,opt,name=rest_polling_delay,json=restPollingDelay,proto3" json:"rest_polling_delay,omitempty"` - ApiKey string `protobuf:"bytes,5,opt,name=api_key,json=apiKey,proto3" json:"api_key,omitempty"` - ApiKeyLevel int64 `protobuf:"varint,6,opt,name=api_key_level,json=apiKeyLevel,proto3" json:"api_key_level,omitempty"` - PrimaryProvider bool `protobuf:"varint,7,opt,name=primary_provider,json=primaryProvider,proto3" json:"primary_provider,omitempty"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Enabled bool `protobuf:"varint,2,opt,name=enabled,proto3" json:"enabled,omitempty"` + Verbose bool `protobuf:"varint,3,opt,name=verbose,proto3" json:"verbose,omitempty"` + RestPollingDelay string `protobuf:"bytes,4,opt,name=rest_polling_delay,json=restPollingDelay,proto3" json:"rest_polling_delay,omitempty"` + ApiKey string `protobuf:"bytes,5,opt,name=api_key,json=apiKey,proto3" json:"api_key,omitempty"` + ApiKeyLevel int64 `protobuf:"varint,6,opt,name=api_key_level,json=apiKeyLevel,proto3" json:"api_key_level,omitempty"` + PrimaryProvider bool `protobuf:"varint,7,opt,name=primary_provider,json=primaryProvider,proto3" json:"primary_provider,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *ForexProvider) Reset() { - *x = ForexProvider{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[55] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *ForexProvider) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ForexProvider) ProtoMessage() {} - -func (x *ForexProvider) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[55] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ForexProvider.ProtoReflect.Descriptor instead. +func (m *ForexProvider) Reset() { *m = ForexProvider{} } +func (m *ForexProvider) String() string { return proto.CompactTextString(m) } +func (*ForexProvider) ProtoMessage() {} func (*ForexProvider) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{55} + return fileDescriptor_77a6da22d6a3feb1, []int{51} } -func (x *ForexProvider) GetName() string { - if x != nil { - return x.Name +func (m *ForexProvider) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ForexProvider.Unmarshal(m, b) +} +func (m *ForexProvider) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ForexProvider.Marshal(b, m, deterministic) +} +func (m *ForexProvider) XXX_Merge(src proto.Message) { + xxx_messageInfo_ForexProvider.Merge(m, src) +} +func (m *ForexProvider) XXX_Size() int { + return xxx_messageInfo_ForexProvider.Size(m) +} +func (m *ForexProvider) XXX_DiscardUnknown() { + xxx_messageInfo_ForexProvider.DiscardUnknown(m) +} + +var xxx_messageInfo_ForexProvider proto.InternalMessageInfo + +func (m *ForexProvider) GetName() string { + if m != nil { + return m.Name } return "" } -func (x *ForexProvider) GetEnabled() bool { - if x != nil { - return x.Enabled +func (m *ForexProvider) GetEnabled() bool { + if m != nil { + return m.Enabled } return false } -func (x *ForexProvider) GetVerbose() bool { - if x != nil { - return x.Verbose +func (m *ForexProvider) GetVerbose() bool { + if m != nil { + return m.Verbose } return false } -func (x *ForexProvider) GetRestPollingDelay() string { - if x != nil { - return x.RestPollingDelay +func (m *ForexProvider) GetRestPollingDelay() string { + if m != nil { + return m.RestPollingDelay } return "" } -func (x *ForexProvider) GetApiKey() string { - if x != nil { - return x.ApiKey +func (m *ForexProvider) GetApiKey() string { + if m != nil { + return m.ApiKey } return "" } -func (x *ForexProvider) GetApiKeyLevel() int64 { - if x != nil { - return x.ApiKeyLevel +func (m *ForexProvider) GetApiKeyLevel() int64 { + if m != nil { + return m.ApiKeyLevel } return 0 } -func (x *ForexProvider) GetPrimaryProvider() bool { - if x != nil { - return x.PrimaryProvider +func (m *ForexProvider) GetPrimaryProvider() bool { + if m != nil { + return m.PrimaryProvider } return false } type GetForexProvidersResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - ForexProviders []*ForexProvider `protobuf:"bytes,1,rep,name=forex_providers,json=forexProviders,proto3" json:"forex_providers,omitempty"` + ForexProviders []*ForexProvider `protobuf:"bytes,1,rep,name=forex_providers,json=forexProviders,proto3" json:"forex_providers,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GetForexProvidersResponse) Reset() { - *x = GetForexProvidersResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[56] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetForexProvidersResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetForexProvidersResponse) ProtoMessage() {} - -func (x *GetForexProvidersResponse) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[56] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetForexProvidersResponse.ProtoReflect.Descriptor instead. +func (m *GetForexProvidersResponse) Reset() { *m = GetForexProvidersResponse{} } +func (m *GetForexProvidersResponse) String() string { return proto.CompactTextString(m) } +func (*GetForexProvidersResponse) ProtoMessage() {} func (*GetForexProvidersResponse) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{56} + return fileDescriptor_77a6da22d6a3feb1, []int{52} } -func (x *GetForexProvidersResponse) GetForexProviders() []*ForexProvider { - if x != nil { - return x.ForexProviders +func (m *GetForexProvidersResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetForexProvidersResponse.Unmarshal(m, b) +} +func (m *GetForexProvidersResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetForexProvidersResponse.Marshal(b, m, deterministic) +} +func (m *GetForexProvidersResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetForexProvidersResponse.Merge(m, src) +} +func (m *GetForexProvidersResponse) XXX_Size() int { + return xxx_messageInfo_GetForexProvidersResponse.Size(m) +} +func (m *GetForexProvidersResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetForexProvidersResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetForexProvidersResponse proto.InternalMessageInfo + +func (m *GetForexProvidersResponse) GetForexProviders() []*ForexProvider { + if m != nil { + return m.ForexProviders } return nil } type GetForexRatesRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GetForexRatesRequest) Reset() { - *x = GetForexRatesRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[57] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetForexRatesRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetForexRatesRequest) ProtoMessage() {} - -func (x *GetForexRatesRequest) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[57] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetForexRatesRequest.ProtoReflect.Descriptor instead. +func (m *GetForexRatesRequest) Reset() { *m = GetForexRatesRequest{} } +func (m *GetForexRatesRequest) String() string { return proto.CompactTextString(m) } +func (*GetForexRatesRequest) ProtoMessage() {} func (*GetForexRatesRequest) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{57} + return fileDescriptor_77a6da22d6a3feb1, []int{53} } +func (m *GetForexRatesRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetForexRatesRequest.Unmarshal(m, b) +} +func (m *GetForexRatesRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetForexRatesRequest.Marshal(b, m, deterministic) +} +func (m *GetForexRatesRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetForexRatesRequest.Merge(m, src) +} +func (m *GetForexRatesRequest) XXX_Size() int { + return xxx_messageInfo_GetForexRatesRequest.Size(m) +} +func (m *GetForexRatesRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetForexRatesRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetForexRatesRequest proto.InternalMessageInfo + type ForexRatesConversion struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - From string `protobuf:"bytes,1,opt,name=from,proto3" json:"from,omitempty"` - To string `protobuf:"bytes,2,opt,name=to,proto3" json:"to,omitempty"` - Rate float64 `protobuf:"fixed64,3,opt,name=rate,proto3" json:"rate,omitempty"` - InverseRate float64 `protobuf:"fixed64,4,opt,name=inverse_rate,json=inverseRate,proto3" json:"inverse_rate,omitempty"` + From string `protobuf:"bytes,1,opt,name=from,proto3" json:"from,omitempty"` + To string `protobuf:"bytes,2,opt,name=to,proto3" json:"to,omitempty"` + Rate float64 `protobuf:"fixed64,3,opt,name=rate,proto3" json:"rate,omitempty"` + InverseRate float64 `protobuf:"fixed64,4,opt,name=inverse_rate,json=inverseRate,proto3" json:"inverse_rate,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *ForexRatesConversion) Reset() { - *x = ForexRatesConversion{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[58] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *ForexRatesConversion) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ForexRatesConversion) ProtoMessage() {} - -func (x *ForexRatesConversion) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[58] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ForexRatesConversion.ProtoReflect.Descriptor instead. +func (m *ForexRatesConversion) Reset() { *m = ForexRatesConversion{} } +func (m *ForexRatesConversion) String() string { return proto.CompactTextString(m) } +func (*ForexRatesConversion) ProtoMessage() {} func (*ForexRatesConversion) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{58} + return fileDescriptor_77a6da22d6a3feb1, []int{54} } -func (x *ForexRatesConversion) GetFrom() string { - if x != nil { - return x.From +func (m *ForexRatesConversion) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ForexRatesConversion.Unmarshal(m, b) +} +func (m *ForexRatesConversion) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ForexRatesConversion.Marshal(b, m, deterministic) +} +func (m *ForexRatesConversion) XXX_Merge(src proto.Message) { + xxx_messageInfo_ForexRatesConversion.Merge(m, src) +} +func (m *ForexRatesConversion) XXX_Size() int { + return xxx_messageInfo_ForexRatesConversion.Size(m) +} +func (m *ForexRatesConversion) XXX_DiscardUnknown() { + xxx_messageInfo_ForexRatesConversion.DiscardUnknown(m) +} + +var xxx_messageInfo_ForexRatesConversion proto.InternalMessageInfo + +func (m *ForexRatesConversion) GetFrom() string { + if m != nil { + return m.From } return "" } -func (x *ForexRatesConversion) GetTo() string { - if x != nil { - return x.To +func (m *ForexRatesConversion) GetTo() string { + if m != nil { + return m.To } return "" } -func (x *ForexRatesConversion) GetRate() float64 { - if x != nil { - return x.Rate +func (m *ForexRatesConversion) GetRate() float64 { + if m != nil { + return m.Rate } return 0 } -func (x *ForexRatesConversion) GetInverseRate() float64 { - if x != nil { - return x.InverseRate +func (m *ForexRatesConversion) GetInverseRate() float64 { + if m != nil { + return m.InverseRate } return 0 } type GetForexRatesResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - ForexRates []*ForexRatesConversion `protobuf:"bytes,1,rep,name=forex_rates,json=forexRates,proto3" json:"forex_rates,omitempty"` + ForexRates []*ForexRatesConversion `protobuf:"bytes,1,rep,name=forex_rates,json=forexRates,proto3" json:"forex_rates,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GetForexRatesResponse) Reset() { - *x = GetForexRatesResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[59] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetForexRatesResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetForexRatesResponse) ProtoMessage() {} - -func (x *GetForexRatesResponse) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[59] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetForexRatesResponse.ProtoReflect.Descriptor instead. +func (m *GetForexRatesResponse) Reset() { *m = GetForexRatesResponse{} } +func (m *GetForexRatesResponse) String() string { return proto.CompactTextString(m) } +func (*GetForexRatesResponse) ProtoMessage() {} func (*GetForexRatesResponse) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{59} + return fileDescriptor_77a6da22d6a3feb1, []int{55} } -func (x *GetForexRatesResponse) GetForexRates() []*ForexRatesConversion { - if x != nil { - return x.ForexRates +func (m *GetForexRatesResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetForexRatesResponse.Unmarshal(m, b) +} +func (m *GetForexRatesResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetForexRatesResponse.Marshal(b, m, deterministic) +} +func (m *GetForexRatesResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetForexRatesResponse.Merge(m, src) +} +func (m *GetForexRatesResponse) XXX_Size() int { + return xxx_messageInfo_GetForexRatesResponse.Size(m) +} +func (m *GetForexRatesResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetForexRatesResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetForexRatesResponse proto.InternalMessageInfo + +func (m *GetForexRatesResponse) GetForexRates() []*ForexRatesConversion { + if m != nil { + return m.ForexRates } return nil } type OrderDetails struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` - Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` - BaseCurrency string `protobuf:"bytes,3,opt,name=base_currency,json=baseCurrency,proto3" json:"base_currency,omitempty"` - QuoteCurrency string `protobuf:"bytes,4,opt,name=quote_currency,json=quoteCurrency,proto3" json:"quote_currency,omitempty"` - AssetType string `protobuf:"bytes,5,opt,name=asset_type,json=assetType,proto3" json:"asset_type,omitempty"` - OrderSide string `protobuf:"bytes,6,opt,name=order_side,json=orderSide,proto3" json:"order_side,omitempty"` - OrderType string `protobuf:"bytes,7,opt,name=order_type,json=orderType,proto3" json:"order_type,omitempty"` - CreationTime int64 `protobuf:"varint,8,opt,name=creation_time,json=creationTime,proto3" json:"creation_time,omitempty"` - Status string `protobuf:"bytes,9,opt,name=status,proto3" json:"status,omitempty"` - Price float64 `protobuf:"fixed64,10,opt,name=price,proto3" json:"price,omitempty"` - Amount float64 `protobuf:"fixed64,11,opt,name=amount,proto3" json:"amount,omitempty"` - OpenVolume float64 `protobuf:"fixed64,12,opt,name=open_volume,json=openVolume,proto3" json:"open_volume,omitempty"` - Fee float64 `protobuf:"fixed64,13,opt,name=fee,proto3" json:"fee,omitempty"` - Trades []*TradeHistory `protobuf:"bytes,14,rep,name=trades,proto3" json:"trades,omitempty"` + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` + BaseCurrency string `protobuf:"bytes,3,opt,name=base_currency,json=baseCurrency,proto3" json:"base_currency,omitempty"` + QuoteCurrency string `protobuf:"bytes,4,opt,name=quote_currency,json=quoteCurrency,proto3" json:"quote_currency,omitempty"` + AssetType string `protobuf:"bytes,5,opt,name=asset_type,json=assetType,proto3" json:"asset_type,omitempty"` + OrderSide string `protobuf:"bytes,6,opt,name=order_side,json=orderSide,proto3" json:"order_side,omitempty"` + OrderType string `protobuf:"bytes,7,opt,name=order_type,json=orderType,proto3" json:"order_type,omitempty"` + CreationTime int64 `protobuf:"varint,8,opt,name=creation_time,json=creationTime,proto3" json:"creation_time,omitempty"` + Status string `protobuf:"bytes,9,opt,name=status,proto3" json:"status,omitempty"` + Price float64 `protobuf:"fixed64,10,opt,name=price,proto3" json:"price,omitempty"` + Amount float64 `protobuf:"fixed64,11,opt,name=amount,proto3" json:"amount,omitempty"` + OpenVolume float64 `protobuf:"fixed64,12,opt,name=open_volume,json=openVolume,proto3" json:"open_volume,omitempty"` + Fee float64 `protobuf:"fixed64,13,opt,name=fee,proto3" json:"fee,omitempty"` + Trades []*TradeHistory `protobuf:"bytes,14,rep,name=trades,proto3" json:"trades,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *OrderDetails) Reset() { - *x = OrderDetails{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[60] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *OrderDetails) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*OrderDetails) ProtoMessage() {} - -func (x *OrderDetails) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[60] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use OrderDetails.ProtoReflect.Descriptor instead. +func (m *OrderDetails) Reset() { *m = OrderDetails{} } +func (m *OrderDetails) String() string { return proto.CompactTextString(m) } +func (*OrderDetails) ProtoMessage() {} func (*OrderDetails) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{60} + return fileDescriptor_77a6da22d6a3feb1, []int{56} } -func (x *OrderDetails) GetExchange() string { - if x != nil { - return x.Exchange +func (m *OrderDetails) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_OrderDetails.Unmarshal(m, b) +} +func (m *OrderDetails) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_OrderDetails.Marshal(b, m, deterministic) +} +func (m *OrderDetails) XXX_Merge(src proto.Message) { + xxx_messageInfo_OrderDetails.Merge(m, src) +} +func (m *OrderDetails) XXX_Size() int { + return xxx_messageInfo_OrderDetails.Size(m) +} +func (m *OrderDetails) XXX_DiscardUnknown() { + xxx_messageInfo_OrderDetails.DiscardUnknown(m) +} + +var xxx_messageInfo_OrderDetails proto.InternalMessageInfo + +func (m *OrderDetails) GetExchange() string { + if m != nil { + return m.Exchange } return "" } -func (x *OrderDetails) GetId() string { - if x != nil { - return x.Id +func (m *OrderDetails) GetId() string { + if m != nil { + return m.Id } return "" } -func (x *OrderDetails) GetBaseCurrency() string { - if x != nil { - return x.BaseCurrency +func (m *OrderDetails) GetBaseCurrency() string { + if m != nil { + return m.BaseCurrency } return "" } -func (x *OrderDetails) GetQuoteCurrency() string { - if x != nil { - return x.QuoteCurrency +func (m *OrderDetails) GetQuoteCurrency() string { + if m != nil { + return m.QuoteCurrency } return "" } -func (x *OrderDetails) GetAssetType() string { - if x != nil { - return x.AssetType +func (m *OrderDetails) GetAssetType() string { + if m != nil { + return m.AssetType } return "" } -func (x *OrderDetails) GetOrderSide() string { - if x != nil { - return x.OrderSide +func (m *OrderDetails) GetOrderSide() string { + if m != nil { + return m.OrderSide } return "" } -func (x *OrderDetails) GetOrderType() string { - if x != nil { - return x.OrderType +func (m *OrderDetails) GetOrderType() string { + if m != nil { + return m.OrderType } return "" } -func (x *OrderDetails) GetCreationTime() int64 { - if x != nil { - return x.CreationTime +func (m *OrderDetails) GetCreationTime() int64 { + if m != nil { + return m.CreationTime } return 0 } -func (x *OrderDetails) GetStatus() string { - if x != nil { - return x.Status +func (m *OrderDetails) GetStatus() string { + if m != nil { + return m.Status } return "" } -func (x *OrderDetails) GetPrice() float64 { - if x != nil { - return x.Price +func (m *OrderDetails) GetPrice() float64 { + if m != nil { + return m.Price } return 0 } -func (x *OrderDetails) GetAmount() float64 { - if x != nil { - return x.Amount +func (m *OrderDetails) GetAmount() float64 { + if m != nil { + return m.Amount } return 0 } -func (x *OrderDetails) GetOpenVolume() float64 { - if x != nil { - return x.OpenVolume +func (m *OrderDetails) GetOpenVolume() float64 { + if m != nil { + return m.OpenVolume } return 0 } -func (x *OrderDetails) GetFee() float64 { - if x != nil { - return x.Fee +func (m *OrderDetails) GetFee() float64 { + if m != nil { + return m.Fee } return 0 } -func (x *OrderDetails) GetTrades() []*TradeHistory { - if x != nil { - return x.Trades +func (m *OrderDetails) GetTrades() []*TradeHistory { + if m != nil { + return m.Trades } return nil } type TradeHistory struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - CreationTime int64 `protobuf:"varint,1,opt,name=creation_time,json=creationTime,proto3" json:"creation_time,omitempty"` - Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` - Price float64 `protobuf:"fixed64,3,opt,name=price,proto3" json:"price,omitempty"` - Amount float64 `protobuf:"fixed64,4,opt,name=amount,proto3" json:"amount,omitempty"` - Exchange string `protobuf:"bytes,5,opt,name=exchange,proto3" json:"exchange,omitempty"` - AssetType string `protobuf:"bytes,6,opt,name=asset_type,json=assetType,proto3" json:"asset_type,omitempty"` - OrderSide string `protobuf:"bytes,7,opt,name=order_side,json=orderSide,proto3" json:"order_side,omitempty"` - Fee float64 `protobuf:"fixed64,8,opt,name=fee,proto3" json:"fee,omitempty"` + CreationTime int64 `protobuf:"varint,1,opt,name=creation_time,json=creationTime,proto3" json:"creation_time,omitempty"` + Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` + Price float64 `protobuf:"fixed64,3,opt,name=price,proto3" json:"price,omitempty"` + Amount float64 `protobuf:"fixed64,4,opt,name=amount,proto3" json:"amount,omitempty"` + Exchange string `protobuf:"bytes,5,opt,name=exchange,proto3" json:"exchange,omitempty"` + AssetType string `protobuf:"bytes,6,opt,name=asset_type,json=assetType,proto3" json:"asset_type,omitempty"` + OrderSide string `protobuf:"bytes,7,opt,name=order_side,json=orderSide,proto3" json:"order_side,omitempty"` + Fee float64 `protobuf:"fixed64,8,opt,name=fee,proto3" json:"fee,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *TradeHistory) Reset() { - *x = TradeHistory{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[61] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *TradeHistory) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*TradeHistory) ProtoMessage() {} - -func (x *TradeHistory) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[61] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use TradeHistory.ProtoReflect.Descriptor instead. +func (m *TradeHistory) Reset() { *m = TradeHistory{} } +func (m *TradeHistory) String() string { return proto.CompactTextString(m) } +func (*TradeHistory) ProtoMessage() {} func (*TradeHistory) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{61} + return fileDescriptor_77a6da22d6a3feb1, []int{57} } -func (x *TradeHistory) GetCreationTime() int64 { - if x != nil { - return x.CreationTime +func (m *TradeHistory) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_TradeHistory.Unmarshal(m, b) +} +func (m *TradeHistory) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_TradeHistory.Marshal(b, m, deterministic) +} +func (m *TradeHistory) XXX_Merge(src proto.Message) { + xxx_messageInfo_TradeHistory.Merge(m, src) +} +func (m *TradeHistory) XXX_Size() int { + return xxx_messageInfo_TradeHistory.Size(m) +} +func (m *TradeHistory) XXX_DiscardUnknown() { + xxx_messageInfo_TradeHistory.DiscardUnknown(m) +} + +var xxx_messageInfo_TradeHistory proto.InternalMessageInfo + +func (m *TradeHistory) GetCreationTime() int64 { + if m != nil { + return m.CreationTime } return 0 } -func (x *TradeHistory) GetId() string { - if x != nil { - return x.Id +func (m *TradeHistory) GetId() string { + if m != nil { + return m.Id } return "" } -func (x *TradeHistory) GetPrice() float64 { - if x != nil { - return x.Price +func (m *TradeHistory) GetPrice() float64 { + if m != nil { + return m.Price } return 0 } -func (x *TradeHistory) GetAmount() float64 { - if x != nil { - return x.Amount +func (m *TradeHistory) GetAmount() float64 { + if m != nil { + return m.Amount } return 0 } -func (x *TradeHistory) GetExchange() string { - if x != nil { - return x.Exchange +func (m *TradeHistory) GetExchange() string { + if m != nil { + return m.Exchange } return "" } -func (x *TradeHistory) GetAssetType() string { - if x != nil { - return x.AssetType +func (m *TradeHistory) GetAssetType() string { + if m != nil { + return m.AssetType } return "" } -func (x *TradeHistory) GetOrderSide() string { - if x != nil { - return x.OrderSide +func (m *TradeHistory) GetOrderSide() string { + if m != nil { + return m.OrderSide } return "" } -func (x *TradeHistory) GetFee() float64 { - if x != nil { - return x.Fee +func (m *TradeHistory) GetFee() float64 { + if m != nil { + return m.Fee } return 0 } type GetOrdersRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` - AssetType string `protobuf:"bytes,2,opt,name=asset_type,json=assetType,proto3" json:"asset_type,omitempty"` - Pair *CurrencyPair `protobuf:"bytes,3,opt,name=pair,proto3" json:"pair,omitempty"` + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + AssetType string `protobuf:"bytes,2,opt,name=asset_type,json=assetType,proto3" json:"asset_type,omitempty"` + Pair *CurrencyPair `protobuf:"bytes,3,opt,name=pair,proto3" json:"pair,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GetOrdersRequest) Reset() { - *x = GetOrdersRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[62] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetOrdersRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetOrdersRequest) ProtoMessage() {} - -func (x *GetOrdersRequest) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[62] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetOrdersRequest.ProtoReflect.Descriptor instead. +func (m *GetOrdersRequest) Reset() { *m = GetOrdersRequest{} } +func (m *GetOrdersRequest) String() string { return proto.CompactTextString(m) } +func (*GetOrdersRequest) ProtoMessage() {} func (*GetOrdersRequest) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{62} + return fileDescriptor_77a6da22d6a3feb1, []int{58} } -func (x *GetOrdersRequest) GetExchange() string { - if x != nil { - return x.Exchange +func (m *GetOrdersRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetOrdersRequest.Unmarshal(m, b) +} +func (m *GetOrdersRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetOrdersRequest.Marshal(b, m, deterministic) +} +func (m *GetOrdersRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetOrdersRequest.Merge(m, src) +} +func (m *GetOrdersRequest) XXX_Size() int { + return xxx_messageInfo_GetOrdersRequest.Size(m) +} +func (m *GetOrdersRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetOrdersRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetOrdersRequest proto.InternalMessageInfo + +func (m *GetOrdersRequest) GetExchange() string { + if m != nil { + return m.Exchange } return "" } -func (x *GetOrdersRequest) GetAssetType() string { - if x != nil { - return x.AssetType +func (m *GetOrdersRequest) GetAssetType() string { + if m != nil { + return m.AssetType } return "" } -func (x *GetOrdersRequest) GetPair() *CurrencyPair { - if x != nil { - return x.Pair +func (m *GetOrdersRequest) GetPair() *CurrencyPair { + if m != nil { + return m.Pair } return nil } type GetOrdersResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Orders []*OrderDetails `protobuf:"bytes,1,rep,name=orders,proto3" json:"orders,omitempty"` + Orders []*OrderDetails `protobuf:"bytes,1,rep,name=orders,proto3" json:"orders,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GetOrdersResponse) Reset() { - *x = GetOrdersResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[63] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetOrdersResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetOrdersResponse) ProtoMessage() {} - -func (x *GetOrdersResponse) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[63] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetOrdersResponse.ProtoReflect.Descriptor instead. +func (m *GetOrdersResponse) Reset() { *m = GetOrdersResponse{} } +func (m *GetOrdersResponse) String() string { return proto.CompactTextString(m) } +func (*GetOrdersResponse) ProtoMessage() {} func (*GetOrdersResponse) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{63} + return fileDescriptor_77a6da22d6a3feb1, []int{59} } -func (x *GetOrdersResponse) GetOrders() []*OrderDetails { - if x != nil { - return x.Orders +func (m *GetOrdersResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetOrdersResponse.Unmarshal(m, b) +} +func (m *GetOrdersResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetOrdersResponse.Marshal(b, m, deterministic) +} +func (m *GetOrdersResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetOrdersResponse.Merge(m, src) +} +func (m *GetOrdersResponse) XXX_Size() int { + return xxx_messageInfo_GetOrdersResponse.Size(m) +} +func (m *GetOrdersResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetOrdersResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetOrdersResponse proto.InternalMessageInfo + +func (m *GetOrdersResponse) GetOrders() []*OrderDetails { + if m != nil { + return m.Orders } return nil } type GetOrderRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` - OrderId string `protobuf:"bytes,2,opt,name=order_id,json=orderId,proto3" json:"order_id,omitempty"` + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + OrderId string `protobuf:"bytes,2,opt,name=order_id,json=orderId,proto3" json:"order_id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GetOrderRequest) Reset() { - *x = GetOrderRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[64] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetOrderRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetOrderRequest) ProtoMessage() {} - -func (x *GetOrderRequest) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[64] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetOrderRequest.ProtoReflect.Descriptor instead. +func (m *GetOrderRequest) Reset() { *m = GetOrderRequest{} } +func (m *GetOrderRequest) String() string { return proto.CompactTextString(m) } +func (*GetOrderRequest) ProtoMessage() {} func (*GetOrderRequest) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{64} + return fileDescriptor_77a6da22d6a3feb1, []int{60} } -func (x *GetOrderRequest) GetExchange() string { - if x != nil { - return x.Exchange +func (m *GetOrderRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetOrderRequest.Unmarshal(m, b) +} +func (m *GetOrderRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetOrderRequest.Marshal(b, m, deterministic) +} +func (m *GetOrderRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetOrderRequest.Merge(m, src) +} +func (m *GetOrderRequest) XXX_Size() int { + return xxx_messageInfo_GetOrderRequest.Size(m) +} +func (m *GetOrderRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetOrderRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetOrderRequest proto.InternalMessageInfo + +func (m *GetOrderRequest) GetExchange() string { + if m != nil { + return m.Exchange } return "" } -func (x *GetOrderRequest) GetOrderId() string { - if x != nil { - return x.OrderId +func (m *GetOrderRequest) GetOrderId() string { + if m != nil { + return m.OrderId } return "" } type SubmitOrderRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` - Pair *CurrencyPair `protobuf:"bytes,2,opt,name=pair,proto3" json:"pair,omitempty"` - Side string `protobuf:"bytes,3,opt,name=side,proto3" json:"side,omitempty"` - OrderType string `protobuf:"bytes,4,opt,name=order_type,json=orderType,proto3" json:"order_type,omitempty"` - Amount float64 `protobuf:"fixed64,5,opt,name=amount,proto3" json:"amount,omitempty"` - Price float64 `protobuf:"fixed64,6,opt,name=price,proto3" json:"price,omitempty"` - ClientId string `protobuf:"bytes,7,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + Pair *CurrencyPair `protobuf:"bytes,2,opt,name=pair,proto3" json:"pair,omitempty"` + Side string `protobuf:"bytes,3,opt,name=side,proto3" json:"side,omitempty"` + OrderType string `protobuf:"bytes,4,opt,name=order_type,json=orderType,proto3" json:"order_type,omitempty"` + Amount float64 `protobuf:"fixed64,5,opt,name=amount,proto3" json:"amount,omitempty"` + Price float64 `protobuf:"fixed64,6,opt,name=price,proto3" json:"price,omitempty"` + ClientId string `protobuf:"bytes,7,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *SubmitOrderRequest) Reset() { - *x = SubmitOrderRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[65] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *SubmitOrderRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*SubmitOrderRequest) ProtoMessage() {} - -func (x *SubmitOrderRequest) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[65] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use SubmitOrderRequest.ProtoReflect.Descriptor instead. +func (m *SubmitOrderRequest) Reset() { *m = SubmitOrderRequest{} } +func (m *SubmitOrderRequest) String() string { return proto.CompactTextString(m) } +func (*SubmitOrderRequest) ProtoMessage() {} func (*SubmitOrderRequest) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{65} + return fileDescriptor_77a6da22d6a3feb1, []int{61} } -func (x *SubmitOrderRequest) GetExchange() string { - if x != nil { - return x.Exchange +func (m *SubmitOrderRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_SubmitOrderRequest.Unmarshal(m, b) +} +func (m *SubmitOrderRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_SubmitOrderRequest.Marshal(b, m, deterministic) +} +func (m *SubmitOrderRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_SubmitOrderRequest.Merge(m, src) +} +func (m *SubmitOrderRequest) XXX_Size() int { + return xxx_messageInfo_SubmitOrderRequest.Size(m) +} +func (m *SubmitOrderRequest) XXX_DiscardUnknown() { + xxx_messageInfo_SubmitOrderRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_SubmitOrderRequest proto.InternalMessageInfo + +func (m *SubmitOrderRequest) GetExchange() string { + if m != nil { + return m.Exchange } return "" } -func (x *SubmitOrderRequest) GetPair() *CurrencyPair { - if x != nil { - return x.Pair +func (m *SubmitOrderRequest) GetPair() *CurrencyPair { + if m != nil { + return m.Pair } return nil } -func (x *SubmitOrderRequest) GetSide() string { - if x != nil { - return x.Side +func (m *SubmitOrderRequest) GetSide() string { + if m != nil { + return m.Side } return "" } -func (x *SubmitOrderRequest) GetOrderType() string { - if x != nil { - return x.OrderType +func (m *SubmitOrderRequest) GetOrderType() string { + if m != nil { + return m.OrderType } return "" } -func (x *SubmitOrderRequest) GetAmount() float64 { - if x != nil { - return x.Amount +func (m *SubmitOrderRequest) GetAmount() float64 { + if m != nil { + return m.Amount } return 0 } -func (x *SubmitOrderRequest) GetPrice() float64 { - if x != nil { - return x.Price +func (m *SubmitOrderRequest) GetPrice() float64 { + if m != nil { + return m.Price } return 0 } -func (x *SubmitOrderRequest) GetClientId() string { - if x != nil { - return x.ClientId +func (m *SubmitOrderRequest) GetClientId() string { + if m != nil { + return m.ClientId } return "" } type SubmitOrderResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - OrderPlaced bool `protobuf:"varint,1,opt,name=order_placed,json=orderPlaced,proto3" json:"order_placed,omitempty"` - OrderId string `protobuf:"bytes,2,opt,name=order_id,json=orderId,proto3" json:"order_id,omitempty"` + OrderPlaced bool `protobuf:"varint,1,opt,name=order_placed,json=orderPlaced,proto3" json:"order_placed,omitempty"` + OrderId string `protobuf:"bytes,2,opt,name=order_id,json=orderId,proto3" json:"order_id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *SubmitOrderResponse) Reset() { - *x = SubmitOrderResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[66] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *SubmitOrderResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*SubmitOrderResponse) ProtoMessage() {} - -func (x *SubmitOrderResponse) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[66] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use SubmitOrderResponse.ProtoReflect.Descriptor instead. +func (m *SubmitOrderResponse) Reset() { *m = SubmitOrderResponse{} } +func (m *SubmitOrderResponse) String() string { return proto.CompactTextString(m) } +func (*SubmitOrderResponse) ProtoMessage() {} func (*SubmitOrderResponse) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{66} + return fileDescriptor_77a6da22d6a3feb1, []int{62} } -func (x *SubmitOrderResponse) GetOrderPlaced() bool { - if x != nil { - return x.OrderPlaced +func (m *SubmitOrderResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_SubmitOrderResponse.Unmarshal(m, b) +} +func (m *SubmitOrderResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_SubmitOrderResponse.Marshal(b, m, deterministic) +} +func (m *SubmitOrderResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_SubmitOrderResponse.Merge(m, src) +} +func (m *SubmitOrderResponse) XXX_Size() int { + return xxx_messageInfo_SubmitOrderResponse.Size(m) +} +func (m *SubmitOrderResponse) XXX_DiscardUnknown() { + xxx_messageInfo_SubmitOrderResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_SubmitOrderResponse proto.InternalMessageInfo + +func (m *SubmitOrderResponse) GetOrderPlaced() bool { + if m != nil { + return m.OrderPlaced } return false } -func (x *SubmitOrderResponse) GetOrderId() string { - if x != nil { - return x.OrderId +func (m *SubmitOrderResponse) GetOrderId() string { + if m != nil { + return m.OrderId } return "" } type SimulateOrderRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` - Pair *CurrencyPair `protobuf:"bytes,2,opt,name=pair,proto3" json:"pair,omitempty"` - Amount float64 `protobuf:"fixed64,3,opt,name=amount,proto3" json:"amount,omitempty"` - Side string `protobuf:"bytes,4,opt,name=side,proto3" json:"side,omitempty"` + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + Pair *CurrencyPair `protobuf:"bytes,2,opt,name=pair,proto3" json:"pair,omitempty"` + Amount float64 `protobuf:"fixed64,3,opt,name=amount,proto3" json:"amount,omitempty"` + Side string `protobuf:"bytes,4,opt,name=side,proto3" json:"side,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *SimulateOrderRequest) Reset() { - *x = SimulateOrderRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[67] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *SimulateOrderRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*SimulateOrderRequest) ProtoMessage() {} - -func (x *SimulateOrderRequest) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[67] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use SimulateOrderRequest.ProtoReflect.Descriptor instead. +func (m *SimulateOrderRequest) Reset() { *m = SimulateOrderRequest{} } +func (m *SimulateOrderRequest) String() string { return proto.CompactTextString(m) } +func (*SimulateOrderRequest) ProtoMessage() {} func (*SimulateOrderRequest) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{67} + return fileDescriptor_77a6da22d6a3feb1, []int{63} } -func (x *SimulateOrderRequest) GetExchange() string { - if x != nil { - return x.Exchange +func (m *SimulateOrderRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_SimulateOrderRequest.Unmarshal(m, b) +} +func (m *SimulateOrderRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_SimulateOrderRequest.Marshal(b, m, deterministic) +} +func (m *SimulateOrderRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_SimulateOrderRequest.Merge(m, src) +} +func (m *SimulateOrderRequest) XXX_Size() int { + return xxx_messageInfo_SimulateOrderRequest.Size(m) +} +func (m *SimulateOrderRequest) XXX_DiscardUnknown() { + xxx_messageInfo_SimulateOrderRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_SimulateOrderRequest proto.InternalMessageInfo + +func (m *SimulateOrderRequest) GetExchange() string { + if m != nil { + return m.Exchange } return "" } -func (x *SimulateOrderRequest) GetPair() *CurrencyPair { - if x != nil { - return x.Pair +func (m *SimulateOrderRequest) GetPair() *CurrencyPair { + if m != nil { + return m.Pair } return nil } -func (x *SimulateOrderRequest) GetAmount() float64 { - if x != nil { - return x.Amount +func (m *SimulateOrderRequest) GetAmount() float64 { + if m != nil { + return m.Amount } return 0 } -func (x *SimulateOrderRequest) GetSide() string { - if x != nil { - return x.Side +func (m *SimulateOrderRequest) GetSide() string { + if m != nil { + return m.Side } return "" } type SimulateOrderResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Orders []*OrderbookItem `protobuf:"bytes,1,rep,name=orders,proto3" json:"orders,omitempty"` - Amount float64 `protobuf:"fixed64,2,opt,name=amount,proto3" json:"amount,omitempty"` - MinimumPrice float64 `protobuf:"fixed64,3,opt,name=minimum_price,json=minimumPrice,proto3" json:"minimum_price,omitempty"` - MaximumPrice float64 `protobuf:"fixed64,4,opt,name=maximum_price,json=maximumPrice,proto3" json:"maximum_price,omitempty"` - PercentageGainLoss float64 `protobuf:"fixed64,5,opt,name=percentage_gain_loss,json=percentageGainLoss,proto3" json:"percentage_gain_loss,omitempty"` - Status string `protobuf:"bytes,6,opt,name=status,proto3" json:"status,omitempty"` + Orders []*OrderbookItem `protobuf:"bytes,1,rep,name=orders,proto3" json:"orders,omitempty"` + Amount float64 `protobuf:"fixed64,2,opt,name=amount,proto3" json:"amount,omitempty"` + MinimumPrice float64 `protobuf:"fixed64,3,opt,name=minimum_price,json=minimumPrice,proto3" json:"minimum_price,omitempty"` + MaximumPrice float64 `protobuf:"fixed64,4,opt,name=maximum_price,json=maximumPrice,proto3" json:"maximum_price,omitempty"` + PercentageGainLoss float64 `protobuf:"fixed64,5,opt,name=percentage_gain_loss,json=percentageGainLoss,proto3" json:"percentage_gain_loss,omitempty"` + Status string `protobuf:"bytes,6,opt,name=status,proto3" json:"status,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *SimulateOrderResponse) Reset() { - *x = SimulateOrderResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[68] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *SimulateOrderResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*SimulateOrderResponse) ProtoMessage() {} - -func (x *SimulateOrderResponse) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[68] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use SimulateOrderResponse.ProtoReflect.Descriptor instead. +func (m *SimulateOrderResponse) Reset() { *m = SimulateOrderResponse{} } +func (m *SimulateOrderResponse) String() string { return proto.CompactTextString(m) } +func (*SimulateOrderResponse) ProtoMessage() {} func (*SimulateOrderResponse) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{68} + return fileDescriptor_77a6da22d6a3feb1, []int{64} } -func (x *SimulateOrderResponse) GetOrders() []*OrderbookItem { - if x != nil { - return x.Orders +func (m *SimulateOrderResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_SimulateOrderResponse.Unmarshal(m, b) +} +func (m *SimulateOrderResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_SimulateOrderResponse.Marshal(b, m, deterministic) +} +func (m *SimulateOrderResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_SimulateOrderResponse.Merge(m, src) +} +func (m *SimulateOrderResponse) XXX_Size() int { + return xxx_messageInfo_SimulateOrderResponse.Size(m) +} +func (m *SimulateOrderResponse) XXX_DiscardUnknown() { + xxx_messageInfo_SimulateOrderResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_SimulateOrderResponse proto.InternalMessageInfo + +func (m *SimulateOrderResponse) GetOrders() []*OrderbookItem { + if m != nil { + return m.Orders } return nil } -func (x *SimulateOrderResponse) GetAmount() float64 { - if x != nil { - return x.Amount +func (m *SimulateOrderResponse) GetAmount() float64 { + if m != nil { + return m.Amount } return 0 } -func (x *SimulateOrderResponse) GetMinimumPrice() float64 { - if x != nil { - return x.MinimumPrice +func (m *SimulateOrderResponse) GetMinimumPrice() float64 { + if m != nil { + return m.MinimumPrice } return 0 } -func (x *SimulateOrderResponse) GetMaximumPrice() float64 { - if x != nil { - return x.MaximumPrice +func (m *SimulateOrderResponse) GetMaximumPrice() float64 { + if m != nil { + return m.MaximumPrice } return 0 } -func (x *SimulateOrderResponse) GetPercentageGainLoss() float64 { - if x != nil { - return x.PercentageGainLoss +func (m *SimulateOrderResponse) GetPercentageGainLoss() float64 { + if m != nil { + return m.PercentageGainLoss } return 0 } -func (x *SimulateOrderResponse) GetStatus() string { - if x != nil { - return x.Status +func (m *SimulateOrderResponse) GetStatus() string { + if m != nil { + return m.Status } return "" } type WhaleBombRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` - Pair *CurrencyPair `protobuf:"bytes,2,opt,name=pair,proto3" json:"pair,omitempty"` - PriceTarget float64 `protobuf:"fixed64,3,opt,name=price_target,json=priceTarget,proto3" json:"price_target,omitempty"` - Side string `protobuf:"bytes,4,opt,name=side,proto3" json:"side,omitempty"` + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + Pair *CurrencyPair `protobuf:"bytes,2,opt,name=pair,proto3" json:"pair,omitempty"` + PriceTarget float64 `protobuf:"fixed64,3,opt,name=price_target,json=priceTarget,proto3" json:"price_target,omitempty"` + Side string `protobuf:"bytes,4,opt,name=side,proto3" json:"side,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *WhaleBombRequest) Reset() { - *x = WhaleBombRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[69] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *WhaleBombRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*WhaleBombRequest) ProtoMessage() {} - -func (x *WhaleBombRequest) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[69] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use WhaleBombRequest.ProtoReflect.Descriptor instead. +func (m *WhaleBombRequest) Reset() { *m = WhaleBombRequest{} } +func (m *WhaleBombRequest) String() string { return proto.CompactTextString(m) } +func (*WhaleBombRequest) ProtoMessage() {} func (*WhaleBombRequest) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{69} + return fileDescriptor_77a6da22d6a3feb1, []int{65} } -func (x *WhaleBombRequest) GetExchange() string { - if x != nil { - return x.Exchange +func (m *WhaleBombRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_WhaleBombRequest.Unmarshal(m, b) +} +func (m *WhaleBombRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_WhaleBombRequest.Marshal(b, m, deterministic) +} +func (m *WhaleBombRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_WhaleBombRequest.Merge(m, src) +} +func (m *WhaleBombRequest) XXX_Size() int { + return xxx_messageInfo_WhaleBombRequest.Size(m) +} +func (m *WhaleBombRequest) XXX_DiscardUnknown() { + xxx_messageInfo_WhaleBombRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_WhaleBombRequest proto.InternalMessageInfo + +func (m *WhaleBombRequest) GetExchange() string { + if m != nil { + return m.Exchange } return "" } -func (x *WhaleBombRequest) GetPair() *CurrencyPair { - if x != nil { - return x.Pair +func (m *WhaleBombRequest) GetPair() *CurrencyPair { + if m != nil { + return m.Pair } return nil } -func (x *WhaleBombRequest) GetPriceTarget() float64 { - if x != nil { - return x.PriceTarget +func (m *WhaleBombRequest) GetPriceTarget() float64 { + if m != nil { + return m.PriceTarget } return 0 } -func (x *WhaleBombRequest) GetSide() string { - if x != nil { - return x.Side +func (m *WhaleBombRequest) GetSide() string { + if m != nil { + return m.Side } return "" } type CancelOrderRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` - AccountId string `protobuf:"bytes,2,opt,name=account_id,json=accountId,proto3" json:"account_id,omitempty"` - OrderId string `protobuf:"bytes,3,opt,name=order_id,json=orderId,proto3" json:"order_id,omitempty"` - Pair *CurrencyPair `protobuf:"bytes,4,opt,name=pair,proto3" json:"pair,omitempty"` - AssetType string `protobuf:"bytes,5,opt,name=asset_type,json=assetType,proto3" json:"asset_type,omitempty"` - WalletAddress string `protobuf:"bytes,6,opt,name=wallet_address,json=walletAddress,proto3" json:"wallet_address,omitempty"` - Side string `protobuf:"bytes,7,opt,name=side,proto3" json:"side,omitempty"` + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + AccountId string `protobuf:"bytes,2,opt,name=account_id,json=accountId,proto3" json:"account_id,omitempty"` + OrderId string `protobuf:"bytes,3,opt,name=order_id,json=orderId,proto3" json:"order_id,omitempty"` + Pair *CurrencyPair `protobuf:"bytes,4,opt,name=pair,proto3" json:"pair,omitempty"` + AssetType string `protobuf:"bytes,5,opt,name=asset_type,json=assetType,proto3" json:"asset_type,omitempty"` + WalletAddress string `protobuf:"bytes,6,opt,name=wallet_address,json=walletAddress,proto3" json:"wallet_address,omitempty"` + Side string `protobuf:"bytes,7,opt,name=side,proto3" json:"side,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *CancelOrderRequest) Reset() { - *x = CancelOrderRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[70] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *CancelOrderRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*CancelOrderRequest) ProtoMessage() {} - -func (x *CancelOrderRequest) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[70] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use CancelOrderRequest.ProtoReflect.Descriptor instead. +func (m *CancelOrderRequest) Reset() { *m = CancelOrderRequest{} } +func (m *CancelOrderRequest) String() string { return proto.CompactTextString(m) } +func (*CancelOrderRequest) ProtoMessage() {} func (*CancelOrderRequest) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{70} + return fileDescriptor_77a6da22d6a3feb1, []int{66} } -func (x *CancelOrderRequest) GetExchange() string { - if x != nil { - return x.Exchange +func (m *CancelOrderRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_CancelOrderRequest.Unmarshal(m, b) +} +func (m *CancelOrderRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_CancelOrderRequest.Marshal(b, m, deterministic) +} +func (m *CancelOrderRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_CancelOrderRequest.Merge(m, src) +} +func (m *CancelOrderRequest) XXX_Size() int { + return xxx_messageInfo_CancelOrderRequest.Size(m) +} +func (m *CancelOrderRequest) XXX_DiscardUnknown() { + xxx_messageInfo_CancelOrderRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_CancelOrderRequest proto.InternalMessageInfo + +func (m *CancelOrderRequest) GetExchange() string { + if m != nil { + return m.Exchange } return "" } -func (x *CancelOrderRequest) GetAccountId() string { - if x != nil { - return x.AccountId +func (m *CancelOrderRequest) GetAccountId() string { + if m != nil { + return m.AccountId } return "" } -func (x *CancelOrderRequest) GetOrderId() string { - if x != nil { - return x.OrderId +func (m *CancelOrderRequest) GetOrderId() string { + if m != nil { + return m.OrderId } return "" } -func (x *CancelOrderRequest) GetPair() *CurrencyPair { - if x != nil { - return x.Pair +func (m *CancelOrderRequest) GetPair() *CurrencyPair { + if m != nil { + return m.Pair } return nil } -func (x *CancelOrderRequest) GetAssetType() string { - if x != nil { - return x.AssetType +func (m *CancelOrderRequest) GetAssetType() string { + if m != nil { + return m.AssetType } return "" } -func (x *CancelOrderRequest) GetWalletAddress() string { - if x != nil { - return x.WalletAddress +func (m *CancelOrderRequest) GetWalletAddress() string { + if m != nil { + return m.WalletAddress } return "" } -func (x *CancelOrderRequest) GetSide() string { - if x != nil { - return x.Side +func (m *CancelOrderRequest) GetSide() string { + if m != nil { + return m.Side } return "" } -type CancelOrderResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields -} - -func (x *CancelOrderResponse) Reset() { - *x = CancelOrderResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[71] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *CancelOrderResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*CancelOrderResponse) ProtoMessage() {} - -func (x *CancelOrderResponse) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[71] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use CancelOrderResponse.ProtoReflect.Descriptor instead. -func (*CancelOrderResponse) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{71} -} - type CancelAllOrdersRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *CancelAllOrdersRequest) Reset() { - *x = CancelAllOrdersRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[72] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *CancelAllOrdersRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*CancelAllOrdersRequest) ProtoMessage() {} - -func (x *CancelAllOrdersRequest) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[72] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use CancelAllOrdersRequest.ProtoReflect.Descriptor instead. +func (m *CancelAllOrdersRequest) Reset() { *m = CancelAllOrdersRequest{} } +func (m *CancelAllOrdersRequest) String() string { return proto.CompactTextString(m) } +func (*CancelAllOrdersRequest) ProtoMessage() {} func (*CancelAllOrdersRequest) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{72} + return fileDescriptor_77a6da22d6a3feb1, []int{67} } -func (x *CancelAllOrdersRequest) GetExchange() string { - if x != nil { - return x.Exchange +func (m *CancelAllOrdersRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_CancelAllOrdersRequest.Unmarshal(m, b) +} +func (m *CancelAllOrdersRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_CancelAllOrdersRequest.Marshal(b, m, deterministic) +} +func (m *CancelAllOrdersRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_CancelAllOrdersRequest.Merge(m, src) +} +func (m *CancelAllOrdersRequest) XXX_Size() int { + return xxx_messageInfo_CancelAllOrdersRequest.Size(m) +} +func (m *CancelAllOrdersRequest) XXX_DiscardUnknown() { + xxx_messageInfo_CancelAllOrdersRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_CancelAllOrdersRequest proto.InternalMessageInfo + +func (m *CancelAllOrdersRequest) GetExchange() string { + if m != nil { + return m.Exchange } return "" } type CancelAllOrdersResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Orders []*CancelAllOrdersResponse_Orders `protobuf:"bytes,1,rep,name=orders,proto3" json:"orders,omitempty"` + Orders []*CancelAllOrdersResponse_Orders `protobuf:"bytes,1,rep,name=orders,proto3" json:"orders,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *CancelAllOrdersResponse) Reset() { - *x = CancelAllOrdersResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[73] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *CancelAllOrdersResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*CancelAllOrdersResponse) ProtoMessage() {} - -func (x *CancelAllOrdersResponse) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[73] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use CancelAllOrdersResponse.ProtoReflect.Descriptor instead. +func (m *CancelAllOrdersResponse) Reset() { *m = CancelAllOrdersResponse{} } +func (m *CancelAllOrdersResponse) String() string { return proto.CompactTextString(m) } +func (*CancelAllOrdersResponse) ProtoMessage() {} func (*CancelAllOrdersResponse) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{73} + return fileDescriptor_77a6da22d6a3feb1, []int{68} } -func (x *CancelAllOrdersResponse) GetOrders() []*CancelAllOrdersResponse_Orders { - if x != nil { - return x.Orders +func (m *CancelAllOrdersResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_CancelAllOrdersResponse.Unmarshal(m, b) +} +func (m *CancelAllOrdersResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_CancelAllOrdersResponse.Marshal(b, m, deterministic) +} +func (m *CancelAllOrdersResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_CancelAllOrdersResponse.Merge(m, src) +} +func (m *CancelAllOrdersResponse) XXX_Size() int { + return xxx_messageInfo_CancelAllOrdersResponse.Size(m) +} +func (m *CancelAllOrdersResponse) XXX_DiscardUnknown() { + xxx_messageInfo_CancelAllOrdersResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_CancelAllOrdersResponse proto.InternalMessageInfo + +func (m *CancelAllOrdersResponse) GetOrders() []*CancelAllOrdersResponse_Orders { + if m != nil { + return m.Orders + } + return nil +} + +type CancelAllOrdersResponse_Orders struct { + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + OrderStatus map[string]string `protobuf:"bytes,2,rep,name=order_status,json=orderStatus,proto3" json:"order_status,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *CancelAllOrdersResponse_Orders) Reset() { *m = CancelAllOrdersResponse_Orders{} } +func (m *CancelAllOrdersResponse_Orders) String() string { return proto.CompactTextString(m) } +func (*CancelAllOrdersResponse_Orders) ProtoMessage() {} +func (*CancelAllOrdersResponse_Orders) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{68, 0} +} + +func (m *CancelAllOrdersResponse_Orders) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_CancelAllOrdersResponse_Orders.Unmarshal(m, b) +} +func (m *CancelAllOrdersResponse_Orders) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_CancelAllOrdersResponse_Orders.Marshal(b, m, deterministic) +} +func (m *CancelAllOrdersResponse_Orders) XXX_Merge(src proto.Message) { + xxx_messageInfo_CancelAllOrdersResponse_Orders.Merge(m, src) +} +func (m *CancelAllOrdersResponse_Orders) XXX_Size() int { + return xxx_messageInfo_CancelAllOrdersResponse_Orders.Size(m) +} +func (m *CancelAllOrdersResponse_Orders) XXX_DiscardUnknown() { + xxx_messageInfo_CancelAllOrdersResponse_Orders.DiscardUnknown(m) +} + +var xxx_messageInfo_CancelAllOrdersResponse_Orders proto.InternalMessageInfo + +func (m *CancelAllOrdersResponse_Orders) GetExchange() string { + if m != nil { + return m.Exchange + } + return "" +} + +func (m *CancelAllOrdersResponse_Orders) GetOrderStatus() map[string]string { + if m != nil { + return m.OrderStatus } return nil } type GetEventsRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GetEventsRequest) Reset() { - *x = GetEventsRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[74] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetEventsRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetEventsRequest) ProtoMessage() {} - -func (x *GetEventsRequest) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[74] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetEventsRequest.ProtoReflect.Descriptor instead. +func (m *GetEventsRequest) Reset() { *m = GetEventsRequest{} } +func (m *GetEventsRequest) String() string { return proto.CompactTextString(m) } +func (*GetEventsRequest) ProtoMessage() {} func (*GetEventsRequest) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{74} + return fileDescriptor_77a6da22d6a3feb1, []int{69} } +func (m *GetEventsRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetEventsRequest.Unmarshal(m, b) +} +func (m *GetEventsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetEventsRequest.Marshal(b, m, deterministic) +} +func (m *GetEventsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetEventsRequest.Merge(m, src) +} +func (m *GetEventsRequest) XXX_Size() int { + return xxx_messageInfo_GetEventsRequest.Size(m) +} +func (m *GetEventsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetEventsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetEventsRequest proto.InternalMessageInfo + type ConditionParams struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Condition string `protobuf:"bytes,1,opt,name=condition,proto3" json:"condition,omitempty"` - Price float64 `protobuf:"fixed64,2,opt,name=price,proto3" json:"price,omitempty"` - CheckBids bool `protobuf:"varint,3,opt,name=check_bids,json=checkBids,proto3" json:"check_bids,omitempty"` - CheckBidsAndAsks bool `protobuf:"varint,4,opt,name=check_bids_and_asks,json=checkBidsAndAsks,proto3" json:"check_bids_and_asks,omitempty"` - OrderbookAmount float64 `protobuf:"fixed64,5,opt,name=orderbook_amount,json=orderbookAmount,proto3" json:"orderbook_amount,omitempty"` + Condition string `protobuf:"bytes,1,opt,name=condition,proto3" json:"condition,omitempty"` + Price float64 `protobuf:"fixed64,2,opt,name=price,proto3" json:"price,omitempty"` + CheckBids bool `protobuf:"varint,3,opt,name=check_bids,json=checkBids,proto3" json:"check_bids,omitempty"` + CheckBidsAndAsks bool `protobuf:"varint,4,opt,name=check_bids_and_asks,json=checkBidsAndAsks,proto3" json:"check_bids_and_asks,omitempty"` + OrderbookAmount float64 `protobuf:"fixed64,5,opt,name=orderbook_amount,json=orderbookAmount,proto3" json:"orderbook_amount,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *ConditionParams) Reset() { - *x = ConditionParams{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[75] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *ConditionParams) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ConditionParams) ProtoMessage() {} - -func (x *ConditionParams) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[75] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ConditionParams.ProtoReflect.Descriptor instead. +func (m *ConditionParams) Reset() { *m = ConditionParams{} } +func (m *ConditionParams) String() string { return proto.CompactTextString(m) } +func (*ConditionParams) ProtoMessage() {} func (*ConditionParams) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{75} + return fileDescriptor_77a6da22d6a3feb1, []int{70} } -func (x *ConditionParams) GetCondition() string { - if x != nil { - return x.Condition +func (m *ConditionParams) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ConditionParams.Unmarshal(m, b) +} +func (m *ConditionParams) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ConditionParams.Marshal(b, m, deterministic) +} +func (m *ConditionParams) XXX_Merge(src proto.Message) { + xxx_messageInfo_ConditionParams.Merge(m, src) +} +func (m *ConditionParams) XXX_Size() int { + return xxx_messageInfo_ConditionParams.Size(m) +} +func (m *ConditionParams) XXX_DiscardUnknown() { + xxx_messageInfo_ConditionParams.DiscardUnknown(m) +} + +var xxx_messageInfo_ConditionParams proto.InternalMessageInfo + +func (m *ConditionParams) GetCondition() string { + if m != nil { + return m.Condition } return "" } -func (x *ConditionParams) GetPrice() float64 { - if x != nil { - return x.Price +func (m *ConditionParams) GetPrice() float64 { + if m != nil { + return m.Price } return 0 } -func (x *ConditionParams) GetCheckBids() bool { - if x != nil { - return x.CheckBids +func (m *ConditionParams) GetCheckBids() bool { + if m != nil { + return m.CheckBids } return false } -func (x *ConditionParams) GetCheckBidsAndAsks() bool { - if x != nil { - return x.CheckBidsAndAsks +func (m *ConditionParams) GetCheckBidsAndAsks() bool { + if m != nil { + return m.CheckBidsAndAsks } return false } -func (x *ConditionParams) GetOrderbookAmount() float64 { - if x != nil { - return x.OrderbookAmount +func (m *ConditionParams) GetOrderbookAmount() float64 { + if m != nil { + return m.OrderbookAmount } return 0 } type GetEventsResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` - Exchange string `protobuf:"bytes,2,opt,name=exchange,proto3" json:"exchange,omitempty"` - Item string `protobuf:"bytes,3,opt,name=item,proto3" json:"item,omitempty"` - ConditionParams *ConditionParams `protobuf:"bytes,4,opt,name=condition_params,json=conditionParams,proto3" json:"condition_params,omitempty"` - Pair *CurrencyPair `protobuf:"bytes,5,opt,name=pair,proto3" json:"pair,omitempty"` - Action string `protobuf:"bytes,6,opt,name=action,proto3" json:"action,omitempty"` - Executed bool `protobuf:"varint,7,opt,name=executed,proto3" json:"executed,omitempty"` + Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + Exchange string `protobuf:"bytes,2,opt,name=exchange,proto3" json:"exchange,omitempty"` + Item string `protobuf:"bytes,3,opt,name=item,proto3" json:"item,omitempty"` + ConditionParams *ConditionParams `protobuf:"bytes,4,opt,name=condition_params,json=conditionParams,proto3" json:"condition_params,omitempty"` + Pair *CurrencyPair `protobuf:"bytes,5,opt,name=pair,proto3" json:"pair,omitempty"` + Action string `protobuf:"bytes,6,opt,name=action,proto3" json:"action,omitempty"` + Executed bool `protobuf:"varint,7,opt,name=executed,proto3" json:"executed,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GetEventsResponse) Reset() { - *x = GetEventsResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[76] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetEventsResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetEventsResponse) ProtoMessage() {} - -func (x *GetEventsResponse) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[76] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetEventsResponse.ProtoReflect.Descriptor instead. +func (m *GetEventsResponse) Reset() { *m = GetEventsResponse{} } +func (m *GetEventsResponse) String() string { return proto.CompactTextString(m) } +func (*GetEventsResponse) ProtoMessage() {} func (*GetEventsResponse) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{76} + return fileDescriptor_77a6da22d6a3feb1, []int{71} } -func (x *GetEventsResponse) GetId() int64 { - if x != nil { - return x.Id +func (m *GetEventsResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetEventsResponse.Unmarshal(m, b) +} +func (m *GetEventsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetEventsResponse.Marshal(b, m, deterministic) +} +func (m *GetEventsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetEventsResponse.Merge(m, src) +} +func (m *GetEventsResponse) XXX_Size() int { + return xxx_messageInfo_GetEventsResponse.Size(m) +} +func (m *GetEventsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetEventsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetEventsResponse proto.InternalMessageInfo + +func (m *GetEventsResponse) GetId() int64 { + if m != nil { + return m.Id } return 0 } -func (x *GetEventsResponse) GetExchange() string { - if x != nil { - return x.Exchange +func (m *GetEventsResponse) GetExchange() string { + if m != nil { + return m.Exchange } return "" } -func (x *GetEventsResponse) GetItem() string { - if x != nil { - return x.Item +func (m *GetEventsResponse) GetItem() string { + if m != nil { + return m.Item } return "" } -func (x *GetEventsResponse) GetConditionParams() *ConditionParams { - if x != nil { - return x.ConditionParams +func (m *GetEventsResponse) GetConditionParams() *ConditionParams { + if m != nil { + return m.ConditionParams } return nil } -func (x *GetEventsResponse) GetPair() *CurrencyPair { - if x != nil { - return x.Pair +func (m *GetEventsResponse) GetPair() *CurrencyPair { + if m != nil { + return m.Pair } return nil } -func (x *GetEventsResponse) GetAction() string { - if x != nil { - return x.Action +func (m *GetEventsResponse) GetAction() string { + if m != nil { + return m.Action } return "" } -func (x *GetEventsResponse) GetExecuted() bool { - if x != nil { - return x.Executed +func (m *GetEventsResponse) GetExecuted() bool { + if m != nil { + return m.Executed } return false } type AddEventRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` - Item string `protobuf:"bytes,2,opt,name=item,proto3" json:"item,omitempty"` - ConditionParams *ConditionParams `protobuf:"bytes,3,opt,name=condition_params,json=conditionParams,proto3" json:"condition_params,omitempty"` - Pair *CurrencyPair `protobuf:"bytes,4,opt,name=pair,proto3" json:"pair,omitempty"` - AssetType string `protobuf:"bytes,5,opt,name=asset_type,json=assetType,proto3" json:"asset_type,omitempty"` - Action string `protobuf:"bytes,6,opt,name=action,proto3" json:"action,omitempty"` + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + Item string `protobuf:"bytes,2,opt,name=item,proto3" json:"item,omitempty"` + ConditionParams *ConditionParams `protobuf:"bytes,3,opt,name=condition_params,json=conditionParams,proto3" json:"condition_params,omitempty"` + Pair *CurrencyPair `protobuf:"bytes,4,opt,name=pair,proto3" json:"pair,omitempty"` + AssetType string `protobuf:"bytes,5,opt,name=asset_type,json=assetType,proto3" json:"asset_type,omitempty"` + Action string `protobuf:"bytes,6,opt,name=action,proto3" json:"action,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *AddEventRequest) Reset() { - *x = AddEventRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[77] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *AddEventRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*AddEventRequest) ProtoMessage() {} - -func (x *AddEventRequest) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[77] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use AddEventRequest.ProtoReflect.Descriptor instead. +func (m *AddEventRequest) Reset() { *m = AddEventRequest{} } +func (m *AddEventRequest) String() string { return proto.CompactTextString(m) } +func (*AddEventRequest) ProtoMessage() {} func (*AddEventRequest) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{77} + return fileDescriptor_77a6da22d6a3feb1, []int{72} } -func (x *AddEventRequest) GetExchange() string { - if x != nil { - return x.Exchange +func (m *AddEventRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_AddEventRequest.Unmarshal(m, b) +} +func (m *AddEventRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_AddEventRequest.Marshal(b, m, deterministic) +} +func (m *AddEventRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_AddEventRequest.Merge(m, src) +} +func (m *AddEventRequest) XXX_Size() int { + return xxx_messageInfo_AddEventRequest.Size(m) +} +func (m *AddEventRequest) XXX_DiscardUnknown() { + xxx_messageInfo_AddEventRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_AddEventRequest proto.InternalMessageInfo + +func (m *AddEventRequest) GetExchange() string { + if m != nil { + return m.Exchange } return "" } -func (x *AddEventRequest) GetItem() string { - if x != nil { - return x.Item +func (m *AddEventRequest) GetItem() string { + if m != nil { + return m.Item } return "" } -func (x *AddEventRequest) GetConditionParams() *ConditionParams { - if x != nil { - return x.ConditionParams +func (m *AddEventRequest) GetConditionParams() *ConditionParams { + if m != nil { + return m.ConditionParams } return nil } -func (x *AddEventRequest) GetPair() *CurrencyPair { - if x != nil { - return x.Pair +func (m *AddEventRequest) GetPair() *CurrencyPair { + if m != nil { + return m.Pair } return nil } -func (x *AddEventRequest) GetAssetType() string { - if x != nil { - return x.AssetType +func (m *AddEventRequest) GetAssetType() string { + if m != nil { + return m.AssetType } return "" } -func (x *AddEventRequest) GetAction() string { - if x != nil { - return x.Action +func (m *AddEventRequest) GetAction() string { + if m != nil { + return m.Action } return "" } type AddEventResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *AddEventResponse) Reset() { - *x = AddEventResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[78] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *AddEventResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*AddEventResponse) ProtoMessage() {} - -func (x *AddEventResponse) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[78] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use AddEventResponse.ProtoReflect.Descriptor instead. +func (m *AddEventResponse) Reset() { *m = AddEventResponse{} } +func (m *AddEventResponse) String() string { return proto.CompactTextString(m) } +func (*AddEventResponse) ProtoMessage() {} func (*AddEventResponse) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{78} + return fileDescriptor_77a6da22d6a3feb1, []int{73} } -func (x *AddEventResponse) GetId() int64 { - if x != nil { - return x.Id +func (m *AddEventResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_AddEventResponse.Unmarshal(m, b) +} +func (m *AddEventResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_AddEventResponse.Marshal(b, m, deterministic) +} +func (m *AddEventResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_AddEventResponse.Merge(m, src) +} +func (m *AddEventResponse) XXX_Size() int { + return xxx_messageInfo_AddEventResponse.Size(m) +} +func (m *AddEventResponse) XXX_DiscardUnknown() { + xxx_messageInfo_AddEventResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_AddEventResponse proto.InternalMessageInfo + +func (m *AddEventResponse) GetId() int64 { + if m != nil { + return m.Id } return 0 } type RemoveEventRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *RemoveEventRequest) Reset() { - *x = RemoveEventRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[79] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *RemoveEventRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*RemoveEventRequest) ProtoMessage() {} - -func (x *RemoveEventRequest) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[79] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use RemoveEventRequest.ProtoReflect.Descriptor instead. +func (m *RemoveEventRequest) Reset() { *m = RemoveEventRequest{} } +func (m *RemoveEventRequest) String() string { return proto.CompactTextString(m) } +func (*RemoveEventRequest) ProtoMessage() {} func (*RemoveEventRequest) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{79} + return fileDescriptor_77a6da22d6a3feb1, []int{74} } -func (x *RemoveEventRequest) GetId() int64 { - if x != nil { - return x.Id +func (m *RemoveEventRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_RemoveEventRequest.Unmarshal(m, b) +} +func (m *RemoveEventRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_RemoveEventRequest.Marshal(b, m, deterministic) +} +func (m *RemoveEventRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_RemoveEventRequest.Merge(m, src) +} +func (m *RemoveEventRequest) XXX_Size() int { + return xxx_messageInfo_RemoveEventRequest.Size(m) +} +func (m *RemoveEventRequest) XXX_DiscardUnknown() { + xxx_messageInfo_RemoveEventRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_RemoveEventRequest proto.InternalMessageInfo + +func (m *RemoveEventRequest) GetId() int64 { + if m != nil { + return m.Id } return 0 } -type RemoveEventResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields -} - -func (x *RemoveEventResponse) Reset() { - *x = RemoveEventResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[80] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *RemoveEventResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*RemoveEventResponse) ProtoMessage() {} - -func (x *RemoveEventResponse) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[80] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use RemoveEventResponse.ProtoReflect.Descriptor instead. -func (*RemoveEventResponse) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{80} -} - type GetCryptocurrencyDepositAddressesRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GetCryptocurrencyDepositAddressesRequest) Reset() { - *x = GetCryptocurrencyDepositAddressesRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[81] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } +func (m *GetCryptocurrencyDepositAddressesRequest) Reset() { + *m = GetCryptocurrencyDepositAddressesRequest{} } - -func (x *GetCryptocurrencyDepositAddressesRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetCryptocurrencyDepositAddressesRequest) ProtoMessage() {} - -func (x *GetCryptocurrencyDepositAddressesRequest) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[81] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetCryptocurrencyDepositAddressesRequest.ProtoReflect.Descriptor instead. +func (m *GetCryptocurrencyDepositAddressesRequest) String() string { return proto.CompactTextString(m) } +func (*GetCryptocurrencyDepositAddressesRequest) ProtoMessage() {} func (*GetCryptocurrencyDepositAddressesRequest) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{81} + return fileDescriptor_77a6da22d6a3feb1, []int{75} } -func (x *GetCryptocurrencyDepositAddressesRequest) GetExchange() string { - if x != nil { - return x.Exchange +func (m *GetCryptocurrencyDepositAddressesRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetCryptocurrencyDepositAddressesRequest.Unmarshal(m, b) +} +func (m *GetCryptocurrencyDepositAddressesRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetCryptocurrencyDepositAddressesRequest.Marshal(b, m, deterministic) +} +func (m *GetCryptocurrencyDepositAddressesRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetCryptocurrencyDepositAddressesRequest.Merge(m, src) +} +func (m *GetCryptocurrencyDepositAddressesRequest) XXX_Size() int { + return xxx_messageInfo_GetCryptocurrencyDepositAddressesRequest.Size(m) +} +func (m *GetCryptocurrencyDepositAddressesRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetCryptocurrencyDepositAddressesRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetCryptocurrencyDepositAddressesRequest proto.InternalMessageInfo + +func (m *GetCryptocurrencyDepositAddressesRequest) GetExchange() string { + if m != nil { + return m.Exchange } return "" } type GetCryptocurrencyDepositAddressesResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Addresses map[string]string `protobuf:"bytes,1,rep,name=addresses,proto3" json:"addresses,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + Addresses map[string]string `protobuf:"bytes,1,rep,name=addresses,proto3" json:"addresses,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GetCryptocurrencyDepositAddressesResponse) Reset() { - *x = GetCryptocurrencyDepositAddressesResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[82] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } +func (m *GetCryptocurrencyDepositAddressesResponse) Reset() { + *m = GetCryptocurrencyDepositAddressesResponse{} } - -func (x *GetCryptocurrencyDepositAddressesResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetCryptocurrencyDepositAddressesResponse) ProtoMessage() {} - -func (x *GetCryptocurrencyDepositAddressesResponse) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[82] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetCryptocurrencyDepositAddressesResponse.ProtoReflect.Descriptor instead. +func (m *GetCryptocurrencyDepositAddressesResponse) String() string { return proto.CompactTextString(m) } +func (*GetCryptocurrencyDepositAddressesResponse) ProtoMessage() {} func (*GetCryptocurrencyDepositAddressesResponse) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{82} + return fileDescriptor_77a6da22d6a3feb1, []int{76} } -func (x *GetCryptocurrencyDepositAddressesResponse) GetAddresses() map[string]string { - if x != nil { - return x.Addresses +func (m *GetCryptocurrencyDepositAddressesResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetCryptocurrencyDepositAddressesResponse.Unmarshal(m, b) +} +func (m *GetCryptocurrencyDepositAddressesResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetCryptocurrencyDepositAddressesResponse.Marshal(b, m, deterministic) +} +func (m *GetCryptocurrencyDepositAddressesResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetCryptocurrencyDepositAddressesResponse.Merge(m, src) +} +func (m *GetCryptocurrencyDepositAddressesResponse) XXX_Size() int { + return xxx_messageInfo_GetCryptocurrencyDepositAddressesResponse.Size(m) +} +func (m *GetCryptocurrencyDepositAddressesResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetCryptocurrencyDepositAddressesResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetCryptocurrencyDepositAddressesResponse proto.InternalMessageInfo + +func (m *GetCryptocurrencyDepositAddressesResponse) GetAddresses() map[string]string { + if m != nil { + return m.Addresses } return nil } type GetCryptocurrencyDepositAddressRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` - Cryptocurrency string `protobuf:"bytes,2,opt,name=cryptocurrency,proto3" json:"cryptocurrency,omitempty"` + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + Cryptocurrency string `protobuf:"bytes,2,opt,name=cryptocurrency,proto3" json:"cryptocurrency,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GetCryptocurrencyDepositAddressRequest) Reset() { - *x = GetCryptocurrencyDepositAddressRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[83] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } +func (m *GetCryptocurrencyDepositAddressRequest) Reset() { + *m = GetCryptocurrencyDepositAddressRequest{} } - -func (x *GetCryptocurrencyDepositAddressRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetCryptocurrencyDepositAddressRequest) ProtoMessage() {} - -func (x *GetCryptocurrencyDepositAddressRequest) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[83] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetCryptocurrencyDepositAddressRequest.ProtoReflect.Descriptor instead. +func (m *GetCryptocurrencyDepositAddressRequest) String() string { return proto.CompactTextString(m) } +func (*GetCryptocurrencyDepositAddressRequest) ProtoMessage() {} func (*GetCryptocurrencyDepositAddressRequest) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{83} + return fileDescriptor_77a6da22d6a3feb1, []int{77} } -func (x *GetCryptocurrencyDepositAddressRequest) GetExchange() string { - if x != nil { - return x.Exchange +func (m *GetCryptocurrencyDepositAddressRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetCryptocurrencyDepositAddressRequest.Unmarshal(m, b) +} +func (m *GetCryptocurrencyDepositAddressRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetCryptocurrencyDepositAddressRequest.Marshal(b, m, deterministic) +} +func (m *GetCryptocurrencyDepositAddressRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetCryptocurrencyDepositAddressRequest.Merge(m, src) +} +func (m *GetCryptocurrencyDepositAddressRequest) XXX_Size() int { + return xxx_messageInfo_GetCryptocurrencyDepositAddressRequest.Size(m) +} +func (m *GetCryptocurrencyDepositAddressRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetCryptocurrencyDepositAddressRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetCryptocurrencyDepositAddressRequest proto.InternalMessageInfo + +func (m *GetCryptocurrencyDepositAddressRequest) GetExchange() string { + if m != nil { + return m.Exchange } return "" } -func (x *GetCryptocurrencyDepositAddressRequest) GetCryptocurrency() string { - if x != nil { - return x.Cryptocurrency +func (m *GetCryptocurrencyDepositAddressRequest) GetCryptocurrency() string { + if m != nil { + return m.Cryptocurrency } return "" } type GetCryptocurrencyDepositAddressResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GetCryptocurrencyDepositAddressResponse) Reset() { - *x = GetCryptocurrencyDepositAddressResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[84] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } +func (m *GetCryptocurrencyDepositAddressResponse) Reset() { + *m = GetCryptocurrencyDepositAddressResponse{} } - -func (x *GetCryptocurrencyDepositAddressResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetCryptocurrencyDepositAddressResponse) ProtoMessage() {} - -func (x *GetCryptocurrencyDepositAddressResponse) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[84] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetCryptocurrencyDepositAddressResponse.ProtoReflect.Descriptor instead. +func (m *GetCryptocurrencyDepositAddressResponse) String() string { return proto.CompactTextString(m) } +func (*GetCryptocurrencyDepositAddressResponse) ProtoMessage() {} func (*GetCryptocurrencyDepositAddressResponse) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{84} + return fileDescriptor_77a6da22d6a3feb1, []int{78} } -func (x *GetCryptocurrencyDepositAddressResponse) GetAddress() string { - if x != nil { - return x.Address +func (m *GetCryptocurrencyDepositAddressResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetCryptocurrencyDepositAddressResponse.Unmarshal(m, b) +} +func (m *GetCryptocurrencyDepositAddressResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetCryptocurrencyDepositAddressResponse.Marshal(b, m, deterministic) +} +func (m *GetCryptocurrencyDepositAddressResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetCryptocurrencyDepositAddressResponse.Merge(m, src) +} +func (m *GetCryptocurrencyDepositAddressResponse) XXX_Size() int { + return xxx_messageInfo_GetCryptocurrencyDepositAddressResponse.Size(m) +} +func (m *GetCryptocurrencyDepositAddressResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetCryptocurrencyDepositAddressResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetCryptocurrencyDepositAddressResponse proto.InternalMessageInfo + +func (m *GetCryptocurrencyDepositAddressResponse) GetAddress() string { + if m != nil { + return m.Address } return "" } type WithdrawFiatRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` - Currency string `protobuf:"bytes,2,opt,name=currency,proto3" json:"currency,omitempty"` - Amount float64 `protobuf:"fixed64,3,opt,name=amount,proto3" json:"amount,omitempty"` - Description string `protobuf:"bytes,4,opt,name=description,proto3" json:"description,omitempty"` - BankAccountId string `protobuf:"bytes,5,opt,name=bank_account_id,json=bankAccountId,proto3" json:"bank_account_id,omitempty"` + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + Currency string `protobuf:"bytes,2,opt,name=currency,proto3" json:"currency,omitempty"` + Amount float64 `protobuf:"fixed64,3,opt,name=amount,proto3" json:"amount,omitempty"` + Description string `protobuf:"bytes,4,opt,name=description,proto3" json:"description,omitempty"` + BankAccountId string `protobuf:"bytes,5,opt,name=bank_account_id,json=bankAccountId,proto3" json:"bank_account_id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *WithdrawFiatRequest) Reset() { - *x = WithdrawFiatRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[85] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *WithdrawFiatRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*WithdrawFiatRequest) ProtoMessage() {} - -func (x *WithdrawFiatRequest) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[85] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use WithdrawFiatRequest.ProtoReflect.Descriptor instead. +func (m *WithdrawFiatRequest) Reset() { *m = WithdrawFiatRequest{} } +func (m *WithdrawFiatRequest) String() string { return proto.CompactTextString(m) } +func (*WithdrawFiatRequest) ProtoMessage() {} func (*WithdrawFiatRequest) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{85} + return fileDescriptor_77a6da22d6a3feb1, []int{79} } -func (x *WithdrawFiatRequest) GetExchange() string { - if x != nil { - return x.Exchange +func (m *WithdrawFiatRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_WithdrawFiatRequest.Unmarshal(m, b) +} +func (m *WithdrawFiatRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_WithdrawFiatRequest.Marshal(b, m, deterministic) +} +func (m *WithdrawFiatRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_WithdrawFiatRequest.Merge(m, src) +} +func (m *WithdrawFiatRequest) XXX_Size() int { + return xxx_messageInfo_WithdrawFiatRequest.Size(m) +} +func (m *WithdrawFiatRequest) XXX_DiscardUnknown() { + xxx_messageInfo_WithdrawFiatRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_WithdrawFiatRequest proto.InternalMessageInfo + +func (m *WithdrawFiatRequest) GetExchange() string { + if m != nil { + return m.Exchange } return "" } -func (x *WithdrawFiatRequest) GetCurrency() string { - if x != nil { - return x.Currency +func (m *WithdrawFiatRequest) GetCurrency() string { + if m != nil { + return m.Currency } return "" } -func (x *WithdrawFiatRequest) GetAmount() float64 { - if x != nil { - return x.Amount +func (m *WithdrawFiatRequest) GetAmount() float64 { + if m != nil { + return m.Amount } return 0 } -func (x *WithdrawFiatRequest) GetDescription() string { - if x != nil { - return x.Description +func (m *WithdrawFiatRequest) GetDescription() string { + if m != nil { + return m.Description } return "" } -func (x *WithdrawFiatRequest) GetBankAccountId() string { - if x != nil { - return x.BankAccountId +func (m *WithdrawFiatRequest) GetBankAccountId() string { + if m != nil { + return m.BankAccountId } return "" } type WithdrawCryptoRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` - Address string `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty"` - AddressTag string `protobuf:"bytes,3,opt,name=address_tag,json=addressTag,proto3" json:"address_tag,omitempty"` - Currency string `protobuf:"bytes,4,opt,name=currency,proto3" json:"currency,omitempty"` - Amount float64 `protobuf:"fixed64,5,opt,name=amount,proto3" json:"amount,omitempty"` - Fee float64 `protobuf:"fixed64,6,opt,name=fee,proto3" json:"fee,omitempty"` - Description string `protobuf:"bytes,7,opt,name=description,proto3" json:"description,omitempty"` + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + Address string `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty"` + AddressTag string `protobuf:"bytes,3,opt,name=address_tag,json=addressTag,proto3" json:"address_tag,omitempty"` + Currency string `protobuf:"bytes,4,opt,name=currency,proto3" json:"currency,omitempty"` + Amount float64 `protobuf:"fixed64,5,opt,name=amount,proto3" json:"amount,omitempty"` + Fee float64 `protobuf:"fixed64,6,opt,name=fee,proto3" json:"fee,omitempty"` + Description string `protobuf:"bytes,7,opt,name=description,proto3" json:"description,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *WithdrawCryptoRequest) Reset() { - *x = WithdrawCryptoRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[86] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *WithdrawCryptoRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*WithdrawCryptoRequest) ProtoMessage() {} - -func (x *WithdrawCryptoRequest) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[86] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use WithdrawCryptoRequest.ProtoReflect.Descriptor instead. +func (m *WithdrawCryptoRequest) Reset() { *m = WithdrawCryptoRequest{} } +func (m *WithdrawCryptoRequest) String() string { return proto.CompactTextString(m) } +func (*WithdrawCryptoRequest) ProtoMessage() {} func (*WithdrawCryptoRequest) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{86} + return fileDescriptor_77a6da22d6a3feb1, []int{80} } -func (x *WithdrawCryptoRequest) GetExchange() string { - if x != nil { - return x.Exchange +func (m *WithdrawCryptoRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_WithdrawCryptoRequest.Unmarshal(m, b) +} +func (m *WithdrawCryptoRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_WithdrawCryptoRequest.Marshal(b, m, deterministic) +} +func (m *WithdrawCryptoRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_WithdrawCryptoRequest.Merge(m, src) +} +func (m *WithdrawCryptoRequest) XXX_Size() int { + return xxx_messageInfo_WithdrawCryptoRequest.Size(m) +} +func (m *WithdrawCryptoRequest) XXX_DiscardUnknown() { + xxx_messageInfo_WithdrawCryptoRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_WithdrawCryptoRequest proto.InternalMessageInfo + +func (m *WithdrawCryptoRequest) GetExchange() string { + if m != nil { + return m.Exchange } return "" } -func (x *WithdrawCryptoRequest) GetAddress() string { - if x != nil { - return x.Address +func (m *WithdrawCryptoRequest) GetAddress() string { + if m != nil { + return m.Address } return "" } -func (x *WithdrawCryptoRequest) GetAddressTag() string { - if x != nil { - return x.AddressTag +func (m *WithdrawCryptoRequest) GetAddressTag() string { + if m != nil { + return m.AddressTag } return "" } -func (x *WithdrawCryptoRequest) GetCurrency() string { - if x != nil { - return x.Currency +func (m *WithdrawCryptoRequest) GetCurrency() string { + if m != nil { + return m.Currency } return "" } -func (x *WithdrawCryptoRequest) GetAmount() float64 { - if x != nil { - return x.Amount +func (m *WithdrawCryptoRequest) GetAmount() float64 { + if m != nil { + return m.Amount } return 0 } -func (x *WithdrawCryptoRequest) GetFee() float64 { - if x != nil { - return x.Fee +func (m *WithdrawCryptoRequest) GetFee() float64 { + if m != nil { + return m.Fee } return 0 } -func (x *WithdrawCryptoRequest) GetDescription() string { - if x != nil { - return x.Description +func (m *WithdrawCryptoRequest) GetDescription() string { + if m != nil { + return m.Description } return "" } type WithdrawResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - Status string `protobuf:"bytes,2,opt,name=status,proto3" json:"status,omitempty"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Status string `protobuf:"bytes,2,opt,name=status,proto3" json:"status,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *WithdrawResponse) Reset() { - *x = WithdrawResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[87] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *WithdrawResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*WithdrawResponse) ProtoMessage() {} - -func (x *WithdrawResponse) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[87] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use WithdrawResponse.ProtoReflect.Descriptor instead. +func (m *WithdrawResponse) Reset() { *m = WithdrawResponse{} } +func (m *WithdrawResponse) String() string { return proto.CompactTextString(m) } +func (*WithdrawResponse) ProtoMessage() {} func (*WithdrawResponse) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{87} + return fileDescriptor_77a6da22d6a3feb1, []int{81} } -func (x *WithdrawResponse) GetId() string { - if x != nil { - return x.Id +func (m *WithdrawResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_WithdrawResponse.Unmarshal(m, b) +} +func (m *WithdrawResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_WithdrawResponse.Marshal(b, m, deterministic) +} +func (m *WithdrawResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_WithdrawResponse.Merge(m, src) +} +func (m *WithdrawResponse) XXX_Size() int { + return xxx_messageInfo_WithdrawResponse.Size(m) +} +func (m *WithdrawResponse) XXX_DiscardUnknown() { + xxx_messageInfo_WithdrawResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_WithdrawResponse proto.InternalMessageInfo + +func (m *WithdrawResponse) GetId() string { + if m != nil { + return m.Id } return "" } -func (x *WithdrawResponse) GetStatus() string { - if x != nil { - return x.Status +func (m *WithdrawResponse) GetStatus() string { + if m != nil { + return m.Status } return "" } type WithdrawalEventByIDRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *WithdrawalEventByIDRequest) Reset() { - *x = WithdrawalEventByIDRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[88] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *WithdrawalEventByIDRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*WithdrawalEventByIDRequest) ProtoMessage() {} - -func (x *WithdrawalEventByIDRequest) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[88] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use WithdrawalEventByIDRequest.ProtoReflect.Descriptor instead. +func (m *WithdrawalEventByIDRequest) Reset() { *m = WithdrawalEventByIDRequest{} } +func (m *WithdrawalEventByIDRequest) String() string { return proto.CompactTextString(m) } +func (*WithdrawalEventByIDRequest) ProtoMessage() {} func (*WithdrawalEventByIDRequest) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{88} + return fileDescriptor_77a6da22d6a3feb1, []int{82} } -func (x *WithdrawalEventByIDRequest) GetId() string { - if x != nil { - return x.Id +func (m *WithdrawalEventByIDRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_WithdrawalEventByIDRequest.Unmarshal(m, b) +} +func (m *WithdrawalEventByIDRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_WithdrawalEventByIDRequest.Marshal(b, m, deterministic) +} +func (m *WithdrawalEventByIDRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_WithdrawalEventByIDRequest.Merge(m, src) +} +func (m *WithdrawalEventByIDRequest) XXX_Size() int { + return xxx_messageInfo_WithdrawalEventByIDRequest.Size(m) +} +func (m *WithdrawalEventByIDRequest) XXX_DiscardUnknown() { + xxx_messageInfo_WithdrawalEventByIDRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_WithdrawalEventByIDRequest proto.InternalMessageInfo + +func (m *WithdrawalEventByIDRequest) GetId() string { + if m != nil { + return m.Id } return "" } type WithdrawalEventByIDResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Event *WithdrawalEventResponse `protobuf:"bytes,2,opt,name=event,proto3" json:"event,omitempty"` + Event *WithdrawalEventResponse `protobuf:"bytes,2,opt,name=event,proto3" json:"event,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *WithdrawalEventByIDResponse) Reset() { - *x = WithdrawalEventByIDResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[89] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *WithdrawalEventByIDResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*WithdrawalEventByIDResponse) ProtoMessage() {} - -func (x *WithdrawalEventByIDResponse) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[89] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use WithdrawalEventByIDResponse.ProtoReflect.Descriptor instead. +func (m *WithdrawalEventByIDResponse) Reset() { *m = WithdrawalEventByIDResponse{} } +func (m *WithdrawalEventByIDResponse) String() string { return proto.CompactTextString(m) } +func (*WithdrawalEventByIDResponse) ProtoMessage() {} func (*WithdrawalEventByIDResponse) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{89} + return fileDescriptor_77a6da22d6a3feb1, []int{83} } -func (x *WithdrawalEventByIDResponse) GetEvent() *WithdrawalEventResponse { - if x != nil { - return x.Event +func (m *WithdrawalEventByIDResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_WithdrawalEventByIDResponse.Unmarshal(m, b) +} +func (m *WithdrawalEventByIDResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_WithdrawalEventByIDResponse.Marshal(b, m, deterministic) +} +func (m *WithdrawalEventByIDResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_WithdrawalEventByIDResponse.Merge(m, src) +} +func (m *WithdrawalEventByIDResponse) XXX_Size() int { + return xxx_messageInfo_WithdrawalEventByIDResponse.Size(m) +} +func (m *WithdrawalEventByIDResponse) XXX_DiscardUnknown() { + xxx_messageInfo_WithdrawalEventByIDResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_WithdrawalEventByIDResponse proto.InternalMessageInfo + +func (m *WithdrawalEventByIDResponse) GetEvent() *WithdrawalEventResponse { + if m != nil { + return m.Event } return nil } type WithdrawalEventsByExchangeRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` - Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` - Limit int32 `protobuf:"varint,3,opt,name=limit,proto3" json:"limit,omitempty"` + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` + Limit int32 `protobuf:"varint,3,opt,name=limit,proto3" json:"limit,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *WithdrawalEventsByExchangeRequest) Reset() { - *x = WithdrawalEventsByExchangeRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[90] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *WithdrawalEventsByExchangeRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*WithdrawalEventsByExchangeRequest) ProtoMessage() {} - -func (x *WithdrawalEventsByExchangeRequest) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[90] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use WithdrawalEventsByExchangeRequest.ProtoReflect.Descriptor instead. +func (m *WithdrawalEventsByExchangeRequest) Reset() { *m = WithdrawalEventsByExchangeRequest{} } +func (m *WithdrawalEventsByExchangeRequest) String() string { return proto.CompactTextString(m) } +func (*WithdrawalEventsByExchangeRequest) ProtoMessage() {} func (*WithdrawalEventsByExchangeRequest) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{90} + return fileDescriptor_77a6da22d6a3feb1, []int{84} } -func (x *WithdrawalEventsByExchangeRequest) GetExchange() string { - if x != nil { - return x.Exchange +func (m *WithdrawalEventsByExchangeRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_WithdrawalEventsByExchangeRequest.Unmarshal(m, b) +} +func (m *WithdrawalEventsByExchangeRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_WithdrawalEventsByExchangeRequest.Marshal(b, m, deterministic) +} +func (m *WithdrawalEventsByExchangeRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_WithdrawalEventsByExchangeRequest.Merge(m, src) +} +func (m *WithdrawalEventsByExchangeRequest) XXX_Size() int { + return xxx_messageInfo_WithdrawalEventsByExchangeRequest.Size(m) +} +func (m *WithdrawalEventsByExchangeRequest) XXX_DiscardUnknown() { + xxx_messageInfo_WithdrawalEventsByExchangeRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_WithdrawalEventsByExchangeRequest proto.InternalMessageInfo + +func (m *WithdrawalEventsByExchangeRequest) GetExchange() string { + if m != nil { + return m.Exchange } return "" } -func (x *WithdrawalEventsByExchangeRequest) GetId() string { - if x != nil { - return x.Id +func (m *WithdrawalEventsByExchangeRequest) GetId() string { + if m != nil { + return m.Id } return "" } -func (x *WithdrawalEventsByExchangeRequest) GetLimit() int32 { - if x != nil { - return x.Limit +func (m *WithdrawalEventsByExchangeRequest) GetLimit() int32 { + if m != nil { + return m.Limit } return 0 } type WithdrawalEventsByDateRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` - Start string `protobuf:"bytes,2,opt,name=start,proto3" json:"start,omitempty"` - End string `protobuf:"bytes,3,opt,name=end,proto3" json:"end,omitempty"` - Limit int32 `protobuf:"varint,4,opt,name=limit,proto3" json:"limit,omitempty"` + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + Start string `protobuf:"bytes,2,opt,name=start,proto3" json:"start,omitempty"` + End string `protobuf:"bytes,3,opt,name=end,proto3" json:"end,omitempty"` + Limit int32 `protobuf:"varint,4,opt,name=limit,proto3" json:"limit,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *WithdrawalEventsByDateRequest) Reset() { - *x = WithdrawalEventsByDateRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[91] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *WithdrawalEventsByDateRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*WithdrawalEventsByDateRequest) ProtoMessage() {} - -func (x *WithdrawalEventsByDateRequest) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[91] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use WithdrawalEventsByDateRequest.ProtoReflect.Descriptor instead. +func (m *WithdrawalEventsByDateRequest) Reset() { *m = WithdrawalEventsByDateRequest{} } +func (m *WithdrawalEventsByDateRequest) String() string { return proto.CompactTextString(m) } +func (*WithdrawalEventsByDateRequest) ProtoMessage() {} func (*WithdrawalEventsByDateRequest) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{91} + return fileDescriptor_77a6da22d6a3feb1, []int{85} } -func (x *WithdrawalEventsByDateRequest) GetExchange() string { - if x != nil { - return x.Exchange +func (m *WithdrawalEventsByDateRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_WithdrawalEventsByDateRequest.Unmarshal(m, b) +} +func (m *WithdrawalEventsByDateRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_WithdrawalEventsByDateRequest.Marshal(b, m, deterministic) +} +func (m *WithdrawalEventsByDateRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_WithdrawalEventsByDateRequest.Merge(m, src) +} +func (m *WithdrawalEventsByDateRequest) XXX_Size() int { + return xxx_messageInfo_WithdrawalEventsByDateRequest.Size(m) +} +func (m *WithdrawalEventsByDateRequest) XXX_DiscardUnknown() { + xxx_messageInfo_WithdrawalEventsByDateRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_WithdrawalEventsByDateRequest proto.InternalMessageInfo + +func (m *WithdrawalEventsByDateRequest) GetExchange() string { + if m != nil { + return m.Exchange } return "" } -func (x *WithdrawalEventsByDateRequest) GetStart() string { - if x != nil { - return x.Start +func (m *WithdrawalEventsByDateRequest) GetStart() string { + if m != nil { + return m.Start } return "" } -func (x *WithdrawalEventsByDateRequest) GetEnd() string { - if x != nil { - return x.End +func (m *WithdrawalEventsByDateRequest) GetEnd() string { + if m != nil { + return m.End } return "" } -func (x *WithdrawalEventsByDateRequest) GetLimit() int32 { - if x != nil { - return x.Limit +func (m *WithdrawalEventsByDateRequest) GetLimit() int32 { + if m != nil { + return m.Limit } return 0 } type WithdrawalEventsByExchangeResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Event []*WithdrawalEventResponse `protobuf:"bytes,2,rep,name=event,proto3" json:"event,omitempty"` + Event []*WithdrawalEventResponse `protobuf:"bytes,2,rep,name=event,proto3" json:"event,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *WithdrawalEventsByExchangeResponse) Reset() { - *x = WithdrawalEventsByExchangeResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[92] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *WithdrawalEventsByExchangeResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*WithdrawalEventsByExchangeResponse) ProtoMessage() {} - -func (x *WithdrawalEventsByExchangeResponse) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[92] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use WithdrawalEventsByExchangeResponse.ProtoReflect.Descriptor instead. +func (m *WithdrawalEventsByExchangeResponse) Reset() { *m = WithdrawalEventsByExchangeResponse{} } +func (m *WithdrawalEventsByExchangeResponse) String() string { return proto.CompactTextString(m) } +func (*WithdrawalEventsByExchangeResponse) ProtoMessage() {} func (*WithdrawalEventsByExchangeResponse) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{92} + return fileDescriptor_77a6da22d6a3feb1, []int{86} } -func (x *WithdrawalEventsByExchangeResponse) GetEvent() []*WithdrawalEventResponse { - if x != nil { - return x.Event +func (m *WithdrawalEventsByExchangeResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_WithdrawalEventsByExchangeResponse.Unmarshal(m, b) +} +func (m *WithdrawalEventsByExchangeResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_WithdrawalEventsByExchangeResponse.Marshal(b, m, deterministic) +} +func (m *WithdrawalEventsByExchangeResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_WithdrawalEventsByExchangeResponse.Merge(m, src) +} +func (m *WithdrawalEventsByExchangeResponse) XXX_Size() int { + return xxx_messageInfo_WithdrawalEventsByExchangeResponse.Size(m) +} +func (m *WithdrawalEventsByExchangeResponse) XXX_DiscardUnknown() { + xxx_messageInfo_WithdrawalEventsByExchangeResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_WithdrawalEventsByExchangeResponse proto.InternalMessageInfo + +func (m *WithdrawalEventsByExchangeResponse) GetEvent() []*WithdrawalEventResponse { + if m != nil { + return m.Event } return nil } type WithdrawalEventResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` - Exchange *WithdrawlExchangeEvent `protobuf:"bytes,3,opt,name=exchange,proto3" json:"exchange,omitempty"` - Request *WithdrawalRequestEvent `protobuf:"bytes,4,opt,name=request,proto3" json:"request,omitempty"` - CreatedAt *timestamp.Timestamp `protobuf:"bytes,5,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` - UpdatedAt *timestamp.Timestamp `protobuf:"bytes,6,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` + Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` + Exchange *WithdrawlExchangeEvent `protobuf:"bytes,3,opt,name=exchange,proto3" json:"exchange,omitempty"` + Request *WithdrawalRequestEvent `protobuf:"bytes,4,opt,name=request,proto3" json:"request,omitempty"` + CreatedAt *timestamp.Timestamp `protobuf:"bytes,5,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + UpdatedAt *timestamp.Timestamp `protobuf:"bytes,6,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *WithdrawalEventResponse) Reset() { - *x = WithdrawalEventResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[93] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *WithdrawalEventResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*WithdrawalEventResponse) ProtoMessage() {} - -func (x *WithdrawalEventResponse) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[93] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use WithdrawalEventResponse.ProtoReflect.Descriptor instead. +func (m *WithdrawalEventResponse) Reset() { *m = WithdrawalEventResponse{} } +func (m *WithdrawalEventResponse) String() string { return proto.CompactTextString(m) } +func (*WithdrawalEventResponse) ProtoMessage() {} func (*WithdrawalEventResponse) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{93} + return fileDescriptor_77a6da22d6a3feb1, []int{87} } -func (x *WithdrawalEventResponse) GetId() string { - if x != nil { - return x.Id +func (m *WithdrawalEventResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_WithdrawalEventResponse.Unmarshal(m, b) +} +func (m *WithdrawalEventResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_WithdrawalEventResponse.Marshal(b, m, deterministic) +} +func (m *WithdrawalEventResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_WithdrawalEventResponse.Merge(m, src) +} +func (m *WithdrawalEventResponse) XXX_Size() int { + return xxx_messageInfo_WithdrawalEventResponse.Size(m) +} +func (m *WithdrawalEventResponse) XXX_DiscardUnknown() { + xxx_messageInfo_WithdrawalEventResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_WithdrawalEventResponse proto.InternalMessageInfo + +func (m *WithdrawalEventResponse) GetId() string { + if m != nil { + return m.Id } return "" } -func (x *WithdrawalEventResponse) GetExchange() *WithdrawlExchangeEvent { - if x != nil { - return x.Exchange +func (m *WithdrawalEventResponse) GetExchange() *WithdrawlExchangeEvent { + if m != nil { + return m.Exchange } return nil } -func (x *WithdrawalEventResponse) GetRequest() *WithdrawalRequestEvent { - if x != nil { - return x.Request +func (m *WithdrawalEventResponse) GetRequest() *WithdrawalRequestEvent { + if m != nil { + return m.Request } return nil } -func (x *WithdrawalEventResponse) GetCreatedAt() *timestamp.Timestamp { - if x != nil { - return x.CreatedAt +func (m *WithdrawalEventResponse) GetCreatedAt() *timestamp.Timestamp { + if m != nil { + return m.CreatedAt } return nil } -func (x *WithdrawalEventResponse) GetUpdatedAt() *timestamp.Timestamp { - if x != nil { - return x.UpdatedAt +func (m *WithdrawalEventResponse) GetUpdatedAt() *timestamp.Timestamp { + if m != nil { + return m.UpdatedAt } return nil } type WithdrawlExchangeEvent struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` - Status string `protobuf:"bytes,3,opt,name=status,proto3" json:"status,omitempty"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` + Status string `protobuf:"bytes,3,opt,name=status,proto3" json:"status,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *WithdrawlExchangeEvent) Reset() { - *x = WithdrawlExchangeEvent{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[94] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *WithdrawlExchangeEvent) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*WithdrawlExchangeEvent) ProtoMessage() {} - -func (x *WithdrawlExchangeEvent) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[94] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use WithdrawlExchangeEvent.ProtoReflect.Descriptor instead. +func (m *WithdrawlExchangeEvent) Reset() { *m = WithdrawlExchangeEvent{} } +func (m *WithdrawlExchangeEvent) String() string { return proto.CompactTextString(m) } +func (*WithdrawlExchangeEvent) ProtoMessage() {} func (*WithdrawlExchangeEvent) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{94} + return fileDescriptor_77a6da22d6a3feb1, []int{88} } -func (x *WithdrawlExchangeEvent) GetName() string { - if x != nil { - return x.Name +func (m *WithdrawlExchangeEvent) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_WithdrawlExchangeEvent.Unmarshal(m, b) +} +func (m *WithdrawlExchangeEvent) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_WithdrawlExchangeEvent.Marshal(b, m, deterministic) +} +func (m *WithdrawlExchangeEvent) XXX_Merge(src proto.Message) { + xxx_messageInfo_WithdrawlExchangeEvent.Merge(m, src) +} +func (m *WithdrawlExchangeEvent) XXX_Size() int { + return xxx_messageInfo_WithdrawlExchangeEvent.Size(m) +} +func (m *WithdrawlExchangeEvent) XXX_DiscardUnknown() { + xxx_messageInfo_WithdrawlExchangeEvent.DiscardUnknown(m) +} + +var xxx_messageInfo_WithdrawlExchangeEvent proto.InternalMessageInfo + +func (m *WithdrawlExchangeEvent) GetName() string { + if m != nil { + return m.Name } return "" } -func (x *WithdrawlExchangeEvent) GetId() string { - if x != nil { - return x.Id +func (m *WithdrawlExchangeEvent) GetId() string { + if m != nil { + return m.Id } return "" } -func (x *WithdrawlExchangeEvent) GetStatus() string { - if x != nil { - return x.Status +func (m *WithdrawlExchangeEvent) GetStatus() string { + if m != nil { + return m.Status } return "" } type WithdrawalRequestEvent struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Currency string `protobuf:"bytes,2,opt,name=currency,proto3" json:"currency,omitempty"` - Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"` - Amount float64 `protobuf:"fixed64,4,opt,name=amount,proto3" json:"amount,omitempty"` - Type int32 `protobuf:"varint,5,opt,name=type,proto3" json:"type,omitempty"` - Fiat *FiatWithdrawalEvent `protobuf:"bytes,6,opt,name=fiat,proto3" json:"fiat,omitempty"` - Crypto *CryptoWithdrawalEvent `protobuf:"bytes,7,opt,name=crypto,proto3" json:"crypto,omitempty"` + Currency string `protobuf:"bytes,2,opt,name=currency,proto3" json:"currency,omitempty"` + Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"` + Amount float64 `protobuf:"fixed64,4,opt,name=amount,proto3" json:"amount,omitempty"` + Type int32 `protobuf:"varint,5,opt,name=type,proto3" json:"type,omitempty"` + Fiat *FiatWithdrawalEvent `protobuf:"bytes,6,opt,name=fiat,proto3" json:"fiat,omitempty"` + Crypto *CryptoWithdrawalEvent `protobuf:"bytes,7,opt,name=crypto,proto3" json:"crypto,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *WithdrawalRequestEvent) Reset() { - *x = WithdrawalRequestEvent{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[95] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *WithdrawalRequestEvent) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*WithdrawalRequestEvent) ProtoMessage() {} - -func (x *WithdrawalRequestEvent) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[95] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use WithdrawalRequestEvent.ProtoReflect.Descriptor instead. +func (m *WithdrawalRequestEvent) Reset() { *m = WithdrawalRequestEvent{} } +func (m *WithdrawalRequestEvent) String() string { return proto.CompactTextString(m) } +func (*WithdrawalRequestEvent) ProtoMessage() {} func (*WithdrawalRequestEvent) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{95} + return fileDescriptor_77a6da22d6a3feb1, []int{89} } -func (x *WithdrawalRequestEvent) GetCurrency() string { - if x != nil { - return x.Currency +func (m *WithdrawalRequestEvent) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_WithdrawalRequestEvent.Unmarshal(m, b) +} +func (m *WithdrawalRequestEvent) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_WithdrawalRequestEvent.Marshal(b, m, deterministic) +} +func (m *WithdrawalRequestEvent) XXX_Merge(src proto.Message) { + xxx_messageInfo_WithdrawalRequestEvent.Merge(m, src) +} +func (m *WithdrawalRequestEvent) XXX_Size() int { + return xxx_messageInfo_WithdrawalRequestEvent.Size(m) +} +func (m *WithdrawalRequestEvent) XXX_DiscardUnknown() { + xxx_messageInfo_WithdrawalRequestEvent.DiscardUnknown(m) +} + +var xxx_messageInfo_WithdrawalRequestEvent proto.InternalMessageInfo + +func (m *WithdrawalRequestEvent) GetCurrency() string { + if m != nil { + return m.Currency } return "" } -func (x *WithdrawalRequestEvent) GetDescription() string { - if x != nil { - return x.Description +func (m *WithdrawalRequestEvent) GetDescription() string { + if m != nil { + return m.Description } return "" } -func (x *WithdrawalRequestEvent) GetAmount() float64 { - if x != nil { - return x.Amount +func (m *WithdrawalRequestEvent) GetAmount() float64 { + if m != nil { + return m.Amount } return 0 } -func (x *WithdrawalRequestEvent) GetType() int32 { - if x != nil { - return x.Type +func (m *WithdrawalRequestEvent) GetType() int32 { + if m != nil { + return m.Type } return 0 } -func (x *WithdrawalRequestEvent) GetFiat() *FiatWithdrawalEvent { - if x != nil { - return x.Fiat +func (m *WithdrawalRequestEvent) GetFiat() *FiatWithdrawalEvent { + if m != nil { + return m.Fiat } return nil } -func (x *WithdrawalRequestEvent) GetCrypto() *CryptoWithdrawalEvent { - if x != nil { - return x.Crypto +func (m *WithdrawalRequestEvent) GetCrypto() *CryptoWithdrawalEvent { + if m != nil { + return m.Crypto } return nil } type FiatWithdrawalEvent struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - BankName string `protobuf:"bytes,1,opt,name=bank_name,json=bankName,proto3" json:"bank_name,omitempty"` - AccountName string `protobuf:"bytes,2,opt,name=account_name,json=accountName,proto3" json:"account_name,omitempty"` - AccountNumber string `protobuf:"bytes,3,opt,name=account_number,json=accountNumber,proto3" json:"account_number,omitempty"` - Bsb string `protobuf:"bytes,4,opt,name=bsb,proto3" json:"bsb,omitempty"` - Swift string `protobuf:"bytes,5,opt,name=swift,proto3" json:"swift,omitempty"` - Iban string `protobuf:"bytes,6,opt,name=iban,proto3" json:"iban,omitempty"` + BankName string `protobuf:"bytes,1,opt,name=bank_name,json=bankName,proto3" json:"bank_name,omitempty"` + AccountName string `protobuf:"bytes,2,opt,name=account_name,json=accountName,proto3" json:"account_name,omitempty"` + AccountNumber string `protobuf:"bytes,3,opt,name=account_number,json=accountNumber,proto3" json:"account_number,omitempty"` + Bsb string `protobuf:"bytes,4,opt,name=bsb,proto3" json:"bsb,omitempty"` + Swift string `protobuf:"bytes,5,opt,name=swift,proto3" json:"swift,omitempty"` + Iban string `protobuf:"bytes,6,opt,name=iban,proto3" json:"iban,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *FiatWithdrawalEvent) Reset() { - *x = FiatWithdrawalEvent{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[96] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *FiatWithdrawalEvent) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*FiatWithdrawalEvent) ProtoMessage() {} - -func (x *FiatWithdrawalEvent) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[96] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use FiatWithdrawalEvent.ProtoReflect.Descriptor instead. +func (m *FiatWithdrawalEvent) Reset() { *m = FiatWithdrawalEvent{} } +func (m *FiatWithdrawalEvent) String() string { return proto.CompactTextString(m) } +func (*FiatWithdrawalEvent) ProtoMessage() {} func (*FiatWithdrawalEvent) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{96} + return fileDescriptor_77a6da22d6a3feb1, []int{90} } -func (x *FiatWithdrawalEvent) GetBankName() string { - if x != nil { - return x.BankName +func (m *FiatWithdrawalEvent) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_FiatWithdrawalEvent.Unmarshal(m, b) +} +func (m *FiatWithdrawalEvent) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_FiatWithdrawalEvent.Marshal(b, m, deterministic) +} +func (m *FiatWithdrawalEvent) XXX_Merge(src proto.Message) { + xxx_messageInfo_FiatWithdrawalEvent.Merge(m, src) +} +func (m *FiatWithdrawalEvent) XXX_Size() int { + return xxx_messageInfo_FiatWithdrawalEvent.Size(m) +} +func (m *FiatWithdrawalEvent) XXX_DiscardUnknown() { + xxx_messageInfo_FiatWithdrawalEvent.DiscardUnknown(m) +} + +var xxx_messageInfo_FiatWithdrawalEvent proto.InternalMessageInfo + +func (m *FiatWithdrawalEvent) GetBankName() string { + if m != nil { + return m.BankName } return "" } -func (x *FiatWithdrawalEvent) GetAccountName() string { - if x != nil { - return x.AccountName +func (m *FiatWithdrawalEvent) GetAccountName() string { + if m != nil { + return m.AccountName } return "" } -func (x *FiatWithdrawalEvent) GetAccountNumber() string { - if x != nil { - return x.AccountNumber +func (m *FiatWithdrawalEvent) GetAccountNumber() string { + if m != nil { + return m.AccountNumber } return "" } -func (x *FiatWithdrawalEvent) GetBsb() string { - if x != nil { - return x.Bsb +func (m *FiatWithdrawalEvent) GetBsb() string { + if m != nil { + return m.Bsb } return "" } -func (x *FiatWithdrawalEvent) GetSwift() string { - if x != nil { - return x.Swift +func (m *FiatWithdrawalEvent) GetSwift() string { + if m != nil { + return m.Swift } return "" } -func (x *FiatWithdrawalEvent) GetIban() string { - if x != nil { - return x.Iban +func (m *FiatWithdrawalEvent) GetIban() string { + if m != nil { + return m.Iban } return "" } type CryptoWithdrawalEvent struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` - AddressTag string `protobuf:"bytes,2,opt,name=address_tag,json=addressTag,proto3" json:"address_tag,omitempty"` - Fee float64 `protobuf:"fixed64,3,opt,name=fee,proto3" json:"fee,omitempty"` + Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + AddressTag string `protobuf:"bytes,2,opt,name=address_tag,json=addressTag,proto3" json:"address_tag,omitempty"` + Fee float64 `protobuf:"fixed64,3,opt,name=fee,proto3" json:"fee,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *CryptoWithdrawalEvent) Reset() { - *x = CryptoWithdrawalEvent{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[97] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *CryptoWithdrawalEvent) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*CryptoWithdrawalEvent) ProtoMessage() {} - -func (x *CryptoWithdrawalEvent) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[97] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use CryptoWithdrawalEvent.ProtoReflect.Descriptor instead. +func (m *CryptoWithdrawalEvent) Reset() { *m = CryptoWithdrawalEvent{} } +func (m *CryptoWithdrawalEvent) String() string { return proto.CompactTextString(m) } +func (*CryptoWithdrawalEvent) ProtoMessage() {} func (*CryptoWithdrawalEvent) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{97} + return fileDescriptor_77a6da22d6a3feb1, []int{91} } -func (x *CryptoWithdrawalEvent) GetAddress() string { - if x != nil { - return x.Address +func (m *CryptoWithdrawalEvent) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_CryptoWithdrawalEvent.Unmarshal(m, b) +} +func (m *CryptoWithdrawalEvent) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_CryptoWithdrawalEvent.Marshal(b, m, deterministic) +} +func (m *CryptoWithdrawalEvent) XXX_Merge(src proto.Message) { + xxx_messageInfo_CryptoWithdrawalEvent.Merge(m, src) +} +func (m *CryptoWithdrawalEvent) XXX_Size() int { + return xxx_messageInfo_CryptoWithdrawalEvent.Size(m) +} +func (m *CryptoWithdrawalEvent) XXX_DiscardUnknown() { + xxx_messageInfo_CryptoWithdrawalEvent.DiscardUnknown(m) +} + +var xxx_messageInfo_CryptoWithdrawalEvent proto.InternalMessageInfo + +func (m *CryptoWithdrawalEvent) GetAddress() string { + if m != nil { + return m.Address } return "" } -func (x *CryptoWithdrawalEvent) GetAddressTag() string { - if x != nil { - return x.AddressTag +func (m *CryptoWithdrawalEvent) GetAddressTag() string { + if m != nil { + return m.AddressTag } return "" } -func (x *CryptoWithdrawalEvent) GetFee() float64 { - if x != nil { - return x.Fee +func (m *CryptoWithdrawalEvent) GetFee() float64 { + if m != nil { + return m.Fee } return 0 } type GetLoggerDetailsRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Logger string `protobuf:"bytes,1,opt,name=logger,proto3" json:"logger,omitempty"` + Logger string `protobuf:"bytes,1,opt,name=logger,proto3" json:"logger,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GetLoggerDetailsRequest) Reset() { - *x = GetLoggerDetailsRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[98] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetLoggerDetailsRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetLoggerDetailsRequest) ProtoMessage() {} - -func (x *GetLoggerDetailsRequest) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[98] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetLoggerDetailsRequest.ProtoReflect.Descriptor instead. +func (m *GetLoggerDetailsRequest) Reset() { *m = GetLoggerDetailsRequest{} } +func (m *GetLoggerDetailsRequest) String() string { return proto.CompactTextString(m) } +func (*GetLoggerDetailsRequest) ProtoMessage() {} func (*GetLoggerDetailsRequest) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{98} + return fileDescriptor_77a6da22d6a3feb1, []int{92} } -func (x *GetLoggerDetailsRequest) GetLogger() string { - if x != nil { - return x.Logger +func (m *GetLoggerDetailsRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetLoggerDetailsRequest.Unmarshal(m, b) +} +func (m *GetLoggerDetailsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetLoggerDetailsRequest.Marshal(b, m, deterministic) +} +func (m *GetLoggerDetailsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetLoggerDetailsRequest.Merge(m, src) +} +func (m *GetLoggerDetailsRequest) XXX_Size() int { + return xxx_messageInfo_GetLoggerDetailsRequest.Size(m) +} +func (m *GetLoggerDetailsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetLoggerDetailsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetLoggerDetailsRequest proto.InternalMessageInfo + +func (m *GetLoggerDetailsRequest) GetLogger() string { + if m != nil { + return m.Logger } return "" } type GetLoggerDetailsResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Info bool `protobuf:"varint,1,opt,name=info,proto3" json:"info,omitempty"` - Debug bool `protobuf:"varint,2,opt,name=debug,proto3" json:"debug,omitempty"` - Warn bool `protobuf:"varint,3,opt,name=warn,proto3" json:"warn,omitempty"` - Error bool `protobuf:"varint,4,opt,name=error,proto3" json:"error,omitempty"` + Info bool `protobuf:"varint,1,opt,name=info,proto3" json:"info,omitempty"` + Debug bool `protobuf:"varint,2,opt,name=debug,proto3" json:"debug,omitempty"` + Warn bool `protobuf:"varint,3,opt,name=warn,proto3" json:"warn,omitempty"` + Error bool `protobuf:"varint,4,opt,name=error,proto3" json:"error,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GetLoggerDetailsResponse) Reset() { - *x = GetLoggerDetailsResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[99] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetLoggerDetailsResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetLoggerDetailsResponse) ProtoMessage() {} - -func (x *GetLoggerDetailsResponse) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[99] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetLoggerDetailsResponse.ProtoReflect.Descriptor instead. +func (m *GetLoggerDetailsResponse) Reset() { *m = GetLoggerDetailsResponse{} } +func (m *GetLoggerDetailsResponse) String() string { return proto.CompactTextString(m) } +func (*GetLoggerDetailsResponse) ProtoMessage() {} func (*GetLoggerDetailsResponse) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{99} + return fileDescriptor_77a6da22d6a3feb1, []int{93} } -func (x *GetLoggerDetailsResponse) GetInfo() bool { - if x != nil { - return x.Info +func (m *GetLoggerDetailsResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetLoggerDetailsResponse.Unmarshal(m, b) +} +func (m *GetLoggerDetailsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetLoggerDetailsResponse.Marshal(b, m, deterministic) +} +func (m *GetLoggerDetailsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetLoggerDetailsResponse.Merge(m, src) +} +func (m *GetLoggerDetailsResponse) XXX_Size() int { + return xxx_messageInfo_GetLoggerDetailsResponse.Size(m) +} +func (m *GetLoggerDetailsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetLoggerDetailsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetLoggerDetailsResponse proto.InternalMessageInfo + +func (m *GetLoggerDetailsResponse) GetInfo() bool { + if m != nil { + return m.Info } return false } -func (x *GetLoggerDetailsResponse) GetDebug() bool { - if x != nil { - return x.Debug +func (m *GetLoggerDetailsResponse) GetDebug() bool { + if m != nil { + return m.Debug } return false } -func (x *GetLoggerDetailsResponse) GetWarn() bool { - if x != nil { - return x.Warn +func (m *GetLoggerDetailsResponse) GetWarn() bool { + if m != nil { + return m.Warn } return false } -func (x *GetLoggerDetailsResponse) GetError() bool { - if x != nil { - return x.Error +func (m *GetLoggerDetailsResponse) GetError() bool { + if m != nil { + return m.Error } return false } type SetLoggerDetailsRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Logger string `protobuf:"bytes,1,opt,name=logger,proto3" json:"logger,omitempty"` - Level string `protobuf:"bytes,2,opt,name=level,proto3" json:"level,omitempty"` + Logger string `protobuf:"bytes,1,opt,name=logger,proto3" json:"logger,omitempty"` + Level string `protobuf:"bytes,2,opt,name=level,proto3" json:"level,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *SetLoggerDetailsRequest) Reset() { - *x = SetLoggerDetailsRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[100] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *SetLoggerDetailsRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*SetLoggerDetailsRequest) ProtoMessage() {} - -func (x *SetLoggerDetailsRequest) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[100] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use SetLoggerDetailsRequest.ProtoReflect.Descriptor instead. +func (m *SetLoggerDetailsRequest) Reset() { *m = SetLoggerDetailsRequest{} } +func (m *SetLoggerDetailsRequest) String() string { return proto.CompactTextString(m) } +func (*SetLoggerDetailsRequest) ProtoMessage() {} func (*SetLoggerDetailsRequest) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{100} + return fileDescriptor_77a6da22d6a3feb1, []int{94} } -func (x *SetLoggerDetailsRequest) GetLogger() string { - if x != nil { - return x.Logger +func (m *SetLoggerDetailsRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_SetLoggerDetailsRequest.Unmarshal(m, b) +} +func (m *SetLoggerDetailsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_SetLoggerDetailsRequest.Marshal(b, m, deterministic) +} +func (m *SetLoggerDetailsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_SetLoggerDetailsRequest.Merge(m, src) +} +func (m *SetLoggerDetailsRequest) XXX_Size() int { + return xxx_messageInfo_SetLoggerDetailsRequest.Size(m) +} +func (m *SetLoggerDetailsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_SetLoggerDetailsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_SetLoggerDetailsRequest proto.InternalMessageInfo + +func (m *SetLoggerDetailsRequest) GetLogger() string { + if m != nil { + return m.Logger } return "" } -func (x *SetLoggerDetailsRequest) GetLevel() string { - if x != nil { - return x.Level +func (m *SetLoggerDetailsRequest) GetLevel() string { + if m != nil { + return m.Level } return "" } type GetExchangePairsRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` - Asset string `protobuf:"bytes,2,opt,name=asset,proto3" json:"asset,omitempty"` + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + Asset string `protobuf:"bytes,2,opt,name=asset,proto3" json:"asset,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GetExchangePairsRequest) Reset() { - *x = GetExchangePairsRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[101] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetExchangePairsRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetExchangePairsRequest) ProtoMessage() {} - -func (x *GetExchangePairsRequest) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[101] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetExchangePairsRequest.ProtoReflect.Descriptor instead. +func (m *GetExchangePairsRequest) Reset() { *m = GetExchangePairsRequest{} } +func (m *GetExchangePairsRequest) String() string { return proto.CompactTextString(m) } +func (*GetExchangePairsRequest) ProtoMessage() {} func (*GetExchangePairsRequest) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{101} + return fileDescriptor_77a6da22d6a3feb1, []int{95} } -func (x *GetExchangePairsRequest) GetExchange() string { - if x != nil { - return x.Exchange +func (m *GetExchangePairsRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetExchangePairsRequest.Unmarshal(m, b) +} +func (m *GetExchangePairsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetExchangePairsRequest.Marshal(b, m, deterministic) +} +func (m *GetExchangePairsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetExchangePairsRequest.Merge(m, src) +} +func (m *GetExchangePairsRequest) XXX_Size() int { + return xxx_messageInfo_GetExchangePairsRequest.Size(m) +} +func (m *GetExchangePairsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetExchangePairsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetExchangePairsRequest proto.InternalMessageInfo + +func (m *GetExchangePairsRequest) GetExchange() string { + if m != nil { + return m.Exchange } return "" } -func (x *GetExchangePairsRequest) GetAsset() string { - if x != nil { - return x.Asset +func (m *GetExchangePairsRequest) GetAsset() string { + if m != nil { + return m.Asset } return "" } type GetExchangePairsResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - SupportedAssets map[string]*PairsSupported `protobuf:"bytes,1,rep,name=supported_assets,json=supportedAssets,proto3" json:"supported_assets,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + SupportedAssets map[string]*PairsSupported `protobuf:"bytes,1,rep,name=supported_assets,json=supportedAssets,proto3" json:"supported_assets,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GetExchangePairsResponse) Reset() { - *x = GetExchangePairsResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[102] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetExchangePairsResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetExchangePairsResponse) ProtoMessage() {} - -func (x *GetExchangePairsResponse) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[102] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetExchangePairsResponse.ProtoReflect.Descriptor instead. +func (m *GetExchangePairsResponse) Reset() { *m = GetExchangePairsResponse{} } +func (m *GetExchangePairsResponse) String() string { return proto.CompactTextString(m) } +func (*GetExchangePairsResponse) ProtoMessage() {} func (*GetExchangePairsResponse) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{102} + return fileDescriptor_77a6da22d6a3feb1, []int{96} } -func (x *GetExchangePairsResponse) GetSupportedAssets() map[string]*PairsSupported { - if x != nil { - return x.SupportedAssets +func (m *GetExchangePairsResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetExchangePairsResponse.Unmarshal(m, b) +} +func (m *GetExchangePairsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetExchangePairsResponse.Marshal(b, m, deterministic) +} +func (m *GetExchangePairsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetExchangePairsResponse.Merge(m, src) +} +func (m *GetExchangePairsResponse) XXX_Size() int { + return xxx_messageInfo_GetExchangePairsResponse.Size(m) +} +func (m *GetExchangePairsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetExchangePairsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetExchangePairsResponse proto.InternalMessageInfo + +func (m *GetExchangePairsResponse) GetSupportedAssets() map[string]*PairsSupported { + if m != nil { + return m.SupportedAssets } return nil } -type ExchangePairRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` - AssetType string `protobuf:"bytes,2,opt,name=asset_type,json=assetType,proto3" json:"asset_type,omitempty"` - Pair *CurrencyPair `protobuf:"bytes,3,opt,name=pair,proto3" json:"pair,omitempty"` +type SetExchangePairRequest struct { + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + AssetType string `protobuf:"bytes,2,opt,name=asset_type,json=assetType,proto3" json:"asset_type,omitempty"` + Pairs []*CurrencyPair `protobuf:"bytes,3,rep,name=pairs,proto3" json:"pairs,omitempty"` + Enable bool `protobuf:"varint,4,opt,name=enable,proto3" json:"enable,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *ExchangePairRequest) Reset() { - *x = ExchangePairRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[103] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } +func (m *SetExchangePairRequest) Reset() { *m = SetExchangePairRequest{} } +func (m *SetExchangePairRequest) String() string { return proto.CompactTextString(m) } +func (*SetExchangePairRequest) ProtoMessage() {} +func (*SetExchangePairRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{97} } -func (x *ExchangePairRequest) String() string { - return protoimpl.X.MessageStringOf(x) +func (m *SetExchangePairRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_SetExchangePairRequest.Unmarshal(m, b) +} +func (m *SetExchangePairRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_SetExchangePairRequest.Marshal(b, m, deterministic) +} +func (m *SetExchangePairRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_SetExchangePairRequest.Merge(m, src) +} +func (m *SetExchangePairRequest) XXX_Size() int { + return xxx_messageInfo_SetExchangePairRequest.Size(m) +} +func (m *SetExchangePairRequest) XXX_DiscardUnknown() { + xxx_messageInfo_SetExchangePairRequest.DiscardUnknown(m) } -func (*ExchangePairRequest) ProtoMessage() {} +var xxx_messageInfo_SetExchangePairRequest proto.InternalMessageInfo -func (x *ExchangePairRequest) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[103] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ExchangePairRequest.ProtoReflect.Descriptor instead. -func (*ExchangePairRequest) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{103} -} - -func (x *ExchangePairRequest) GetExchange() string { - if x != nil { - return x.Exchange +func (m *SetExchangePairRequest) GetExchange() string { + if m != nil { + return m.Exchange } return "" } -func (x *ExchangePairRequest) GetAssetType() string { - if x != nil { - return x.AssetType +func (m *SetExchangePairRequest) GetAssetType() string { + if m != nil { + return m.AssetType } return "" } -func (x *ExchangePairRequest) GetPair() *CurrencyPair { - if x != nil { - return x.Pair +func (m *SetExchangePairRequest) GetPairs() []*CurrencyPair { + if m != nil { + return m.Pairs } return nil } +func (m *SetExchangePairRequest) GetEnable() bool { + if m != nil { + return m.Enable + } + return false +} + type GetOrderbookStreamRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` - Pair *CurrencyPair `protobuf:"bytes,2,opt,name=pair,proto3" json:"pair,omitempty"` - AssetType string `protobuf:"bytes,3,opt,name=asset_type,json=assetType,proto3" json:"asset_type,omitempty"` + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + Pair *CurrencyPair `protobuf:"bytes,2,opt,name=pair,proto3" json:"pair,omitempty"` + AssetType string `protobuf:"bytes,3,opt,name=asset_type,json=assetType,proto3" json:"asset_type,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GetOrderbookStreamRequest) Reset() { - *x = GetOrderbookStreamRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[104] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetOrderbookStreamRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetOrderbookStreamRequest) ProtoMessage() {} - -func (x *GetOrderbookStreamRequest) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[104] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetOrderbookStreamRequest.ProtoReflect.Descriptor instead. +func (m *GetOrderbookStreamRequest) Reset() { *m = GetOrderbookStreamRequest{} } +func (m *GetOrderbookStreamRequest) String() string { return proto.CompactTextString(m) } +func (*GetOrderbookStreamRequest) ProtoMessage() {} func (*GetOrderbookStreamRequest) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{104} + return fileDescriptor_77a6da22d6a3feb1, []int{98} } -func (x *GetOrderbookStreamRequest) GetExchange() string { - if x != nil { - return x.Exchange +func (m *GetOrderbookStreamRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetOrderbookStreamRequest.Unmarshal(m, b) +} +func (m *GetOrderbookStreamRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetOrderbookStreamRequest.Marshal(b, m, deterministic) +} +func (m *GetOrderbookStreamRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetOrderbookStreamRequest.Merge(m, src) +} +func (m *GetOrderbookStreamRequest) XXX_Size() int { + return xxx_messageInfo_GetOrderbookStreamRequest.Size(m) +} +func (m *GetOrderbookStreamRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetOrderbookStreamRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetOrderbookStreamRequest proto.InternalMessageInfo + +func (m *GetOrderbookStreamRequest) GetExchange() string { + if m != nil { + return m.Exchange } return "" } -func (x *GetOrderbookStreamRequest) GetPair() *CurrencyPair { - if x != nil { - return x.Pair +func (m *GetOrderbookStreamRequest) GetPair() *CurrencyPair { + if m != nil { + return m.Pair } return nil } -func (x *GetOrderbookStreamRequest) GetAssetType() string { - if x != nil { - return x.AssetType +func (m *GetOrderbookStreamRequest) GetAssetType() string { + if m != nil { + return m.AssetType } return "" } type GetExchangeOrderbookStreamRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GetExchangeOrderbookStreamRequest) Reset() { - *x = GetExchangeOrderbookStreamRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[105] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetExchangeOrderbookStreamRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetExchangeOrderbookStreamRequest) ProtoMessage() {} - -func (x *GetExchangeOrderbookStreamRequest) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[105] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetExchangeOrderbookStreamRequest.ProtoReflect.Descriptor instead. +func (m *GetExchangeOrderbookStreamRequest) Reset() { *m = GetExchangeOrderbookStreamRequest{} } +func (m *GetExchangeOrderbookStreamRequest) String() string { return proto.CompactTextString(m) } +func (*GetExchangeOrderbookStreamRequest) ProtoMessage() {} func (*GetExchangeOrderbookStreamRequest) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{105} + return fileDescriptor_77a6da22d6a3feb1, []int{99} } -func (x *GetExchangeOrderbookStreamRequest) GetExchange() string { - if x != nil { - return x.Exchange +func (m *GetExchangeOrderbookStreamRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetExchangeOrderbookStreamRequest.Unmarshal(m, b) +} +func (m *GetExchangeOrderbookStreamRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetExchangeOrderbookStreamRequest.Marshal(b, m, deterministic) +} +func (m *GetExchangeOrderbookStreamRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetExchangeOrderbookStreamRequest.Merge(m, src) +} +func (m *GetExchangeOrderbookStreamRequest) XXX_Size() int { + return xxx_messageInfo_GetExchangeOrderbookStreamRequest.Size(m) +} +func (m *GetExchangeOrderbookStreamRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetExchangeOrderbookStreamRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetExchangeOrderbookStreamRequest proto.InternalMessageInfo + +func (m *GetExchangeOrderbookStreamRequest) GetExchange() string { + if m != nil { + return m.Exchange } return "" } type GetTickerStreamRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` - Pair *CurrencyPair `protobuf:"bytes,2,opt,name=pair,proto3" json:"pair,omitempty"` - AssetType string `protobuf:"bytes,3,opt,name=asset_type,json=assetType,proto3" json:"asset_type,omitempty"` + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + Pair *CurrencyPair `protobuf:"bytes,2,opt,name=pair,proto3" json:"pair,omitempty"` + AssetType string `protobuf:"bytes,3,opt,name=asset_type,json=assetType,proto3" json:"asset_type,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GetTickerStreamRequest) Reset() { - *x = GetTickerStreamRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[106] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetTickerStreamRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetTickerStreamRequest) ProtoMessage() {} - -func (x *GetTickerStreamRequest) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[106] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetTickerStreamRequest.ProtoReflect.Descriptor instead. +func (m *GetTickerStreamRequest) Reset() { *m = GetTickerStreamRequest{} } +func (m *GetTickerStreamRequest) String() string { return proto.CompactTextString(m) } +func (*GetTickerStreamRequest) ProtoMessage() {} func (*GetTickerStreamRequest) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{106} + return fileDescriptor_77a6da22d6a3feb1, []int{100} } -func (x *GetTickerStreamRequest) GetExchange() string { - if x != nil { - return x.Exchange +func (m *GetTickerStreamRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetTickerStreamRequest.Unmarshal(m, b) +} +func (m *GetTickerStreamRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetTickerStreamRequest.Marshal(b, m, deterministic) +} +func (m *GetTickerStreamRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetTickerStreamRequest.Merge(m, src) +} +func (m *GetTickerStreamRequest) XXX_Size() int { + return xxx_messageInfo_GetTickerStreamRequest.Size(m) +} +func (m *GetTickerStreamRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetTickerStreamRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetTickerStreamRequest proto.InternalMessageInfo + +func (m *GetTickerStreamRequest) GetExchange() string { + if m != nil { + return m.Exchange } return "" } -func (x *GetTickerStreamRequest) GetPair() *CurrencyPair { - if x != nil { - return x.Pair +func (m *GetTickerStreamRequest) GetPair() *CurrencyPair { + if m != nil { + return m.Pair } return nil } -func (x *GetTickerStreamRequest) GetAssetType() string { - if x != nil { - return x.AssetType +func (m *GetTickerStreamRequest) GetAssetType() string { + if m != nil { + return m.AssetType } return "" } type GetExchangeTickerStreamRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GetExchangeTickerStreamRequest) Reset() { - *x = GetExchangeTickerStreamRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[107] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetExchangeTickerStreamRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetExchangeTickerStreamRequest) ProtoMessage() {} - -func (x *GetExchangeTickerStreamRequest) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[107] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetExchangeTickerStreamRequest.ProtoReflect.Descriptor instead. +func (m *GetExchangeTickerStreamRequest) Reset() { *m = GetExchangeTickerStreamRequest{} } +func (m *GetExchangeTickerStreamRequest) String() string { return proto.CompactTextString(m) } +func (*GetExchangeTickerStreamRequest) ProtoMessage() {} func (*GetExchangeTickerStreamRequest) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{107} + return fileDescriptor_77a6da22d6a3feb1, []int{101} } -func (x *GetExchangeTickerStreamRequest) GetExchange() string { - if x != nil { - return x.Exchange +func (m *GetExchangeTickerStreamRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetExchangeTickerStreamRequest.Unmarshal(m, b) +} +func (m *GetExchangeTickerStreamRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetExchangeTickerStreamRequest.Marshal(b, m, deterministic) +} +func (m *GetExchangeTickerStreamRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetExchangeTickerStreamRequest.Merge(m, src) +} +func (m *GetExchangeTickerStreamRequest) XXX_Size() int { + return xxx_messageInfo_GetExchangeTickerStreamRequest.Size(m) +} +func (m *GetExchangeTickerStreamRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetExchangeTickerStreamRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetExchangeTickerStreamRequest proto.InternalMessageInfo + +func (m *GetExchangeTickerStreamRequest) GetExchange() string { + if m != nil { + return m.Exchange } return "" } type GetAuditEventRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - StartDate string `protobuf:"bytes,1,opt,name=start_date,json=startDate,proto3" json:"start_date,omitempty"` - EndDate string `protobuf:"bytes,2,opt,name=end_date,json=endDate,proto3" json:"end_date,omitempty"` - OrderBy string `protobuf:"bytes,3,opt,name=order_by,json=orderBy,proto3" json:"order_by,omitempty"` - Limit int32 `protobuf:"varint,4,opt,name=limit,proto3" json:"limit,omitempty"` - Offset int32 `protobuf:"varint,5,opt,name=offset,proto3" json:"offset,omitempty"` + StartDate string `protobuf:"bytes,1,opt,name=start_date,json=startDate,proto3" json:"start_date,omitempty"` + EndDate string `protobuf:"bytes,2,opt,name=end_date,json=endDate,proto3" json:"end_date,omitempty"` + OrderBy string `protobuf:"bytes,3,opt,name=order_by,json=orderBy,proto3" json:"order_by,omitempty"` + Limit int32 `protobuf:"varint,4,opt,name=limit,proto3" json:"limit,omitempty"` + Offset int32 `protobuf:"varint,5,opt,name=offset,proto3" json:"offset,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GetAuditEventRequest) Reset() { - *x = GetAuditEventRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[108] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetAuditEventRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetAuditEventRequest) ProtoMessage() {} - -func (x *GetAuditEventRequest) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[108] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetAuditEventRequest.ProtoReflect.Descriptor instead. +func (m *GetAuditEventRequest) Reset() { *m = GetAuditEventRequest{} } +func (m *GetAuditEventRequest) String() string { return proto.CompactTextString(m) } +func (*GetAuditEventRequest) ProtoMessage() {} func (*GetAuditEventRequest) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{108} + return fileDescriptor_77a6da22d6a3feb1, []int{102} } -func (x *GetAuditEventRequest) GetStartDate() string { - if x != nil { - return x.StartDate +func (m *GetAuditEventRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetAuditEventRequest.Unmarshal(m, b) +} +func (m *GetAuditEventRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetAuditEventRequest.Marshal(b, m, deterministic) +} +func (m *GetAuditEventRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetAuditEventRequest.Merge(m, src) +} +func (m *GetAuditEventRequest) XXX_Size() int { + return xxx_messageInfo_GetAuditEventRequest.Size(m) +} +func (m *GetAuditEventRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetAuditEventRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetAuditEventRequest proto.InternalMessageInfo + +func (m *GetAuditEventRequest) GetStartDate() string { + if m != nil { + return m.StartDate } return "" } -func (x *GetAuditEventRequest) GetEndDate() string { - if x != nil { - return x.EndDate +func (m *GetAuditEventRequest) GetEndDate() string { + if m != nil { + return m.EndDate } return "" } -func (x *GetAuditEventRequest) GetOrderBy() string { - if x != nil { - return x.OrderBy +func (m *GetAuditEventRequest) GetOrderBy() string { + if m != nil { + return m.OrderBy } return "" } -func (x *GetAuditEventRequest) GetLimit() int32 { - if x != nil { - return x.Limit +func (m *GetAuditEventRequest) GetLimit() int32 { + if m != nil { + return m.Limit } return 0 } -func (x *GetAuditEventRequest) GetOffset() int32 { - if x != nil { - return x.Offset +func (m *GetAuditEventRequest) GetOffset() int32 { + if m != nil { + return m.Offset } return 0 } type GetAuditEventResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Events []*AuditEvent `protobuf:"bytes,1,rep,name=events,proto3" json:"events,omitempty"` + Events []*AuditEvent `protobuf:"bytes,1,rep,name=events,proto3" json:"events,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GetAuditEventResponse) Reset() { - *x = GetAuditEventResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[109] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetAuditEventResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetAuditEventResponse) ProtoMessage() {} - -func (x *GetAuditEventResponse) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[109] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetAuditEventResponse.ProtoReflect.Descriptor instead. +func (m *GetAuditEventResponse) Reset() { *m = GetAuditEventResponse{} } +func (m *GetAuditEventResponse) String() string { return proto.CompactTextString(m) } +func (*GetAuditEventResponse) ProtoMessage() {} func (*GetAuditEventResponse) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{109} + return fileDescriptor_77a6da22d6a3feb1, []int{103} } -func (x *GetAuditEventResponse) GetEvents() []*AuditEvent { - if x != nil { - return x.Events +func (m *GetAuditEventResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetAuditEventResponse.Unmarshal(m, b) +} +func (m *GetAuditEventResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetAuditEventResponse.Marshal(b, m, deterministic) +} +func (m *GetAuditEventResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetAuditEventResponse.Merge(m, src) +} +func (m *GetAuditEventResponse) XXX_Size() int { + return xxx_messageInfo_GetAuditEventResponse.Size(m) +} +func (m *GetAuditEventResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetAuditEventResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetAuditEventResponse proto.InternalMessageInfo + +func (m *GetAuditEventResponse) GetEvents() []*AuditEvent { + if m != nil { + return m.Events } return nil } type GetHistoricCandlesRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` - Pair *CurrencyPair `protobuf:"bytes,2,opt,name=pair,proto3" json:"pair,omitempty"` - AssetType string `protobuf:"bytes,3,opt,name=asset_type,json=assetType,proto3" json:"asset_type,omitempty"` - Start int64 `protobuf:"varint,4,opt,name=start,proto3" json:"start,omitempty"` - End int64 `protobuf:"varint,5,opt,name=end,proto3" json:"end,omitempty"` - TimeInterval int64 `protobuf:"varint,6,opt,name=time_interval,json=timeInterval,proto3" json:"time_interval,omitempty"` - ExRequest bool `protobuf:"varint,7,opt,name=ex_request,json=exRequest,proto3" json:"ex_request,omitempty"` + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + Pair *CurrencyPair `protobuf:"bytes,2,opt,name=pair,proto3" json:"pair,omitempty"` + AssetType string `protobuf:"bytes,3,opt,name=asset_type,json=assetType,proto3" json:"asset_type,omitempty"` + Start int64 `protobuf:"varint,4,opt,name=start,proto3" json:"start,omitempty"` + End int64 `protobuf:"varint,5,opt,name=end,proto3" json:"end,omitempty"` + TimeInterval int64 `protobuf:"varint,6,opt,name=time_interval,json=timeInterval,proto3" json:"time_interval,omitempty"` + ExRequest bool `protobuf:"varint,7,opt,name=ex_request,json=exRequest,proto3" json:"ex_request,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GetHistoricCandlesRequest) Reset() { - *x = GetHistoricCandlesRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[110] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetHistoricCandlesRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetHistoricCandlesRequest) ProtoMessage() {} - -func (x *GetHistoricCandlesRequest) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[110] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetHistoricCandlesRequest.ProtoReflect.Descriptor instead. +func (m *GetHistoricCandlesRequest) Reset() { *m = GetHistoricCandlesRequest{} } +func (m *GetHistoricCandlesRequest) String() string { return proto.CompactTextString(m) } +func (*GetHistoricCandlesRequest) ProtoMessage() {} func (*GetHistoricCandlesRequest) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{110} + return fileDescriptor_77a6da22d6a3feb1, []int{104} } -func (x *GetHistoricCandlesRequest) GetExchange() string { - if x != nil { - return x.Exchange +func (m *GetHistoricCandlesRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetHistoricCandlesRequest.Unmarshal(m, b) +} +func (m *GetHistoricCandlesRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetHistoricCandlesRequest.Marshal(b, m, deterministic) +} +func (m *GetHistoricCandlesRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetHistoricCandlesRequest.Merge(m, src) +} +func (m *GetHistoricCandlesRequest) XXX_Size() int { + return xxx_messageInfo_GetHistoricCandlesRequest.Size(m) +} +func (m *GetHistoricCandlesRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetHistoricCandlesRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetHistoricCandlesRequest proto.InternalMessageInfo + +func (m *GetHistoricCandlesRequest) GetExchange() string { + if m != nil { + return m.Exchange } return "" } -func (x *GetHistoricCandlesRequest) GetPair() *CurrencyPair { - if x != nil { - return x.Pair +func (m *GetHistoricCandlesRequest) GetPair() *CurrencyPair { + if m != nil { + return m.Pair } return nil } -func (x *GetHistoricCandlesRequest) GetAssetType() string { - if x != nil { - return x.AssetType +func (m *GetHistoricCandlesRequest) GetAssetType() string { + if m != nil { + return m.AssetType } return "" } -func (x *GetHistoricCandlesRequest) GetStart() int64 { - if x != nil { - return x.Start +func (m *GetHistoricCandlesRequest) GetStart() int64 { + if m != nil { + return m.Start } return 0 } -func (x *GetHistoricCandlesRequest) GetEnd() int64 { - if x != nil { - return x.End +func (m *GetHistoricCandlesRequest) GetEnd() int64 { + if m != nil { + return m.End } return 0 } -func (x *GetHistoricCandlesRequest) GetTimeInterval() int64 { - if x != nil { - return x.TimeInterval +func (m *GetHistoricCandlesRequest) GetTimeInterval() int64 { + if m != nil { + return m.TimeInterval } return 0 } -func (x *GetHistoricCandlesRequest) GetExRequest() bool { - if x != nil { - return x.ExRequest +func (m *GetHistoricCandlesRequest) GetExRequest() bool { + if m != nil { + return m.ExRequest } return false } type GetHistoricCandlesResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` - Pair *CurrencyPair `protobuf:"bytes,2,opt,name=pair,proto3" json:"pair,omitempty"` - Start int64 `protobuf:"varint,3,opt,name=start,proto3" json:"start,omitempty"` - End int64 `protobuf:"varint,4,opt,name=end,proto3" json:"end,omitempty"` - Interval string `protobuf:"bytes,6,opt,name=interval,proto3" json:"interval,omitempty"` - Candle []*Candle `protobuf:"bytes,5,rep,name=candle,proto3" json:"candle,omitempty"` + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + Pair *CurrencyPair `protobuf:"bytes,2,opt,name=pair,proto3" json:"pair,omitempty"` + Start int64 `protobuf:"varint,3,opt,name=start,proto3" json:"start,omitempty"` + End int64 `protobuf:"varint,4,opt,name=end,proto3" json:"end,omitempty"` + Interval string `protobuf:"bytes,6,opt,name=interval,proto3" json:"interval,omitempty"` + Candle []*Candle `protobuf:"bytes,5,rep,name=candle,proto3" json:"candle,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GetHistoricCandlesResponse) Reset() { - *x = GetHistoricCandlesResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[111] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetHistoricCandlesResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetHistoricCandlesResponse) ProtoMessage() {} - -func (x *GetHistoricCandlesResponse) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[111] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetHistoricCandlesResponse.ProtoReflect.Descriptor instead. +func (m *GetHistoricCandlesResponse) Reset() { *m = GetHistoricCandlesResponse{} } +func (m *GetHistoricCandlesResponse) String() string { return proto.CompactTextString(m) } +func (*GetHistoricCandlesResponse) ProtoMessage() {} func (*GetHistoricCandlesResponse) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{111} + return fileDescriptor_77a6da22d6a3feb1, []int{105} } -func (x *GetHistoricCandlesResponse) GetExchange() string { - if x != nil { - return x.Exchange +func (m *GetHistoricCandlesResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetHistoricCandlesResponse.Unmarshal(m, b) +} +func (m *GetHistoricCandlesResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetHistoricCandlesResponse.Marshal(b, m, deterministic) +} +func (m *GetHistoricCandlesResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetHistoricCandlesResponse.Merge(m, src) +} +func (m *GetHistoricCandlesResponse) XXX_Size() int { + return xxx_messageInfo_GetHistoricCandlesResponse.Size(m) +} +func (m *GetHistoricCandlesResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetHistoricCandlesResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetHistoricCandlesResponse proto.InternalMessageInfo + +func (m *GetHistoricCandlesResponse) GetExchange() string { + if m != nil { + return m.Exchange } return "" } -func (x *GetHistoricCandlesResponse) GetPair() *CurrencyPair { - if x != nil { - return x.Pair +func (m *GetHistoricCandlesResponse) GetPair() *CurrencyPair { + if m != nil { + return m.Pair } return nil } -func (x *GetHistoricCandlesResponse) GetStart() int64 { - if x != nil { - return x.Start +func (m *GetHistoricCandlesResponse) GetStart() int64 { + if m != nil { + return m.Start } return 0 } -func (x *GetHistoricCandlesResponse) GetEnd() int64 { - if x != nil { - return x.End +func (m *GetHistoricCandlesResponse) GetEnd() int64 { + if m != nil { + return m.End } return 0 } -func (x *GetHistoricCandlesResponse) GetInterval() string { - if x != nil { - return x.Interval +func (m *GetHistoricCandlesResponse) GetInterval() string { + if m != nil { + return m.Interval } return "" } -func (x *GetHistoricCandlesResponse) GetCandle() []*Candle { - if x != nil { - return x.Candle +func (m *GetHistoricCandlesResponse) GetCandle() []*Candle { + if m != nil { + return m.Candle } return nil } type Candle struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Time int64 `protobuf:"varint,1,opt,name=time,proto3" json:"time,omitempty"` - Low float64 `protobuf:"fixed64,2,opt,name=low,proto3" json:"low,omitempty"` - High float64 `protobuf:"fixed64,3,opt,name=high,proto3" json:"high,omitempty"` - Open float64 `protobuf:"fixed64,4,opt,name=open,proto3" json:"open,omitempty"` - Close float64 `protobuf:"fixed64,5,opt,name=close,proto3" json:"close,omitempty"` - Volume float64 `protobuf:"fixed64,6,opt,name=volume,proto3" json:"volume,omitempty"` + Time int64 `protobuf:"varint,1,opt,name=time,proto3" json:"time,omitempty"` + Low float64 `protobuf:"fixed64,2,opt,name=low,proto3" json:"low,omitempty"` + High float64 `protobuf:"fixed64,3,opt,name=high,proto3" json:"high,omitempty"` + Open float64 `protobuf:"fixed64,4,opt,name=open,proto3" json:"open,omitempty"` + Close float64 `protobuf:"fixed64,5,opt,name=close,proto3" json:"close,omitempty"` + Volume float64 `protobuf:"fixed64,6,opt,name=volume,proto3" json:"volume,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *Candle) Reset() { - *x = Candle{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[112] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Candle) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Candle) ProtoMessage() {} - -func (x *Candle) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[112] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Candle.ProtoReflect.Descriptor instead. +func (m *Candle) Reset() { *m = Candle{} } +func (m *Candle) String() string { return proto.CompactTextString(m) } +func (*Candle) ProtoMessage() {} func (*Candle) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{112} + return fileDescriptor_77a6da22d6a3feb1, []int{106} } -func (x *Candle) GetTime() int64 { - if x != nil { - return x.Time +func (m *Candle) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Candle.Unmarshal(m, b) +} +func (m *Candle) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Candle.Marshal(b, m, deterministic) +} +func (m *Candle) XXX_Merge(src proto.Message) { + xxx_messageInfo_Candle.Merge(m, src) +} +func (m *Candle) XXX_Size() int { + return xxx_messageInfo_Candle.Size(m) +} +func (m *Candle) XXX_DiscardUnknown() { + xxx_messageInfo_Candle.DiscardUnknown(m) +} + +var xxx_messageInfo_Candle proto.InternalMessageInfo + +func (m *Candle) GetTime() int64 { + if m != nil { + return m.Time } return 0 } -func (x *Candle) GetLow() float64 { - if x != nil { - return x.Low +func (m *Candle) GetLow() float64 { + if m != nil { + return m.Low } return 0 } -func (x *Candle) GetHigh() float64 { - if x != nil { - return x.High +func (m *Candle) GetHigh() float64 { + if m != nil { + return m.High } return 0 } -func (x *Candle) GetOpen() float64 { - if x != nil { - return x.Open +func (m *Candle) GetOpen() float64 { + if m != nil { + return m.Open } return 0 } -func (x *Candle) GetClose() float64 { - if x != nil { - return x.Close +func (m *Candle) GetClose() float64 { + if m != nil { + return m.Close } return 0 } -func (x *Candle) GetVolume() float64 { - if x != nil { - return x.Volume +func (m *Candle) GetVolume() float64 { + if m != nil { + return m.Volume } return 0 } type AuditEvent struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` - Identifier string `protobuf:"bytes,2,opt,name=identifier,proto3" json:"identifier,omitempty"` - Message string `protobuf:"bytes,3,opt,name=message,proto3" json:"message,omitempty"` - Timestamp string `protobuf:"bytes,4,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` + Identifier string `protobuf:"bytes,2,opt,name=identifier,proto3" json:"identifier,omitempty"` + Message string `protobuf:"bytes,3,opt,name=message,proto3" json:"message,omitempty"` + Timestamp string `protobuf:"bytes,4,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *AuditEvent) Reset() { - *x = AuditEvent{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[113] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *AuditEvent) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*AuditEvent) ProtoMessage() {} - -func (x *AuditEvent) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[113] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use AuditEvent.ProtoReflect.Descriptor instead. +func (m *AuditEvent) Reset() { *m = AuditEvent{} } +func (m *AuditEvent) String() string { return proto.CompactTextString(m) } +func (*AuditEvent) ProtoMessage() {} func (*AuditEvent) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{113} + return fileDescriptor_77a6da22d6a3feb1, []int{107} } -func (x *AuditEvent) GetType() string { - if x != nil { - return x.Type +func (m *AuditEvent) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_AuditEvent.Unmarshal(m, b) +} +func (m *AuditEvent) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_AuditEvent.Marshal(b, m, deterministic) +} +func (m *AuditEvent) XXX_Merge(src proto.Message) { + xxx_messageInfo_AuditEvent.Merge(m, src) +} +func (m *AuditEvent) XXX_Size() int { + return xxx_messageInfo_AuditEvent.Size(m) +} +func (m *AuditEvent) XXX_DiscardUnknown() { + xxx_messageInfo_AuditEvent.DiscardUnknown(m) +} + +var xxx_messageInfo_AuditEvent proto.InternalMessageInfo + +func (m *AuditEvent) GetType() string { + if m != nil { + return m.Type } return "" } -func (x *AuditEvent) GetIdentifier() string { - if x != nil { - return x.Identifier +func (m *AuditEvent) GetIdentifier() string { + if m != nil { + return m.Identifier } return "" } -func (x *AuditEvent) GetMessage() string { - if x != nil { - return x.Message +func (m *AuditEvent) GetMessage() string { + if m != nil { + return m.Message } return "" } -func (x *AuditEvent) GetTimestamp() string { - if x != nil { - return x.Timestamp +func (m *AuditEvent) GetTimestamp() string { + if m != nil { + return m.Timestamp } return "" } type GCTScript struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - UUID string `protobuf:"bytes,1,opt,name=UUID,proto3" json:"UUID,omitempty"` - Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` - Path string `protobuf:"bytes,3,opt,name=path,proto3" json:"path,omitempty"` - NextRun string `protobuf:"bytes,4,opt,name=next_run,json=nextRun,proto3" json:"next_run,omitempty"` + UUID string `protobuf:"bytes,1,opt,name=UUID,proto3" json:"UUID,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Path string `protobuf:"bytes,3,opt,name=path,proto3" json:"path,omitempty"` + NextRun string `protobuf:"bytes,4,opt,name=next_run,json=nextRun,proto3" json:"next_run,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GCTScript) Reset() { - *x = GCTScript{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[114] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GCTScript) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GCTScript) ProtoMessage() {} - -func (x *GCTScript) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[114] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GCTScript.ProtoReflect.Descriptor instead. +func (m *GCTScript) Reset() { *m = GCTScript{} } +func (m *GCTScript) String() string { return proto.CompactTextString(m) } +func (*GCTScript) ProtoMessage() {} func (*GCTScript) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{114} + return fileDescriptor_77a6da22d6a3feb1, []int{108} } -func (x *GCTScript) GetUUID() string { - if x != nil { - return x.UUID +func (m *GCTScript) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GCTScript.Unmarshal(m, b) +} +func (m *GCTScript) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GCTScript.Marshal(b, m, deterministic) +} +func (m *GCTScript) XXX_Merge(src proto.Message) { + xxx_messageInfo_GCTScript.Merge(m, src) +} +func (m *GCTScript) XXX_Size() int { + return xxx_messageInfo_GCTScript.Size(m) +} +func (m *GCTScript) XXX_DiscardUnknown() { + xxx_messageInfo_GCTScript.DiscardUnknown(m) +} + +var xxx_messageInfo_GCTScript proto.InternalMessageInfo + +func (m *GCTScript) GetUUID() string { + if m != nil { + return m.UUID } return "" } -func (x *GCTScript) GetName() string { - if x != nil { - return x.Name +func (m *GCTScript) GetName() string { + if m != nil { + return m.Name } return "" } -func (x *GCTScript) GetPath() string { - if x != nil { - return x.Path +func (m *GCTScript) GetPath() string { + if m != nil { + return m.Path } return "" } -func (x *GCTScript) GetNextRun() string { - if x != nil { - return x.NextRun +func (m *GCTScript) GetNextRun() string { + if m != nil { + return m.NextRun } return "" } type GCTScriptExecuteRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Script *GCTScript `protobuf:"bytes,1,opt,name=script,proto3" json:"script,omitempty"` + Script *GCTScript `protobuf:"bytes,1,opt,name=script,proto3" json:"script,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GCTScriptExecuteRequest) Reset() { - *x = GCTScriptExecuteRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[115] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GCTScriptExecuteRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GCTScriptExecuteRequest) ProtoMessage() {} - -func (x *GCTScriptExecuteRequest) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[115] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GCTScriptExecuteRequest.ProtoReflect.Descriptor instead. +func (m *GCTScriptExecuteRequest) Reset() { *m = GCTScriptExecuteRequest{} } +func (m *GCTScriptExecuteRequest) String() string { return proto.CompactTextString(m) } +func (*GCTScriptExecuteRequest) ProtoMessage() {} func (*GCTScriptExecuteRequest) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{115} + return fileDescriptor_77a6da22d6a3feb1, []int{109} } -func (x *GCTScriptExecuteRequest) GetScript() *GCTScript { - if x != nil { - return x.Script +func (m *GCTScriptExecuteRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GCTScriptExecuteRequest.Unmarshal(m, b) +} +func (m *GCTScriptExecuteRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GCTScriptExecuteRequest.Marshal(b, m, deterministic) +} +func (m *GCTScriptExecuteRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GCTScriptExecuteRequest.Merge(m, src) +} +func (m *GCTScriptExecuteRequest) XXX_Size() int { + return xxx_messageInfo_GCTScriptExecuteRequest.Size(m) +} +func (m *GCTScriptExecuteRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GCTScriptExecuteRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GCTScriptExecuteRequest proto.InternalMessageInfo + +func (m *GCTScriptExecuteRequest) GetScript() *GCTScript { + if m != nil { + return m.Script } return nil } type GCTScriptStopRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Script *GCTScript `protobuf:"bytes,1,opt,name=script,proto3" json:"script,omitempty"` + Script *GCTScript `protobuf:"bytes,1,opt,name=script,proto3" json:"script,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GCTScriptStopRequest) Reset() { - *x = GCTScriptStopRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[116] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GCTScriptStopRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GCTScriptStopRequest) ProtoMessage() {} - -func (x *GCTScriptStopRequest) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[116] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GCTScriptStopRequest.ProtoReflect.Descriptor instead. +func (m *GCTScriptStopRequest) Reset() { *m = GCTScriptStopRequest{} } +func (m *GCTScriptStopRequest) String() string { return proto.CompactTextString(m) } +func (*GCTScriptStopRequest) ProtoMessage() {} func (*GCTScriptStopRequest) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{116} + return fileDescriptor_77a6da22d6a3feb1, []int{110} } -func (x *GCTScriptStopRequest) GetScript() *GCTScript { - if x != nil { - return x.Script +func (m *GCTScriptStopRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GCTScriptStopRequest.Unmarshal(m, b) +} +func (m *GCTScriptStopRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GCTScriptStopRequest.Marshal(b, m, deterministic) +} +func (m *GCTScriptStopRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GCTScriptStopRequest.Merge(m, src) +} +func (m *GCTScriptStopRequest) XXX_Size() int { + return xxx_messageInfo_GCTScriptStopRequest.Size(m) +} +func (m *GCTScriptStopRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GCTScriptStopRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GCTScriptStopRequest proto.InternalMessageInfo + +func (m *GCTScriptStopRequest) GetScript() *GCTScript { + if m != nil { + return m.Script } return nil } type GCTScriptStopAllRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GCTScriptStopAllRequest) Reset() { - *x = GCTScriptStopAllRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[117] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GCTScriptStopAllRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GCTScriptStopAllRequest) ProtoMessage() {} - -func (x *GCTScriptStopAllRequest) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[117] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GCTScriptStopAllRequest.ProtoReflect.Descriptor instead. +func (m *GCTScriptStopAllRequest) Reset() { *m = GCTScriptStopAllRequest{} } +func (m *GCTScriptStopAllRequest) String() string { return proto.CompactTextString(m) } +func (*GCTScriptStopAllRequest) ProtoMessage() {} func (*GCTScriptStopAllRequest) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{117} + return fileDescriptor_77a6da22d6a3feb1, []int{111} } +func (m *GCTScriptStopAllRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GCTScriptStopAllRequest.Unmarshal(m, b) +} +func (m *GCTScriptStopAllRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GCTScriptStopAllRequest.Marshal(b, m, deterministic) +} +func (m *GCTScriptStopAllRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GCTScriptStopAllRequest.Merge(m, src) +} +func (m *GCTScriptStopAllRequest) XXX_Size() int { + return xxx_messageInfo_GCTScriptStopAllRequest.Size(m) +} +func (m *GCTScriptStopAllRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GCTScriptStopAllRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GCTScriptStopAllRequest proto.InternalMessageInfo + type GCTScriptStatusRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GCTScriptStatusRequest) Reset() { - *x = GCTScriptStatusRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[118] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GCTScriptStatusRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GCTScriptStatusRequest) ProtoMessage() {} - -func (x *GCTScriptStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[118] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GCTScriptStatusRequest.ProtoReflect.Descriptor instead. +func (m *GCTScriptStatusRequest) Reset() { *m = GCTScriptStatusRequest{} } +func (m *GCTScriptStatusRequest) String() string { return proto.CompactTextString(m) } +func (*GCTScriptStatusRequest) ProtoMessage() {} func (*GCTScriptStatusRequest) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{118} + return fileDescriptor_77a6da22d6a3feb1, []int{112} } +func (m *GCTScriptStatusRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GCTScriptStatusRequest.Unmarshal(m, b) +} +func (m *GCTScriptStatusRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GCTScriptStatusRequest.Marshal(b, m, deterministic) +} +func (m *GCTScriptStatusRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GCTScriptStatusRequest.Merge(m, src) +} +func (m *GCTScriptStatusRequest) XXX_Size() int { + return xxx_messageInfo_GCTScriptStatusRequest.Size(m) +} +func (m *GCTScriptStatusRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GCTScriptStatusRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GCTScriptStatusRequest proto.InternalMessageInfo + type GCTScriptListAllRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GCTScriptListAllRequest) Reset() { - *x = GCTScriptListAllRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[119] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GCTScriptListAllRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GCTScriptListAllRequest) ProtoMessage() {} - -func (x *GCTScriptListAllRequest) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[119] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GCTScriptListAllRequest.ProtoReflect.Descriptor instead. +func (m *GCTScriptListAllRequest) Reset() { *m = GCTScriptListAllRequest{} } +func (m *GCTScriptListAllRequest) String() string { return proto.CompactTextString(m) } +func (*GCTScriptListAllRequest) ProtoMessage() {} func (*GCTScriptListAllRequest) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{119} + return fileDescriptor_77a6da22d6a3feb1, []int{113} } +func (m *GCTScriptListAllRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GCTScriptListAllRequest.Unmarshal(m, b) +} +func (m *GCTScriptListAllRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GCTScriptListAllRequest.Marshal(b, m, deterministic) +} +func (m *GCTScriptListAllRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GCTScriptListAllRequest.Merge(m, src) +} +func (m *GCTScriptListAllRequest) XXX_Size() int { + return xxx_messageInfo_GCTScriptListAllRequest.Size(m) +} +func (m *GCTScriptListAllRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GCTScriptListAllRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GCTScriptListAllRequest proto.InternalMessageInfo + type GCTScriptUploadRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - ScriptName string `protobuf:"bytes,1,opt,name=script_name,json=scriptName,proto3" json:"script_name,omitempty"` - ScriptData string `protobuf:"bytes,2,opt,name=script_data,json=scriptData,proto3" json:"script_data,omitempty"` - Data []byte `protobuf:"bytes,3,opt,name=data,proto3" json:"data,omitempty"` - Archived bool `protobuf:"varint,4,opt,name=archived,proto3" json:"archived,omitempty"` - Overwrite bool `protobuf:"varint,5,opt,name=overwrite,proto3" json:"overwrite,omitempty"` + ScriptName string `protobuf:"bytes,1,opt,name=script_name,json=scriptName,proto3" json:"script_name,omitempty"` + ScriptData string `protobuf:"bytes,2,opt,name=script_data,json=scriptData,proto3" json:"script_data,omitempty"` + Data []byte `protobuf:"bytes,3,opt,name=data,proto3" json:"data,omitempty"` + Archived bool `protobuf:"varint,4,opt,name=archived,proto3" json:"archived,omitempty"` + Overwrite bool `protobuf:"varint,5,opt,name=overwrite,proto3" json:"overwrite,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GCTScriptUploadRequest) Reset() { - *x = GCTScriptUploadRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[120] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GCTScriptUploadRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GCTScriptUploadRequest) ProtoMessage() {} - -func (x *GCTScriptUploadRequest) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[120] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GCTScriptUploadRequest.ProtoReflect.Descriptor instead. +func (m *GCTScriptUploadRequest) Reset() { *m = GCTScriptUploadRequest{} } +func (m *GCTScriptUploadRequest) String() string { return proto.CompactTextString(m) } +func (*GCTScriptUploadRequest) ProtoMessage() {} func (*GCTScriptUploadRequest) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{120} + return fileDescriptor_77a6da22d6a3feb1, []int{114} } -func (x *GCTScriptUploadRequest) GetScriptName() string { - if x != nil { - return x.ScriptName +func (m *GCTScriptUploadRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GCTScriptUploadRequest.Unmarshal(m, b) +} +func (m *GCTScriptUploadRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GCTScriptUploadRequest.Marshal(b, m, deterministic) +} +func (m *GCTScriptUploadRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GCTScriptUploadRequest.Merge(m, src) +} +func (m *GCTScriptUploadRequest) XXX_Size() int { + return xxx_messageInfo_GCTScriptUploadRequest.Size(m) +} +func (m *GCTScriptUploadRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GCTScriptUploadRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GCTScriptUploadRequest proto.InternalMessageInfo + +func (m *GCTScriptUploadRequest) GetScriptName() string { + if m != nil { + return m.ScriptName } return "" } -func (x *GCTScriptUploadRequest) GetScriptData() string { - if x != nil { - return x.ScriptData +func (m *GCTScriptUploadRequest) GetScriptData() string { + if m != nil { + return m.ScriptData } return "" } -func (x *GCTScriptUploadRequest) GetData() []byte { - if x != nil { - return x.Data +func (m *GCTScriptUploadRequest) GetData() []byte { + if m != nil { + return m.Data } return nil } -func (x *GCTScriptUploadRequest) GetArchived() bool { - if x != nil { - return x.Archived +func (m *GCTScriptUploadRequest) GetArchived() bool { + if m != nil { + return m.Archived } return false } -func (x *GCTScriptUploadRequest) GetOverwrite() bool { - if x != nil { - return x.Overwrite +func (m *GCTScriptUploadRequest) GetOverwrite() bool { + if m != nil { + return m.Overwrite } return false } type GCTScriptReadScriptRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Script *GCTScript `protobuf:"bytes,1,opt,name=script,proto3" json:"script,omitempty"` + Script *GCTScript `protobuf:"bytes,1,opt,name=script,proto3" json:"script,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GCTScriptReadScriptRequest) Reset() { - *x = GCTScriptReadScriptRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[121] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GCTScriptReadScriptRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GCTScriptReadScriptRequest) ProtoMessage() {} - -func (x *GCTScriptReadScriptRequest) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[121] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GCTScriptReadScriptRequest.ProtoReflect.Descriptor instead. +func (m *GCTScriptReadScriptRequest) Reset() { *m = GCTScriptReadScriptRequest{} } +func (m *GCTScriptReadScriptRequest) String() string { return proto.CompactTextString(m) } +func (*GCTScriptReadScriptRequest) ProtoMessage() {} func (*GCTScriptReadScriptRequest) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{121} + return fileDescriptor_77a6da22d6a3feb1, []int{115} } -func (x *GCTScriptReadScriptRequest) GetScript() *GCTScript { - if x != nil { - return x.Script +func (m *GCTScriptReadScriptRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GCTScriptReadScriptRequest.Unmarshal(m, b) +} +func (m *GCTScriptReadScriptRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GCTScriptReadScriptRequest.Marshal(b, m, deterministic) +} +func (m *GCTScriptReadScriptRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GCTScriptReadScriptRequest.Merge(m, src) +} +func (m *GCTScriptReadScriptRequest) XXX_Size() int { + return xxx_messageInfo_GCTScriptReadScriptRequest.Size(m) +} +func (m *GCTScriptReadScriptRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GCTScriptReadScriptRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GCTScriptReadScriptRequest proto.InternalMessageInfo + +func (m *GCTScriptReadScriptRequest) GetScript() *GCTScript { + if m != nil { + return m.Script } return nil } type GCTScriptQueryRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Script *GCTScript `protobuf:"bytes,1,opt,name=script,proto3" json:"script,omitempty"` + Script *GCTScript `protobuf:"bytes,1,opt,name=script,proto3" json:"script,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GCTScriptQueryRequest) Reset() { - *x = GCTScriptQueryRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[122] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GCTScriptQueryRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GCTScriptQueryRequest) ProtoMessage() {} - -func (x *GCTScriptQueryRequest) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[122] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GCTScriptQueryRequest.ProtoReflect.Descriptor instead. +func (m *GCTScriptQueryRequest) Reset() { *m = GCTScriptQueryRequest{} } +func (m *GCTScriptQueryRequest) String() string { return proto.CompactTextString(m) } +func (*GCTScriptQueryRequest) ProtoMessage() {} func (*GCTScriptQueryRequest) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{122} + return fileDescriptor_77a6da22d6a3feb1, []int{116} } -func (x *GCTScriptQueryRequest) GetScript() *GCTScript { - if x != nil { - return x.Script +func (m *GCTScriptQueryRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GCTScriptQueryRequest.Unmarshal(m, b) +} +func (m *GCTScriptQueryRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GCTScriptQueryRequest.Marshal(b, m, deterministic) +} +func (m *GCTScriptQueryRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GCTScriptQueryRequest.Merge(m, src) +} +func (m *GCTScriptQueryRequest) XXX_Size() int { + return xxx_messageInfo_GCTScriptQueryRequest.Size(m) +} +func (m *GCTScriptQueryRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GCTScriptQueryRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GCTScriptQueryRequest proto.InternalMessageInfo + +func (m *GCTScriptQueryRequest) GetScript() *GCTScript { + if m != nil { + return m.Script } return nil } type GCTScriptAutoLoadRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Script string `protobuf:"bytes,1,opt,name=script,proto3" json:"script,omitempty"` - Status bool `protobuf:"varint,2,opt,name=status,proto3" json:"status,omitempty"` + Script string `protobuf:"bytes,1,opt,name=script,proto3" json:"script,omitempty"` + Status bool `protobuf:"varint,2,opt,name=status,proto3" json:"status,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GCTScriptAutoLoadRequest) Reset() { - *x = GCTScriptAutoLoadRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[123] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GCTScriptAutoLoadRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GCTScriptAutoLoadRequest) ProtoMessage() {} - -func (x *GCTScriptAutoLoadRequest) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[123] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GCTScriptAutoLoadRequest.ProtoReflect.Descriptor instead. +func (m *GCTScriptAutoLoadRequest) Reset() { *m = GCTScriptAutoLoadRequest{} } +func (m *GCTScriptAutoLoadRequest) String() string { return proto.CompactTextString(m) } +func (*GCTScriptAutoLoadRequest) ProtoMessage() {} func (*GCTScriptAutoLoadRequest) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{123} + return fileDescriptor_77a6da22d6a3feb1, []int{117} } -func (x *GCTScriptAutoLoadRequest) GetScript() string { - if x != nil { - return x.Script +func (m *GCTScriptAutoLoadRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GCTScriptAutoLoadRequest.Unmarshal(m, b) +} +func (m *GCTScriptAutoLoadRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GCTScriptAutoLoadRequest.Marshal(b, m, deterministic) +} +func (m *GCTScriptAutoLoadRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GCTScriptAutoLoadRequest.Merge(m, src) +} +func (m *GCTScriptAutoLoadRequest) XXX_Size() int { + return xxx_messageInfo_GCTScriptAutoLoadRequest.Size(m) +} +func (m *GCTScriptAutoLoadRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GCTScriptAutoLoadRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GCTScriptAutoLoadRequest proto.InternalMessageInfo + +func (m *GCTScriptAutoLoadRequest) GetScript() string { + if m != nil { + return m.Script } return "" } -func (x *GCTScriptAutoLoadRequest) GetStatus() bool { - if x != nil { - return x.Status +func (m *GCTScriptAutoLoadRequest) GetStatus() bool { + if m != nil { + return m.Status } return false } type GCTScriptStatusResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` - Scripts []*GCTScript `protobuf:"bytes,2,rep,name=scripts,proto3" json:"scripts,omitempty"` + Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` + Scripts []*GCTScript `protobuf:"bytes,2,rep,name=scripts,proto3" json:"scripts,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GCTScriptStatusResponse) Reset() { - *x = GCTScriptStatusResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[124] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GCTScriptStatusResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GCTScriptStatusResponse) ProtoMessage() {} - -func (x *GCTScriptStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[124] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GCTScriptStatusResponse.ProtoReflect.Descriptor instead. +func (m *GCTScriptStatusResponse) Reset() { *m = GCTScriptStatusResponse{} } +func (m *GCTScriptStatusResponse) String() string { return proto.CompactTextString(m) } +func (*GCTScriptStatusResponse) ProtoMessage() {} func (*GCTScriptStatusResponse) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{124} + return fileDescriptor_77a6da22d6a3feb1, []int{118} } -func (x *GCTScriptStatusResponse) GetStatus() string { - if x != nil { - return x.Status +func (m *GCTScriptStatusResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GCTScriptStatusResponse.Unmarshal(m, b) +} +func (m *GCTScriptStatusResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GCTScriptStatusResponse.Marshal(b, m, deterministic) +} +func (m *GCTScriptStatusResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GCTScriptStatusResponse.Merge(m, src) +} +func (m *GCTScriptStatusResponse) XXX_Size() int { + return xxx_messageInfo_GCTScriptStatusResponse.Size(m) +} +func (m *GCTScriptStatusResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GCTScriptStatusResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GCTScriptStatusResponse proto.InternalMessageInfo + +func (m *GCTScriptStatusResponse) GetStatus() string { + if m != nil { + return m.Status } return "" } -func (x *GCTScriptStatusResponse) GetScripts() []*GCTScript { - if x != nil { - return x.Scripts +func (m *GCTScriptStatusResponse) GetScripts() []*GCTScript { + if m != nil { + return m.Scripts } return nil } type GCTScriptQueryResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` - Script *GCTScript `protobuf:"bytes,2,opt,name=script,proto3" json:"script,omitempty"` - Data string `protobuf:"bytes,3,opt,name=data,proto3" json:"data,omitempty"` + Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` + Script *GCTScript `protobuf:"bytes,2,opt,name=script,proto3" json:"script,omitempty"` + Data string `protobuf:"bytes,3,opt,name=data,proto3" json:"data,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GCTScriptQueryResponse) Reset() { - *x = GCTScriptQueryResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[125] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GCTScriptQueryResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GCTScriptQueryResponse) ProtoMessage() {} - -func (x *GCTScriptQueryResponse) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[125] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GCTScriptQueryResponse.ProtoReflect.Descriptor instead. +func (m *GCTScriptQueryResponse) Reset() { *m = GCTScriptQueryResponse{} } +func (m *GCTScriptQueryResponse) String() string { return proto.CompactTextString(m) } +func (*GCTScriptQueryResponse) ProtoMessage() {} func (*GCTScriptQueryResponse) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{125} + return fileDescriptor_77a6da22d6a3feb1, []int{119} } -func (x *GCTScriptQueryResponse) GetStatus() string { - if x != nil { - return x.Status +func (m *GCTScriptQueryResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GCTScriptQueryResponse.Unmarshal(m, b) +} +func (m *GCTScriptQueryResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GCTScriptQueryResponse.Marshal(b, m, deterministic) +} +func (m *GCTScriptQueryResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GCTScriptQueryResponse.Merge(m, src) +} +func (m *GCTScriptQueryResponse) XXX_Size() int { + return xxx_messageInfo_GCTScriptQueryResponse.Size(m) +} +func (m *GCTScriptQueryResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GCTScriptQueryResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GCTScriptQueryResponse proto.InternalMessageInfo + +func (m *GCTScriptQueryResponse) GetStatus() string { + if m != nil { + return m.Status } return "" } -func (x *GCTScriptQueryResponse) GetScript() *GCTScript { - if x != nil { - return x.Script +func (m *GCTScriptQueryResponse) GetScript() *GCTScript { + if m != nil { + return m.Script } return nil } -func (x *GCTScriptQueryResponse) GetData() string { - if x != nil { - return x.Data +func (m *GCTScriptQueryResponse) GetData() string { + if m != nil { + return m.Data } return "" } -type GCTScriptGenericResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` - Data string `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` +type GenericResponse struct { + Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` + Data string `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *GCTScriptGenericResponse) Reset() { - *x = GCTScriptGenericResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[126] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } +func (m *GenericResponse) Reset() { *m = GenericResponse{} } +func (m *GenericResponse) String() string { return proto.CompactTextString(m) } +func (*GenericResponse) ProtoMessage() {} +func (*GenericResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{120} } -func (x *GCTScriptGenericResponse) String() string { - return protoimpl.X.MessageStringOf(x) +func (m *GenericResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GenericResponse.Unmarshal(m, b) +} +func (m *GenericResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GenericResponse.Marshal(b, m, deterministic) +} +func (m *GenericResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GenericResponse.Merge(m, src) +} +func (m *GenericResponse) XXX_Size() int { + return xxx_messageInfo_GenericResponse.Size(m) +} +func (m *GenericResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GenericResponse.DiscardUnknown(m) } -func (*GCTScriptGenericResponse) ProtoMessage() {} +var xxx_messageInfo_GenericResponse proto.InternalMessageInfo -func (x *GCTScriptGenericResponse) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[126] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GCTScriptGenericResponse.ProtoReflect.Descriptor instead. -func (*GCTScriptGenericResponse) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{126} -} - -func (x *GCTScriptGenericResponse) GetStatus() string { - if x != nil { - return x.Status +func (m *GenericResponse) GetStatus() string { + if m != nil { + return m.Status } return "" } -func (x *GCTScriptGenericResponse) GetData() string { - if x != nil { - return x.Data +func (m *GenericResponse) GetData() string { + if m != nil { + return m.Data } return "" } -type CancelAllOrdersResponse_Orders struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` - OrderStatus map[string]string `protobuf:"bytes,2,rep,name=order_status,json=orderStatus,proto3" json:"order_status,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +type SetExchangeAssetRequest struct { + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + Asset string `protobuf:"bytes,2,opt,name=asset,proto3" json:"asset,omitempty"` + Enable bool `protobuf:"varint,3,opt,name=enable,proto3" json:"enable,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (x *CancelAllOrdersResponse_Orders) Reset() { - *x = CancelAllOrdersResponse_Orders{} - if protoimpl.UnsafeEnabled { - mi := &file_rpc_proto_msgTypes[137] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } +func (m *SetExchangeAssetRequest) Reset() { *m = SetExchangeAssetRequest{} } +func (m *SetExchangeAssetRequest) String() string { return proto.CompactTextString(m) } +func (*SetExchangeAssetRequest) ProtoMessage() {} +func (*SetExchangeAssetRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{121} } -func (x *CancelAllOrdersResponse_Orders) String() string { - return protoimpl.X.MessageStringOf(x) +func (m *SetExchangeAssetRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_SetExchangeAssetRequest.Unmarshal(m, b) +} +func (m *SetExchangeAssetRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_SetExchangeAssetRequest.Marshal(b, m, deterministic) +} +func (m *SetExchangeAssetRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_SetExchangeAssetRequest.Merge(m, src) +} +func (m *SetExchangeAssetRequest) XXX_Size() int { + return xxx_messageInfo_SetExchangeAssetRequest.Size(m) +} +func (m *SetExchangeAssetRequest) XXX_DiscardUnknown() { + xxx_messageInfo_SetExchangeAssetRequest.DiscardUnknown(m) } -func (*CancelAllOrdersResponse_Orders) ProtoMessage() {} +var xxx_messageInfo_SetExchangeAssetRequest proto.InternalMessageInfo -func (x *CancelAllOrdersResponse_Orders) ProtoReflect() protoreflect.Message { - mi := &file_rpc_proto_msgTypes[137] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use CancelAllOrdersResponse_Orders.ProtoReflect.Descriptor instead. -func (*CancelAllOrdersResponse_Orders) Descriptor() ([]byte, []int) { - return file_rpc_proto_rawDescGZIP(), []int{73, 0} -} - -func (x *CancelAllOrdersResponse_Orders) GetExchange() string { - if x != nil { - return x.Exchange +func (m *SetExchangeAssetRequest) GetExchange() string { + if m != nil { + return m.Exchange } return "" } -func (x *CancelAllOrdersResponse_Orders) GetOrderStatus() map[string]string { - if x != nil { - return x.OrderStatus +func (m *SetExchangeAssetRequest) GetAsset() string { + if m != nil { + return m.Asset + } + return "" +} + +func (m *SetExchangeAssetRequest) GetEnable() bool { + if m != nil { + return m.Enable + } + return false +} + +type SetExchangeAllPairsRequest struct { + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + Enable bool `protobuf:"varint,2,opt,name=enable,proto3" json:"enable,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *SetExchangeAllPairsRequest) Reset() { *m = SetExchangeAllPairsRequest{} } +func (m *SetExchangeAllPairsRequest) String() string { return proto.CompactTextString(m) } +func (*SetExchangeAllPairsRequest) ProtoMessage() {} +func (*SetExchangeAllPairsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{122} +} + +func (m *SetExchangeAllPairsRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_SetExchangeAllPairsRequest.Unmarshal(m, b) +} +func (m *SetExchangeAllPairsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_SetExchangeAllPairsRequest.Marshal(b, m, deterministic) +} +func (m *SetExchangeAllPairsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_SetExchangeAllPairsRequest.Merge(m, src) +} +func (m *SetExchangeAllPairsRequest) XXX_Size() int { + return xxx_messageInfo_SetExchangeAllPairsRequest.Size(m) +} +func (m *SetExchangeAllPairsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_SetExchangeAllPairsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_SetExchangeAllPairsRequest proto.InternalMessageInfo + +func (m *SetExchangeAllPairsRequest) GetExchange() string { + if m != nil { + return m.Exchange + } + return "" +} + +func (m *SetExchangeAllPairsRequest) GetEnable() bool { + if m != nil { + return m.Enable + } + return false +} + +type UpdateExchangeSupportedPairsRequest struct { + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *UpdateExchangeSupportedPairsRequest) Reset() { *m = UpdateExchangeSupportedPairsRequest{} } +func (m *UpdateExchangeSupportedPairsRequest) String() string { return proto.CompactTextString(m) } +func (*UpdateExchangeSupportedPairsRequest) ProtoMessage() {} +func (*UpdateExchangeSupportedPairsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{123} +} + +func (m *UpdateExchangeSupportedPairsRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_UpdateExchangeSupportedPairsRequest.Unmarshal(m, b) +} +func (m *UpdateExchangeSupportedPairsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_UpdateExchangeSupportedPairsRequest.Marshal(b, m, deterministic) +} +func (m *UpdateExchangeSupportedPairsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_UpdateExchangeSupportedPairsRequest.Merge(m, src) +} +func (m *UpdateExchangeSupportedPairsRequest) XXX_Size() int { + return xxx_messageInfo_UpdateExchangeSupportedPairsRequest.Size(m) +} +func (m *UpdateExchangeSupportedPairsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_UpdateExchangeSupportedPairsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_UpdateExchangeSupportedPairsRequest proto.InternalMessageInfo + +func (m *UpdateExchangeSupportedPairsRequest) GetExchange() string { + if m != nil { + return m.Exchange + } + return "" +} + +type GetExchangeAssetsRequest struct { + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetExchangeAssetsRequest) Reset() { *m = GetExchangeAssetsRequest{} } +func (m *GetExchangeAssetsRequest) String() string { return proto.CompactTextString(m) } +func (*GetExchangeAssetsRequest) ProtoMessage() {} +func (*GetExchangeAssetsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{124} +} + +func (m *GetExchangeAssetsRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetExchangeAssetsRequest.Unmarshal(m, b) +} +func (m *GetExchangeAssetsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetExchangeAssetsRequest.Marshal(b, m, deterministic) +} +func (m *GetExchangeAssetsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetExchangeAssetsRequest.Merge(m, src) +} +func (m *GetExchangeAssetsRequest) XXX_Size() int { + return xxx_messageInfo_GetExchangeAssetsRequest.Size(m) +} +func (m *GetExchangeAssetsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetExchangeAssetsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetExchangeAssetsRequest proto.InternalMessageInfo + +func (m *GetExchangeAssetsRequest) GetExchange() string { + if m != nil { + return m.Exchange + } + return "" +} + +type GetExchangeAssetsResponse struct { + Assets string `protobuf:"bytes,1,opt,name=assets,proto3" json:"assets,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetExchangeAssetsResponse) Reset() { *m = GetExchangeAssetsResponse{} } +func (m *GetExchangeAssetsResponse) String() string { return proto.CompactTextString(m) } +func (*GetExchangeAssetsResponse) ProtoMessage() {} +func (*GetExchangeAssetsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{125} +} + +func (m *GetExchangeAssetsResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetExchangeAssetsResponse.Unmarshal(m, b) +} +func (m *GetExchangeAssetsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetExchangeAssetsResponse.Marshal(b, m, deterministic) +} +func (m *GetExchangeAssetsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetExchangeAssetsResponse.Merge(m, src) +} +func (m *GetExchangeAssetsResponse) XXX_Size() int { + return xxx_messageInfo_GetExchangeAssetsResponse.Size(m) +} +func (m *GetExchangeAssetsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetExchangeAssetsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetExchangeAssetsResponse proto.InternalMessageInfo + +func (m *GetExchangeAssetsResponse) GetAssets() string { + if m != nil { + return m.Assets + } + return "" +} + +type WebsocketGetInfoRequest struct { + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *WebsocketGetInfoRequest) Reset() { *m = WebsocketGetInfoRequest{} } +func (m *WebsocketGetInfoRequest) String() string { return proto.CompactTextString(m) } +func (*WebsocketGetInfoRequest) ProtoMessage() {} +func (*WebsocketGetInfoRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{126} +} + +func (m *WebsocketGetInfoRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_WebsocketGetInfoRequest.Unmarshal(m, b) +} +func (m *WebsocketGetInfoRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_WebsocketGetInfoRequest.Marshal(b, m, deterministic) +} +func (m *WebsocketGetInfoRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_WebsocketGetInfoRequest.Merge(m, src) +} +func (m *WebsocketGetInfoRequest) XXX_Size() int { + return xxx_messageInfo_WebsocketGetInfoRequest.Size(m) +} +func (m *WebsocketGetInfoRequest) XXX_DiscardUnknown() { + xxx_messageInfo_WebsocketGetInfoRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_WebsocketGetInfoRequest proto.InternalMessageInfo + +func (m *WebsocketGetInfoRequest) GetExchange() string { + if m != nil { + return m.Exchange + } + return "" +} + +type WebsocketGetInfoResponse struct { + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + Supported bool `protobuf:"varint,2,opt,name=supported,proto3" json:"supported,omitempty"` + Enabled bool `protobuf:"varint,3,opt,name=enabled,proto3" json:"enabled,omitempty"` + AuthenticatedSupported bool `protobuf:"varint,4,opt,name=authenticated_supported,json=authenticatedSupported,proto3" json:"authenticated_supported,omitempty"` + Authenticated bool `protobuf:"varint,5,opt,name=authenticated,proto3" json:"authenticated,omitempty"` + RunningUrl string `protobuf:"bytes,6,opt,name=running_url,json=runningUrl,proto3" json:"running_url,omitempty"` + ProxyAddress string `protobuf:"bytes,7,opt,name=proxy_address,json=proxyAddress,proto3" json:"proxy_address,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *WebsocketGetInfoResponse) Reset() { *m = WebsocketGetInfoResponse{} } +func (m *WebsocketGetInfoResponse) String() string { return proto.CompactTextString(m) } +func (*WebsocketGetInfoResponse) ProtoMessage() {} +func (*WebsocketGetInfoResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{127} +} + +func (m *WebsocketGetInfoResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_WebsocketGetInfoResponse.Unmarshal(m, b) +} +func (m *WebsocketGetInfoResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_WebsocketGetInfoResponse.Marshal(b, m, deterministic) +} +func (m *WebsocketGetInfoResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_WebsocketGetInfoResponse.Merge(m, src) +} +func (m *WebsocketGetInfoResponse) XXX_Size() int { + return xxx_messageInfo_WebsocketGetInfoResponse.Size(m) +} +func (m *WebsocketGetInfoResponse) XXX_DiscardUnknown() { + xxx_messageInfo_WebsocketGetInfoResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_WebsocketGetInfoResponse proto.InternalMessageInfo + +func (m *WebsocketGetInfoResponse) GetExchange() string { + if m != nil { + return m.Exchange + } + return "" +} + +func (m *WebsocketGetInfoResponse) GetSupported() bool { + if m != nil { + return m.Supported + } + return false +} + +func (m *WebsocketGetInfoResponse) GetEnabled() bool { + if m != nil { + return m.Enabled + } + return false +} + +func (m *WebsocketGetInfoResponse) GetAuthenticatedSupported() bool { + if m != nil { + return m.AuthenticatedSupported + } + return false +} + +func (m *WebsocketGetInfoResponse) GetAuthenticated() bool { + if m != nil { + return m.Authenticated + } + return false +} + +func (m *WebsocketGetInfoResponse) GetRunningUrl() string { + if m != nil { + return m.RunningUrl + } + return "" +} + +func (m *WebsocketGetInfoResponse) GetProxyAddress() string { + if m != nil { + return m.ProxyAddress + } + return "" +} + +type WebsocketSetEnabledRequest struct { + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + Enable bool `protobuf:"varint,2,opt,name=enable,proto3" json:"enable,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *WebsocketSetEnabledRequest) Reset() { *m = WebsocketSetEnabledRequest{} } +func (m *WebsocketSetEnabledRequest) String() string { return proto.CompactTextString(m) } +func (*WebsocketSetEnabledRequest) ProtoMessage() {} +func (*WebsocketSetEnabledRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{128} +} + +func (m *WebsocketSetEnabledRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_WebsocketSetEnabledRequest.Unmarshal(m, b) +} +func (m *WebsocketSetEnabledRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_WebsocketSetEnabledRequest.Marshal(b, m, deterministic) +} +func (m *WebsocketSetEnabledRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_WebsocketSetEnabledRequest.Merge(m, src) +} +func (m *WebsocketSetEnabledRequest) XXX_Size() int { + return xxx_messageInfo_WebsocketSetEnabledRequest.Size(m) +} +func (m *WebsocketSetEnabledRequest) XXX_DiscardUnknown() { + xxx_messageInfo_WebsocketSetEnabledRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_WebsocketSetEnabledRequest proto.InternalMessageInfo + +func (m *WebsocketSetEnabledRequest) GetExchange() string { + if m != nil { + return m.Exchange + } + return "" +} + +func (m *WebsocketSetEnabledRequest) GetEnable() bool { + if m != nil { + return m.Enable + } + return false +} + +type WebsocketGetSubscriptionsRequest struct { + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *WebsocketGetSubscriptionsRequest) Reset() { *m = WebsocketGetSubscriptionsRequest{} } +func (m *WebsocketGetSubscriptionsRequest) String() string { return proto.CompactTextString(m) } +func (*WebsocketGetSubscriptionsRequest) ProtoMessage() {} +func (*WebsocketGetSubscriptionsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{129} +} + +func (m *WebsocketGetSubscriptionsRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_WebsocketGetSubscriptionsRequest.Unmarshal(m, b) +} +func (m *WebsocketGetSubscriptionsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_WebsocketGetSubscriptionsRequest.Marshal(b, m, deterministic) +} +func (m *WebsocketGetSubscriptionsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_WebsocketGetSubscriptionsRequest.Merge(m, src) +} +func (m *WebsocketGetSubscriptionsRequest) XXX_Size() int { + return xxx_messageInfo_WebsocketGetSubscriptionsRequest.Size(m) +} +func (m *WebsocketGetSubscriptionsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_WebsocketGetSubscriptionsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_WebsocketGetSubscriptionsRequest proto.InternalMessageInfo + +func (m *WebsocketGetSubscriptionsRequest) GetExchange() string { + if m != nil { + return m.Exchange + } + return "" +} + +type WebsocketSubscription struct { + Channel string `protobuf:"bytes,1,opt,name=channel,proto3" json:"channel,omitempty"` + Currency string `protobuf:"bytes,2,opt,name=currency,proto3" json:"currency,omitempty"` + Asset string `protobuf:"bytes,3,opt,name=asset,proto3" json:"asset,omitempty"` + Params string `protobuf:"bytes,4,opt,name=params,proto3" json:"params,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *WebsocketSubscription) Reset() { *m = WebsocketSubscription{} } +func (m *WebsocketSubscription) String() string { return proto.CompactTextString(m) } +func (*WebsocketSubscription) ProtoMessage() {} +func (*WebsocketSubscription) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{130} +} + +func (m *WebsocketSubscription) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_WebsocketSubscription.Unmarshal(m, b) +} +func (m *WebsocketSubscription) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_WebsocketSubscription.Marshal(b, m, deterministic) +} +func (m *WebsocketSubscription) XXX_Merge(src proto.Message) { + xxx_messageInfo_WebsocketSubscription.Merge(m, src) +} +func (m *WebsocketSubscription) XXX_Size() int { + return xxx_messageInfo_WebsocketSubscription.Size(m) +} +func (m *WebsocketSubscription) XXX_DiscardUnknown() { + xxx_messageInfo_WebsocketSubscription.DiscardUnknown(m) +} + +var xxx_messageInfo_WebsocketSubscription proto.InternalMessageInfo + +func (m *WebsocketSubscription) GetChannel() string { + if m != nil { + return m.Channel + } + return "" +} + +func (m *WebsocketSubscription) GetCurrency() string { + if m != nil { + return m.Currency + } + return "" +} + +func (m *WebsocketSubscription) GetAsset() string { + if m != nil { + return m.Asset + } + return "" +} + +func (m *WebsocketSubscription) GetParams() string { + if m != nil { + return m.Params + } + return "" +} + +type WebsocketGetSubscriptionsResponse struct { + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + Subscriptions []*WebsocketSubscription `protobuf:"bytes,2,rep,name=subscriptions,proto3" json:"subscriptions,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *WebsocketGetSubscriptionsResponse) Reset() { *m = WebsocketGetSubscriptionsResponse{} } +func (m *WebsocketGetSubscriptionsResponse) String() string { return proto.CompactTextString(m) } +func (*WebsocketGetSubscriptionsResponse) ProtoMessage() {} +func (*WebsocketGetSubscriptionsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{131} +} + +func (m *WebsocketGetSubscriptionsResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_WebsocketGetSubscriptionsResponse.Unmarshal(m, b) +} +func (m *WebsocketGetSubscriptionsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_WebsocketGetSubscriptionsResponse.Marshal(b, m, deterministic) +} +func (m *WebsocketGetSubscriptionsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_WebsocketGetSubscriptionsResponse.Merge(m, src) +} +func (m *WebsocketGetSubscriptionsResponse) XXX_Size() int { + return xxx_messageInfo_WebsocketGetSubscriptionsResponse.Size(m) +} +func (m *WebsocketGetSubscriptionsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_WebsocketGetSubscriptionsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_WebsocketGetSubscriptionsResponse proto.InternalMessageInfo + +func (m *WebsocketGetSubscriptionsResponse) GetExchange() string { + if m != nil { + return m.Exchange + } + return "" +} + +func (m *WebsocketGetSubscriptionsResponse) GetSubscriptions() []*WebsocketSubscription { + if m != nil { + return m.Subscriptions } return nil } -var File_rpc_proto protoreflect.FileDescriptor - -var file_rpc_proto_rawDesc = []byte{ - 0x0a, 0x09, 0x72, 0x70, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x67, 0x63, 0x74, - 0x72, 0x70, 0x63, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, - 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x22, 0x10, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x22, 0xb4, 0x04, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x70, 0x74, 0x69, - 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x70, 0x74, 0x69, 0x6d, 0x65, - 0x12, 0x2f, 0x0a, 0x13, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x65, 0x78, - 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x61, - 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, - 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x5f, 0x65, 0x78, 0x63, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x65, 0x6e, - 0x61, 0x62, 0x6c, 0x65, 0x64, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x12, 0x34, - 0x0a, 0x16, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x66, 0x6f, 0x72, 0x65, 0x78, 0x5f, - 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, - 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x46, 0x6f, 0x72, 0x65, 0x78, 0x50, 0x72, 0x6f, 0x76, - 0x69, 0x64, 0x65, 0x72, 0x12, 0x32, 0x0a, 0x15, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, - 0x66, 0x69, 0x61, 0x74, 0x5f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x13, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x46, 0x69, 0x61, 0x74, - 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x57, 0x0a, 0x10, 0x73, 0x75, 0x62, 0x73, - 0x79, 0x73, 0x74, 0x65, 0x6d, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x06, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x49, - 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x53, 0x75, 0x62, 0x73, - 0x79, 0x73, 0x74, 0x65, 0x6d, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x52, 0x0f, 0x73, 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x12, 0x4e, 0x0a, 0x0d, 0x72, 0x70, 0x63, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, - 0x74, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, - 0x63, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x2e, 0x52, 0x70, 0x63, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x45, 0x6e, - 0x74, 0x72, 0x79, 0x52, 0x0c, 0x72, 0x70, 0x63, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, - 0x73, 0x1a, 0x42, 0x0a, 0x14, 0x53, 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x54, 0x0a, 0x11, 0x52, 0x70, 0x63, 0x45, 0x6e, 0x64, 0x70, - 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, - 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x29, 0x0a, 0x05, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x67, 0x63, - 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x50, 0x43, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, - 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x21, 0x0a, 0x1f, 0x47, - 0x65, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x75, 0x6e, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, - 0x65, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x4e, - 0x0a, 0x14, 0x43, 0x6f, 0x6d, 0x6d, 0x75, 0x6e, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, - 0x65, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, - 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x22, 0x86, - 0x02, 0x0a, 0x20, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x75, 0x6e, 0x69, 0x63, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x7a, 0x0a, 0x16, 0x63, 0x6f, 0x6d, 0x6d, 0x75, 0x6e, 0x69, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x43, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, - 0x43, 0x6f, 0x6d, 0x6d, 0x75, 0x6e, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x6c, - 0x61, 0x79, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x43, 0x6f, - 0x6d, 0x6d, 0x75, 0x6e, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x6c, 0x61, 0x79, - 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x15, 0x63, 0x6f, 0x6d, 0x6d, 0x75, 0x6e, - 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x1a, - 0x66, 0x0a, 0x1a, 0x43, 0x6f, 0x6d, 0x6d, 0x75, 0x6e, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x52, 0x65, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, - 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, - 0x32, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, - 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x75, 0x6e, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x52, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x37, 0x0a, 0x17, 0x47, 0x65, 0x6e, 0x65, 0x72, - 0x69, 0x63, 0x53, 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, - 0x22, 0x1a, 0x0a, 0x18, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x53, 0x75, 0x62, 0x73, 0x79, - 0x73, 0x74, 0x65, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x16, 0x0a, 0x14, - 0x47, 0x65, 0x74, 0x53, 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x22, 0xbe, 0x01, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x53, 0x75, 0x73, 0x62, - 0x73, 0x79, 0x74, 0x65, 0x6d, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x60, - 0x0a, 0x11, 0x73, 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x73, 0x5f, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x67, 0x63, 0x74, 0x72, - 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x75, 0x73, 0x62, 0x73, 0x79, 0x74, 0x65, 0x6d, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, - 0x65, 0x6d, 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x10, - 0x73, 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x1a, 0x43, 0x0a, 0x15, 0x53, 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x73, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x18, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x52, 0x50, 0x43, 0x45, - 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, - 0x4e, 0x0a, 0x0b, 0x52, 0x50, 0x43, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x18, - 0x0a, 0x07, 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x07, 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x6c, 0x69, 0x73, 0x74, - 0x65, 0x6e, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0d, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, - 0xba, 0x01, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x52, 0x50, 0x43, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, - 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x09, 0x65, - 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2e, - 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x50, 0x43, 0x45, 0x6e, - 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, - 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x09, - 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x1a, 0x51, 0x0a, 0x0e, 0x45, 0x6e, 0x64, - 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, - 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x29, 0x0a, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x67, - 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x50, 0x43, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, - 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x38, 0x0a, 0x1a, - 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4e, - 0x61, 0x6d, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, - 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, - 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x22, 0x1d, 0x0a, 0x1b, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, - 0x63, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2f, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, - 0x61, 0x6e, 0x67, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, - 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, - 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0x34, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x45, 0x78, 0x63, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, - 0x0a, 0x09, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x09, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x22, 0x32, 0x0a, 0x15, - 0x47, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4f, 0x54, 0x50, 0x52, 0x65, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x74, 0x70, 0x5f, 0x63, 0x6f, 0x64, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x74, 0x70, 0x43, 0x6f, 0x64, 0x65, - 0x22, 0x18, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4f, - 0x54, 0x50, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xa2, 0x01, 0x0a, 0x17, 0x47, - 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4f, 0x54, 0x50, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x09, 0x6f, 0x74, 0x70, 0x5f, 0x63, 0x6f, - 0x64, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x67, 0x63, 0x74, 0x72, - 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4f, 0x54, - 0x50, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x4f, 0x74, 0x70, 0x43, 0x6f, - 0x64, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x6f, 0x74, 0x70, 0x43, 0x6f, 0x64, - 0x65, 0x73, 0x1a, 0x3b, 0x0a, 0x0d, 0x4f, 0x74, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x73, 0x45, 0x6e, - 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, - 0x34, 0x0a, 0x16, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, - 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x22, 0x5e, 0x0a, 0x0e, 0x50, 0x61, 0x69, 0x72, 0x73, 0x53, 0x75, - 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x61, 0x76, 0x61, 0x69, 0x6c, - 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x69, 0x72, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0e, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x50, 0x61, 0x69, 0x72, 0x73, - 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x5f, 0x70, 0x61, 0x69, 0x72, - 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, - 0x50, 0x61, 0x69, 0x72, 0x73, 0x22, 0x82, 0x04, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x45, 0x78, 0x63, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, - 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x07, 0x76, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x75, 0x73, 0x69, - 0x6e, 0x67, 0x5f, 0x73, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x0c, 0x75, 0x73, 0x69, 0x6e, 0x67, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x12, 0x21, - 0x0a, 0x0c, 0x68, 0x74, 0x74, 0x70, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x68, 0x74, 0x74, 0x70, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, - 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x68, 0x74, 0x74, 0x70, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x61, 0x67, - 0x65, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x68, 0x74, 0x74, 0x70, 0x55, - 0x73, 0x65, 0x72, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x68, 0x74, 0x74, 0x70, - 0x5f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x68, 0x74, - 0x74, 0x70, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x12, 0x27, 0x0a, 0x0f, 0x62, 0x61, 0x73, 0x65, 0x5f, - 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0e, 0x62, 0x61, 0x73, 0x65, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, - 0x12, 0x5f, 0x0a, 0x10, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x73, - 0x73, 0x65, 0x74, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x34, 0x2e, 0x67, 0x63, 0x74, - 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x49, - 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x53, 0x75, 0x70, 0x70, - 0x6f, 0x72, 0x74, 0x65, 0x64, 0x41, 0x73, 0x73, 0x65, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x52, 0x0f, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x41, 0x73, 0x73, 0x65, 0x74, - 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, - 0x65, 0x64, 0x5f, 0x61, 0x70, 0x69, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x61, 0x75, - 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x41, 0x70, 0x69, 0x1a, 0x5a, - 0x0a, 0x14, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x41, 0x73, 0x73, 0x65, 0x74, - 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2c, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, - 0x2e, 0x50, 0x61, 0x69, 0x72, 0x73, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x52, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x77, 0x0a, 0x10, 0x47, 0x65, - 0x74, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, - 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x28, 0x0a, 0x04, 0x70, 0x61, - 0x69, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, - 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, - 0x70, 0x61, 0x69, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x74, 0x79, - 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x73, 0x73, 0x65, 0x74, 0x54, - 0x79, 0x70, 0x65, 0x22, 0x56, 0x0a, 0x0c, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, - 0x61, 0x69, 0x72, 0x12, 0x1c, 0x0a, 0x09, 0x64, 0x65, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x65, 0x72, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x64, 0x65, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x65, - 0x72, 0x12, 0x12, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x62, 0x61, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x6f, 0x74, 0x65, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x71, 0x75, 0x6f, 0x74, 0x65, 0x22, 0x95, 0x02, 0x0a, 0x0e, - 0x54, 0x69, 0x63, 0x6b, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x28, - 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, - 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, - 0x69, 0x72, 0x52, 0x04, 0x70, 0x61, 0x69, 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x6c, 0x61, 0x73, 0x74, - 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, - 0x6c, 0x61, 0x73, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x63, - 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x70, 0x61, 0x69, 0x72, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0c, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, - 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x61, 0x73, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x04, - 0x6c, 0x61, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x69, 0x67, 0x68, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x01, 0x52, 0x04, 0x68, 0x69, 0x67, 0x68, 0x12, 0x10, 0x0a, 0x03, 0x6c, 0x6f, 0x77, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x01, 0x52, 0x03, 0x6c, 0x6f, 0x77, 0x12, 0x10, 0x0a, 0x03, 0x62, 0x69, - 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x01, 0x52, 0x03, 0x62, 0x69, 0x64, 0x12, 0x10, 0x0a, 0x03, - 0x61, 0x73, 0x6b, 0x18, 0x08, 0x20, 0x01, 0x28, 0x01, 0x52, 0x03, 0x61, 0x73, 0x6b, 0x12, 0x16, - 0x0a, 0x06, 0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x01, 0x52, 0x06, - 0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x72, 0x69, 0x63, 0x65, 0x5f, - 0x61, 0x74, 0x68, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x01, 0x52, 0x08, 0x70, 0x72, 0x69, 0x63, 0x65, - 0x41, 0x74, 0x68, 0x22, 0x13, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x72, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x57, 0x0a, 0x07, 0x54, 0x69, 0x63, 0x6b, - 0x65, 0x72, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, - 0x30, 0x0a, 0x07, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x16, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x72, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x07, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x72, - 0x73, 0x22, 0x3f, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x72, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x29, 0x0a, 0x07, 0x74, 0x69, 0x63, 0x6b, 0x65, - 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, - 0x63, 0x2e, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x72, 0x73, 0x52, 0x07, 0x74, 0x69, 0x63, 0x6b, 0x65, - 0x72, 0x73, 0x22, 0x7a, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, - 0x6f, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x28, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, - 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, 0x70, 0x61, 0x69, 0x72, 0x12, - 0x1d, 0x0a, 0x0a, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0x4d, - 0x0a, 0x0d, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x49, 0x74, 0x65, 0x6d, 0x12, - 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x01, 0x52, - 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x72, 0x69, 0x63, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x70, 0x72, 0x69, 0x63, 0x65, 0x12, 0x0e, 0x0a, - 0x02, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x69, 0x64, 0x22, 0xfa, 0x01, - 0x0a, 0x11, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x28, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, - 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, 0x70, 0x61, 0x69, 0x72, 0x12, 0x23, 0x0a, - 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x70, 0x61, 0x69, 0x72, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, - 0x69, 0x72, 0x12, 0x29, 0x0a, 0x04, 0x62, 0x69, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x15, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, - 0x6f, 0x6f, 0x6b, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x04, 0x62, 0x69, 0x64, 0x73, 0x12, 0x29, 0x0a, - 0x04, 0x61, 0x73, 0x6b, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x67, 0x63, - 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x49, 0x74, - 0x65, 0x6d, 0x52, 0x04, 0x61, 0x73, 0x6b, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x6c, 0x61, 0x73, 0x74, - 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, - 0x6c, 0x61, 0x73, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x61, - 0x73, 0x73, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x09, 0x61, 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0x16, 0x0a, 0x14, 0x47, 0x65, - 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x22, 0x63, 0x0a, 0x0a, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x73, - 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x39, 0x0a, 0x0a, - 0x6f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x19, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, - 0x6f, 0x6f, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x0a, 0x6f, 0x72, 0x64, - 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x73, 0x22, 0x4b, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x4f, 0x72, - 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x32, 0x0a, 0x0a, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x73, 0x18, 0x01, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x72, - 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x73, 0x52, 0x0a, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x62, - 0x6f, 0x6f, 0x6b, 0x73, 0x22, 0x33, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, - 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x22, 0x56, 0x0a, 0x07, 0x41, 0x63, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x02, 0x69, 0x64, 0x12, 0x3b, 0x0a, 0x0a, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x69, - 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, - 0x63, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, - 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0a, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x69, 0x65, - 0x73, 0x22, 0x66, 0x0a, 0x13, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x43, 0x75, 0x72, 0x72, - 0x65, 0x6e, 0x63, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x75, 0x72, 0x72, - 0x65, 0x6e, 0x63, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x75, 0x72, 0x72, - 0x65, 0x6e, 0x63, 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, - 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x01, 0x52, 0x04, 0x68, 0x6f, 0x6c, 0x64, 0x22, 0x61, 0x0a, 0x16, 0x47, 0x65, 0x74, - 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, - 0x2b, 0x0a, 0x08, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x0f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x52, 0x08, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x22, 0x12, 0x0a, 0x10, - 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x22, 0x27, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x85, 0x01, 0x0a, 0x10, 0x50, 0x6f, - 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x18, - 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6f, 0x69, 0x6e, - 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6f, 0x69, - 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, - 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, - 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, - 0x65, 0x22, 0x15, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, - 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x4e, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x50, - 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x36, 0x0a, 0x09, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x6f, 0x72, - 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x09, 0x70, - 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x22, 0x1c, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x50, - 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x6e, 0x0a, 0x04, 0x43, 0x6f, 0x69, 0x6e, 0x12, 0x12, - 0x0a, 0x04, 0x63, 0x6f, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x6f, - 0x69, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x01, 0x52, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x18, 0x0a, 0x07, - 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, - 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, - 0x74, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x63, - 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x22, 0x68, 0x0a, 0x12, 0x4f, 0x66, 0x66, 0x6c, 0x69, 0x6e, - 0x65, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x18, 0x0a, 0x07, - 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, - 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, - 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x01, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, - 0x22, 0x4d, 0x0a, 0x11, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x75, - 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x01, 0x52, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, - 0x1e, 0x0a, 0x0a, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x01, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x22, - 0x48, 0x0a, 0x0c, 0x4f, 0x66, 0x66, 0x6c, 0x69, 0x6e, 0x65, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x12, - 0x38, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x66, 0x66, 0x6c, - 0x69, 0x6e, 0x65, 0x43, 0x6f, 0x69, 0x6e, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x09, - 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x22, 0x98, 0x01, 0x0a, 0x0b, 0x4f, 0x6e, - 0x6c, 0x69, 0x6e, 0x65, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x12, 0x34, 0x0a, 0x05, 0x63, 0x6f, 0x69, - 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, - 0x63, 0x2e, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x2e, 0x43, 0x6f, - 0x69, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x05, 0x63, 0x6f, 0x69, 0x6e, 0x73, 0x1a, - 0x53, 0x0a, 0x0a, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, - 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, - 0x2f, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, - 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x43, 0x6f, - 0x69, 0x6e, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x3a, 0x02, 0x38, 0x01, 0x22, 0xcb, 0x04, 0x0a, 0x1b, 0x47, 0x65, 0x74, 0x50, 0x6f, 0x72, 0x74, - 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x0b, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x74, 0x6f, 0x74, - 0x61, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x67, 0x63, 0x74, 0x72, - 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x69, 0x6e, 0x52, 0x0a, 0x63, 0x6f, 0x69, 0x6e, 0x54, 0x6f, 0x74, - 0x61, 0x6c, 0x73, 0x12, 0x31, 0x0a, 0x0d, 0x63, 0x6f, 0x69, 0x6e, 0x73, 0x5f, 0x6f, 0x66, 0x66, - 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x67, 0x63, 0x74, - 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x69, 0x6e, 0x52, 0x0c, 0x63, 0x6f, 0x69, 0x6e, 0x73, 0x4f, - 0x66, 0x66, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x70, 0x0a, 0x15, 0x63, 0x6f, 0x69, 0x6e, 0x73, 0x5f, - 0x6f, 0x66, 0x66, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x18, - 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3c, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, - 0x65, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x53, 0x75, 0x6d, 0x6d, 0x61, - 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x43, 0x6f, 0x69, 0x6e, 0x73, - 0x4f, 0x66, 0x66, 0x6c, 0x69, 0x6e, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x45, 0x6e, - 0x74, 0x72, 0x79, 0x52, 0x13, 0x63, 0x6f, 0x69, 0x6e, 0x73, 0x4f, 0x66, 0x66, 0x6c, 0x69, 0x6e, - 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x2f, 0x0a, 0x0c, 0x63, 0x6f, 0x69, 0x6e, - 0x73, 0x5f, 0x6f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, - 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x69, 0x6e, 0x52, 0x0b, 0x63, 0x6f, - 0x69, 0x6e, 0x73, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x6d, 0x0a, 0x14, 0x63, 0x6f, 0x69, - 0x6e, 0x73, 0x5f, 0x6f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, - 0x79, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3b, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, - 0x2e, 0x47, 0x65, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x53, 0x75, 0x6d, - 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x43, 0x6f, 0x69, - 0x6e, 0x73, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x52, 0x12, 0x63, 0x6f, 0x69, 0x6e, 0x73, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, - 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x1a, 0x5c, 0x0a, 0x18, 0x43, 0x6f, 0x69, 0x6e, - 0x73, 0x4f, 0x66, 0x66, 0x6c, 0x69, 0x6e, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2a, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4f, - 0x66, 0x66, 0x6c, 0x69, 0x6e, 0x65, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x52, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x5a, 0x0a, 0x17, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x4f, - 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x45, 0x6e, 0x74, 0x72, - 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, - 0x6b, 0x65, 0x79, 0x12, 0x29, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x6e, 0x6c, 0x69, - 0x6e, 0x65, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, - 0x38, 0x01, 0x22, 0xe3, 0x01, 0x0a, 0x1a, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, - 0x6c, 0x69, 0x6f, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x63, - 0x6f, 0x69, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x63, 0x6f, 0x69, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, - 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x62, 0x61, - 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x07, 0x62, 0x61, 0x6c, - 0x61, 0x6e, 0x63, 0x65, 0x12, 0x2f, 0x0a, 0x13, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, - 0x64, 0x5f, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x12, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x45, 0x78, 0x63, 0x68, - 0x61, 0x6e, 0x67, 0x65, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6c, 0x64, 0x5f, 0x73, 0x74, - 0x6f, 0x72, 0x61, 0x67, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x63, 0x6f, 0x6c, - 0x64, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x22, 0x1d, 0x0a, 0x1b, 0x41, 0x64, 0x64, 0x50, - 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x78, 0x0a, 0x1d, 0x52, 0x65, 0x6d, 0x6f, 0x76, - 0x65, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, - 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, - 0x73, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6f, 0x69, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, - 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, - 0x6e, 0x22, 0x20, 0x0a, 0x1e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x66, - 0x6f, 0x6c, 0x69, 0x6f, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x1a, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x46, 0x6f, 0x72, 0x65, 0x78, 0x50, - 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, - 0xed, 0x01, 0x0a, 0x0d, 0x46, 0x6f, 0x72, 0x65, 0x78, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, - 0x72, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, - 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x07, 0x76, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x72, 0x65, 0x73, - 0x74, 0x5f, 0x70, 0x6f, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x72, 0x65, 0x73, 0x74, 0x50, 0x6f, 0x6c, 0x6c, 0x69, - 0x6e, 0x67, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x12, 0x17, 0x0a, 0x07, 0x61, 0x70, 0x69, 0x5f, 0x6b, - 0x65, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x70, 0x69, 0x4b, 0x65, 0x79, - 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x70, 0x69, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x6c, 0x65, 0x76, 0x65, - 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x4c, - 0x65, 0x76, 0x65, 0x6c, 0x12, 0x29, 0x0a, 0x10, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x5f, - 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, - 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x22, - 0x5b, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x46, 0x6f, 0x72, 0x65, 0x78, 0x50, 0x72, 0x6f, 0x76, 0x69, - 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x0f, - 0x66, 0x6f, 0x72, 0x65, 0x78, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, - 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x46, - 0x6f, 0x72, 0x65, 0x78, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x0e, 0x66, 0x6f, - 0x72, 0x65, 0x78, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x22, 0x16, 0x0a, 0x14, - 0x47, 0x65, 0x74, 0x46, 0x6f, 0x72, 0x65, 0x78, 0x52, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x22, 0x71, 0x0a, 0x14, 0x46, 0x6f, 0x72, 0x65, 0x78, 0x52, 0x61, 0x74, - 0x65, 0x73, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, - 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x72, 0x6f, 0x6d, - 0x12, 0x0e, 0x0a, 0x02, 0x74, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x74, 0x6f, - 0x12, 0x12, 0x0a, 0x04, 0x72, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x04, - 0x72, 0x61, 0x74, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x65, 0x5f, - 0x72, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0b, 0x69, 0x6e, 0x76, 0x65, - 0x72, 0x73, 0x65, 0x52, 0x61, 0x74, 0x65, 0x22, 0x56, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x46, 0x6f, - 0x72, 0x65, 0x78, 0x52, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x3d, 0x0a, 0x0b, 0x66, 0x6f, 0x72, 0x65, 0x78, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x73, 0x18, - 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x46, - 0x6f, 0x72, 0x65, 0x78, 0x52, 0x61, 0x74, 0x65, 0x73, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, - 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x66, 0x6f, 0x72, 0x65, 0x78, 0x52, 0x61, 0x74, 0x65, 0x73, 0x22, - 0xaf, 0x03, 0x0a, 0x0c, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, - 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x0e, 0x0a, 0x02, - 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x23, 0x0a, 0x0d, - 0x62, 0x61, 0x73, 0x65, 0x5f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0c, 0x62, 0x61, 0x73, 0x65, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, - 0x79, 0x12, 0x25, 0x0a, 0x0e, 0x71, 0x75, 0x6f, 0x74, 0x65, 0x5f, 0x63, 0x75, 0x72, 0x72, 0x65, - 0x6e, 0x63, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x71, 0x75, 0x6f, 0x74, 0x65, - 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x73, 0x73, 0x65, - 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x73, - 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x6f, 0x72, 0x64, 0x65, 0x72, - 0x5f, 0x73, 0x69, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6f, 0x72, 0x64, - 0x65, 0x72, 0x53, 0x69, 0x64, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, - 0x74, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6f, 0x72, 0x64, 0x65, - 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x63, 0x72, - 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, - 0x01, 0x52, 0x05, 0x70, 0x72, 0x69, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, - 0x6e, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x01, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, - 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x18, - 0x0c, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0a, 0x6f, 0x70, 0x65, 0x6e, 0x56, 0x6f, 0x6c, 0x75, 0x6d, - 0x65, 0x12, 0x10, 0x0a, 0x03, 0x66, 0x65, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x01, 0x52, 0x03, - 0x66, 0x65, 0x65, 0x12, 0x2c, 0x0a, 0x06, 0x74, 0x72, 0x61, 0x64, 0x65, 0x73, 0x18, 0x0e, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x72, 0x61, - 0x64, 0x65, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x06, 0x74, 0x72, 0x61, 0x64, 0x65, - 0x73, 0x22, 0xdd, 0x01, 0x0a, 0x0c, 0x54, 0x72, 0x61, 0x64, 0x65, 0x48, 0x69, 0x73, 0x74, 0x6f, - 0x72, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, - 0x69, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x72, 0x69, 0x63, 0x65, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x70, 0x72, 0x69, 0x63, 0x65, 0x12, 0x16, 0x0a, - 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x06, 0x61, - 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, - 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, - 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, - 0x12, 0x1d, 0x0a, 0x0a, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x73, 0x69, 0x64, 0x65, 0x18, 0x07, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x53, 0x69, 0x64, 0x65, 0x12, - 0x10, 0x0a, 0x03, 0x66, 0x65, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x01, 0x52, 0x03, 0x66, 0x65, - 0x65, 0x22, 0x77, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, - 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, - 0x12, 0x28, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, - 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, - 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, 0x70, 0x61, 0x69, 0x72, 0x22, 0x41, 0x0a, 0x11, 0x47, 0x65, - 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x2c, 0x0a, 0x06, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x44, 0x65, - 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x06, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x22, 0x48, 0x0a, - 0x0f, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x19, 0x0a, 0x08, - 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, - 0x6f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x22, 0xd8, 0x01, 0x0a, 0x12, 0x53, 0x75, 0x62, 0x6d, - 0x69, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, - 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x28, 0x0a, 0x04, 0x70, 0x61, - 0x69, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, - 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, - 0x70, 0x61, 0x69, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x04, 0x73, 0x69, 0x64, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x6f, 0x72, 0x64, 0x65, - 0x72, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6f, 0x72, - 0x64, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, - 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x01, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, - 0x14, 0x0a, 0x05, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, - 0x70, 0x72, 0x69, 0x63, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, - 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, - 0x49, 0x64, 0x22, 0x53, 0x0a, 0x13, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x4f, 0x72, 0x64, 0x65, - 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x6f, 0x72, 0x64, - 0x65, 0x72, 0x5f, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x0b, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x64, 0x12, 0x19, 0x0a, 0x08, - 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, - 0x6f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x22, 0x88, 0x01, 0x0a, 0x14, 0x53, 0x69, 0x6d, 0x75, - 0x6c, 0x61, 0x74, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x28, 0x0a, 0x04, - 0x70, 0x61, 0x69, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, - 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, - 0x52, 0x04, 0x70, 0x61, 0x69, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x12, - 0x0a, 0x04, 0x73, 0x69, 0x64, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x73, 0x69, - 0x64, 0x65, 0x22, 0xf2, 0x01, 0x0a, 0x15, 0x53, 0x69, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x65, 0x4f, - 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x06, - 0x6f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x67, - 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x49, - 0x74, 0x65, 0x6d, 0x52, 0x06, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x61, - 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x06, 0x61, 0x6d, 0x6f, - 0x75, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x6d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x5f, 0x70, - 0x72, 0x69, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0c, 0x6d, 0x69, 0x6e, 0x69, - 0x6d, 0x75, 0x6d, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x6d, 0x61, 0x78, 0x69, - 0x6d, 0x75, 0x6d, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, - 0x0c, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x30, 0x0a, - 0x14, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x5f, 0x67, 0x61, 0x69, 0x6e, - 0x5f, 0x6c, 0x6f, 0x73, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x01, 0x52, 0x12, 0x70, 0x65, 0x72, - 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x47, 0x61, 0x69, 0x6e, 0x4c, 0x6f, 0x73, 0x73, 0x12, - 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x8f, 0x01, 0x0a, 0x10, 0x57, 0x68, 0x61, 0x6c, - 0x65, 0x42, 0x6f, 0x6d, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, - 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x28, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, - 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, 0x70, 0x61, - 0x69, 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x69, 0x63, 0x65, 0x5f, 0x74, 0x61, 0x72, 0x67, - 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0b, 0x70, 0x72, 0x69, 0x63, 0x65, 0x54, - 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x64, 0x65, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x73, 0x69, 0x64, 0x65, 0x22, 0xee, 0x01, 0x0a, 0x12, 0x43, 0x61, - 0x6e, 0x63, 0x65, 0x6c, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x1d, 0x0a, 0x0a, - 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x09, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x6f, - 0x72, 0x64, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6f, - 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, - 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, 0x70, 0x61, 0x69, 0x72, - 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, - 0x25, 0x0a, 0x0e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, - 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x41, - 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x64, 0x65, 0x18, 0x07, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x73, 0x69, 0x64, 0x65, 0x22, 0x15, 0x0a, 0x13, 0x43, 0x61, - 0x6e, 0x63, 0x65, 0x6c, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x34, 0x0a, 0x16, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x41, 0x6c, 0x6c, 0x4f, 0x72, - 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, - 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, - 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x22, 0x9c, 0x02, 0x0a, 0x17, 0x43, 0x61, 0x6e, 0x63, - 0x65, 0x6c, 0x41, 0x6c, 0x6c, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x06, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x61, 0x6e, - 0x63, 0x65, 0x6c, 0x41, 0x6c, 0x6c, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x52, 0x06, 0x6f, 0x72, 0x64, - 0x65, 0x72, 0x73, 0x1a, 0xc0, 0x01, 0x0a, 0x06, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x12, 0x1a, - 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x5a, 0x0a, 0x0c, 0x6f, 0x72, - 0x64, 0x65, 0x72, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x37, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, - 0x41, 0x6c, 0x6c, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0b, 0x6f, 0x72, 0x64, 0x65, 0x72, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x1a, 0x3e, 0x0a, 0x10, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, - 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x12, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xbe, 0x01, 0x0a, 0x0f, 0x43, - 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12, 0x1c, - 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, - 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x70, 0x72, 0x69, - 0x63, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x5f, 0x62, 0x69, 0x64, 0x73, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x42, 0x69, 0x64, - 0x73, 0x12, 0x2d, 0x0a, 0x13, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x5f, 0x62, 0x69, 0x64, 0x73, 0x5f, - 0x61, 0x6e, 0x64, 0x5f, 0x61, 0x73, 0x6b, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, - 0x63, 0x68, 0x65, 0x63, 0x6b, 0x42, 0x69, 0x64, 0x73, 0x41, 0x6e, 0x64, 0x41, 0x73, 0x6b, 0x73, - 0x12, 0x29, 0x0a, 0x10, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x5f, 0x61, 0x6d, - 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0f, 0x6f, 0x72, 0x64, 0x65, - 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xf5, 0x01, 0x0a, 0x11, - 0x47, 0x65, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x69, - 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x12, 0x0a, - 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x74, 0x65, - 0x6d, 0x12, 0x42, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x70, - 0x61, 0x72, 0x61, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x63, - 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x61, - 0x72, 0x61, 0x6d, 0x73, 0x52, 0x0f, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x50, - 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12, 0x28, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, - 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, 0x70, 0x61, 0x69, 0x72, 0x12, - 0x16, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x65, 0x63, 0x75, - 0x74, 0x65, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x65, 0x78, 0x65, 0x63, 0x75, - 0x74, 0x65, 0x64, 0x22, 0xe6, 0x01, 0x0a, 0x0f, 0x41, 0x64, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, - 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, - 0x6e, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x12, 0x42, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x64, 0x69, - 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6e, 0x64, 0x69, - 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x52, 0x0f, 0x63, 0x6f, 0x6e, 0x64, - 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12, 0x28, 0x0a, 0x04, 0x70, - 0x61, 0x69, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, - 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, - 0x04, 0x70, 0x61, 0x69, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x74, - 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x73, 0x73, 0x65, 0x74, - 0x54, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x22, 0x0a, 0x10, - 0x41, 0x64, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x69, 0x64, - 0x22, 0x24, 0x0a, 0x12, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x02, 0x69, 0x64, 0x22, 0x15, 0x0a, 0x13, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x46, 0x0a, - 0x28, 0x47, 0x65, 0x74, 0x43, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, - 0x63, 0x79, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, - 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x22, 0xc9, 0x01, 0x0a, 0x29, 0x47, 0x65, 0x74, 0x43, 0x72, 0x79, - 0x70, 0x74, 0x6f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x44, 0x65, 0x70, 0x6f, 0x73, - 0x69, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x5e, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x40, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, - 0x47, 0x65, 0x74, 0x43, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, - 0x79, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, - 0x73, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, - 0x73, 0x65, 0x73, 0x1a, 0x3c, 0x0a, 0x0e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, - 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, - 0x01, 0x22, 0x6c, 0x0a, 0x26, 0x47, 0x65, 0x74, 0x43, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x63, 0x75, - 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x41, 0x64, 0x64, - 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, - 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, - 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x26, 0x0a, 0x0e, 0x63, 0x72, 0x79, 0x70, 0x74, - 0x6f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x22, - 0x43, 0x0a, 0x27, 0x47, 0x65, 0x74, 0x43, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x63, 0x75, 0x72, 0x72, - 0x65, 0x6e, 0x63, 0x79, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, - 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, - 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, - 0x72, 0x65, 0x73, 0x73, 0x22, 0xaf, 0x01, 0x0a, 0x13, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, - 0x77, 0x46, 0x69, 0x61, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, - 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x75, 0x72, 0x72, - 0x65, 0x6e, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x75, 0x72, 0x72, - 0x65, 0x6e, 0x63, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x01, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x20, 0x0a, 0x0b, - 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x26, - 0x0a, 0x0f, 0x62, 0x61, 0x6e, 0x6b, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x69, - 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x62, 0x61, 0x6e, 0x6b, 0x41, 0x63, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x64, 0x22, 0xd6, 0x01, 0x0a, 0x15, 0x57, 0x69, 0x74, 0x68, 0x64, - 0x72, 0x61, 0x77, 0x43, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x18, 0x0a, 0x07, - 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, - 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, - 0x73, 0x5f, 0x74, 0x61, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x64, 0x64, - 0x72, 0x65, 0x73, 0x73, 0x54, 0x61, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x75, 0x72, 0x72, 0x65, - 0x6e, 0x63, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x75, 0x72, 0x72, 0x65, - 0x6e, 0x63, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x01, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x66, - 0x65, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x01, 0x52, 0x03, 0x66, 0x65, 0x65, 0x12, 0x20, 0x0a, - 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, - 0x3a, 0x0a, 0x10, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x2c, 0x0a, 0x1a, 0x57, - 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x42, 0x79, - 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x54, 0x0a, 0x1b, 0x57, 0x69, 0x74, - 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x42, 0x79, 0x49, 0x44, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, - 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, - 0x2e, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, - 0x65, 0x0a, 0x21, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x73, 0x42, 0x79, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, - 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, - 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, - 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x22, 0x79, 0x0a, 0x1d, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, - 0x61, 0x77, 0x61, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x42, 0x79, 0x44, 0x61, 0x74, 0x65, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, - 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, - 0x6e, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x6e, 0x64, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, - 0x69, 0x6d, 0x69, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, - 0x74, 0x22, 0x5b, 0x0a, 0x22, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x73, 0x42, 0x79, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, - 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, - 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x95, - 0x02, 0x0a, 0x17, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x3a, 0x0a, 0x08, 0x65, 0x78, - 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x67, - 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x6c, 0x45, - 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x08, 0x65, 0x78, - 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x38, 0x0a, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, - 0x2e, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, - 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x75, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x75, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0x54, 0x0a, 0x16, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, - 0x61, 0x77, 0x6c, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, - 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0xea, 0x01, 0x0a, - 0x16, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x75, 0x72, 0x72, 0x65, - 0x6e, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x75, 0x72, 0x72, 0x65, - 0x6e, 0x63, 0x79, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, - 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x12, 0x0a, - 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x74, 0x79, 0x70, - 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x66, 0x69, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1b, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x69, 0x61, 0x74, 0x57, 0x69, 0x74, - 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x04, 0x66, 0x69, - 0x61, 0x74, 0x12, 0x35, 0x0a, 0x06, 0x63, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x18, 0x07, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x72, 0x79, 0x70, - 0x74, 0x6f, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x45, 0x76, 0x65, 0x6e, - 0x74, 0x52, 0x06, 0x63, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x22, 0xb8, 0x01, 0x0a, 0x13, 0x46, 0x69, - 0x61, 0x74, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x45, 0x76, 0x65, 0x6e, - 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x62, 0x61, 0x6e, 0x6b, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x62, 0x61, 0x6e, 0x6b, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x21, - 0x0a, 0x0c, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4e, 0x61, 0x6d, - 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6e, 0x75, 0x6d, - 0x62, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x61, 0x63, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x10, 0x0a, 0x03, 0x62, 0x73, 0x62, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x62, 0x73, 0x62, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x77, - 0x69, 0x66, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x77, 0x69, 0x66, 0x74, - 0x12, 0x12, 0x0a, 0x04, 0x69, 0x62, 0x61, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, - 0x69, 0x62, 0x61, 0x6e, 0x22, 0x64, 0x0a, 0x15, 0x43, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x57, 0x69, - 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x18, 0x0a, - 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, - 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x64, 0x64, 0x72, 0x65, - 0x73, 0x73, 0x5f, 0x74, 0x61, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x64, - 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x61, 0x67, 0x12, 0x10, 0x0a, 0x03, 0x66, 0x65, 0x65, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x03, 0x66, 0x65, 0x65, 0x22, 0x31, 0x0a, 0x17, 0x47, 0x65, - 0x74, 0x4c, 0x6f, 0x67, 0x67, 0x65, 0x72, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6c, 0x6f, 0x67, 0x67, 0x65, 0x72, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6c, 0x6f, 0x67, 0x67, 0x65, 0x72, 0x22, 0x6e, 0x0a, - 0x18, 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x67, 0x65, 0x72, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x6e, 0x66, - 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x12, 0x14, 0x0a, - 0x05, 0x64, 0x65, 0x62, 0x75, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x64, 0x65, - 0x62, 0x75, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x77, 0x61, 0x72, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x04, 0x77, 0x61, 0x72, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x47, 0x0a, - 0x17, 0x53, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x67, 0x65, 0x72, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6c, 0x6f, 0x67, 0x67, - 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6c, 0x6f, 0x67, 0x67, 0x65, 0x72, - 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x22, 0x4b, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x45, 0x78, 0x63, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x69, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x14, 0x0a, - 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x73, - 0x73, 0x65, 0x74, 0x22, 0xd8, 0x01, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, - 0x6e, 0x67, 0x65, 0x50, 0x61, 0x69, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x60, 0x0a, 0x10, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x73, - 0x73, 0x65, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x67, 0x63, 0x74, - 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, - 0x61, 0x69, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x53, 0x75, 0x70, - 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x41, 0x73, 0x73, 0x65, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, - 0x79, 0x52, 0x0f, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x41, 0x73, 0x73, 0x65, - 0x74, 0x73, 0x1a, 0x5a, 0x0a, 0x14, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x41, - 0x73, 0x73, 0x65, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, - 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2c, 0x0a, 0x05, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x63, - 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x69, 0x72, 0x73, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, - 0x74, 0x65, 0x64, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x7a, - 0x0a, 0x13, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x69, 0x72, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, - 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, - 0x12, 0x28, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, - 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, - 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, 0x70, 0x61, 0x69, 0x72, 0x22, 0x80, 0x01, 0x0a, 0x19, 0x47, - 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x53, 0x74, 0x72, 0x65, 0x61, - 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, - 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, - 0x61, 0x6e, 0x67, 0x65, 0x12, 0x28, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, - 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, 0x70, 0x61, 0x69, 0x72, 0x12, 0x1d, - 0x0a, 0x0a, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x09, 0x61, 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0x3f, 0x0a, - 0x21, 0x47, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4f, 0x72, 0x64, 0x65, - 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x22, 0x7d, - 0x0a, 0x16, 0x47, 0x65, 0x74, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x72, 0x53, 0x74, 0x72, 0x65, 0x61, - 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, - 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, - 0x61, 0x6e, 0x67, 0x65, 0x12, 0x28, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, - 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, 0x70, 0x61, 0x69, 0x72, 0x12, 0x1d, - 0x0a, 0x0a, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x09, 0x61, 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0x3c, 0x0a, - 0x1e, 0x47, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x54, 0x69, 0x63, 0x6b, - 0x65, 0x72, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x22, 0x99, 0x01, 0x0a, 0x14, - 0x47, 0x65, 0x74, 0x41, 0x75, 0x64, 0x69, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x64, 0x61, - 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x44, - 0x61, 0x74, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x44, 0x61, 0x74, 0x65, 0x12, 0x19, - 0x0a, 0x08, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x62, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x07, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x42, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, - 0x69, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, - 0x16, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, - 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x22, 0x43, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x41, 0x75, - 0x64, 0x69, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x2a, 0x0a, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x12, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x75, 0x64, 0x69, 0x74, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x22, 0xec, 0x01, 0x0a, - 0x19, 0x47, 0x65, 0x74, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x69, 0x63, 0x43, 0x61, 0x6e, 0x64, - 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, - 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, - 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x28, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, - 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, 0x70, 0x61, 0x69, 0x72, - 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, - 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, - 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x69, 0x6d, 0x65, 0x5f, - 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, - 0x74, 0x69, 0x6d, 0x65, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x1d, 0x0a, 0x0a, - 0x65, 0x78, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x09, 0x65, 0x78, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xce, 0x01, 0x0a, 0x1a, - 0x47, 0x65, 0x74, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x69, 0x63, 0x43, 0x61, 0x6e, 0x64, 0x6c, - 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, - 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, - 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x28, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, - 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, 0x70, 0x61, 0x69, 0x72, - 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x74, 0x65, - 0x72, 0x76, 0x61, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x65, - 0x72, 0x76, 0x61, 0x6c, 0x12, 0x26, 0x0a, 0x06, 0x63, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x18, 0x05, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x61, - 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x06, 0x63, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x22, 0x84, 0x01, 0x0a, - 0x06, 0x43, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6c, - 0x6f, 0x77, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x03, 0x6c, 0x6f, 0x77, 0x12, 0x12, 0x0a, - 0x04, 0x68, 0x69, 0x67, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x04, 0x68, 0x69, 0x67, - 0x68, 0x12, 0x12, 0x0a, 0x04, 0x6f, 0x70, 0x65, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, - 0x04, 0x6f, 0x70, 0x65, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x76, - 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x01, 0x52, 0x06, 0x76, 0x6f, 0x6c, - 0x75, 0x6d, 0x65, 0x22, 0x78, 0x0a, 0x0a, 0x41, 0x75, 0x64, 0x69, 0x74, 0x45, 0x76, 0x65, 0x6e, - 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, - 0x69, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x64, 0x65, 0x6e, 0x74, - 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, - 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0x62, 0x0a, - 0x09, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x55, 0x55, - 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x55, 0x55, 0x49, 0x44, 0x12, 0x12, - 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, - 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x72, - 0x75, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x65, 0x78, 0x74, 0x52, 0x75, - 0x6e, 0x22, 0x44, 0x0a, 0x17, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x45, 0x78, - 0x65, 0x63, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x29, 0x0a, 0x06, - 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x67, - 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x52, - 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x22, 0x41, 0x0a, 0x14, 0x47, 0x43, 0x54, 0x53, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x53, 0x74, 0x6f, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x29, 0x0a, 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x11, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x52, 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x22, 0x19, 0x0a, 0x17, 0x47, 0x43, - 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x53, 0x74, 0x6f, 0x70, 0x41, 0x6c, 0x6c, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x18, 0x0a, 0x16, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, - 0x19, 0x0a, 0x17, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x4c, 0x69, 0x73, 0x74, - 0x41, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xa8, 0x01, 0x0a, 0x16, 0x47, - 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x5f, - 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, - 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x44, 0x61, 0x74, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x12, 0x1a, 0x0a, 0x08, 0x61, - 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x61, - 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x6f, 0x76, 0x65, 0x72, 0x77, - 0x72, 0x69, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x6f, 0x76, 0x65, 0x72, - 0x77, 0x72, 0x69, 0x74, 0x65, 0x22, 0x47, 0x0a, 0x1a, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x52, 0x65, 0x61, 0x64, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x29, 0x0a, 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x43, 0x54, - 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x52, 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x22, 0x42, - 0x0a, 0x15, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x51, 0x75, 0x65, 0x72, 0x79, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x29, 0x0a, 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, - 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, - 0x2e, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x52, 0x06, 0x73, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x22, 0x4a, 0x0a, 0x18, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x41, - 0x75, 0x74, 0x6f, 0x4c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, - 0x0a, 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x5e, - 0x0a, 0x17, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x12, 0x2b, 0x0a, 0x07, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x43, 0x54, 0x53, - 0x63, 0x72, 0x69, 0x70, 0x74, 0x52, 0x07, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x73, 0x22, 0x6f, - 0x0a, 0x16, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x51, 0x75, 0x65, 0x72, 0x79, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x12, 0x29, 0x0a, 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x11, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x52, 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x64, - 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, - 0x46, 0x0a, 0x18, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x47, 0x65, 0x6e, 0x65, - 0x72, 0x69, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x32, 0xcd, 0x38, 0x0a, 0x0e, 0x47, 0x6f, 0x43, 0x72, - 0x79, 0x70, 0x74, 0x6f, 0x54, 0x72, 0x61, 0x64, 0x65, 0x72, 0x12, 0x4f, 0x0a, 0x07, 0x47, 0x65, - 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x16, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, - 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, - 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x13, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0d, 0x12, 0x0b, - 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x69, 0x6e, 0x66, 0x6f, 0x12, 0x67, 0x0a, 0x0d, 0x47, - 0x65, 0x74, 0x53, 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x1c, 0x2e, 0x67, - 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, - 0x65, 0x6d, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x67, 0x63, 0x74, - 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x75, 0x73, 0x62, 0x73, 0x79, 0x74, 0x65, 0x6d, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x19, 0x82, 0xd3, 0xe4, 0x93, 0x02, - 0x13, 0x12, 0x11, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x73, 0x75, 0x62, 0x73, 0x79, 0x73, - 0x74, 0x65, 0x6d, 0x73, 0x12, 0x71, 0x0a, 0x0f, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x53, 0x75, - 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x12, 0x1f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, - 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x53, 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, - 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, - 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x53, 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, - 0x65, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1b, 0x82, 0xd3, 0xe4, 0x93, - 0x02, 0x15, 0x12, 0x13, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x75, - 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x12, 0x73, 0x0a, 0x10, 0x44, 0x69, 0x73, 0x61, 0x62, - 0x6c, 0x65, 0x53, 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x12, 0x1f, 0x2e, 0x67, 0x63, - 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x53, 0x75, 0x62, 0x73, - 0x79, 0x73, 0x74, 0x65, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x67, - 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x53, 0x75, 0x62, - 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1c, - 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x16, 0x12, 0x14, 0x2f, 0x76, 0x31, 0x2f, 0x64, 0x69, 0x73, 0x61, - 0x62, 0x6c, 0x65, 0x73, 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x12, 0x6f, 0x0a, 0x0f, - 0x47, 0x65, 0x74, 0x52, 0x50, 0x43, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x12, - 0x1e, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x50, 0x43, 0x45, - 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x1f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x50, 0x43, 0x45, - 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x1b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x15, 0x12, 0x13, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, - 0x74, 0x72, 0x70, 0x63, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x93, 0x01, - 0x0a, 0x18, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x75, 0x6e, 0x69, 0x63, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x12, 0x27, 0x2e, 0x67, 0x63, 0x74, - 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x75, 0x6e, 0x69, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, - 0x43, 0x6f, 0x6d, 0x6d, 0x75, 0x6e, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x6c, - 0x61, 0x79, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x24, 0x82, - 0xd3, 0xe4, 0x93, 0x02, 0x1e, 0x12, 0x1c, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x63, 0x6f, - 0x6d, 0x6d, 0x75, 0x6e, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x72, 0x65, 0x6c, 0x61, 0x79, - 0x65, 0x72, 0x73, 0x12, 0x63, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, - 0x67, 0x65, 0x73, 0x12, 0x1b, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, - 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1c, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x78, 0x63, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x18, - 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x12, 0x10, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x65, - 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x12, 0x7a, 0x0a, 0x0f, 0x44, 0x69, 0x73, 0x61, - 0x62, 0x6c, 0x65, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x22, 0x2e, 0x67, 0x63, - 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x45, 0x78, 0x63, 0x68, - 0x61, 0x6e, 0x67, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x23, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, - 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x22, 0x13, 0x2f, 0x76, - 0x31, 0x2f, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, - 0x65, 0x3a, 0x01, 0x2a, 0x12, 0x73, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, - 0x6e, 0x67, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x22, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, - 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, - 0x4e, 0x61, 0x6d, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x67, 0x63, - 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, - 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1b, 0x82, 0xd3, - 0xe4, 0x93, 0x02, 0x15, 0x12, 0x13, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x65, 0x78, 0x63, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x69, 0x6e, 0x66, 0x6f, 0x12, 0x73, 0x0a, 0x12, 0x47, 0x65, 0x74, - 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4f, 0x54, 0x50, 0x43, 0x6f, 0x64, 0x65, 0x12, - 0x22, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, - 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, - 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4f, 0x54, 0x50, 0x52, 0x65, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x1a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x14, 0x12, 0x12, 0x2f, 0x76, 0x31, 0x2f, - 0x67, 0x65, 0x74, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x6f, 0x74, 0x70, 0x12, 0x73, - 0x0a, 0x13, 0x47, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4f, 0x54, 0x50, - 0x43, 0x6f, 0x64, 0x65, 0x73, 0x12, 0x1e, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, - 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4f, 0x54, 0x50, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, - 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4f, 0x54, 0x50, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x15, 0x12, 0x13, - 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x6f, - 0x74, 0x70, 0x73, 0x12, 0x78, 0x0a, 0x0e, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x45, 0x78, 0x63, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x22, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, - 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4e, 0x61, - 0x6d, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x67, 0x63, 0x74, 0x72, - 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, - 0x67, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1d, - 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x22, 0x12, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x6e, 0x61, 0x62, - 0x6c, 0x65, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x3a, 0x01, 0x2a, 0x12, 0x57, 0x0a, - 0x09, 0x47, 0x65, 0x74, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x72, 0x12, 0x18, 0x2e, 0x67, 0x63, 0x74, - 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x72, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x69, - 0x63, 0x6b, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x18, 0x82, 0xd3, - 0xe4, 0x93, 0x02, 0x12, 0x22, 0x0d, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x74, 0x69, 0x63, - 0x6b, 0x65, 0x72, 0x3a, 0x01, 0x2a, 0x12, 0x5b, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x54, 0x69, 0x63, - 0x6b, 0x65, 0x72, 0x73, 0x12, 0x19, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, - 0x74, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x1a, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x69, 0x63, 0x6b, - 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x16, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x10, 0x12, 0x0e, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x74, 0x69, 0x63, 0x6b, - 0x65, 0x72, 0x73, 0x12, 0x63, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, - 0x6f, 0x6f, 0x6b, 0x12, 0x1b, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, - 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x19, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, - 0x6f, 0x6f, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1b, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x15, 0x22, 0x10, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x6f, 0x72, 0x64, 0x65, - 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x3a, 0x01, 0x2a, 0x12, 0x67, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x4f, - 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x73, 0x12, 0x1c, 0x2e, 0x67, 0x63, 0x74, 0x72, - 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, - 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x19, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x12, 0x11, - 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, - 0x73, 0x12, 0x6b, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, - 0x6e, 0x66, 0x6f, 0x12, 0x1d, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, - 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x41, - 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x1a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x14, 0x12, 0x12, 0x2f, 0x76, 0x31, 0x2f, - 0x67, 0x65, 0x74, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x69, 0x6e, 0x66, 0x6f, 0x12, 0x79, - 0x0a, 0x14, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, - 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x1d, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, - 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, - 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x12, 0x18, 0x2f, - 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x69, 0x6e, 0x66, - 0x6f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x30, 0x01, 0x12, 0x57, 0x0a, 0x09, 0x47, 0x65, 0x74, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, - 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x19, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x15, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x0f, 0x12, 0x0d, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x63, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x12, 0x63, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, - 0x69, 0x6f, 0x12, 0x1b, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x50, - 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x1c, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x6f, 0x72, 0x74, - 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x18, 0x82, - 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x12, 0x10, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x70, 0x6f, - 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x12, 0x7f, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x50, 0x6f, - 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x22, - 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x66, - 0x6f, 0x6c, 0x69, 0x6f, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x50, - 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x12, - 0x17, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, - 0x6f, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x82, 0x01, 0x0a, 0x13, 0x41, 0x64, 0x64, - 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, - 0x12, 0x22, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x72, - 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, - 0x64, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x22, 0x82, 0xd3, 0xe4, 0x93, 0x02, - 0x1c, 0x22, 0x17, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x64, 0x64, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, - 0x6c, 0x69, 0x6f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x3a, 0x01, 0x2a, 0x12, 0x8e, 0x01, - 0x0a, 0x16, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, - 0x6f, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x25, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, - 0x63, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, - 0x6f, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x26, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x50, - 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x25, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1f, 0x22, - 0x1a, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x66, - 0x6f, 0x6c, 0x69, 0x6f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x3a, 0x01, 0x2a, 0x12, 0x77, - 0x0a, 0x11, 0x47, 0x65, 0x74, 0x46, 0x6f, 0x72, 0x65, 0x78, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, - 0x65, 0x72, 0x73, 0x12, 0x20, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, - 0x46, 0x6f, 0x72, 0x65, 0x78, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, - 0x65, 0x74, 0x46, 0x6f, 0x72, 0x65, 0x78, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, - 0x12, 0x15, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x66, 0x6f, 0x72, 0x65, 0x78, 0x70, 0x72, - 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x12, 0x67, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x46, 0x6f, - 0x72, 0x65, 0x78, 0x52, 0x61, 0x74, 0x65, 0x73, 0x12, 0x1c, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, - 0x63, 0x2e, 0x47, 0x65, 0x74, 0x46, 0x6f, 0x72, 0x65, 0x78, 0x52, 0x61, 0x74, 0x65, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, - 0x47, 0x65, 0x74, 0x46, 0x6f, 0x72, 0x65, 0x78, 0x52, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x19, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x12, 0x11, 0x2f, - 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x66, 0x6f, 0x72, 0x65, 0x78, 0x72, 0x61, 0x74, 0x65, 0x73, - 0x12, 0x5a, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x12, 0x18, 0x2e, - 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, - 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x18, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x22, 0x0d, 0x2f, 0x76, 0x31, 0x2f, - 0x67, 0x65, 0x74, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x3a, 0x01, 0x2a, 0x12, 0x52, 0x0a, 0x08, - 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x17, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, - 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, - 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x22, 0x17, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x11, 0x22, - 0x0c, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x3a, 0x01, 0x2a, - 0x12, 0x62, 0x0a, 0x0b, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, - 0x1a, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x4f, - 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x67, 0x63, - 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x14, - 0x22, 0x0f, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x6f, 0x72, 0x64, 0x65, - 0x72, 0x3a, 0x01, 0x2a, 0x12, 0x6a, 0x0a, 0x0d, 0x53, 0x69, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x65, - 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x1c, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, - 0x69, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x6d, - 0x75, 0x6c, 0x61, 0x74, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x1c, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x16, 0x22, 0x11, 0x2f, 0x76, 0x31, 0x2f, - 0x73, 0x69, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x65, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x3a, 0x01, 0x2a, - 0x12, 0x5e, 0x0a, 0x09, 0x57, 0x68, 0x61, 0x6c, 0x65, 0x42, 0x6f, 0x6d, 0x62, 0x12, 0x18, 0x2e, - 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x68, 0x61, 0x6c, 0x65, 0x42, 0x6f, 0x6d, 0x62, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, - 0x2e, 0x53, 0x69, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x18, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x22, 0x0d, - 0x2f, 0x76, 0x31, 0x2f, 0x77, 0x68, 0x61, 0x6c, 0x65, 0x62, 0x6f, 0x6d, 0x62, 0x3a, 0x01, 0x2a, - 0x12, 0x62, 0x0a, 0x0b, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, - 0x1a, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x4f, - 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x67, 0x63, - 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x4f, 0x72, 0x64, 0x65, 0x72, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x14, - 0x22, 0x0f, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x6f, 0x72, 0x64, 0x65, - 0x72, 0x3a, 0x01, 0x2a, 0x12, 0x72, 0x0a, 0x0f, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x41, 0x6c, - 0x6c, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x12, 0x1e, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, - 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x41, 0x6c, 0x6c, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, - 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x41, 0x6c, 0x6c, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, - 0x22, 0x13, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x61, 0x6c, 0x6c, 0x6f, - 0x72, 0x64, 0x65, 0x72, 0x73, 0x3a, 0x01, 0x2a, 0x12, 0x57, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x18, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, - 0x65, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x19, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x76, 0x65, 0x6e, - 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x15, 0x82, 0xd3, 0xe4, 0x93, - 0x02, 0x0f, 0x12, 0x0d, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x65, 0x76, 0x65, 0x6e, 0x74, - 0x73, 0x12, 0x56, 0x0a, 0x08, 0x41, 0x64, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x17, 0x2e, - 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, - 0x41, 0x64, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x17, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x11, 0x22, 0x0c, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x64, - 0x64, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x3a, 0x01, 0x2a, 0x12, 0x62, 0x0a, 0x0b, 0x52, 0x65, 0x6d, - 0x6f, 0x76, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1a, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, - 0x63, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, - 0x6d, 0x6f, 0x76, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x1a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x14, 0x22, 0x0f, 0x2f, 0x76, 0x31, 0x2f, 0x72, - 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x3a, 0x01, 0x2a, 0x12, 0xb2, 0x01, - 0x0a, 0x21, 0x47, 0x65, 0x74, 0x43, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x63, 0x75, 0x72, 0x72, 0x65, - 0x6e, 0x63, 0x79, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, - 0x73, 0x65, 0x73, 0x12, 0x30, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, - 0x43, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x44, 0x65, - 0x70, 0x6f, 0x73, 0x69, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, - 0x65, 0x74, 0x43, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, - 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x28, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x22, - 0x22, 0x1d, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x63, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x64, - 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x3a, - 0x01, 0x2a, 0x12, 0xaa, 0x01, 0x0a, 0x1f, 0x47, 0x65, 0x74, 0x43, 0x72, 0x79, 0x70, 0x74, 0x6f, - 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x41, - 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x2e, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, - 0x47, 0x65, 0x74, 0x43, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, - 0x79, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, - 0x47, 0x65, 0x74, 0x43, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, - 0x79, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x26, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x20, 0x22, - 0x1b, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x63, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x64, 0x65, - 0x70, 0x6f, 0x73, 0x69, 0x74, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x3a, 0x01, 0x2a, 0x12, - 0x6c, 0x0a, 0x11, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x46, 0x69, 0x61, 0x74, 0x46, - 0x75, 0x6e, 0x64, 0x73, 0x12, 0x1b, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x69, - 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x46, 0x69, 0x61, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x18, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x69, 0x74, 0x68, 0x64, - 0x72, 0x61, 0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x20, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x1a, 0x22, 0x15, 0x2f, 0x76, 0x31, 0x2f, 0x77, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, - 0x77, 0x66, 0x69, 0x61, 0x74, 0x66, 0x75, 0x6e, 0x64, 0x73, 0x3a, 0x01, 0x2a, 0x12, 0x8b, 0x01, - 0x0a, 0x1b, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x43, 0x72, 0x79, 0x70, 0x74, 0x6f, - 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x46, 0x75, 0x6e, 0x64, 0x73, 0x12, 0x1d, 0x2e, - 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x43, - 0x72, 0x79, 0x70, 0x74, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x67, - 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x33, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2d, 0x22, 0x28, - 0x2f, 0x76, 0x31, 0x2f, 0x77, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x69, 0x74, 0x68, 0x64, - 0x72, 0x61, 0x77, 0x63, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x66, 0x75, 0x6e, 0x64, 0x73, 0x77, 0x66, - 0x69, 0x61, 0x74, 0x66, 0x75, 0x6e, 0x64, 0x73, 0x3a, 0x01, 0x2a, 0x12, 0x82, 0x01, 0x0a, 0x13, - 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x42, - 0x79, 0x49, 0x44, 0x12, 0x22, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x69, 0x74, - 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x42, 0x79, 0x49, 0x44, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, - 0x2e, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x42, 0x79, 0x49, 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x22, 0x82, 0xd3, - 0xe4, 0x93, 0x02, 0x1c, 0x22, 0x17, 0x2f, 0x76, 0x31, 0x2f, 0x77, 0x69, 0x74, 0x68, 0x64, 0x72, - 0x61, 0x77, 0x61, 0x6c, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x62, 0x79, 0x69, 0x64, 0x3a, 0x01, 0x2a, - 0x12, 0x97, 0x01, 0x0a, 0x1a, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x73, 0x42, 0x79, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, - 0x29, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, - 0x77, 0x61, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x42, 0x79, 0x45, 0x78, 0x63, 0x68, 0x61, - 0x6e, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x67, 0x63, 0x74, - 0x72, 0x70, 0x63, 0x2e, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x45, 0x76, - 0x65, 0x6e, 0x74, 0x73, 0x42, 0x79, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x22, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1c, 0x22, 0x17, - 0x2f, 0x76, 0x31, 0x2f, 0x77, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x65, 0x76, - 0x65, 0x6e, 0x74, 0x62, 0x79, 0x69, 0x64, 0x3a, 0x01, 0x2a, 0x12, 0x91, 0x01, 0x0a, 0x16, 0x57, - 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x42, - 0x79, 0x44, 0x61, 0x74, 0x65, 0x12, 0x25, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x57, - 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x42, - 0x79, 0x44, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x67, - 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x42, 0x79, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x24, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1e, - 0x22, 0x19, 0x2f, 0x76, 0x31, 0x2f, 0x77, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, - 0x65, 0x76, 0x65, 0x6e, 0x74, 0x62, 0x79, 0x64, 0x61, 0x74, 0x65, 0x3a, 0x01, 0x2a, 0x12, 0x73, - 0x0a, 0x10, 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x67, 0x65, 0x72, 0x44, 0x65, 0x74, 0x61, 0x69, - 0x6c, 0x73, 0x12, 0x1f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4c, - 0x6f, 0x67, 0x67, 0x65, 0x72, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, - 0x4c, 0x6f, 0x67, 0x67, 0x65, 0x72, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1c, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x16, 0x12, 0x14, 0x2f, - 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x6c, 0x6f, 0x67, 0x67, 0x65, 0x72, 0x64, 0x65, 0x74, 0x61, - 0x69, 0x6c, 0x73, 0x12, 0x76, 0x0a, 0x10, 0x53, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x67, 0x65, 0x72, - 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x1f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, - 0x2e, 0x53, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x67, 0x65, 0x72, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, - 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x67, 0x65, 0x72, 0x44, 0x65, 0x74, 0x61, 0x69, - 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1f, 0x82, 0xd3, 0xe4, 0x93, - 0x02, 0x19, 0x22, 0x14, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x65, 0x74, 0x6c, 0x6f, 0x67, 0x67, 0x65, - 0x72, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x3a, 0x01, 0x2a, 0x12, 0x76, 0x0a, 0x10, 0x47, - 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x69, 0x72, 0x73, 0x12, - 0x1f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, - 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x69, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x20, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x78, 0x63, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x69, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x1f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x22, 0x14, 0x2f, 0x76, 0x31, 0x2f, - 0x67, 0x65, 0x74, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x70, 0x61, 0x69, 0x72, 0x73, - 0x3a, 0x01, 0x2a, 0x12, 0x79, 0x0a, 0x12, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x45, 0x78, 0x63, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x69, 0x72, 0x12, 0x1b, 0x2e, 0x67, 0x63, 0x74, 0x72, - 0x70, 0x63, 0x2e, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x69, 0x72, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, - 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4e, - 0x61, 0x6d, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x21, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x1b, 0x22, 0x16, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x65, - 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x70, 0x61, 0x69, 0x72, 0x3a, 0x01, 0x2a, 0x12, 0x7b, - 0x0a, 0x13, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, - 0x65, 0x50, 0x61, 0x69, 0x72, 0x12, 0x1b, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x45, - 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x69, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, - 0x72, 0x69, 0x63, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x22, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1c, 0x22, - 0x17, 0x2f, 0x76, 0x31, 0x2f, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x65, 0x78, 0x63, 0x68, - 0x61, 0x6e, 0x67, 0x65, 0x70, 0x61, 0x69, 0x72, 0x3a, 0x01, 0x2a, 0x12, 0x74, 0x0a, 0x12, 0x47, - 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x53, 0x74, 0x72, 0x65, 0x61, - 0x6d, 0x12, 0x21, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x72, - 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x72, - 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x1e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x12, 0x16, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, - 0x6f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x30, - 0x01, 0x12, 0x8c, 0x01, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, - 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, - 0x12, 0x29, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x78, 0x63, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x53, 0x74, - 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x67, 0x63, - 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x26, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x20, 0x12, 0x1e, - 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x6f, - 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x30, 0x01, - 0x12, 0x68, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x72, 0x53, 0x74, 0x72, - 0x65, 0x61, 0x6d, 0x12, 0x1e, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, - 0x54, 0x69, 0x63, 0x6b, 0x65, 0x72, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x69, 0x63, - 0x6b, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1b, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x15, 0x12, 0x13, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x74, 0x69, 0x63, 0x6b, - 0x65, 0x72, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x30, 0x01, 0x12, 0x80, 0x01, 0x0a, 0x17, 0x47, - 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x72, - 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x26, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, - 0x47, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x54, 0x69, 0x63, 0x6b, 0x65, - 0x72, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, - 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x72, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x23, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1d, 0x12, 0x1b, - 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x74, - 0x69, 0x63, 0x6b, 0x65, 0x72, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x30, 0x01, 0x12, 0x67, 0x0a, - 0x0d, 0x47, 0x65, 0x74, 0x41, 0x75, 0x64, 0x69, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1c, - 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x75, 0x64, 0x69, 0x74, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x67, - 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x75, 0x64, 0x69, 0x74, 0x45, 0x76, - 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x19, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x13, 0x12, 0x11, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x61, 0x75, 0x64, 0x69, - 0x74, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x74, 0x0a, 0x10, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x12, 0x1f, 0x2e, 0x67, 0x63, 0x74, - 0x72, 0x70, 0x63, 0x2e, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x45, 0x78, 0x65, - 0x63, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x67, 0x63, - 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x47, 0x65, - 0x6e, 0x65, 0x72, 0x69, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1d, 0x82, - 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x12, 0x15, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x63, 0x74, 0x73, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x2f, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x12, 0x74, 0x0a, 0x0f, - 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x12, - 0x1e, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x20, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x1f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x22, 0x14, 0x2f, 0x76, 0x31, 0x2f, 0x67, - 0x63, 0x74, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x2f, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x3a, - 0x01, 0x2a, 0x12, 0x78, 0x0a, 0x13, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x52, - 0x65, 0x61, 0x64, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x12, 0x22, 0x2e, 0x67, 0x63, 0x74, 0x72, - 0x70, 0x63, 0x2e, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x52, 0x65, 0x61, 0x64, - 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, - 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, - 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1d, 0x82, - 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x22, 0x12, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x63, 0x74, 0x73, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x2f, 0x72, 0x65, 0x61, 0x64, 0x3a, 0x01, 0x2a, 0x12, 0x70, 0x0a, 0x0f, - 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, - 0x1e, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x1f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x1c, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x16, 0x12, 0x14, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x63, - 0x74, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x6c, - 0x0a, 0x0e, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x51, 0x75, 0x65, 0x72, 0x79, - 0x12, 0x1d, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x1e, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x1b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x15, 0x12, 0x13, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x63, 0x74, - 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x2f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x12, 0x6e, 0x0a, 0x0d, - 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x53, 0x74, 0x6f, 0x70, 0x12, 0x1c, 0x2e, - 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, - 0x53, 0x74, 0x6f, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x67, 0x63, - 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x47, 0x65, - 0x6e, 0x65, 0x72, 0x69, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1d, 0x82, - 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x22, 0x12, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x63, 0x74, 0x73, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x2f, 0x73, 0x74, 0x6f, 0x70, 0x3a, 0x01, 0x2a, 0x12, 0x74, 0x0a, 0x10, - 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x53, 0x74, 0x6f, 0x70, 0x41, 0x6c, 0x6c, - 0x12, 0x1f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x53, 0x74, 0x6f, 0x70, 0x41, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x20, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x43, 0x54, 0x53, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x22, 0x12, 0x2f, 0x76, 0x31, - 0x2f, 0x67, 0x63, 0x74, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x2f, 0x73, 0x74, 0x6f, 0x70, 0x3a, - 0x01, 0x2a, 0x12, 0x73, 0x0a, 0x10, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x4c, - 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, 0x12, 0x1f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, - 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, - 0x2e, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, - 0x22, 0x12, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x63, 0x74, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x2f, - 0x73, 0x74, 0x6f, 0x70, 0x3a, 0x01, 0x2a, 0x12, 0x80, 0x01, 0x0a, 0x17, 0x47, 0x43, 0x54, 0x53, - 0x63, 0x72, 0x69, 0x70, 0x74, 0x41, 0x75, 0x74, 0x6f, 0x4c, 0x6f, 0x61, 0x64, 0x54, 0x6f, 0x67, - 0x67, 0x6c, 0x65, 0x12, 0x20, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x43, 0x54, - 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x41, 0x75, 0x74, 0x6f, 0x4c, 0x6f, 0x61, 0x64, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, - 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x21, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1b, 0x22, - 0x16, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x63, 0x74, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x2f, 0x61, - 0x75, 0x74, 0x6f, 0x6c, 0x6f, 0x61, 0x64, 0x3a, 0x01, 0x2a, 0x12, 0x7b, 0x0a, 0x12, 0x47, 0x65, - 0x74, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x69, 0x63, 0x43, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x73, - 0x12, 0x21, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x48, 0x69, 0x73, - 0x74, 0x6f, 0x72, 0x69, 0x63, 0x43, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, - 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x69, 0x63, 0x43, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x12, - 0x16, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x69, 0x63, - 0x63, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +type WebsocketSetProxyRequest struct { + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + Proxy string `protobuf:"bytes,2,opt,name=proxy,proto3" json:"proxy,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -var ( - file_rpc_proto_rawDescOnce sync.Once - file_rpc_proto_rawDescData = file_rpc_proto_rawDesc -) - -func file_rpc_proto_rawDescGZIP() []byte { - file_rpc_proto_rawDescOnce.Do(func() { - file_rpc_proto_rawDescData = protoimpl.X.CompressGZIP(file_rpc_proto_rawDescData) - }) - return file_rpc_proto_rawDescData +func (m *WebsocketSetProxyRequest) Reset() { *m = WebsocketSetProxyRequest{} } +func (m *WebsocketSetProxyRequest) String() string { return proto.CompactTextString(m) } +func (*WebsocketSetProxyRequest) ProtoMessage() {} +func (*WebsocketSetProxyRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{132} } -var file_rpc_proto_msgTypes = make([]protoimpl.MessageInfo, 141) -var file_rpc_proto_goTypes = []interface{}{ - (*GetInfoRequest)(nil), // 0: gctrpc.GetInfoRequest - (*GetInfoResponse)(nil), // 1: gctrpc.GetInfoResponse - (*GetCommunicationRelayersRequest)(nil), // 2: gctrpc.GetCommunicationRelayersRequest - (*CommunicationRelayer)(nil), // 3: gctrpc.CommunicationRelayer - (*GetCommunicationRelayersResponse)(nil), // 4: gctrpc.GetCommunicationRelayersResponse - (*GenericSubsystemRequest)(nil), // 5: gctrpc.GenericSubsystemRequest - (*GenericSubsystemResponse)(nil), // 6: gctrpc.GenericSubsystemResponse - (*GetSubsystemsRequest)(nil), // 7: gctrpc.GetSubsystemsRequest - (*GetSusbsytemsResponse)(nil), // 8: gctrpc.GetSusbsytemsResponse - (*GetRPCEndpointsRequest)(nil), // 9: gctrpc.GetRPCEndpointsRequest - (*RPCEndpoint)(nil), // 10: gctrpc.RPCEndpoint - (*GetRPCEndpointsResponse)(nil), // 11: gctrpc.GetRPCEndpointsResponse - (*GenericExchangeNameRequest)(nil), // 12: gctrpc.GenericExchangeNameRequest - (*GenericExchangeNameResponse)(nil), // 13: gctrpc.GenericExchangeNameResponse - (*GetExchangesRequest)(nil), // 14: gctrpc.GetExchangesRequest - (*GetExchangesResponse)(nil), // 15: gctrpc.GetExchangesResponse - (*GetExchangeOTPReponse)(nil), // 16: gctrpc.GetExchangeOTPReponse - (*GetExchangeOTPsRequest)(nil), // 17: gctrpc.GetExchangeOTPsRequest - (*GetExchangeOTPsResponse)(nil), // 18: gctrpc.GetExchangeOTPsResponse - (*DisableExchangeRequest)(nil), // 19: gctrpc.DisableExchangeRequest - (*PairsSupported)(nil), // 20: gctrpc.PairsSupported - (*GetExchangeInfoResponse)(nil), // 21: gctrpc.GetExchangeInfoResponse - (*GetTickerRequest)(nil), // 22: gctrpc.GetTickerRequest - (*CurrencyPair)(nil), // 23: gctrpc.CurrencyPair - (*TickerResponse)(nil), // 24: gctrpc.TickerResponse - (*GetTickersRequest)(nil), // 25: gctrpc.GetTickersRequest - (*Tickers)(nil), // 26: gctrpc.Tickers - (*GetTickersResponse)(nil), // 27: gctrpc.GetTickersResponse - (*GetOrderbookRequest)(nil), // 28: gctrpc.GetOrderbookRequest - (*OrderbookItem)(nil), // 29: gctrpc.OrderbookItem - (*OrderbookResponse)(nil), // 30: gctrpc.OrderbookResponse - (*GetOrderbooksRequest)(nil), // 31: gctrpc.GetOrderbooksRequest - (*Orderbooks)(nil), // 32: gctrpc.Orderbooks - (*GetOrderbooksResponse)(nil), // 33: gctrpc.GetOrderbooksResponse - (*GetAccountInfoRequest)(nil), // 34: gctrpc.GetAccountInfoRequest - (*Account)(nil), // 35: gctrpc.Account - (*AccountCurrencyInfo)(nil), // 36: gctrpc.AccountCurrencyInfo - (*GetAccountInfoResponse)(nil), // 37: gctrpc.GetAccountInfoResponse - (*GetConfigRequest)(nil), // 38: gctrpc.GetConfigRequest - (*GetConfigResponse)(nil), // 39: gctrpc.GetConfigResponse - (*PortfolioAddress)(nil), // 40: gctrpc.PortfolioAddress - (*GetPortfolioRequest)(nil), // 41: gctrpc.GetPortfolioRequest - (*GetPortfolioResponse)(nil), // 42: gctrpc.GetPortfolioResponse - (*GetPortfolioSummaryRequest)(nil), // 43: gctrpc.GetPortfolioSummaryRequest - (*Coin)(nil), // 44: gctrpc.Coin - (*OfflineCoinSummary)(nil), // 45: gctrpc.OfflineCoinSummary - (*OnlineCoinSummary)(nil), // 46: gctrpc.OnlineCoinSummary - (*OfflineCoins)(nil), // 47: gctrpc.OfflineCoins - (*OnlineCoins)(nil), // 48: gctrpc.OnlineCoins - (*GetPortfolioSummaryResponse)(nil), // 49: gctrpc.GetPortfolioSummaryResponse - (*AddPortfolioAddressRequest)(nil), // 50: gctrpc.AddPortfolioAddressRequest - (*AddPortfolioAddressResponse)(nil), // 51: gctrpc.AddPortfolioAddressResponse - (*RemovePortfolioAddressRequest)(nil), // 52: gctrpc.RemovePortfolioAddressRequest - (*RemovePortfolioAddressResponse)(nil), // 53: gctrpc.RemovePortfolioAddressResponse - (*GetForexProvidersRequest)(nil), // 54: gctrpc.GetForexProvidersRequest - (*ForexProvider)(nil), // 55: gctrpc.ForexProvider - (*GetForexProvidersResponse)(nil), // 56: gctrpc.GetForexProvidersResponse - (*GetForexRatesRequest)(nil), // 57: gctrpc.GetForexRatesRequest - (*ForexRatesConversion)(nil), // 58: gctrpc.ForexRatesConversion - (*GetForexRatesResponse)(nil), // 59: gctrpc.GetForexRatesResponse - (*OrderDetails)(nil), // 60: gctrpc.OrderDetails - (*TradeHistory)(nil), // 61: gctrpc.TradeHistory - (*GetOrdersRequest)(nil), // 62: gctrpc.GetOrdersRequest - (*GetOrdersResponse)(nil), // 63: gctrpc.GetOrdersResponse - (*GetOrderRequest)(nil), // 64: gctrpc.GetOrderRequest - (*SubmitOrderRequest)(nil), // 65: gctrpc.SubmitOrderRequest - (*SubmitOrderResponse)(nil), // 66: gctrpc.SubmitOrderResponse - (*SimulateOrderRequest)(nil), // 67: gctrpc.SimulateOrderRequest - (*SimulateOrderResponse)(nil), // 68: gctrpc.SimulateOrderResponse - (*WhaleBombRequest)(nil), // 69: gctrpc.WhaleBombRequest - (*CancelOrderRequest)(nil), // 70: gctrpc.CancelOrderRequest - (*CancelOrderResponse)(nil), // 71: gctrpc.CancelOrderResponse - (*CancelAllOrdersRequest)(nil), // 72: gctrpc.CancelAllOrdersRequest - (*CancelAllOrdersResponse)(nil), // 73: gctrpc.CancelAllOrdersResponse - (*GetEventsRequest)(nil), // 74: gctrpc.GetEventsRequest - (*ConditionParams)(nil), // 75: gctrpc.ConditionParams - (*GetEventsResponse)(nil), // 76: gctrpc.GetEventsResponse - (*AddEventRequest)(nil), // 77: gctrpc.AddEventRequest - (*AddEventResponse)(nil), // 78: gctrpc.AddEventResponse - (*RemoveEventRequest)(nil), // 79: gctrpc.RemoveEventRequest - (*RemoveEventResponse)(nil), // 80: gctrpc.RemoveEventResponse - (*GetCryptocurrencyDepositAddressesRequest)(nil), // 81: gctrpc.GetCryptocurrencyDepositAddressesRequest - (*GetCryptocurrencyDepositAddressesResponse)(nil), // 82: gctrpc.GetCryptocurrencyDepositAddressesResponse - (*GetCryptocurrencyDepositAddressRequest)(nil), // 83: gctrpc.GetCryptocurrencyDepositAddressRequest - (*GetCryptocurrencyDepositAddressResponse)(nil), // 84: gctrpc.GetCryptocurrencyDepositAddressResponse - (*WithdrawFiatRequest)(nil), // 85: gctrpc.WithdrawFiatRequest - (*WithdrawCryptoRequest)(nil), // 86: gctrpc.WithdrawCryptoRequest - (*WithdrawResponse)(nil), // 87: gctrpc.WithdrawResponse - (*WithdrawalEventByIDRequest)(nil), // 88: gctrpc.WithdrawalEventByIDRequest - (*WithdrawalEventByIDResponse)(nil), // 89: gctrpc.WithdrawalEventByIDResponse - (*WithdrawalEventsByExchangeRequest)(nil), // 90: gctrpc.WithdrawalEventsByExchangeRequest - (*WithdrawalEventsByDateRequest)(nil), // 91: gctrpc.WithdrawalEventsByDateRequest - (*WithdrawalEventsByExchangeResponse)(nil), // 92: gctrpc.WithdrawalEventsByExchangeResponse - (*WithdrawalEventResponse)(nil), // 93: gctrpc.WithdrawalEventResponse - (*WithdrawlExchangeEvent)(nil), // 94: gctrpc.WithdrawlExchangeEvent - (*WithdrawalRequestEvent)(nil), // 95: gctrpc.WithdrawalRequestEvent - (*FiatWithdrawalEvent)(nil), // 96: gctrpc.FiatWithdrawalEvent - (*CryptoWithdrawalEvent)(nil), // 97: gctrpc.CryptoWithdrawalEvent - (*GetLoggerDetailsRequest)(nil), // 98: gctrpc.GetLoggerDetailsRequest - (*GetLoggerDetailsResponse)(nil), // 99: gctrpc.GetLoggerDetailsResponse - (*SetLoggerDetailsRequest)(nil), // 100: gctrpc.SetLoggerDetailsRequest - (*GetExchangePairsRequest)(nil), // 101: gctrpc.GetExchangePairsRequest - (*GetExchangePairsResponse)(nil), // 102: gctrpc.GetExchangePairsResponse - (*ExchangePairRequest)(nil), // 103: gctrpc.ExchangePairRequest - (*GetOrderbookStreamRequest)(nil), // 104: gctrpc.GetOrderbookStreamRequest - (*GetExchangeOrderbookStreamRequest)(nil), // 105: gctrpc.GetExchangeOrderbookStreamRequest - (*GetTickerStreamRequest)(nil), // 106: gctrpc.GetTickerStreamRequest - (*GetExchangeTickerStreamRequest)(nil), // 107: gctrpc.GetExchangeTickerStreamRequest - (*GetAuditEventRequest)(nil), // 108: gctrpc.GetAuditEventRequest - (*GetAuditEventResponse)(nil), // 109: gctrpc.GetAuditEventResponse - (*GetHistoricCandlesRequest)(nil), // 110: gctrpc.GetHistoricCandlesRequest - (*GetHistoricCandlesResponse)(nil), // 111: gctrpc.GetHistoricCandlesResponse - (*Candle)(nil), // 112: gctrpc.Candle - (*AuditEvent)(nil), // 113: gctrpc.AuditEvent - (*GCTScript)(nil), // 114: gctrpc.GCTScript - (*GCTScriptExecuteRequest)(nil), // 115: gctrpc.GCTScriptExecuteRequest - (*GCTScriptStopRequest)(nil), // 116: gctrpc.GCTScriptStopRequest - (*GCTScriptStopAllRequest)(nil), // 117: gctrpc.GCTScriptStopAllRequest - (*GCTScriptStatusRequest)(nil), // 118: gctrpc.GCTScriptStatusRequest - (*GCTScriptListAllRequest)(nil), // 119: gctrpc.GCTScriptListAllRequest - (*GCTScriptUploadRequest)(nil), // 120: gctrpc.GCTScriptUploadRequest - (*GCTScriptReadScriptRequest)(nil), // 121: gctrpc.GCTScriptReadScriptRequest - (*GCTScriptQueryRequest)(nil), // 122: gctrpc.GCTScriptQueryRequest - (*GCTScriptAutoLoadRequest)(nil), // 123: gctrpc.GCTScriptAutoLoadRequest - (*GCTScriptStatusResponse)(nil), // 124: gctrpc.GCTScriptStatusResponse - (*GCTScriptQueryResponse)(nil), // 125: gctrpc.GCTScriptQueryResponse - (*GCTScriptGenericResponse)(nil), // 126: gctrpc.GCTScriptGenericResponse - nil, // 127: gctrpc.GetInfoResponse.SubsystemStatusEntry - nil, // 128: gctrpc.GetInfoResponse.RpcEndpointsEntry - nil, // 129: gctrpc.GetCommunicationRelayersResponse.CommunicationRelayersEntry - nil, // 130: gctrpc.GetSusbsytemsResponse.SubsystemsStatusEntry - nil, // 131: gctrpc.GetRPCEndpointsResponse.EndpointsEntry - nil, // 132: gctrpc.GetExchangeOTPsResponse.OtpCodesEntry - nil, // 133: gctrpc.GetExchangeInfoResponse.SupportedAssetsEntry - nil, // 134: gctrpc.OnlineCoins.CoinsEntry - nil, // 135: gctrpc.GetPortfolioSummaryResponse.CoinsOfflineSummaryEntry - nil, // 136: gctrpc.GetPortfolioSummaryResponse.CoinsOnlineSummaryEntry - (*CancelAllOrdersResponse_Orders)(nil), // 137: gctrpc.CancelAllOrdersResponse.Orders - nil, // 138: gctrpc.CancelAllOrdersResponse.Orders.OrderStatusEntry - nil, // 139: gctrpc.GetCryptocurrencyDepositAddressesResponse.AddressesEntry - nil, // 140: gctrpc.GetExchangePairsResponse.SupportedAssetsEntry - (*timestamp.Timestamp)(nil), // 141: google.protobuf.Timestamp +func (m *WebsocketSetProxyRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_WebsocketSetProxyRequest.Unmarshal(m, b) } -var file_rpc_proto_depIdxs = []int32{ - 127, // 0: gctrpc.GetInfoResponse.subsystem_status:type_name -> gctrpc.GetInfoResponse.SubsystemStatusEntry - 128, // 1: gctrpc.GetInfoResponse.rpc_endpoints:type_name -> gctrpc.GetInfoResponse.RpcEndpointsEntry - 129, // 2: gctrpc.GetCommunicationRelayersResponse.communication_relayers:type_name -> gctrpc.GetCommunicationRelayersResponse.CommunicationRelayersEntry - 130, // 3: gctrpc.GetSusbsytemsResponse.subsystems_status:type_name -> gctrpc.GetSusbsytemsResponse.SubsystemsStatusEntry - 131, // 4: gctrpc.GetRPCEndpointsResponse.endpoints:type_name -> gctrpc.GetRPCEndpointsResponse.EndpointsEntry - 132, // 5: gctrpc.GetExchangeOTPsResponse.otp_codes:type_name -> gctrpc.GetExchangeOTPsResponse.OtpCodesEntry - 133, // 6: gctrpc.GetExchangeInfoResponse.supported_assets:type_name -> gctrpc.GetExchangeInfoResponse.SupportedAssetsEntry - 23, // 7: gctrpc.GetTickerRequest.pair:type_name -> gctrpc.CurrencyPair - 23, // 8: gctrpc.TickerResponse.pair:type_name -> gctrpc.CurrencyPair - 24, // 9: gctrpc.Tickers.tickers:type_name -> gctrpc.TickerResponse - 26, // 10: gctrpc.GetTickersResponse.tickers:type_name -> gctrpc.Tickers - 23, // 11: gctrpc.GetOrderbookRequest.pair:type_name -> gctrpc.CurrencyPair - 23, // 12: gctrpc.OrderbookResponse.pair:type_name -> gctrpc.CurrencyPair - 29, // 13: gctrpc.OrderbookResponse.bids:type_name -> gctrpc.OrderbookItem - 29, // 14: gctrpc.OrderbookResponse.asks:type_name -> gctrpc.OrderbookItem - 30, // 15: gctrpc.Orderbooks.orderbooks:type_name -> gctrpc.OrderbookResponse - 32, // 16: gctrpc.GetOrderbooksResponse.orderbooks:type_name -> gctrpc.Orderbooks - 36, // 17: gctrpc.Account.currencies:type_name -> gctrpc.AccountCurrencyInfo - 35, // 18: gctrpc.GetAccountInfoResponse.accounts:type_name -> gctrpc.Account - 40, // 19: gctrpc.GetPortfolioResponse.portfolio:type_name -> gctrpc.PortfolioAddress - 45, // 20: gctrpc.OfflineCoins.addresses:type_name -> gctrpc.OfflineCoinSummary - 134, // 21: gctrpc.OnlineCoins.coins:type_name -> gctrpc.OnlineCoins.CoinsEntry - 44, // 22: gctrpc.GetPortfolioSummaryResponse.coin_totals:type_name -> gctrpc.Coin - 44, // 23: gctrpc.GetPortfolioSummaryResponse.coins_offline:type_name -> gctrpc.Coin - 135, // 24: gctrpc.GetPortfolioSummaryResponse.coins_offline_summary:type_name -> gctrpc.GetPortfolioSummaryResponse.CoinsOfflineSummaryEntry - 44, // 25: gctrpc.GetPortfolioSummaryResponse.coins_online:type_name -> gctrpc.Coin - 136, // 26: gctrpc.GetPortfolioSummaryResponse.coins_online_summary:type_name -> gctrpc.GetPortfolioSummaryResponse.CoinsOnlineSummaryEntry - 55, // 27: gctrpc.GetForexProvidersResponse.forex_providers:type_name -> gctrpc.ForexProvider - 58, // 28: gctrpc.GetForexRatesResponse.forex_rates:type_name -> gctrpc.ForexRatesConversion - 61, // 29: gctrpc.OrderDetails.trades:type_name -> gctrpc.TradeHistory - 23, // 30: gctrpc.GetOrdersRequest.pair:type_name -> gctrpc.CurrencyPair - 60, // 31: gctrpc.GetOrdersResponse.orders:type_name -> gctrpc.OrderDetails - 23, // 32: gctrpc.SubmitOrderRequest.pair:type_name -> gctrpc.CurrencyPair - 23, // 33: gctrpc.SimulateOrderRequest.pair:type_name -> gctrpc.CurrencyPair - 29, // 34: gctrpc.SimulateOrderResponse.orders:type_name -> gctrpc.OrderbookItem - 23, // 35: gctrpc.WhaleBombRequest.pair:type_name -> gctrpc.CurrencyPair - 23, // 36: gctrpc.CancelOrderRequest.pair:type_name -> gctrpc.CurrencyPair - 137, // 37: gctrpc.CancelAllOrdersResponse.orders:type_name -> gctrpc.CancelAllOrdersResponse.Orders - 75, // 38: gctrpc.GetEventsResponse.condition_params:type_name -> gctrpc.ConditionParams - 23, // 39: gctrpc.GetEventsResponse.pair:type_name -> gctrpc.CurrencyPair - 75, // 40: gctrpc.AddEventRequest.condition_params:type_name -> gctrpc.ConditionParams - 23, // 41: gctrpc.AddEventRequest.pair:type_name -> gctrpc.CurrencyPair - 139, // 42: gctrpc.GetCryptocurrencyDepositAddressesResponse.addresses:type_name -> gctrpc.GetCryptocurrencyDepositAddressesResponse.AddressesEntry - 93, // 43: gctrpc.WithdrawalEventByIDResponse.event:type_name -> gctrpc.WithdrawalEventResponse - 93, // 44: gctrpc.WithdrawalEventsByExchangeResponse.event:type_name -> gctrpc.WithdrawalEventResponse - 94, // 45: gctrpc.WithdrawalEventResponse.exchange:type_name -> gctrpc.WithdrawlExchangeEvent - 95, // 46: gctrpc.WithdrawalEventResponse.request:type_name -> gctrpc.WithdrawalRequestEvent - 141, // 47: gctrpc.WithdrawalEventResponse.created_at:type_name -> google.protobuf.Timestamp - 141, // 48: gctrpc.WithdrawalEventResponse.updated_at:type_name -> google.protobuf.Timestamp - 96, // 49: gctrpc.WithdrawalRequestEvent.fiat:type_name -> gctrpc.FiatWithdrawalEvent - 97, // 50: gctrpc.WithdrawalRequestEvent.crypto:type_name -> gctrpc.CryptoWithdrawalEvent - 140, // 51: gctrpc.GetExchangePairsResponse.supported_assets:type_name -> gctrpc.GetExchangePairsResponse.SupportedAssetsEntry - 23, // 52: gctrpc.ExchangePairRequest.pair:type_name -> gctrpc.CurrencyPair - 23, // 53: gctrpc.GetOrderbookStreamRequest.pair:type_name -> gctrpc.CurrencyPair - 23, // 54: gctrpc.GetTickerStreamRequest.pair:type_name -> gctrpc.CurrencyPair - 113, // 55: gctrpc.GetAuditEventResponse.events:type_name -> gctrpc.AuditEvent - 23, // 56: gctrpc.GetHistoricCandlesRequest.pair:type_name -> gctrpc.CurrencyPair - 23, // 57: gctrpc.GetHistoricCandlesResponse.pair:type_name -> gctrpc.CurrencyPair - 112, // 58: gctrpc.GetHistoricCandlesResponse.candle:type_name -> gctrpc.Candle - 114, // 59: gctrpc.GCTScriptExecuteRequest.script:type_name -> gctrpc.GCTScript - 114, // 60: gctrpc.GCTScriptStopRequest.script:type_name -> gctrpc.GCTScript - 114, // 61: gctrpc.GCTScriptReadScriptRequest.script:type_name -> gctrpc.GCTScript - 114, // 62: gctrpc.GCTScriptQueryRequest.script:type_name -> gctrpc.GCTScript - 114, // 63: gctrpc.GCTScriptStatusResponse.scripts:type_name -> gctrpc.GCTScript - 114, // 64: gctrpc.GCTScriptQueryResponse.script:type_name -> gctrpc.GCTScript - 10, // 65: gctrpc.GetInfoResponse.RpcEndpointsEntry.value:type_name -> gctrpc.RPCEndpoint - 3, // 66: gctrpc.GetCommunicationRelayersResponse.CommunicationRelayersEntry.value:type_name -> gctrpc.CommunicationRelayer - 10, // 67: gctrpc.GetRPCEndpointsResponse.EndpointsEntry.value:type_name -> gctrpc.RPCEndpoint - 20, // 68: gctrpc.GetExchangeInfoResponse.SupportedAssetsEntry.value:type_name -> gctrpc.PairsSupported - 46, // 69: gctrpc.OnlineCoins.CoinsEntry.value:type_name -> gctrpc.OnlineCoinSummary - 47, // 70: gctrpc.GetPortfolioSummaryResponse.CoinsOfflineSummaryEntry.value:type_name -> gctrpc.OfflineCoins - 48, // 71: gctrpc.GetPortfolioSummaryResponse.CoinsOnlineSummaryEntry.value:type_name -> gctrpc.OnlineCoins - 138, // 72: gctrpc.CancelAllOrdersResponse.Orders.order_status:type_name -> gctrpc.CancelAllOrdersResponse.Orders.OrderStatusEntry - 20, // 73: gctrpc.GetExchangePairsResponse.SupportedAssetsEntry.value:type_name -> gctrpc.PairsSupported - 0, // 74: gctrpc.GoCryptoTrader.GetInfo:input_type -> gctrpc.GetInfoRequest - 7, // 75: gctrpc.GoCryptoTrader.GetSubsystems:input_type -> gctrpc.GetSubsystemsRequest - 5, // 76: gctrpc.GoCryptoTrader.EnableSubsystem:input_type -> gctrpc.GenericSubsystemRequest - 5, // 77: gctrpc.GoCryptoTrader.DisableSubsystem:input_type -> gctrpc.GenericSubsystemRequest - 9, // 78: gctrpc.GoCryptoTrader.GetRPCEndpoints:input_type -> gctrpc.GetRPCEndpointsRequest - 2, // 79: gctrpc.GoCryptoTrader.GetCommunicationRelayers:input_type -> gctrpc.GetCommunicationRelayersRequest - 14, // 80: gctrpc.GoCryptoTrader.GetExchanges:input_type -> gctrpc.GetExchangesRequest - 12, // 81: gctrpc.GoCryptoTrader.DisableExchange:input_type -> gctrpc.GenericExchangeNameRequest - 12, // 82: gctrpc.GoCryptoTrader.GetExchangeInfo:input_type -> gctrpc.GenericExchangeNameRequest - 12, // 83: gctrpc.GoCryptoTrader.GetExchangeOTPCode:input_type -> gctrpc.GenericExchangeNameRequest - 17, // 84: gctrpc.GoCryptoTrader.GetExchangeOTPCodes:input_type -> gctrpc.GetExchangeOTPsRequest - 12, // 85: gctrpc.GoCryptoTrader.EnableExchange:input_type -> gctrpc.GenericExchangeNameRequest - 22, // 86: gctrpc.GoCryptoTrader.GetTicker:input_type -> gctrpc.GetTickerRequest - 25, // 87: gctrpc.GoCryptoTrader.GetTickers:input_type -> gctrpc.GetTickersRequest - 28, // 88: gctrpc.GoCryptoTrader.GetOrderbook:input_type -> gctrpc.GetOrderbookRequest - 31, // 89: gctrpc.GoCryptoTrader.GetOrderbooks:input_type -> gctrpc.GetOrderbooksRequest - 34, // 90: gctrpc.GoCryptoTrader.GetAccountInfo:input_type -> gctrpc.GetAccountInfoRequest - 34, // 91: gctrpc.GoCryptoTrader.GetAccountInfoStream:input_type -> gctrpc.GetAccountInfoRequest - 38, // 92: gctrpc.GoCryptoTrader.GetConfig:input_type -> gctrpc.GetConfigRequest - 41, // 93: gctrpc.GoCryptoTrader.GetPortfolio:input_type -> gctrpc.GetPortfolioRequest - 43, // 94: gctrpc.GoCryptoTrader.GetPortfolioSummary:input_type -> gctrpc.GetPortfolioSummaryRequest - 50, // 95: gctrpc.GoCryptoTrader.AddPortfolioAddress:input_type -> gctrpc.AddPortfolioAddressRequest - 52, // 96: gctrpc.GoCryptoTrader.RemovePortfolioAddress:input_type -> gctrpc.RemovePortfolioAddressRequest - 54, // 97: gctrpc.GoCryptoTrader.GetForexProviders:input_type -> gctrpc.GetForexProvidersRequest - 57, // 98: gctrpc.GoCryptoTrader.GetForexRates:input_type -> gctrpc.GetForexRatesRequest - 62, // 99: gctrpc.GoCryptoTrader.GetOrders:input_type -> gctrpc.GetOrdersRequest - 64, // 100: gctrpc.GoCryptoTrader.GetOrder:input_type -> gctrpc.GetOrderRequest - 65, // 101: gctrpc.GoCryptoTrader.SubmitOrder:input_type -> gctrpc.SubmitOrderRequest - 67, // 102: gctrpc.GoCryptoTrader.SimulateOrder:input_type -> gctrpc.SimulateOrderRequest - 69, // 103: gctrpc.GoCryptoTrader.WhaleBomb:input_type -> gctrpc.WhaleBombRequest - 70, // 104: gctrpc.GoCryptoTrader.CancelOrder:input_type -> gctrpc.CancelOrderRequest - 72, // 105: gctrpc.GoCryptoTrader.CancelAllOrders:input_type -> gctrpc.CancelAllOrdersRequest - 74, // 106: gctrpc.GoCryptoTrader.GetEvents:input_type -> gctrpc.GetEventsRequest - 77, // 107: gctrpc.GoCryptoTrader.AddEvent:input_type -> gctrpc.AddEventRequest - 79, // 108: gctrpc.GoCryptoTrader.RemoveEvent:input_type -> gctrpc.RemoveEventRequest - 81, // 109: gctrpc.GoCryptoTrader.GetCryptocurrencyDepositAddresses:input_type -> gctrpc.GetCryptocurrencyDepositAddressesRequest - 83, // 110: gctrpc.GoCryptoTrader.GetCryptocurrencyDepositAddress:input_type -> gctrpc.GetCryptocurrencyDepositAddressRequest - 85, // 111: gctrpc.GoCryptoTrader.WithdrawFiatFunds:input_type -> gctrpc.WithdrawFiatRequest - 86, // 112: gctrpc.GoCryptoTrader.WithdrawCryptocurrencyFunds:input_type -> gctrpc.WithdrawCryptoRequest - 88, // 113: gctrpc.GoCryptoTrader.WithdrawalEventByID:input_type -> gctrpc.WithdrawalEventByIDRequest - 90, // 114: gctrpc.GoCryptoTrader.WithdrawalEventsByExchange:input_type -> gctrpc.WithdrawalEventsByExchangeRequest - 91, // 115: gctrpc.GoCryptoTrader.WithdrawalEventsByDate:input_type -> gctrpc.WithdrawalEventsByDateRequest - 98, // 116: gctrpc.GoCryptoTrader.GetLoggerDetails:input_type -> gctrpc.GetLoggerDetailsRequest - 100, // 117: gctrpc.GoCryptoTrader.SetLoggerDetails:input_type -> gctrpc.SetLoggerDetailsRequest - 101, // 118: gctrpc.GoCryptoTrader.GetExchangePairs:input_type -> gctrpc.GetExchangePairsRequest - 103, // 119: gctrpc.GoCryptoTrader.EnableExchangePair:input_type -> gctrpc.ExchangePairRequest - 103, // 120: gctrpc.GoCryptoTrader.DisableExchangePair:input_type -> gctrpc.ExchangePairRequest - 104, // 121: gctrpc.GoCryptoTrader.GetOrderbookStream:input_type -> gctrpc.GetOrderbookStreamRequest - 105, // 122: gctrpc.GoCryptoTrader.GetExchangeOrderbookStream:input_type -> gctrpc.GetExchangeOrderbookStreamRequest - 106, // 123: gctrpc.GoCryptoTrader.GetTickerStream:input_type -> gctrpc.GetTickerStreamRequest - 107, // 124: gctrpc.GoCryptoTrader.GetExchangeTickerStream:input_type -> gctrpc.GetExchangeTickerStreamRequest - 108, // 125: gctrpc.GoCryptoTrader.GetAuditEvent:input_type -> gctrpc.GetAuditEventRequest - 115, // 126: gctrpc.GoCryptoTrader.GCTScriptExecute:input_type -> gctrpc.GCTScriptExecuteRequest - 120, // 127: gctrpc.GoCryptoTrader.GCTScriptUpload:input_type -> gctrpc.GCTScriptUploadRequest - 121, // 128: gctrpc.GoCryptoTrader.GCTScriptReadScript:input_type -> gctrpc.GCTScriptReadScriptRequest - 118, // 129: gctrpc.GoCryptoTrader.GCTScriptStatus:input_type -> gctrpc.GCTScriptStatusRequest - 122, // 130: gctrpc.GoCryptoTrader.GCTScriptQuery:input_type -> gctrpc.GCTScriptQueryRequest - 116, // 131: gctrpc.GoCryptoTrader.GCTScriptStop:input_type -> gctrpc.GCTScriptStopRequest - 117, // 132: gctrpc.GoCryptoTrader.GCTScriptStopAll:input_type -> gctrpc.GCTScriptStopAllRequest - 119, // 133: gctrpc.GoCryptoTrader.GCTScriptListAll:input_type -> gctrpc.GCTScriptListAllRequest - 123, // 134: gctrpc.GoCryptoTrader.GCTScriptAutoLoadToggle:input_type -> gctrpc.GCTScriptAutoLoadRequest - 110, // 135: gctrpc.GoCryptoTrader.GetHistoricCandles:input_type -> gctrpc.GetHistoricCandlesRequest - 1, // 136: gctrpc.GoCryptoTrader.GetInfo:output_type -> gctrpc.GetInfoResponse - 8, // 137: gctrpc.GoCryptoTrader.GetSubsystems:output_type -> gctrpc.GetSusbsytemsResponse - 6, // 138: gctrpc.GoCryptoTrader.EnableSubsystem:output_type -> gctrpc.GenericSubsystemResponse - 6, // 139: gctrpc.GoCryptoTrader.DisableSubsystem:output_type -> gctrpc.GenericSubsystemResponse - 11, // 140: gctrpc.GoCryptoTrader.GetRPCEndpoints:output_type -> gctrpc.GetRPCEndpointsResponse - 4, // 141: gctrpc.GoCryptoTrader.GetCommunicationRelayers:output_type -> gctrpc.GetCommunicationRelayersResponse - 15, // 142: gctrpc.GoCryptoTrader.GetExchanges:output_type -> gctrpc.GetExchangesResponse - 13, // 143: gctrpc.GoCryptoTrader.DisableExchange:output_type -> gctrpc.GenericExchangeNameResponse - 21, // 144: gctrpc.GoCryptoTrader.GetExchangeInfo:output_type -> gctrpc.GetExchangeInfoResponse - 16, // 145: gctrpc.GoCryptoTrader.GetExchangeOTPCode:output_type -> gctrpc.GetExchangeOTPReponse - 18, // 146: gctrpc.GoCryptoTrader.GetExchangeOTPCodes:output_type -> gctrpc.GetExchangeOTPsResponse - 13, // 147: gctrpc.GoCryptoTrader.EnableExchange:output_type -> gctrpc.GenericExchangeNameResponse - 24, // 148: gctrpc.GoCryptoTrader.GetTicker:output_type -> gctrpc.TickerResponse - 27, // 149: gctrpc.GoCryptoTrader.GetTickers:output_type -> gctrpc.GetTickersResponse - 30, // 150: gctrpc.GoCryptoTrader.GetOrderbook:output_type -> gctrpc.OrderbookResponse - 33, // 151: gctrpc.GoCryptoTrader.GetOrderbooks:output_type -> gctrpc.GetOrderbooksResponse - 37, // 152: gctrpc.GoCryptoTrader.GetAccountInfo:output_type -> gctrpc.GetAccountInfoResponse - 37, // 153: gctrpc.GoCryptoTrader.GetAccountInfoStream:output_type -> gctrpc.GetAccountInfoResponse - 39, // 154: gctrpc.GoCryptoTrader.GetConfig:output_type -> gctrpc.GetConfigResponse - 42, // 155: gctrpc.GoCryptoTrader.GetPortfolio:output_type -> gctrpc.GetPortfolioResponse - 49, // 156: gctrpc.GoCryptoTrader.GetPortfolioSummary:output_type -> gctrpc.GetPortfolioSummaryResponse - 51, // 157: gctrpc.GoCryptoTrader.AddPortfolioAddress:output_type -> gctrpc.AddPortfolioAddressResponse - 53, // 158: gctrpc.GoCryptoTrader.RemovePortfolioAddress:output_type -> gctrpc.RemovePortfolioAddressResponse - 56, // 159: gctrpc.GoCryptoTrader.GetForexProviders:output_type -> gctrpc.GetForexProvidersResponse - 59, // 160: gctrpc.GoCryptoTrader.GetForexRates:output_type -> gctrpc.GetForexRatesResponse - 63, // 161: gctrpc.GoCryptoTrader.GetOrders:output_type -> gctrpc.GetOrdersResponse - 60, // 162: gctrpc.GoCryptoTrader.GetOrder:output_type -> gctrpc.OrderDetails - 66, // 163: gctrpc.GoCryptoTrader.SubmitOrder:output_type -> gctrpc.SubmitOrderResponse - 68, // 164: gctrpc.GoCryptoTrader.SimulateOrder:output_type -> gctrpc.SimulateOrderResponse - 68, // 165: gctrpc.GoCryptoTrader.WhaleBomb:output_type -> gctrpc.SimulateOrderResponse - 71, // 166: gctrpc.GoCryptoTrader.CancelOrder:output_type -> gctrpc.CancelOrderResponse - 73, // 167: gctrpc.GoCryptoTrader.CancelAllOrders:output_type -> gctrpc.CancelAllOrdersResponse - 76, // 168: gctrpc.GoCryptoTrader.GetEvents:output_type -> gctrpc.GetEventsResponse - 78, // 169: gctrpc.GoCryptoTrader.AddEvent:output_type -> gctrpc.AddEventResponse - 80, // 170: gctrpc.GoCryptoTrader.RemoveEvent:output_type -> gctrpc.RemoveEventResponse - 82, // 171: gctrpc.GoCryptoTrader.GetCryptocurrencyDepositAddresses:output_type -> gctrpc.GetCryptocurrencyDepositAddressesResponse - 84, // 172: gctrpc.GoCryptoTrader.GetCryptocurrencyDepositAddress:output_type -> gctrpc.GetCryptocurrencyDepositAddressResponse - 87, // 173: gctrpc.GoCryptoTrader.WithdrawFiatFunds:output_type -> gctrpc.WithdrawResponse - 87, // 174: gctrpc.GoCryptoTrader.WithdrawCryptocurrencyFunds:output_type -> gctrpc.WithdrawResponse - 89, // 175: gctrpc.GoCryptoTrader.WithdrawalEventByID:output_type -> gctrpc.WithdrawalEventByIDResponse - 92, // 176: gctrpc.GoCryptoTrader.WithdrawalEventsByExchange:output_type -> gctrpc.WithdrawalEventsByExchangeResponse - 92, // 177: gctrpc.GoCryptoTrader.WithdrawalEventsByDate:output_type -> gctrpc.WithdrawalEventsByExchangeResponse - 99, // 178: gctrpc.GoCryptoTrader.GetLoggerDetails:output_type -> gctrpc.GetLoggerDetailsResponse - 99, // 179: gctrpc.GoCryptoTrader.SetLoggerDetails:output_type -> gctrpc.GetLoggerDetailsResponse - 102, // 180: gctrpc.GoCryptoTrader.GetExchangePairs:output_type -> gctrpc.GetExchangePairsResponse - 13, // 181: gctrpc.GoCryptoTrader.EnableExchangePair:output_type -> gctrpc.GenericExchangeNameResponse - 13, // 182: gctrpc.GoCryptoTrader.DisableExchangePair:output_type -> gctrpc.GenericExchangeNameResponse - 30, // 183: gctrpc.GoCryptoTrader.GetOrderbookStream:output_type -> gctrpc.OrderbookResponse - 30, // 184: gctrpc.GoCryptoTrader.GetExchangeOrderbookStream:output_type -> gctrpc.OrderbookResponse - 24, // 185: gctrpc.GoCryptoTrader.GetTickerStream:output_type -> gctrpc.TickerResponse - 24, // 186: gctrpc.GoCryptoTrader.GetExchangeTickerStream:output_type -> gctrpc.TickerResponse - 109, // 187: gctrpc.GoCryptoTrader.GetAuditEvent:output_type -> gctrpc.GetAuditEventResponse - 126, // 188: gctrpc.GoCryptoTrader.GCTScriptExecute:output_type -> gctrpc.GCTScriptGenericResponse - 126, // 189: gctrpc.GoCryptoTrader.GCTScriptUpload:output_type -> gctrpc.GCTScriptGenericResponse - 125, // 190: gctrpc.GoCryptoTrader.GCTScriptReadScript:output_type -> gctrpc.GCTScriptQueryResponse - 124, // 191: gctrpc.GoCryptoTrader.GCTScriptStatus:output_type -> gctrpc.GCTScriptStatusResponse - 125, // 192: gctrpc.GoCryptoTrader.GCTScriptQuery:output_type -> gctrpc.GCTScriptQueryResponse - 126, // 193: gctrpc.GoCryptoTrader.GCTScriptStop:output_type -> gctrpc.GCTScriptGenericResponse - 126, // 194: gctrpc.GoCryptoTrader.GCTScriptStopAll:output_type -> gctrpc.GCTScriptGenericResponse - 124, // 195: gctrpc.GoCryptoTrader.GCTScriptListAll:output_type -> gctrpc.GCTScriptStatusResponse - 126, // 196: gctrpc.GoCryptoTrader.GCTScriptAutoLoadToggle:output_type -> gctrpc.GCTScriptGenericResponse - 111, // 197: gctrpc.GoCryptoTrader.GetHistoricCandles:output_type -> gctrpc.GetHistoricCandlesResponse - 136, // [136:198] is the sub-list for method output_type - 74, // [74:136] is the sub-list for method input_type - 74, // [74:74] is the sub-list for extension type_name - 74, // [74:74] is the sub-list for extension extendee - 0, // [0:74] is the sub-list for field type_name +func (m *WebsocketSetProxyRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_WebsocketSetProxyRequest.Marshal(b, m, deterministic) +} +func (m *WebsocketSetProxyRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_WebsocketSetProxyRequest.Merge(m, src) +} +func (m *WebsocketSetProxyRequest) XXX_Size() int { + return xxx_messageInfo_WebsocketSetProxyRequest.Size(m) +} +func (m *WebsocketSetProxyRequest) XXX_DiscardUnknown() { + xxx_messageInfo_WebsocketSetProxyRequest.DiscardUnknown(m) } -func init() { file_rpc_proto_init() } -func file_rpc_proto_init() { - if File_rpc_proto != nil { - return +var xxx_messageInfo_WebsocketSetProxyRequest proto.InternalMessageInfo + +func (m *WebsocketSetProxyRequest) GetExchange() string { + if m != nil { + return m.Exchange } - if !protoimpl.UnsafeEnabled { - file_rpc_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetInfoRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetInfoResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetCommunicationRelayersRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CommunicationRelayer); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetCommunicationRelayersResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GenericSubsystemRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GenericSubsystemResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetSubsystemsRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetSusbsytemsResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetRPCEndpointsRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RPCEndpoint); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetRPCEndpointsResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GenericExchangeNameRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GenericExchangeNameResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetExchangesRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetExchangesResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetExchangeOTPReponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetExchangeOTPsRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetExchangeOTPsResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DisableExchangeRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PairsSupported); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetExchangeInfoResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetTickerRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CurrencyPair); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*TickerResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetTickersRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Tickers); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetTickersResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetOrderbookRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[29].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*OrderbookItem); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[30].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*OrderbookResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[31].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetOrderbooksRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[32].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Orderbooks); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[33].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetOrderbooksResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[34].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetAccountInfoRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[35].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Account); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[36].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AccountCurrencyInfo); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[37].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetAccountInfoResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[38].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetConfigRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[39].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetConfigResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[40].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PortfolioAddress); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[41].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetPortfolioRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[42].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetPortfolioResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[43].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetPortfolioSummaryRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[44].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Coin); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[45].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*OfflineCoinSummary); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[46].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*OnlineCoinSummary); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[47].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*OfflineCoins); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[48].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*OnlineCoins); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[49].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetPortfolioSummaryResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[50].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AddPortfolioAddressRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[51].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AddPortfolioAddressResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[52].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RemovePortfolioAddressRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[53].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RemovePortfolioAddressResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[54].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetForexProvidersRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[55].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ForexProvider); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[56].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetForexProvidersResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[57].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetForexRatesRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[58].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ForexRatesConversion); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[59].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetForexRatesResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[60].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*OrderDetails); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[61].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*TradeHistory); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[62].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetOrdersRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[63].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetOrdersResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[64].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetOrderRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[65].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SubmitOrderRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[66].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SubmitOrderResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[67].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SimulateOrderRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[68].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SimulateOrderResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[69].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*WhaleBombRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[70].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CancelOrderRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[71].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CancelOrderResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[72].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CancelAllOrdersRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[73].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CancelAllOrdersResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[74].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetEventsRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[75].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ConditionParams); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[76].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetEventsResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[77].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AddEventRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[78].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AddEventResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[79].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RemoveEventRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[80].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RemoveEventResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[81].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetCryptocurrencyDepositAddressesRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[82].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetCryptocurrencyDepositAddressesResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[83].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetCryptocurrencyDepositAddressRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[84].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetCryptocurrencyDepositAddressResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[85].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*WithdrawFiatRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[86].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*WithdrawCryptoRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[87].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*WithdrawResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[88].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*WithdrawalEventByIDRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[89].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*WithdrawalEventByIDResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[90].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*WithdrawalEventsByExchangeRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[91].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*WithdrawalEventsByDateRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[92].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*WithdrawalEventsByExchangeResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[93].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*WithdrawalEventResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[94].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*WithdrawlExchangeEvent); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[95].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*WithdrawalRequestEvent); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[96].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*FiatWithdrawalEvent); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[97].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CryptoWithdrawalEvent); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[98].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetLoggerDetailsRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[99].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetLoggerDetailsResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[100].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SetLoggerDetailsRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[101].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetExchangePairsRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[102].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetExchangePairsResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[103].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ExchangePairRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[104].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetOrderbookStreamRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[105].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetExchangeOrderbookStreamRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[106].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetTickerStreamRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[107].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetExchangeTickerStreamRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[108].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetAuditEventRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[109].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetAuditEventResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[110].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetHistoricCandlesRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[111].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetHistoricCandlesResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[112].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Candle); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[113].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AuditEvent); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[114].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GCTScript); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[115].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GCTScriptExecuteRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[116].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GCTScriptStopRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[117].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GCTScriptStopAllRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[118].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GCTScriptStatusRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[119].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GCTScriptListAllRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[120].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GCTScriptUploadRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[121].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GCTScriptReadScriptRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[122].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GCTScriptQueryRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[123].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GCTScriptAutoLoadRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[124].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GCTScriptStatusResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[125].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GCTScriptQueryResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[126].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GCTScriptGenericResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_rpc_proto_msgTypes[137].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CancelAllOrdersResponse_Orders); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } + return "" +} + +func (m *WebsocketSetProxyRequest) GetProxy() string { + if m != nil { + return m.Proxy } - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_rpc_proto_rawDesc, - NumEnums: 0, - NumMessages: 141, - NumExtensions: 0, - NumServices: 1, - }, - GoTypes: file_rpc_proto_goTypes, - DependencyIndexes: file_rpc_proto_depIdxs, - MessageInfos: file_rpc_proto_msgTypes, - }.Build() - File_rpc_proto = out.File - file_rpc_proto_rawDesc = nil - file_rpc_proto_goTypes = nil - file_rpc_proto_depIdxs = nil + return "" +} + +type WebsocketSetURLRequest struct { + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + Url string `protobuf:"bytes,2,opt,name=url,proto3" json:"url,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *WebsocketSetURLRequest) Reset() { *m = WebsocketSetURLRequest{} } +func (m *WebsocketSetURLRequest) String() string { return proto.CompactTextString(m) } +func (*WebsocketSetURLRequest) ProtoMessage() {} +func (*WebsocketSetURLRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{133} +} + +func (m *WebsocketSetURLRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_WebsocketSetURLRequest.Unmarshal(m, b) +} +func (m *WebsocketSetURLRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_WebsocketSetURLRequest.Marshal(b, m, deterministic) +} +func (m *WebsocketSetURLRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_WebsocketSetURLRequest.Merge(m, src) +} +func (m *WebsocketSetURLRequest) XXX_Size() int { + return xxx_messageInfo_WebsocketSetURLRequest.Size(m) +} +func (m *WebsocketSetURLRequest) XXX_DiscardUnknown() { + xxx_messageInfo_WebsocketSetURLRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_WebsocketSetURLRequest proto.InternalMessageInfo + +func (m *WebsocketSetURLRequest) GetExchange() string { + if m != nil { + return m.Exchange + } + return "" +} + +func (m *WebsocketSetURLRequest) GetUrl() string { + if m != nil { + return m.Url + } + return "" +} + +func init() { + proto.RegisterType((*GetInfoRequest)(nil), "gctrpc.GetInfoRequest") + proto.RegisterType((*GetInfoResponse)(nil), "gctrpc.GetInfoResponse") + proto.RegisterMapType((map[string]*RPCEndpoint)(nil), "gctrpc.GetInfoResponse.RpcEndpointsEntry") + proto.RegisterMapType((map[string]bool)(nil), "gctrpc.GetInfoResponse.SubsystemStatusEntry") + proto.RegisterType((*GetCommunicationRelayersRequest)(nil), "gctrpc.GetCommunicationRelayersRequest") + proto.RegisterType((*CommunicationRelayer)(nil), "gctrpc.CommunicationRelayer") + proto.RegisterType((*GetCommunicationRelayersResponse)(nil), "gctrpc.GetCommunicationRelayersResponse") + proto.RegisterMapType((map[string]*CommunicationRelayer)(nil), "gctrpc.GetCommunicationRelayersResponse.CommunicationRelayersEntry") + proto.RegisterType((*GenericSubsystemRequest)(nil), "gctrpc.GenericSubsystemRequest") + proto.RegisterType((*GetSubsystemsRequest)(nil), "gctrpc.GetSubsystemsRequest") + proto.RegisterType((*GetSusbsytemsResponse)(nil), "gctrpc.GetSusbsytemsResponse") + proto.RegisterMapType((map[string]bool)(nil), "gctrpc.GetSusbsytemsResponse.SubsystemsStatusEntry") + proto.RegisterType((*GetRPCEndpointsRequest)(nil), "gctrpc.GetRPCEndpointsRequest") + proto.RegisterType((*RPCEndpoint)(nil), "gctrpc.RPCEndpoint") + proto.RegisterType((*GetRPCEndpointsResponse)(nil), "gctrpc.GetRPCEndpointsResponse") + proto.RegisterMapType((map[string]*RPCEndpoint)(nil), "gctrpc.GetRPCEndpointsResponse.EndpointsEntry") + proto.RegisterType((*GenericExchangeNameRequest)(nil), "gctrpc.GenericExchangeNameRequest") + proto.RegisterType((*GetExchangesRequest)(nil), "gctrpc.GetExchangesRequest") + proto.RegisterType((*GetExchangesResponse)(nil), "gctrpc.GetExchangesResponse") + proto.RegisterType((*GetExchangeOTPReponse)(nil), "gctrpc.GetExchangeOTPReponse") + proto.RegisterType((*GetExchangeOTPsRequest)(nil), "gctrpc.GetExchangeOTPsRequest") + proto.RegisterType((*GetExchangeOTPsResponse)(nil), "gctrpc.GetExchangeOTPsResponse") + proto.RegisterMapType((map[string]string)(nil), "gctrpc.GetExchangeOTPsResponse.OtpCodesEntry") + proto.RegisterType((*DisableExchangeRequest)(nil), "gctrpc.DisableExchangeRequest") + proto.RegisterType((*PairsSupported)(nil), "gctrpc.PairsSupported") + proto.RegisterType((*GetExchangeInfoResponse)(nil), "gctrpc.GetExchangeInfoResponse") + proto.RegisterMapType((map[string]*PairsSupported)(nil), "gctrpc.GetExchangeInfoResponse.SupportedAssetsEntry") + proto.RegisterType((*GetTickerRequest)(nil), "gctrpc.GetTickerRequest") + proto.RegisterType((*CurrencyPair)(nil), "gctrpc.CurrencyPair") + proto.RegisterType((*TickerResponse)(nil), "gctrpc.TickerResponse") + proto.RegisterType((*GetTickersRequest)(nil), "gctrpc.GetTickersRequest") + proto.RegisterType((*Tickers)(nil), "gctrpc.Tickers") + proto.RegisterType((*GetTickersResponse)(nil), "gctrpc.GetTickersResponse") + proto.RegisterType((*GetOrderbookRequest)(nil), "gctrpc.GetOrderbookRequest") + proto.RegisterType((*OrderbookItem)(nil), "gctrpc.OrderbookItem") + proto.RegisterType((*OrderbookResponse)(nil), "gctrpc.OrderbookResponse") + proto.RegisterType((*GetOrderbooksRequest)(nil), "gctrpc.GetOrderbooksRequest") + proto.RegisterType((*Orderbooks)(nil), "gctrpc.Orderbooks") + proto.RegisterType((*GetOrderbooksResponse)(nil), "gctrpc.GetOrderbooksResponse") + proto.RegisterType((*GetAccountInfoRequest)(nil), "gctrpc.GetAccountInfoRequest") + proto.RegisterType((*Account)(nil), "gctrpc.Account") + proto.RegisterType((*AccountCurrencyInfo)(nil), "gctrpc.AccountCurrencyInfo") + proto.RegisterType((*GetAccountInfoResponse)(nil), "gctrpc.GetAccountInfoResponse") + proto.RegisterType((*GetConfigRequest)(nil), "gctrpc.GetConfigRequest") + proto.RegisterType((*GetConfigResponse)(nil), "gctrpc.GetConfigResponse") + proto.RegisterType((*PortfolioAddress)(nil), "gctrpc.PortfolioAddress") + proto.RegisterType((*GetPortfolioRequest)(nil), "gctrpc.GetPortfolioRequest") + proto.RegisterType((*GetPortfolioResponse)(nil), "gctrpc.GetPortfolioResponse") + proto.RegisterType((*GetPortfolioSummaryRequest)(nil), "gctrpc.GetPortfolioSummaryRequest") + proto.RegisterType((*Coin)(nil), "gctrpc.Coin") + proto.RegisterType((*OfflineCoinSummary)(nil), "gctrpc.OfflineCoinSummary") + proto.RegisterType((*OnlineCoinSummary)(nil), "gctrpc.OnlineCoinSummary") + proto.RegisterType((*OfflineCoins)(nil), "gctrpc.OfflineCoins") + proto.RegisterType((*OnlineCoins)(nil), "gctrpc.OnlineCoins") + proto.RegisterMapType((map[string]*OnlineCoinSummary)(nil), "gctrpc.OnlineCoins.CoinsEntry") + proto.RegisterType((*GetPortfolioSummaryResponse)(nil), "gctrpc.GetPortfolioSummaryResponse") + proto.RegisterMapType((map[string]*OfflineCoins)(nil), "gctrpc.GetPortfolioSummaryResponse.CoinsOfflineSummaryEntry") + proto.RegisterMapType((map[string]*OnlineCoins)(nil), "gctrpc.GetPortfolioSummaryResponse.CoinsOnlineSummaryEntry") + proto.RegisterType((*AddPortfolioAddressRequest)(nil), "gctrpc.AddPortfolioAddressRequest") + proto.RegisterType((*RemovePortfolioAddressRequest)(nil), "gctrpc.RemovePortfolioAddressRequest") + proto.RegisterType((*GetForexProvidersRequest)(nil), "gctrpc.GetForexProvidersRequest") + proto.RegisterType((*ForexProvider)(nil), "gctrpc.ForexProvider") + proto.RegisterType((*GetForexProvidersResponse)(nil), "gctrpc.GetForexProvidersResponse") + proto.RegisterType((*GetForexRatesRequest)(nil), "gctrpc.GetForexRatesRequest") + proto.RegisterType((*ForexRatesConversion)(nil), "gctrpc.ForexRatesConversion") + proto.RegisterType((*GetForexRatesResponse)(nil), "gctrpc.GetForexRatesResponse") + proto.RegisterType((*OrderDetails)(nil), "gctrpc.OrderDetails") + proto.RegisterType((*TradeHistory)(nil), "gctrpc.TradeHistory") + proto.RegisterType((*GetOrdersRequest)(nil), "gctrpc.GetOrdersRequest") + proto.RegisterType((*GetOrdersResponse)(nil), "gctrpc.GetOrdersResponse") + proto.RegisterType((*GetOrderRequest)(nil), "gctrpc.GetOrderRequest") + proto.RegisterType((*SubmitOrderRequest)(nil), "gctrpc.SubmitOrderRequest") + proto.RegisterType((*SubmitOrderResponse)(nil), "gctrpc.SubmitOrderResponse") + proto.RegisterType((*SimulateOrderRequest)(nil), "gctrpc.SimulateOrderRequest") + proto.RegisterType((*SimulateOrderResponse)(nil), "gctrpc.SimulateOrderResponse") + proto.RegisterType((*WhaleBombRequest)(nil), "gctrpc.WhaleBombRequest") + proto.RegisterType((*CancelOrderRequest)(nil), "gctrpc.CancelOrderRequest") + proto.RegisterType((*CancelAllOrdersRequest)(nil), "gctrpc.CancelAllOrdersRequest") + proto.RegisterType((*CancelAllOrdersResponse)(nil), "gctrpc.CancelAllOrdersResponse") + proto.RegisterType((*CancelAllOrdersResponse_Orders)(nil), "gctrpc.CancelAllOrdersResponse.Orders") + proto.RegisterMapType((map[string]string)(nil), "gctrpc.CancelAllOrdersResponse.Orders.OrderStatusEntry") + proto.RegisterType((*GetEventsRequest)(nil), "gctrpc.GetEventsRequest") + proto.RegisterType((*ConditionParams)(nil), "gctrpc.ConditionParams") + proto.RegisterType((*GetEventsResponse)(nil), "gctrpc.GetEventsResponse") + proto.RegisterType((*AddEventRequest)(nil), "gctrpc.AddEventRequest") + proto.RegisterType((*AddEventResponse)(nil), "gctrpc.AddEventResponse") + proto.RegisterType((*RemoveEventRequest)(nil), "gctrpc.RemoveEventRequest") + proto.RegisterType((*GetCryptocurrencyDepositAddressesRequest)(nil), "gctrpc.GetCryptocurrencyDepositAddressesRequest") + proto.RegisterType((*GetCryptocurrencyDepositAddressesResponse)(nil), "gctrpc.GetCryptocurrencyDepositAddressesResponse") + proto.RegisterMapType((map[string]string)(nil), "gctrpc.GetCryptocurrencyDepositAddressesResponse.AddressesEntry") + proto.RegisterType((*GetCryptocurrencyDepositAddressRequest)(nil), "gctrpc.GetCryptocurrencyDepositAddressRequest") + proto.RegisterType((*GetCryptocurrencyDepositAddressResponse)(nil), "gctrpc.GetCryptocurrencyDepositAddressResponse") + proto.RegisterType((*WithdrawFiatRequest)(nil), "gctrpc.WithdrawFiatRequest") + proto.RegisterType((*WithdrawCryptoRequest)(nil), "gctrpc.WithdrawCryptoRequest") + proto.RegisterType((*WithdrawResponse)(nil), "gctrpc.WithdrawResponse") + proto.RegisterType((*WithdrawalEventByIDRequest)(nil), "gctrpc.WithdrawalEventByIDRequest") + proto.RegisterType((*WithdrawalEventByIDResponse)(nil), "gctrpc.WithdrawalEventByIDResponse") + proto.RegisterType((*WithdrawalEventsByExchangeRequest)(nil), "gctrpc.WithdrawalEventsByExchangeRequest") + proto.RegisterType((*WithdrawalEventsByDateRequest)(nil), "gctrpc.WithdrawalEventsByDateRequest") + proto.RegisterType((*WithdrawalEventsByExchangeResponse)(nil), "gctrpc.WithdrawalEventsByExchangeResponse") + proto.RegisterType((*WithdrawalEventResponse)(nil), "gctrpc.WithdrawalEventResponse") + proto.RegisterType((*WithdrawlExchangeEvent)(nil), "gctrpc.WithdrawlExchangeEvent") + proto.RegisterType((*WithdrawalRequestEvent)(nil), "gctrpc.WithdrawalRequestEvent") + proto.RegisterType((*FiatWithdrawalEvent)(nil), "gctrpc.FiatWithdrawalEvent") + proto.RegisterType((*CryptoWithdrawalEvent)(nil), "gctrpc.CryptoWithdrawalEvent") + proto.RegisterType((*GetLoggerDetailsRequest)(nil), "gctrpc.GetLoggerDetailsRequest") + proto.RegisterType((*GetLoggerDetailsResponse)(nil), "gctrpc.GetLoggerDetailsResponse") + proto.RegisterType((*SetLoggerDetailsRequest)(nil), "gctrpc.SetLoggerDetailsRequest") + proto.RegisterType((*GetExchangePairsRequest)(nil), "gctrpc.GetExchangePairsRequest") + proto.RegisterType((*GetExchangePairsResponse)(nil), "gctrpc.GetExchangePairsResponse") + proto.RegisterMapType((map[string]*PairsSupported)(nil), "gctrpc.GetExchangePairsResponse.SupportedAssetsEntry") + proto.RegisterType((*SetExchangePairRequest)(nil), "gctrpc.SetExchangePairRequest") + proto.RegisterType((*GetOrderbookStreamRequest)(nil), "gctrpc.GetOrderbookStreamRequest") + proto.RegisterType((*GetExchangeOrderbookStreamRequest)(nil), "gctrpc.GetExchangeOrderbookStreamRequest") + proto.RegisterType((*GetTickerStreamRequest)(nil), "gctrpc.GetTickerStreamRequest") + proto.RegisterType((*GetExchangeTickerStreamRequest)(nil), "gctrpc.GetExchangeTickerStreamRequest") + proto.RegisterType((*GetAuditEventRequest)(nil), "gctrpc.GetAuditEventRequest") + proto.RegisterType((*GetAuditEventResponse)(nil), "gctrpc.GetAuditEventResponse") + proto.RegisterType((*GetHistoricCandlesRequest)(nil), "gctrpc.GetHistoricCandlesRequest") + proto.RegisterType((*GetHistoricCandlesResponse)(nil), "gctrpc.GetHistoricCandlesResponse") + proto.RegisterType((*Candle)(nil), "gctrpc.Candle") + proto.RegisterType((*AuditEvent)(nil), "gctrpc.AuditEvent") + proto.RegisterType((*GCTScript)(nil), "gctrpc.GCTScript") + proto.RegisterType((*GCTScriptExecuteRequest)(nil), "gctrpc.GCTScriptExecuteRequest") + proto.RegisterType((*GCTScriptStopRequest)(nil), "gctrpc.GCTScriptStopRequest") + proto.RegisterType((*GCTScriptStopAllRequest)(nil), "gctrpc.GCTScriptStopAllRequest") + proto.RegisterType((*GCTScriptStatusRequest)(nil), "gctrpc.GCTScriptStatusRequest") + proto.RegisterType((*GCTScriptListAllRequest)(nil), "gctrpc.GCTScriptListAllRequest") + proto.RegisterType((*GCTScriptUploadRequest)(nil), "gctrpc.GCTScriptUploadRequest") + proto.RegisterType((*GCTScriptReadScriptRequest)(nil), "gctrpc.GCTScriptReadScriptRequest") + proto.RegisterType((*GCTScriptQueryRequest)(nil), "gctrpc.GCTScriptQueryRequest") + proto.RegisterType((*GCTScriptAutoLoadRequest)(nil), "gctrpc.GCTScriptAutoLoadRequest") + proto.RegisterType((*GCTScriptStatusResponse)(nil), "gctrpc.GCTScriptStatusResponse") + proto.RegisterType((*GCTScriptQueryResponse)(nil), "gctrpc.GCTScriptQueryResponse") + proto.RegisterType((*GenericResponse)(nil), "gctrpc.GenericResponse") + proto.RegisterType((*SetExchangeAssetRequest)(nil), "gctrpc.SetExchangeAssetRequest") + proto.RegisterType((*SetExchangeAllPairsRequest)(nil), "gctrpc.SetExchangeAllPairsRequest") + proto.RegisterType((*UpdateExchangeSupportedPairsRequest)(nil), "gctrpc.UpdateExchangeSupportedPairsRequest") + proto.RegisterType((*GetExchangeAssetsRequest)(nil), "gctrpc.GetExchangeAssetsRequest") + proto.RegisterType((*GetExchangeAssetsResponse)(nil), "gctrpc.GetExchangeAssetsResponse") + proto.RegisterType((*WebsocketGetInfoRequest)(nil), "gctrpc.WebsocketGetInfoRequest") + proto.RegisterType((*WebsocketGetInfoResponse)(nil), "gctrpc.WebsocketGetInfoResponse") + proto.RegisterType((*WebsocketSetEnabledRequest)(nil), "gctrpc.WebsocketSetEnabledRequest") + proto.RegisterType((*WebsocketGetSubscriptionsRequest)(nil), "gctrpc.WebsocketGetSubscriptionsRequest") + proto.RegisterType((*WebsocketSubscription)(nil), "gctrpc.WebsocketSubscription") + proto.RegisterType((*WebsocketGetSubscriptionsResponse)(nil), "gctrpc.WebsocketGetSubscriptionsResponse") + proto.RegisterType((*WebsocketSetProxyRequest)(nil), "gctrpc.WebsocketSetProxyRequest") + proto.RegisterType((*WebsocketSetURLRequest)(nil), "gctrpc.WebsocketSetURLRequest") +} + +func init() { + proto.RegisterFile("rpc.proto", fileDescriptor_77a6da22d6a3feb1) +} + +var fileDescriptor_77a6da22d6a3feb1 = []byte{ + // 6423 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x7c, 0x4d, 0x8c, 0x1c, 0xc7, + 0x75, 0x30, 0x7a, 0x66, 0xf6, 0x67, 0xde, 0xfe, 0xb2, 0xf6, 0x6f, 0x38, 0x5c, 0x72, 0xc9, 0x92, + 0x45, 0x91, 0xfa, 0x59, 0x4a, 0x94, 0x64, 0xcb, 0xf2, 0xdf, 0xb7, 0x5c, 0x4a, 0x6b, 0xda, 0xb4, + 0x44, 0xf7, 0x2e, 0x25, 0x40, 0xfe, 0xa0, 0x49, 0xcf, 0x74, 0xed, 0x6e, 0x9b, 0x3d, 0xdd, 0xa3, + 0xee, 0x9e, 0x5d, 0xae, 0x8d, 0xc0, 0x86, 0x91, 0x04, 0x01, 0x1c, 0x24, 0x08, 0x0c, 0xc3, 0x09, + 0x90, 0x53, 0x4e, 0x41, 0x2e, 0x06, 0x82, 0x1c, 0x82, 0x1c, 0x8c, 0x5c, 0x83, 0x00, 0xb9, 0x04, + 0x08, 0x7c, 0xc9, 0x29, 0x41, 0x0e, 0x41, 0x92, 0x43, 0x80, 0x5c, 0x72, 0x0a, 0xea, 0xd5, 0x4f, + 0x57, 0x75, 0xf7, 0xcc, 0xce, 0xca, 0xb4, 0x72, 0x21, 0xa7, 0x5f, 0xbd, 0x7a, 0xef, 0xd5, 0xab, + 0x57, 0xaf, 0x5e, 0xbd, 0x7a, 0xb5, 0xd0, 0x4c, 0x06, 0xbd, 0xed, 0x41, 0x12, 0x67, 0x31, 0x99, + 0x3e, 0xea, 0x65, 0xc9, 0xa0, 0xd7, 0xde, 0x3c, 0x8a, 0xe3, 0xa3, 0x90, 0xdd, 0xf1, 0x06, 0xc1, + 0x1d, 0x2f, 0x8a, 0xe2, 0xcc, 0xcb, 0x82, 0x38, 0x4a, 0x05, 0x56, 0x7b, 0x4b, 0xb6, 0xe2, 0x57, + 0x77, 0x78, 0x78, 0x27, 0x0b, 0xfa, 0x2c, 0xcd, 0xbc, 0xfe, 0x40, 0x20, 0xd0, 0x65, 0x58, 0xdc, + 0x63, 0xd9, 0x83, 0xe8, 0x30, 0x76, 0xd9, 0x27, 0x43, 0x96, 0x66, 0xf4, 0x2f, 0x1b, 0xb0, 0xa4, + 0x41, 0xe9, 0x20, 0x8e, 0x52, 0x46, 0xd6, 0x61, 0x7a, 0x38, 0xe0, 0x5d, 0x5b, 0xce, 0x75, 0xe7, + 0x56, 0xd3, 0x95, 0x5f, 0xe4, 0x0e, 0xac, 0x78, 0x27, 0x5e, 0x10, 0x7a, 0xdd, 0x90, 0x75, 0xd8, + 0xd3, 0xde, 0xb1, 0x17, 0x1d, 0xb1, 0xb4, 0x55, 0xbb, 0xee, 0xdc, 0xaa, 0xbb, 0x44, 0x37, 0xbd, + 0xa3, 0x5a, 0xc8, 0x4b, 0x70, 0x89, 0x45, 0x1c, 0xe4, 0x1b, 0xe8, 0x75, 0x44, 0x5f, 0x96, 0x0d, + 0x39, 0xf2, 0x1b, 0xb0, 0xee, 0xb3, 0x43, 0x6f, 0x18, 0x66, 0x9d, 0xc3, 0x38, 0x61, 0x4f, 0x3b, + 0x83, 0x24, 0x3e, 0x09, 0x7c, 0x96, 0xb4, 0x1a, 0x28, 0xc5, 0xaa, 0x6c, 0x7d, 0x97, 0x37, 0x3e, + 0x92, 0x6d, 0xe4, 0x2e, 0xac, 0xe9, 0x5e, 0x81, 0x97, 0x75, 0x7a, 0xc3, 0x24, 0x61, 0x51, 0xef, + 0xac, 0x35, 0x85, 0x9d, 0x56, 0x54, 0xa7, 0xc0, 0xcb, 0x76, 0x65, 0x13, 0xf9, 0x10, 0x96, 0xd3, + 0x61, 0x37, 0x3d, 0x4b, 0x33, 0xd6, 0xef, 0xa4, 0x99, 0x97, 0x0d, 0xd3, 0xd6, 0xf4, 0xf5, 0xfa, + 0xad, 0xb9, 0xbb, 0x2f, 0x6f, 0x0b, 0x3d, 0x6f, 0x17, 0x54, 0xb2, 0xbd, 0xaf, 0xf0, 0xf7, 0x11, + 0xfd, 0x9d, 0x28, 0x4b, 0xce, 0xdc, 0xa5, 0xd4, 0x86, 0x92, 0xf7, 0x60, 0x21, 0x19, 0xf4, 0x3a, + 0x2c, 0xf2, 0x07, 0x71, 0x10, 0x65, 0x69, 0x6b, 0x06, 0xa9, 0xde, 0x1e, 0x45, 0xd5, 0x1d, 0xf4, + 0xde, 0x51, 0xb8, 0x82, 0xe4, 0x7c, 0x62, 0x80, 0xda, 0xf7, 0x60, 0xb5, 0x8a, 0x31, 0x59, 0x86, + 0xfa, 0x13, 0x76, 0x26, 0x67, 0x87, 0xff, 0x24, 0xab, 0x30, 0x75, 0xe2, 0x85, 0x43, 0x86, 0x93, + 0x31, 0xeb, 0x8a, 0x8f, 0xb7, 0x6b, 0x6f, 0x39, 0xed, 0x03, 0xb8, 0x54, 0x62, 0x53, 0x41, 0xe0, + 0xb6, 0x49, 0x60, 0xee, 0xee, 0x8a, 0x12, 0xd9, 0x7d, 0xb4, 0xab, 0xfa, 0x1a, 0x54, 0xe9, 0x0d, + 0xd8, 0xda, 0x63, 0xd9, 0x6e, 0xdc, 0xef, 0x0f, 0xa3, 0xa0, 0x87, 0x46, 0xe8, 0xb2, 0xd0, 0x3b, + 0x63, 0x49, 0xaa, 0x2c, 0xeb, 0x3d, 0x58, 0xad, 0x6a, 0x27, 0x2d, 0x98, 0x91, 0x73, 0x8f, 0xfc, + 0x67, 0x5d, 0xf5, 0x49, 0x36, 0xa1, 0xd9, 0x8b, 0xa3, 0x88, 0xf5, 0x32, 0xe6, 0xcb, 0x81, 0xe4, + 0x00, 0xfa, 0x3b, 0x35, 0xb8, 0x3e, 0x9a, 0xa7, 0x34, 0xdd, 0xef, 0xc1, 0x7a, 0xcf, 0x44, 0xe8, + 0x24, 0x12, 0xa3, 0xe5, 0xe0, 0x54, 0xec, 0x1a, 0x53, 0x31, 0x96, 0xd2, 0x76, 0x65, 0xab, 0x98, + 0xa4, 0xb5, 0x5e, 0x55, 0x5b, 0xfb, 0x10, 0xda, 0xa3, 0x3b, 0x55, 0xa8, 0xfc, 0xae, 0xad, 0xf2, + 0x4d, 0x25, 0x5a, 0x15, 0x11, 0x53, 0xf7, 0x5f, 0x80, 0x8d, 0x3d, 0x16, 0xb1, 0x24, 0xe8, 0x69, + 0xe3, 0x90, 0x3a, 0xe7, 0x1a, 0xd4, 0x36, 0x29, 0x59, 0xe5, 0x00, 0xba, 0x0e, 0xab, 0x7b, 0x2c, + 0xd3, 0x9d, 0xf4, 0x4c, 0xfd, 0xc2, 0x81, 0x35, 0x6c, 0x48, 0xbb, 0xe9, 0x99, 0x68, 0x90, 0xea, + 0xfc, 0x0d, 0xb8, 0xa4, 0xbb, 0xa7, 0x6a, 0xa9, 0x08, 0x4d, 0xbe, 0x6e, 0x68, 0xb2, 0xdc, 0x33, + 0x5f, 0x30, 0xa9, 0xb9, 0x62, 0xf2, 0x75, 0x27, 0xc1, 0xed, 0x5d, 0x58, 0xab, 0x44, 0xbd, 0x88, + 0x8d, 0xd3, 0x16, 0xac, 0xef, 0xb1, 0xcc, 0x30, 0x55, 0xc3, 0x08, 0xe7, 0x0c, 0x30, 0xb7, 0xbd, + 0x34, 0xf3, 0x92, 0x2c, 0xb7, 0x3d, 0xf9, 0x49, 0x9e, 0x87, 0xc5, 0x30, 0x48, 0x33, 0x16, 0x75, + 0x3c, 0xdf, 0x4f, 0x58, 0x2a, 0xdc, 0x5a, 0xd3, 0x5d, 0x10, 0xd0, 0x1d, 0x01, 0xa4, 0x7f, 0xed, + 0x70, 0xe5, 0x17, 0x58, 0x49, 0x65, 0x3d, 0x84, 0x66, 0xbe, 0xf2, 0x85, 0x92, 0xb6, 0x0d, 0x25, + 0x55, 0xf5, 0xd9, 0x2e, 0x2c, 0xff, 0x9c, 0x40, 0xfb, 0xdb, 0xb0, 0xf8, 0xac, 0x17, 0xed, 0x5b, + 0xd0, 0x96, 0x86, 0xa3, 0xbc, 0xee, 0x7b, 0x5e, 0x9f, 0x29, 0xdb, 0x69, 0xc3, 0xac, 0x72, 0xd2, + 0x92, 0x87, 0xfe, 0xa6, 0x77, 0x60, 0x65, 0x8f, 0x65, 0xda, 0x57, 0xab, 0x2e, 0x23, 0x97, 0x32, + 0x7d, 0x03, 0x4d, 0xcd, 0xe8, 0x20, 0x75, 0xb4, 0x09, 0xcd, 0x7c, 0x27, 0x90, 0x06, 0xaa, 0x01, + 0xf4, 0x2e, 0xda, 0xa1, 0xea, 0xf5, 0xfe, 0xc1, 0x23, 0x97, 0x89, 0x6e, 0x97, 0x61, 0x36, 0xce, + 0x06, 0x9d, 0x5e, 0xec, 0x2b, 0xd9, 0x66, 0xe2, 0x6c, 0xb0, 0x1b, 0xfb, 0x4c, 0xce, 0xbd, 0xd1, + 0x47, 0xcf, 0xfd, 0x9f, 0x8a, 0xb9, 0xb2, 0x9b, 0xa4, 0x1c, 0xdf, 0x80, 0xa6, 0x22, 0xa8, 0xe6, + 0xea, 0x15, 0x63, 0xae, 0xaa, 0xfa, 0x6c, 0xbf, 0x2f, 0x38, 0xca, 0xa9, 0x9a, 0x95, 0x02, 0xa4, + 0xed, 0x2f, 0xc1, 0x82, 0xd5, 0x74, 0x9e, 0xe9, 0x36, 0xcd, 0x39, 0x79, 0x03, 0xd6, 0xef, 0x07, + 0xa9, 0xb9, 0x6d, 0x4e, 0x32, 0x1f, 0x1f, 0xc3, 0xe2, 0x23, 0x2f, 0x48, 0xd2, 0xfd, 0xe1, 0x60, + 0x10, 0xa3, 0xfd, 0xbe, 0x00, 0x4b, 0xf9, 0xde, 0x3c, 0xe0, 0x6d, 0xb2, 0xd3, 0xa2, 0x06, 0x63, + 0x0f, 0xf2, 0x1c, 0x2c, 0xa8, 0x3d, 0x59, 0xa0, 0x09, 0x91, 0xe6, 0x25, 0x10, 0x91, 0xe8, 0x8f, + 0x1a, 0x96, 0xea, 0xac, 0xe8, 0x80, 0x40, 0x23, 0xf2, 0x74, 0x6c, 0x80, 0xbf, 0x4d, 0x43, 0xa8, + 0xd9, 0x3e, 0xbd, 0x05, 0x33, 0x27, 0x2c, 0xe9, 0xc6, 0x29, 0xc3, 0x8d, 0x7f, 0xd6, 0x55, 0x9f, + 0x5c, 0x90, 0x61, 0x1a, 0x44, 0x47, 0x9d, 0xd4, 0x8b, 0xfc, 0x6e, 0xfc, 0x14, 0xb7, 0xf9, 0x59, + 0x77, 0x1e, 0x81, 0xfb, 0x02, 0x46, 0x6e, 0xc0, 0xfc, 0x71, 0x96, 0x0d, 0x3a, 0x3c, 0xfe, 0x88, + 0x87, 0x99, 0xdc, 0xd5, 0xe7, 0x38, 0xec, 0x40, 0x80, 0xf8, 0xca, 0x45, 0x94, 0x61, 0xca, 0x12, + 0xef, 0x88, 0x45, 0x59, 0x6b, 0x5a, 0xac, 0x5c, 0x0e, 0x7d, 0xac, 0x80, 0xe4, 0x2a, 0x00, 0xa2, + 0x0d, 0x92, 0xf8, 0xe9, 0x59, 0x6b, 0x46, 0x98, 0x1e, 0x87, 0x3c, 0xe2, 0x00, 0xae, 0xbf, 0xae, + 0x97, 0x32, 0x15, 0x3f, 0x04, 0x2c, 0x6d, 0xcd, 0x0a, 0xfd, 0x71, 0xf0, 0xae, 0x86, 0x92, 0x0e, + 0x0f, 0x1e, 0xa4, 0xd6, 0x3b, 0x5e, 0x9a, 0xb2, 0x2c, 0x6d, 0x35, 0xd1, 0x80, 0xde, 0xa8, 0x30, + 0xa0, 0x42, 0x10, 0x21, 0xfb, 0xed, 0x60, 0x37, 0x1d, 0x44, 0x58, 0x50, 0x1e, 0x34, 0x79, 0xc3, + 0xec, 0x98, 0x45, 0x19, 0xdf, 0x02, 0x38, 0x93, 0x41, 0xd0, 0x02, 0xd4, 0xcd, 0xb2, 0xd5, 0xb0, + 0x33, 0x08, 0xda, 0x1f, 0xf1, 0x08, 0xa1, 0x4c, 0xb5, 0xc2, 0x04, 0x5f, 0xb6, 0x7d, 0xc5, 0xba, + 0x12, 0xd6, 0xb6, 0x23, 0xd3, 0x34, 0x4f, 0x61, 0x79, 0x8f, 0x65, 0x07, 0x41, 0xef, 0x09, 0x4b, + 0x26, 0x30, 0x4a, 0x72, 0x0b, 0x1a, 0xdc, 0xa2, 0x24, 0x83, 0x55, 0xbd, 0x9d, 0xc9, 0xb0, 0x8b, + 0x33, 0x72, 0x11, 0x83, 0xcf, 0x05, 0x6a, 0xae, 0x93, 0x9d, 0x0d, 0x84, 0x5d, 0x34, 0xdd, 0x26, + 0x42, 0x0e, 0xce, 0x06, 0x8c, 0x7e, 0x00, 0xf3, 0x66, 0x27, 0xee, 0x34, 0x7c, 0x16, 0x06, 0xfd, + 0x20, 0x63, 0x89, 0x72, 0x1a, 0x1a, 0xc0, 0xed, 0x91, 0x4f, 0x91, 0xb4, 0x63, 0xfc, 0xcd, 0xd7, + 0xdb, 0x27, 0xc3, 0x38, 0x53, 0xb4, 0xc5, 0x07, 0xfd, 0x69, 0x0d, 0x16, 0xd5, 0x70, 0xa4, 0x31, + 0x2b, 0x99, 0x9d, 0x73, 0x65, 0xbe, 0x01, 0xf3, 0xa1, 0x97, 0x66, 0x9d, 0xe1, 0xc0, 0xf7, 0x54, + 0x7c, 0x52, 0x77, 0xe7, 0x38, 0xec, 0xb1, 0x00, 0x71, 0x8b, 0x56, 0xe1, 0x27, 0xae, 0x2d, 0xc9, + 0x7d, 0xbe, 0x67, 0x0e, 0x86, 0x40, 0x83, 0xf7, 0x41, 0x6b, 0x77, 0x5c, 0xfc, 0xcd, 0x61, 0xc7, + 0xc1, 0xd1, 0x31, 0x5a, 0xb7, 0xe3, 0xe2, 0x6f, 0x3e, 0x83, 0x61, 0x7c, 0x8a, 0xb6, 0xec, 0xb8, + 0xfc, 0x27, 0x87, 0x74, 0x03, 0x1f, 0x4d, 0xd7, 0x71, 0xf9, 0x4f, 0x0e, 0xf1, 0xd2, 0x27, 0x68, + 0xa8, 0x8e, 0xcb, 0x7f, 0xf2, 0xd0, 0xfd, 0x24, 0x0e, 0x87, 0x7d, 0xd6, 0x6a, 0x22, 0x50, 0x7e, + 0x91, 0x2b, 0xd0, 0x1c, 0x24, 0x41, 0x8f, 0x75, 0xbc, 0xec, 0x18, 0x8d, 0xc9, 0x71, 0x67, 0x11, + 0xb0, 0x93, 0x1d, 0xd3, 0x15, 0xb8, 0xa4, 0x27, 0x5a, 0x7b, 0xcf, 0x0f, 0x61, 0x46, 0x42, 0xc6, + 0x4e, 0xfa, 0xab, 0x30, 0x93, 0x09, 0xb4, 0x56, 0x0d, 0x57, 0x81, 0x36, 0x2c, 0x5b, 0xd3, 0xae, + 0x42, 0xa3, 0x5f, 0x03, 0x62, 0x72, 0x93, 0x13, 0x71, 0x3b, 0xa7, 0x23, 0xdc, 0xf1, 0x92, 0x4d, + 0x27, 0xcd, 0x09, 0x7c, 0x0f, 0x37, 0xa3, 0xf7, 0x13, 0x9f, 0x3b, 0x92, 0xf8, 0xc9, 0x67, 0x6a, + 0x9a, 0xdf, 0x82, 0x05, 0xcd, 0xf8, 0x41, 0xc6, 0xfa, 0x5c, 0xe1, 0x5e, 0x3f, 0x1e, 0x46, 0x19, + 0xf2, 0x74, 0x5c, 0xf9, 0xc5, 0x2d, 0x10, 0xf5, 0x8b, 0x2c, 0x1d, 0x57, 0x7c, 0x90, 0x45, 0xa8, + 0x05, 0xbe, 0x3c, 0x01, 0xd5, 0x02, 0x9f, 0xfe, 0x8f, 0x03, 0x97, 0x8c, 0x81, 0x5c, 0xd8, 0x28, + 0x4b, 0x16, 0x57, 0xab, 0xb0, 0xb8, 0xdb, 0xd0, 0xe8, 0x06, 0x3e, 0x3f, 0x78, 0x71, 0xbd, 0xae, + 0x29, 0x72, 0xd6, 0x38, 0x5c, 0x44, 0xe1, 0xa8, 0x5e, 0xfa, 0x24, 0x6d, 0x35, 0xc6, 0xa2, 0x72, + 0x94, 0xd2, 0x7a, 0x98, 0x2a, 0xaf, 0x07, 0x5b, 0x97, 0xd3, 0x45, 0x5d, 0x8a, 0x70, 0x54, 0xd3, + 0xd6, 0x96, 0xd7, 0x03, 0xc8, 0x81, 0x63, 0xa7, 0xf5, 0x8b, 0x00, 0xb1, 0xc6, 0x94, 0xf6, 0x77, + 0xb9, 0x24, 0xb4, 0x36, 0x41, 0x03, 0x99, 0x7e, 0x13, 0x43, 0x0d, 0x93, 0xb9, 0x54, 0xfe, 0x5d, + 0x8b, 0xa6, 0xb0, 0x45, 0x52, 0xa2, 0x99, 0x5a, 0xc4, 0x5e, 0x47, 0x62, 0x3b, 0xbd, 0x1e, 0x9f, + 0x7a, 0xe3, 0x74, 0x3d, 0x76, 0x0f, 0xff, 0x00, 0x66, 0x64, 0x0f, 0x69, 0x16, 0x02, 0xa1, 0x16, + 0xf8, 0xe4, 0x4b, 0x00, 0xc6, 0x3e, 0x24, 0xc6, 0x75, 0x45, 0xc9, 0x20, 0x3b, 0x29, 0x6b, 0x40, + 0x76, 0x06, 0x3a, 0x3d, 0x84, 0x95, 0x0a, 0x14, 0x2e, 0x8a, 0x3e, 0x1b, 0x4b, 0x51, 0xd4, 0x37, + 0xd9, 0x82, 0xb9, 0x2c, 0xce, 0xbc, 0xb0, 0x93, 0xef, 0x10, 0x8e, 0x0b, 0x08, 0xfa, 0x80, 0x43, + 0xd0, 0x41, 0xc5, 0xa1, 0xb0, 0x5c, 0xee, 0xa0, 0xe2, 0xd0, 0xa7, 0x1e, 0x06, 0x5e, 0xd6, 0xa0, + 0xa5, 0x0a, 0xc7, 0x4d, 0xd9, 0x4b, 0x30, 0xeb, 0x89, 0x2e, 0x6a, 0x60, 0x4b, 0x85, 0x81, 0xb9, + 0x1a, 0x81, 0x12, 0xdc, 0x81, 0x76, 0xe3, 0xe8, 0x30, 0x38, 0x52, 0xd6, 0xf1, 0x02, 0x3a, 0x2b, + 0x05, 0xcb, 0x63, 0x12, 0xdf, 0xcb, 0x3c, 0xe4, 0x36, 0xef, 0xe2, 0x6f, 0xfa, 0xdb, 0x0e, 0x2c, + 0x3f, 0x8a, 0x93, 0xec, 0x30, 0x0e, 0x83, 0x58, 0xc6, 0xef, 0x3c, 0x1c, 0x51, 0xf1, 0xbd, 0x8c, + 0x23, 0xe5, 0x27, 0xf7, 0x90, 0xbd, 0x38, 0x88, 0x84, 0xad, 0xd6, 0xa4, 0x82, 0xe2, 0x20, 0xe2, + 0xa6, 0x4a, 0xae, 0xc3, 0x9c, 0xcf, 0xd2, 0x5e, 0x12, 0x0c, 0xf8, 0x99, 0x4c, 0xba, 0x05, 0x13, + 0xc4, 0x09, 0x77, 0xbd, 0xd0, 0x8b, 0x7a, 0x4c, 0x7a, 0x76, 0xf5, 0x49, 0xd7, 0xd0, 0x5d, 0x69, + 0x49, 0x8c, 0xe3, 0xb1, 0x0d, 0x96, 0x43, 0xf9, 0x3c, 0x34, 0x07, 0x0a, 0x28, 0xcd, 0xaf, 0xa5, + 0xf7, 0xea, 0xc2, 0x70, 0xdc, 0x1c, 0x95, 0x6e, 0xf2, 0xe0, 0x3e, 0xa7, 0xb7, 0x3f, 0xec, 0xf7, + 0xbd, 0xe4, 0x4c, 0x71, 0x8b, 0xa0, 0xb1, 0x1b, 0x07, 0x11, 0x57, 0x14, 0x1f, 0x94, 0x0a, 0xde, + 0xf8, 0x6f, 0x53, 0xf4, 0x9a, 0x25, 0xba, 0xa9, 0xad, 0xba, 0xad, 0xad, 0x6b, 0x00, 0x03, 0x96, + 0xf4, 0x58, 0x94, 0x79, 0x47, 0x6a, 0xc4, 0x06, 0x84, 0x1e, 0x03, 0x79, 0xff, 0xf0, 0x30, 0x0c, + 0x22, 0xc6, 0xd9, 0x4a, 0x61, 0xc6, 0x68, 0x7f, 0xb4, 0x0c, 0x36, 0xa7, 0x7a, 0x89, 0xd3, 0xb7, + 0xe0, 0xd2, 0xfb, 0x51, 0x05, 0x23, 0x45, 0xce, 0x19, 0x47, 0xae, 0x56, 0x22, 0xf7, 0x75, 0x98, + 0x37, 0x04, 0x4f, 0xc9, 0x5b, 0xd0, 0x94, 0x32, 0xea, 0x83, 0x42, 0x5b, 0x7b, 0x83, 0xd2, 0x08, + 0xdd, 0x1c, 0x99, 0xfe, 0x91, 0x03, 0x73, 0xb9, 0x64, 0x29, 0x79, 0x03, 0xa6, 0xb8, 0xba, 0x15, + 0x95, 0x6b, 0x9a, 0x4a, 0x8e, 0xb3, 0x8d, 0xff, 0x8a, 0xb8, 0x50, 0x20, 0xb7, 0xf7, 0x01, 0x72, + 0x60, 0x45, 0x58, 0x77, 0xc7, 0x0e, 0xeb, 0x2e, 0x97, 0xa9, 0x2a, 0xd1, 0x8c, 0xc8, 0xee, 0xef, + 0x1a, 0x70, 0xa5, 0xd2, 0x58, 0xa4, 0x0d, 0xbe, 0x02, 0x73, 0x62, 0x2d, 0x70, 0x0f, 0xa0, 0x04, + 0x9e, 0xcf, 0xf3, 0x13, 0x41, 0xe4, 0x02, 0xae, 0x0d, 0x6c, 0x27, 0xaf, 0xc1, 0x02, 0x0a, 0xdb, + 0x89, 0x85, 0x42, 0xe4, 0xc2, 0xb6, 0x3b, 0xcc, 0x23, 0x8a, 0x54, 0x19, 0x19, 0xc0, 0x9a, 0xd5, + 0xa5, 0x93, 0x0a, 0x11, 0xe4, 0x26, 0xf5, 0x65, 0x23, 0x94, 0x1e, 0x25, 0xa5, 0x50, 0x96, 0x24, + 0x28, 0xdb, 0x84, 0xea, 0x56, 0x7a, 0xe5, 0x16, 0x72, 0x07, 0xe6, 0x25, 0x47, 0xd4, 0x8c, 0xdc, + 0xe2, 0x6c, 0x19, 0xe7, 0x44, 0x47, 0x44, 0x20, 0x7d, 0x58, 0x35, 0x3b, 0x68, 0x09, 0xa7, 0xb0, + 0xe3, 0x97, 0x26, 0x97, 0x30, 0x2a, 0x09, 0x48, 0x7a, 0xa5, 0x86, 0xf6, 0xff, 0x87, 0xd6, 0xa8, + 0x01, 0x55, 0x4c, 0xfb, 0x8b, 0xf6, 0xb4, 0xaf, 0x56, 0x98, 0x64, 0x6a, 0x66, 0x01, 0x3f, 0x82, + 0x8d, 0x11, 0xc2, 0x5c, 0x20, 0xad, 0x60, 0x58, 0xaa, 0x69, 0x4d, 0xff, 0xec, 0x40, 0x7b, 0xc7, + 0xf7, 0x4b, 0xce, 0x29, 0x4f, 0x12, 0x7c, 0xc6, 0x2e, 0x97, 0xdc, 0x81, 0x95, 0xfc, 0x8c, 0x96, + 0xe7, 0x1b, 0xc4, 0xe1, 0x91, 0xe8, 0xa6, 0x3c, 0xf7, 0x7c, 0x83, 0x1b, 0x47, 0xe8, 0x77, 0xd2, + 0x2c, 0xe6, 0xc7, 0x45, 0x8c, 0x55, 0x66, 0xb9, 0x39, 0x84, 0xfe, 0xbe, 0x00, 0xd1, 0xa7, 0x70, + 0xd5, 0x65, 0xfd, 0xf8, 0x84, 0x7d, 0xd6, 0xe3, 0xa4, 0x6d, 0x68, 0xed, 0x31, 0x3b, 0xed, 0xad, + 0x63, 0xa5, 0xff, 0x70, 0x60, 0xc1, 0x4e, 0x88, 0x3f, 0xab, 0xe3, 0xf9, 0xcb, 0x40, 0x12, 0x96, + 0x66, 0x9d, 0x41, 0x1c, 0x86, 0xfc, 0x94, 0xee, 0xb3, 0xd0, 0x3b, 0x93, 0xa9, 0xf8, 0x65, 0xde, + 0xf2, 0x48, 0x34, 0xdc, 0xe7, 0x70, 0xb2, 0x01, 0x33, 0xde, 0x20, 0xe8, 0x70, 0x43, 0x12, 0x5a, + 0x9e, 0xf6, 0x06, 0xc1, 0x37, 0xd9, 0x19, 0xa1, 0xb0, 0x20, 0x1b, 0x3a, 0x21, 0x3b, 0x61, 0x21, + 0xaa, 0xb6, 0xee, 0xce, 0x89, 0xe6, 0x87, 0x1c, 0x44, 0x6e, 0xc3, 0xf2, 0x20, 0x09, 0xb8, 0x45, + 0xe6, 0x39, 0xff, 0x19, 0x94, 0x66, 0x49, 0xc2, 0xd5, 0xe8, 0xe8, 0x77, 0xe0, 0x72, 0x85, 0x2e, + 0xa4, 0xdb, 0xfa, 0x2a, 0x2c, 0xd9, 0x37, 0x07, 0xca, 0x75, 0xe9, 0x40, 0xd6, 0xea, 0xe8, 0x2e, + 0x1e, 0x5a, 0x74, 0x64, 0x40, 0x8a, 0x38, 0xae, 0x97, 0xe9, 0x34, 0x17, 0xfd, 0x04, 0x56, 0x73, + 0xe0, 0x6e, 0x1c, 0x9d, 0xb0, 0x24, 0xe5, 0x06, 0x48, 0xa0, 0x71, 0x98, 0xc4, 0x2a, 0xd1, 0x8a, + 0xbf, 0x79, 0x28, 0x97, 0xc5, 0x72, 0x92, 0x6b, 0x59, 0xcc, 0x71, 0x12, 0x2f, 0x53, 0x1b, 0x17, + 0xfe, 0xe6, 0xd6, 0x16, 0x20, 0x11, 0xd6, 0xc1, 0x36, 0x61, 0xbd, 0x73, 0x12, 0xc6, 0xb9, 0xd0, + 0x0f, 0x30, 0xa2, 0x34, 0x45, 0x91, 0x63, 0xfc, 0x0a, 0xcc, 0x89, 0x31, 0xf2, 0x9e, 0x6a, 0x7c, + 0x9b, 0xd6, 0xf8, 0x0a, 0x62, 0xba, 0x70, 0xa8, 0xa1, 0xf4, 0xe7, 0x75, 0x98, 0xc7, 0x20, 0xf6, + 0x3e, 0xcb, 0xbc, 0x20, 0x1c, 0x1f, 0x5e, 0x8b, 0xb0, 0xb4, 0xa6, 0xc3, 0xd2, 0xe7, 0x60, 0xc1, + 0xcc, 0x91, 0x9c, 0xa9, 0xf3, 0xad, 0x91, 0x21, 0x39, 0x23, 0xcf, 0xc3, 0x22, 0x9e, 0xb6, 0x73, + 0x2c, 0x61, 0x33, 0x0b, 0x08, 0xd5, 0x68, 0xf6, 0xd9, 0x60, 0xaa, 0x70, 0x36, 0xe0, 0xcd, 0x18, + 0x5f, 0x77, 0xd2, 0xc0, 0xd7, 0x47, 0x07, 0x84, 0xec, 0x07, 0xbe, 0xd1, 0x8c, 0xbd, 0x67, 0x8c, + 0x66, 0xec, 0xcd, 0x8f, 0x45, 0x09, 0x13, 0x17, 0x00, 0x78, 0x8f, 0x35, 0x8b, 0x46, 0x37, 0xaf, + 0x80, 0x07, 0x41, 0x1f, 0x6f, 0xb9, 0x64, 0x42, 0xbb, 0x29, 0x2c, 0x56, 0x7c, 0xe5, 0x27, 0x37, + 0x30, 0x4f, 0x6e, 0xf9, 0x39, 0x6f, 0xce, 0x3a, 0xe7, 0x6d, 0xc1, 0x5c, 0x3c, 0x60, 0x51, 0x47, + 0x9e, 0xba, 0xe7, 0x45, 0x40, 0xc1, 0x41, 0x1f, 0x88, 0x93, 0xf7, 0x32, 0xd4, 0x0f, 0x19, 0x6b, + 0x2d, 0x88, 0x33, 0xfa, 0x21, 0xe3, 0x2b, 0x6b, 0x3a, 0x4b, 0x3c, 0x9f, 0xa5, 0xad, 0x45, 0x9c, + 0x3d, 0xed, 0xbc, 0x0f, 0x38, 0xf4, 0xeb, 0x01, 0x77, 0x42, 0x67, 0xae, 0xc4, 0xa1, 0xff, 0xe4, + 0xc0, 0xbc, 0xd9, 0x50, 0x1e, 0x9c, 0x53, 0x31, 0xb8, 0xe2, 0xd4, 0xe9, 0x41, 0xd5, 0xab, 0x07, + 0xd5, 0xb0, 0x06, 0x65, 0x1a, 0xc5, 0x54, 0xc1, 0x28, 0xc6, 0x1f, 0xea, 0x0a, 0x13, 0x37, 0x53, + 0x9c, 0x38, 0xa9, 0x8d, 0x59, 0xad, 0x0d, 0x99, 0x65, 0x42, 0x9b, 0x4c, 0x27, 0x39, 0xca, 0xdb, + 0xfc, 0x6b, 0x45, 0xfe, 0xea, 0xec, 0x5c, 0x3f, 0xef, 0xec, 0x4c, 0x77, 0xf0, 0x20, 0xa1, 0x18, + 0xcb, 0xe5, 0xf5, 0x32, 0x4c, 0xa3, 0xb0, 0x6a, 0x65, 0xad, 0x5a, 0x27, 0x3f, 0xb9, 0x68, 0x5c, + 0x89, 0x43, 0xbf, 0x8e, 0x77, 0xa7, 0xd8, 0x34, 0x89, 0xe8, 0x97, 0x61, 0x56, 0xe8, 0x46, 0x4f, + 0xcd, 0x0c, 0x7e, 0x3f, 0xf0, 0xe9, 0x2f, 0x1d, 0x20, 0xfb, 0xc3, 0x6e, 0x3f, 0x98, 0x9c, 0xda, + 0xe4, 0x39, 0x0d, 0x02, 0x0d, 0x9c, 0x0d, 0xb1, 0x5c, 0xf1, 0x77, 0x61, 0x05, 0x35, 0x8a, 0x2b, + 0x28, 0xb7, 0x8c, 0xa9, 0xea, 0xb4, 0xc6, 0xb4, 0x69, 0x47, 0x7c, 0x83, 0x0b, 0x03, 0x16, 0x65, + 0x1d, 0x99, 0x9f, 0xe2, 0x1b, 0x1c, 0x02, 0x1e, 0xf8, 0x74, 0x1f, 0x56, 0xac, 0x91, 0x49, 0x4d, + 0xdf, 0x80, 0x79, 0x21, 0xc0, 0x20, 0xf4, 0x7a, 0xfa, 0x02, 0x61, 0x0e, 0x61, 0x8f, 0x10, 0x34, + 0x4e, 0x5f, 0xbf, 0xeb, 0xc0, 0xea, 0x7e, 0xd0, 0x1f, 0x86, 0x5e, 0xc6, 0x7e, 0x0d, 0x1a, 0xcb, + 0x87, 0x5f, 0xb7, 0x86, 0xaf, 0x34, 0xd9, 0xc8, 0x35, 0x49, 0xff, 0xcb, 0x81, 0xb5, 0x82, 0x28, + 0x3a, 0x8c, 0xb6, 0x8d, 0x69, 0x44, 0x3e, 0x45, 0x22, 0x19, 0x4c, 0x6b, 0x16, 0xd3, 0xe7, 0x60, + 0xa1, 0x1f, 0x44, 0x41, 0x7f, 0xd8, 0xef, 0x98, 0x6b, 0x78, 0x5e, 0x02, 0x1f, 0xe1, 0x14, 0x70, + 0x24, 0xef, 0xa9, 0x81, 0xd4, 0x90, 0x48, 0x02, 0x28, 0x90, 0x5e, 0x85, 0xd5, 0xfc, 0xa8, 0xd3, + 0x39, 0xf2, 0x82, 0xa8, 0x13, 0xc6, 0x69, 0x2a, 0xe7, 0x98, 0xe4, 0x6d, 0x7b, 0x5e, 0x10, 0x3d, + 0x8c, 0xd3, 0xd4, 0x70, 0x92, 0xd3, 0xa6, 0x93, 0xa4, 0x7f, 0xe0, 0xc0, 0xf2, 0x87, 0xc7, 0x5e, + 0xc8, 0xee, 0xc5, 0xfd, 0xee, 0xb3, 0xd5, 0xfd, 0x0d, 0x98, 0x17, 0xa9, 0xca, 0xcc, 0x4b, 0x8e, + 0x98, 0x9a, 0x81, 0x39, 0x84, 0x1d, 0x20, 0xa8, 0x72, 0x1a, 0xfe, 0xd3, 0x01, 0xb2, 0xcb, 0xa3, + 0xbf, 0x70, 0x62, 0x7b, 0xe0, 0xae, 0x44, 0xa4, 0x1a, 0x72, 0x0b, 0x6b, 0x4a, 0xc8, 0x03, 0xdb, + 0xfc, 0xea, 0x96, 0xf9, 0xe9, 0xd1, 0x34, 0x2e, 0x98, 0x4f, 0x2c, 0xed, 0x73, 0xcf, 0xc3, 0xe2, + 0xa9, 0x17, 0x86, 0x2c, 0xd3, 0xd7, 0x8e, 0xf2, 0xf2, 0x42, 0x40, 0x55, 0xda, 0x42, 0x0d, 0x78, + 0xc6, 0x18, 0xf0, 0x1b, 0xb0, 0x2e, 0xc6, 0xbb, 0x13, 0x86, 0x13, 0xbb, 0x4f, 0xfa, 0x27, 0x35, + 0xd8, 0x28, 0x75, 0xd3, 0xf1, 0x93, 0x6d, 0xaf, 0x37, 0xf5, 0xb8, 0xaa, 0x3b, 0x6c, 0xcb, 0x4f, + 0xd9, 0xab, 0xfd, 0x37, 0x0e, 0x4c, 0x0b, 0xd0, 0x58, 0xb5, 0x7f, 0xa4, 0x56, 0xbe, 0xb4, 0x2c, + 0x71, 0x5a, 0xfc, 0xc2, 0x64, 0xcc, 0xc4, 0x7f, 0xe6, 0x9d, 0xb2, 0x70, 0x19, 0xf2, 0x3a, 0xf9, + 0xab, 0xb0, 0x5c, 0x44, 0xb8, 0xd0, 0x75, 0x9c, 0xc8, 0x38, 0xbd, 0x73, 0xc2, 0x8c, 0x3b, 0xe4, + 0x5f, 0x38, 0xb0, 0xb4, 0x1b, 0x47, 0x7e, 0xc0, 0x77, 0xd7, 0x47, 0x5e, 0xe2, 0xf5, 0x53, 0x59, + 0xaa, 0x20, 0x40, 0xea, 0x4a, 0x42, 0x03, 0x46, 0x24, 0x7f, 0xaf, 0x02, 0xf4, 0x8e, 0x59, 0xef, + 0x49, 0x47, 0x66, 0x63, 0x45, 0x7d, 0x03, 0x87, 0xdc, 0x0b, 0xfc, 0x94, 0xbc, 0x02, 0x2b, 0x79, + 0x73, 0xc7, 0x8b, 0xfc, 0x8e, 0x4c, 0xc5, 0xe2, 0xcd, 0x8f, 0xc6, 0xdb, 0x89, 0xfc, 0x9d, 0xf4, + 0x49, 0xca, 0x83, 0x66, 0x9d, 0x81, 0xec, 0x58, 0xbe, 0x7a, 0x49, 0xc3, 0x77, 0x10, 0x4c, 0xff, + 0xdb, 0xc1, 0xad, 0x4e, 0x8d, 0x4a, 0xce, 0x76, 0x9e, 0x74, 0xc4, 0x5c, 0xb4, 0x35, 0x65, 0xb5, + 0xc2, 0x94, 0x11, 0x68, 0x04, 0x19, 0xeb, 0xab, 0x1d, 0x84, 0xff, 0x26, 0xf7, 0x60, 0x59, 0x8f, + 0xb8, 0x33, 0x40, 0xb5, 0xc8, 0xf5, 0xb0, 0x91, 0x1f, 0xaa, 0x2d, 0xad, 0xb9, 0x4b, 0xbd, 0x82, + 0x1a, 0xd5, 0x3a, 0x9a, 0x9a, 0xc8, 0x23, 0xf7, 0x50, 0xdb, 0xd2, 0x11, 0x89, 0x2f, 0x21, 0x35, + 0xeb, 0x0d, 0x33, 0xe6, 0xcb, 0x33, 0x83, 0xfe, 0xa6, 0xff, 0xea, 0xc0, 0xd2, 0x8e, 0xef, 0xe3, + 0xb8, 0x27, 0xf1, 0x07, 0x6a, 0x94, 0xb5, 0x73, 0x46, 0x59, 0xff, 0x94, 0xa3, 0xfc, 0x95, 0xbd, + 0xc5, 0x08, 0x25, 0x50, 0x0a, 0xcb, 0xf9, 0x38, 0xab, 0xa7, 0x97, 0x7e, 0x0e, 0x88, 0x38, 0xbf, + 0x5a, 0xea, 0x28, 0x62, 0xbd, 0x0b, 0xb7, 0xf6, 0x58, 0xb6, 0x9b, 0x9c, 0x0d, 0xb2, 0x58, 0x05, + 0xf0, 0xf7, 0xd9, 0x20, 0x4e, 0x03, 0xe5, 0x8b, 0xd8, 0x44, 0x6e, 0xe6, 0x6f, 0x1d, 0xb8, 0x3d, + 0x01, 0x21, 0x29, 0xeb, 0xc7, 0xe5, 0x24, 0xdb, 0xff, 0x33, 0x0b, 0x75, 0x26, 0xa2, 0xb2, 0xad, + 0x21, 0xb2, 0x96, 0x42, 0x93, 0x6c, 0x7f, 0x19, 0x16, 0xed, 0xc6, 0x0b, 0xf9, 0x84, 0x10, 0x6e, + 0x9e, 0x23, 0xc4, 0x24, 0xc6, 0x75, 0x13, 0x16, 0x7b, 0x16, 0x09, 0xc9, 0xa8, 0x00, 0xa5, 0xbb, + 0xf0, 0xc2, 0xb9, 0xdc, 0xa4, 0xda, 0x46, 0x66, 0x1c, 0xe8, 0xcf, 0x1d, 0x58, 0xf9, 0x30, 0xc8, + 0x8e, 0xfd, 0xc4, 0x3b, 0x7d, 0x37, 0xf0, 0x26, 0xb2, 0x7e, 0xf3, 0x82, 0xa0, 0x56, 0xb8, 0x20, + 0x18, 0x15, 0x0f, 0x15, 0x92, 0x17, 0x8d, 0x72, 0x92, 0xe6, 0x26, 0x2c, 0x75, 0xbd, 0xe8, 0x49, + 0xc7, 0xd8, 0x68, 0x85, 0x59, 0x2f, 0x70, 0xb0, 0xba, 0x3d, 0xf0, 0xe9, 0x3f, 0x3a, 0xb0, 0xa6, + 0x24, 0x16, 0x83, 0x9f, 0x44, 0x66, 0x43, 0x03, 0x35, 0x3b, 0xe7, 0xb2, 0x05, 0x73, 0xf2, 0x67, + 0x27, 0xf3, 0x8e, 0xa4, 0xe3, 0x02, 0x09, 0x3a, 0xf0, 0x8e, 0xac, 0xe1, 0x36, 0x46, 0x0e, 0xd7, + 0x8e, 0x7e, 0xe5, 0xe9, 0x65, 0x3a, 0x3f, 0xcb, 0x15, 0x14, 0x30, 0x53, 0xce, 0xde, 0xbc, 0x0d, + 0xcb, 0x6a, 0x5c, 0x15, 0x6b, 0x53, 0x9c, 0xce, 0xf2, 0x28, 0xab, 0x66, 0x45, 0x59, 0x2f, 0x43, + 0x5b, 0xf5, 0xf5, 0x42, 0x5c, 0xb7, 0xf7, 0xce, 0x1e, 0xdc, 0x2f, 0xaf, 0x5d, 0xa4, 0x42, 0x0f, + 0xe0, 0x4a, 0x25, 0xb6, 0x64, 0xfa, 0x26, 0x4c, 0x31, 0x0e, 0x94, 0x21, 0xd8, 0x96, 0x5a, 0x60, + 0x85, 0x3e, 0xfa, 0xb6, 0x4c, 0x60, 0x53, 0x06, 0x37, 0x0a, 0x18, 0xe9, 0xbd, 0xb3, 0x0b, 0xd4, + 0xaa, 0x54, 0x1d, 0x45, 0xf1, 0xea, 0x1e, 0xe7, 0x64, 0xca, 0x15, 0x1f, 0xf4, 0x0c, 0xae, 0x96, + 0xd9, 0xdc, 0xf7, 0xb2, 0x89, 0x58, 0xac, 0xc2, 0x14, 0xd6, 0x71, 0xa9, 0xb5, 0x8b, 0x1f, 0x7c, + 0xb6, 0x58, 0xa4, 0x42, 0x37, 0xfe, 0x33, 0x67, 0xdd, 0x30, 0x59, 0x7f, 0x07, 0xe8, 0xb8, 0x11, + 0x96, 0xd5, 0x57, 0xbf, 0x80, 0xfa, 0x7e, 0x5a, 0x83, 0x8d, 0x11, 0x28, 0x25, 0xcd, 0xbc, 0x6d, + 0x0c, 0x51, 0xec, 0x31, 0xd7, 0x8a, 0x5c, 0x42, 0x25, 0x97, 0xa0, 0x94, 0xab, 0xe0, 0x2d, 0x98, + 0x49, 0x84, 0xa6, 0xe4, 0x36, 0x73, 0xad, 0x2c, 0xa0, 0x54, 0xa5, 0xe8, 0xaa, 0xd0, 0xc9, 0x17, + 0x01, 0x30, 0x75, 0xc0, 0xfc, 0x8e, 0x97, 0xc9, 0x9d, 0xb8, 0xbd, 0x2d, 0x2a, 0x89, 0xb7, 0x55, + 0x25, 0xf1, 0xf6, 0x81, 0xaa, 0x24, 0x76, 0x9b, 0x12, 0x7b, 0x07, 0xbb, 0xca, 0xeb, 0x5f, 0xde, + 0x75, 0xfa, 0xfc, 0xae, 0x12, 0x7b, 0x27, 0xa3, 0x07, 0xb0, 0x5e, 0x3d, 0xa6, 0xca, 0x04, 0x66, + 0x51, 0x53, 0xf9, 0x82, 0xa9, 0x5b, 0x0b, 0xe6, 0xdf, 0x9c, 0x9c, 0xac, 0x3d, 0xde, 0xb1, 0xee, + 0xed, 0xfc, 0x5c, 0xf3, 0xa8, 0x4c, 0x09, 0x81, 0x86, 0xde, 0xaa, 0xa7, 0x5c, 0xfc, 0x4d, 0xee, + 0x40, 0xe3, 0x30, 0xd0, 0xfa, 0xd0, 0xf7, 0xb6, 0xdc, 0x0f, 0x17, 0x2d, 0x01, 0x11, 0xc9, 0x9b, + 0x30, 0x2d, 0x36, 0x01, 0xf4, 0x1f, 0x73, 0x77, 0xaf, 0xea, 0x08, 0x01, 0xa1, 0xc5, 0x4e, 0x12, + 0x99, 0xfe, 0x95, 0x03, 0x2b, 0x15, 0x44, 0xf9, 0x69, 0x1c, 0x5d, 0xae, 0xa1, 0xc5, 0x59, 0x0e, + 0x78, 0x8f, 0x6b, 0xf2, 0x06, 0xcc, 0x2b, 0x57, 0x8c, 0xed, 0x42, 0x15, 0x73, 0x12, 0x86, 0x28, + 0xcf, 0xc3, 0xa2, 0x46, 0x19, 0xf6, 0xbb, 0x4c, 0xd5, 0xb1, 0x2c, 0x28, 0x24, 0x04, 0x62, 0x39, + 0x4a, 0xda, 0x95, 0xbe, 0x93, 0xff, 0xc4, 0x65, 0x78, 0x1a, 0x1c, 0xaa, 0x2a, 0x2d, 0xf1, 0x81, + 0x51, 0x55, 0xd7, 0x53, 0x21, 0x0b, 0xfe, 0xa6, 0x3e, 0xac, 0x55, 0x8e, 0x6d, 0x4c, 0x12, 0xbd, + 0xe0, 0xd0, 0x6b, 0x25, 0x87, 0x2e, 0x9d, 0x73, 0x3d, 0x4f, 0x2d, 0xbd, 0x86, 0x45, 0x6c, 0x0f, + 0xe3, 0xa3, 0xa3, 0x3c, 0x75, 0x23, 0x8d, 0x7e, 0x1d, 0xa6, 0x43, 0x84, 0xab, 0x12, 0x77, 0xf1, + 0x45, 0x23, 0xcc, 0xb5, 0x17, 0xba, 0xe4, 0x97, 0xcc, 0x41, 0x74, 0x18, 0xcb, 0x4c, 0x05, 0xfe, + 0xe6, 0x43, 0xf6, 0x59, 0x77, 0x78, 0xa4, 0x6a, 0x52, 0xf1, 0x83, 0x63, 0x9e, 0x7a, 0x49, 0x24, + 0x63, 0x7c, 0xfc, 0xcd, 0x31, 0x59, 0x92, 0xc4, 0x89, 0x0c, 0xe8, 0xc5, 0x07, 0xdd, 0x83, 0x8d, + 0xfd, 0x8b, 0x89, 0x88, 0x4e, 0x0c, 0x33, 0xe9, 0xd2, 0xd9, 0xe1, 0x07, 0xfd, 0xa6, 0x55, 0xb0, + 0x87, 0x45, 0x5d, 0x13, 0x7a, 0x4e, 0x0c, 0x2f, 0x15, 0x31, 0xfc, 0xa0, 0xbf, 0x74, 0x50, 0x0d, + 0x05, 0x6a, 0xba, 0x26, 0xb8, 0x5c, 0x00, 0x27, 0x62, 0xb6, 0x37, 0x2b, 0x0a, 0xe0, 0xac, 0xbe, + 0x93, 0x55, 0xc0, 0xfd, 0x5a, 0x8b, 0xda, 0x7e, 0xe6, 0xc0, 0xfa, 0xbe, 0x2d, 0xde, 0x33, 0xc8, + 0x3a, 0xbe, 0x08, 0x53, 0xa2, 0x98, 0xb2, 0x6e, 0x67, 0x0d, 0xad, 0x10, 0x5f, 0xa0, 0xf0, 0x79, + 0x15, 0xb7, 0x2f, 0xd2, 0x12, 0xe4, 0x17, 0xfd, 0xa1, 0x83, 0x77, 0x1b, 0x3a, 0x37, 0xb4, 0x9f, + 0x25, 0xcc, 0xeb, 0x7f, 0xa6, 0xd5, 0x4d, 0x5f, 0x83, 0x1b, 0x66, 0xf1, 0xeb, 0x85, 0x25, 0xa1, + 0xbf, 0x89, 0x35, 0x21, 0xa2, 0x62, 0xeb, 0xff, 0x40, 0xfe, 0x2f, 0xc3, 0x35, 0x43, 0xfe, 0x0b, + 0x8a, 0x41, 0xff, 0xd8, 0xc1, 0xfb, 0x9f, 0x9d, 0xa1, 0x1f, 0x64, 0xd6, 0x21, 0xe9, 0x2a, 0x00, + 0x46, 0x14, 0x1d, 0xbe, 0x79, 0xe9, 0xb2, 0x7a, 0x0e, 0xe1, 0x01, 0x0a, 0xb9, 0x0c, 0xb3, 0x2c, + 0xf2, 0x45, 0xa3, 0x8c, 0x42, 0x59, 0xe4, 0xab, 0x26, 0x91, 0xea, 0xe8, 0x9e, 0x59, 0x29, 0xa4, + 0x7b, 0x67, 0xd5, 0xb1, 0x08, 0x37, 0x8e, 0xf8, 0xf0, 0x90, 0x2f, 0x48, 0xb1, 0xa3, 0xc8, 0x2f, + 0xba, 0x2b, 0x2a, 0x8c, 0x0c, 0xd1, 0xe4, 0x6a, 0x7c, 0x11, 0xa6, 0x31, 0xd0, 0x28, 0x95, 0x2a, + 0x19, 0xb8, 0x12, 0x83, 0xfe, 0xbb, 0xb0, 0x30, 0x71, 0x91, 0x10, 0xf4, 0x76, 0xbd, 0xc8, 0x0f, + 0x27, 0x3a, 0xce, 0x3d, 0xb3, 0x19, 0xca, 0x23, 0xb5, 0x06, 0x1e, 0x39, 0xed, 0x48, 0x4d, 0x94, + 0x90, 0x61, 0xa4, 0xf6, 0x1c, 0x2c, 0x64, 0x41, 0x9f, 0x75, 0x82, 0x28, 0x63, 0xc9, 0x89, 0xa7, + 0xae, 0x0d, 0xe7, 0x39, 0xf0, 0x81, 0x84, 0x71, 0x5e, 0xec, 0x69, 0x47, 0x85, 0x3d, 0xe2, 0xf4, + 0xdf, 0x64, 0x4f, 0x55, 0xde, 0xe6, 0xef, 0x1d, 0x2c, 0x89, 0x29, 0x0d, 0x77, 0x82, 0x2a, 0xa5, + 0xc9, 0xc7, 0xab, 0x07, 0x54, 0xaf, 0x18, 0x50, 0x23, 0x1f, 0x50, 0x1b, 0x66, 0xad, 0xb1, 0x34, + 0x5d, 0xfd, 0x4d, 0x6e, 0xc2, 0x74, 0x0f, 0x85, 0x93, 0xb5, 0x05, 0x8b, 0x46, 0x2a, 0xcc, 0x0f, + 0x99, 0x2b, 0x5b, 0xe9, 0x6f, 0x39, 0x30, 0x2d, 0x40, 0x18, 0x77, 0xe4, 0x77, 0x3f, 0xf8, 0x5b, + 0x55, 0x8c, 0xd6, 0xf2, 0x8a, 0x51, 0x55, 0x57, 0x5a, 0x37, 0xea, 0x4a, 0x09, 0x34, 0xe2, 0x01, + 0x8b, 0x54, 0xfd, 0x29, 0xff, 0xcd, 0x07, 0xd1, 0x0b, 0xe3, 0x94, 0xc9, 0xe3, 0x8e, 0xf8, 0x30, + 0x6a, 0x49, 0xa7, 0xcd, 0x5a, 0x52, 0xfa, 0x14, 0x20, 0x37, 0x2e, 0x1d, 0x01, 0xc9, 0x70, 0x0d, + 0x23, 0xa0, 0x6b, 0x00, 0x81, 0xcf, 0xa2, 0x2c, 0x38, 0x0c, 0x98, 0xaa, 0x49, 0x34, 0x20, 0x7c, + 0x97, 0xef, 0xb3, 0x34, 0x55, 0x05, 0x3d, 0x4d, 0x57, 0x7d, 0x92, 0x4d, 0x68, 0xea, 0x37, 0x6b, + 0xea, 0x56, 0x42, 0x03, 0x68, 0x17, 0x9a, 0x7b, 0xbb, 0x07, 0xfb, 0x18, 0x95, 0x71, 0xc6, 0x8f, + 0x1f, 0x3f, 0xb8, 0xaf, 0x18, 0xf3, 0xdf, 0x3a, 0x76, 0xac, 0x19, 0xb1, 0x23, 0xe1, 0x73, 0x99, + 0x1d, 0xab, 0xdc, 0x15, 0xff, 0xcd, 0xd7, 0x65, 0xc4, 0x9e, 0x66, 0x9d, 0x64, 0xa8, 0x0e, 0xad, + 0x33, 0xfc, 0xdb, 0x1d, 0x46, 0xf4, 0x3e, 0x6c, 0x68, 0x1e, 0xef, 0x88, 0x4c, 0x92, 0x5a, 0x21, + 0xb7, 0x61, 0x5a, 0x44, 0x84, 0xb2, 0x32, 0xf3, 0x92, 0xde, 0xef, 0x54, 0x07, 0x57, 0x22, 0xd0, + 0x1d, 0x58, 0xd5, 0xc0, 0xfd, 0x2c, 0x1e, 0x7c, 0x0a, 0x12, 0x97, 0x0d, 0x41, 0x38, 0x89, 0x9d, + 0x50, 0x05, 0xb4, 0xf8, 0xe6, 0x21, 0x6f, 0xe2, 0x91, 0xaf, 0x6a, 0x31, 0x3b, 0x3d, 0x0c, 0xd2, + 0xcc, 0xe8, 0xf4, 0x67, 0x8e, 0xd1, 0xeb, 0xf1, 0x20, 0x8c, 0x3d, 0x5f, 0x49, 0xb5, 0x05, 0x73, + 0x82, 0xa9, 0x19, 0x33, 0x82, 0x00, 0x61, 0x48, 0x98, 0x23, 0x60, 0x99, 0x5d, 0xcd, 0x44, 0xb8, + 0xef, 0x65, 0x9e, 0x2e, 0xc0, 0xab, 0xe7, 0x05, 0x78, 0xdc, 0xe4, 0xbd, 0xa4, 0x77, 0x1c, 0x9c, + 0x30, 0x5f, 0x6e, 0x75, 0xfa, 0x9b, 0xcf, 0x73, 0x7c, 0xc2, 0x92, 0xd3, 0x24, 0xc8, 0x84, 0xd5, + 0xcd, 0xba, 0x39, 0x80, 0xee, 0x41, 0x3b, 0xd7, 0x07, 0xf3, 0x7c, 0xf5, 0xeb, 0xc2, 0x3a, 0xbc, + 0x07, 0x6b, 0x1a, 0xf8, 0xed, 0x21, 0xd3, 0xf5, 0x70, 0x17, 0xa1, 0xf1, 0x0d, 0x68, 0x69, 0xe0, + 0xce, 0x30, 0x8b, 0x1f, 0x1a, 0x8a, 0x5b, 0xb7, 0xc8, 0x34, 0x55, 0x9f, 0xc2, 0x81, 0x7e, 0x56, + 0x9f, 0x4f, 0x3e, 0xb6, 0xe6, 0x54, 0x4c, 0x5c, 0xfe, 0xe8, 0x52, 0xbf, 0xaf, 0x32, 0xaf, 0xa3, + 0x5f, 0x82, 0x19, 0x41, 0x54, 0x25, 0xca, 0x2b, 0x44, 0x55, 0x18, 0x34, 0x36, 0xa6, 0x58, 0x8e, + 0xf7, 0x1c, 0xf2, 0xb9, 0x22, 0x6a, 0xe7, 0x28, 0xc2, 0x9a, 0xe3, 0xa6, 0x2c, 0xb2, 0xfc, 0x0a, + 0x2c, 0xc9, 0x27, 0x45, 0xe7, 0x72, 0x52, 0xdd, 0x6b, 0x46, 0xf7, 0x1e, 0x86, 0xbf, 0x6a, 0xc3, + 0xc6, 0x58, 0xef, 0x53, 0x47, 0xad, 0x46, 0x60, 0x55, 0xb7, 0x02, 0xab, 0x47, 0xd0, 0x36, 0x99, + 0x84, 0xe1, 0xc4, 0xd1, 0x71, 0x4e, 0xb1, 0x66, 0x51, 0xdc, 0x81, 0xe7, 0x44, 0x8d, 0xb3, 0x22, + 0xaa, 0x43, 0xcd, 0x49, 0x49, 0xd3, 0xcf, 0x5b, 0x11, 0xb6, 0x88, 0x72, 0x27, 0xe9, 0xf7, 0x3a, + 0x6e, 0xe1, 0xc5, 0x7e, 0xb9, 0xea, 0x75, 0x40, 0x2e, 0xf2, 0xc3, 0xf8, 0x45, 0xdf, 0x84, 0x8d, + 0x0f, 0x59, 0x37, 0x8d, 0x7b, 0x4f, 0x58, 0x66, 0xbf, 0xff, 0x1d, 0xcb, 0xeb, 0x27, 0x35, 0x68, + 0x95, 0xfb, 0x4d, 0xb0, 0x7d, 0xe2, 0x33, 0x44, 0xa9, 0x11, 0xf5, 0x90, 0x53, 0x03, 0xcc, 0x6a, + 0xa4, 0xba, 0x5d, 0x8d, 0xf4, 0x05, 0xd8, 0xb0, 0x9f, 0xbe, 0xe4, 0x54, 0x84, 0x03, 0x59, 0xb7, + 0x9a, 0xf3, 0xd7, 0x4f, 0x9f, 0x83, 0x05, 0xab, 0x45, 0xba, 0x14, 0x1b, 0xc8, 0xbd, 0x58, 0x32, + 0x8c, 0xa2, 0x20, 0x3a, 0xea, 0x0c, 0x13, 0xb5, 0x0d, 0x83, 0x04, 0x3d, 0x4e, 0x42, 0x1e, 0x75, + 0xe0, 0xf3, 0x20, 0x7d, 0x19, 0x27, 0xf2, 0x79, 0xf3, 0x08, 0x54, 0x4f, 0x00, 0x1f, 0x41, 0x5b, + 0x2b, 0x85, 0xdb, 0x95, 0x90, 0xfd, 0x57, 0x31, 0xa7, 0xaf, 0xc2, 0x75, 0x53, 0xcd, 0xfb, 0xc3, + 0xae, 0x4e, 0x3c, 0x4c, 0x64, 0x13, 0xdf, 0x87, 0xb5, 0x5c, 0x22, 0xa3, 0x33, 0xd7, 0x34, 0x47, + 0x89, 0x58, 0xa8, 0x4e, 0xd3, 0xf2, 0x73, 0x6c, 0x36, 0x44, 0xaf, 0xae, 0x7a, 0x61, 0x75, 0x19, + 0x97, 0x3c, 0x4d, 0x57, 0x7e, 0xf1, 0xa0, 0xe4, 0xc6, 0x18, 0xe9, 0x27, 0xb0, 0x96, 0x5d, 0x58, + 0x48, 0xcd, 0x4e, 0xd2, 0xcf, 0xe9, 0x2c, 0x48, 0xe5, 0xd8, 0x5c, 0xbb, 0x0f, 0x7d, 0x68, 0x98, + 0xea, 0x3e, 0xcb, 0xf0, 0x51, 0xd7, 0x84, 0xae, 0x44, 0xbc, 0x08, 0x93, 0xae, 0x04, 0x3f, 0xe8, + 0xbb, 0xb0, 0x6e, 0x52, 0x7b, 0xec, 0x3e, 0x9c, 0x84, 0xd6, 0x32, 0xd4, 0xb9, 0x5d, 0x09, 0x4a, + 0xfc, 0xe7, 0xdd, 0x1f, 0x7f, 0x0d, 0x16, 0xf7, 0x62, 0x91, 0xea, 0xc0, 0x22, 0x9e, 0x84, 0xbc, + 0x0f, 0x33, 0x72, 0x29, 0x91, 0xf5, 0xd2, 0xbb, 0x70, 0xe4, 0xd1, 0xde, 0x18, 0xf1, 0x5e, 0x9c, + 0xae, 0xfc, 0xe8, 0x1f, 0xfe, 0xe5, 0x27, 0xb5, 0x05, 0x32, 0x77, 0xe7, 0xe4, 0xb5, 0x3b, 0x47, + 0x2c, 0xc3, 0x14, 0xc4, 0x11, 0x2c, 0x58, 0xaf, 0x7a, 0xc9, 0xa6, 0xf5, 0x32, 0xb7, 0xf0, 0xd8, + 0xb7, 0x7d, 0x75, 0xec, 0xbb, 0x5d, 0x7a, 0x19, 0x59, 0xac, 0x90, 0x4b, 0x92, 0x45, 0xfe, 0x60, + 0x97, 0x1c, 0xc3, 0x92, 0x30, 0x76, 0x4d, 0x94, 0x6c, 0xe5, 0xc4, 0x2a, 0x1f, 0x24, 0x9b, 0x43, + 0xb1, 0x76, 0x09, 0x7a, 0x05, 0xf9, 0xac, 0x91, 0x15, 0xce, 0x47, 0xac, 0x03, 0xcd, 0x8a, 0x7c, + 0x17, 0x96, 0xe5, 0xa3, 0xc8, 0x67, 0xc1, 0x6a, 0x13, 0x59, 0xad, 0x93, 0x55, 0xce, 0xca, 0x17, + 0x74, 0x73, 0x5e, 0x31, 0xd6, 0xf0, 0x98, 0x8f, 0x73, 0xc9, 0xb5, 0x91, 0xaf, 0x76, 0x05, 0xa7, + 0xad, 0x73, 0x5e, 0xf5, 0xda, 0x83, 0x3b, 0x62, 0x1c, 0x57, 0x3f, 0xec, 0x25, 0x3f, 0x11, 0xc9, + 0x95, 0xca, 0xa7, 0xe2, 0xe4, 0x85, 0xf3, 0xdf, 0xa7, 0x0b, 0x19, 0x6e, 0x4d, 0xfa, 0x90, 0x9d, + 0x7e, 0x0e, 0x85, 0xb9, 0x46, 0x36, 0xa5, 0x30, 0xd6, 0xe3, 0x75, 0xf5, 0x3c, 0x9e, 0xf4, 0x60, + 0xde, 0x7c, 0xb0, 0x4b, 0xae, 0x54, 0xe4, 0x72, 0x34, 0xf3, 0xcd, 0xea, 0x46, 0xc9, 0xb0, 0x85, + 0x0c, 0x09, 0x59, 0x96, 0x0c, 0x75, 0x01, 0x2e, 0x89, 0x60, 0xa9, 0xf0, 0xd8, 0x95, 0xd0, 0xc2, + 0xac, 0x55, 0xbc, 0x4c, 0x1e, 0x3d, 0xb3, 0xd7, 0x90, 0x53, 0x8b, 0xae, 0x18, 0x33, 0xab, 0xb8, + 0xbd, 0xed, 0xbc, 0x48, 0x52, 0x9c, 0x5b, 0xf3, 0x2d, 0xe6, 0x44, 0xfc, 0xb6, 0xce, 0x79, 0xc8, + 0x59, 0x9a, 0x5f, 0xc5, 0x13, 0xd7, 0x63, 0x8a, 0xef, 0xdb, 0x8c, 0x17, 0xc4, 0xbb, 0xb1, 0x3f, + 0xd9, 0x38, 0xaf, 0x56, 0xbf, 0x40, 0x96, 0x8f, 0xa0, 0x69, 0x1b, 0xb9, 0xae, 0x12, 0x52, 0xe0, + 0x1a, 0x67, 0x03, 0x92, 0x5a, 0x0f, 0xb4, 0x25, 0x53, 0xdb, 0x92, 0x2b, 0x9e, 0x48, 0x57, 0x8e, + 0xd4, 0x7c, 0xf3, 0x3c, 0x72, 0xa4, 0x71, 0x36, 0x48, 0x49, 0x08, 0x8b, 0xc2, 0x21, 0x3c, 0x9b, + 0xd9, 0xbc, 0x8a, 0xbc, 0x36, 0x28, 0xc9, 0x5d, 0x82, 0x39, 0x99, 0x1f, 0x42, 0x53, 0xe7, 0x96, + 0x48, 0xcb, 0x10, 0xdc, 0x7a, 0xa1, 0xda, 0x1e, 0xf1, 0xfe, 0x50, 0x59, 0x25, 0x5d, 0x90, 0x23, + 0x11, 0xaf, 0x09, 0x39, 0xe1, 0xef, 0x00, 0xe4, 0x0f, 0x12, 0xc9, 0xe5, 0x12, 0x65, 0xad, 0xad, + 0x76, 0x55, 0x93, 0x24, 0xbf, 0x8e, 0xe4, 0x97, 0xc9, 0xa2, 0x45, 0x5e, 0xad, 0x2b, 0x9d, 0x4a, + 0xb3, 0xd6, 0x55, 0xf1, 0x09, 0x63, 0x7b, 0xf4, 0xdb, 0x35, 0x35, 0x11, 0x54, 0x2d, 0x2a, 0x5d, + 0xe3, 0xc1, 0x47, 0x20, 0xb6, 0x00, 0xe3, 0xd1, 0xdc, 0x66, 0x15, 0x97, 0xca, 0x2d, 0xa0, 0xfc, + 0x02, 0xae, 0xb4, 0x05, 0xe4, 0x0f, 0xdd, 0xc8, 0x13, 0xfc, 0xfb, 0x31, 0xc6, 0x9b, 0x2f, 0x62, + 0xd2, 0x2a, 0x3f, 0x80, 0x6b, 0x5f, 0x1b, 0xd5, 0x9c, 0x56, 0xdb, 0xb4, 0xbc, 0x62, 0xc0, 0x85, + 0x74, 0x26, 0xd2, 0x71, 0x79, 0x2f, 0x91, 0xca, 0xfb, 0x55, 0x59, 0x5e, 0x47, 0x96, 0x6d, 0xd2, + 0x2a, 0xb3, 0x4c, 0x91, 0xc1, 0xab, 0x8e, 0xb4, 0x35, 0xf1, 0xc8, 0xcc, 0xb2, 0x35, 0xeb, 0x2d, + 0x5a, 0xfb, 0x72, 0x45, 0x8b, 0xe4, 0xb2, 0x86, 0x5c, 0x96, 0xc8, 0x82, 0xf6, 0xba, 0x48, 0x4b, + 0x98, 0x83, 0x7e, 0x42, 0x60, 0x99, 0x43, 0xf1, 0x89, 0x98, 0xe5, 0x66, 0x4b, 0x0f, 0xc5, 0x4a, + 0x6e, 0x56, 0x3f, 0x05, 0x23, 0x3f, 0xb0, 0x5f, 0x9c, 0xa9, 0x17, 0x30, 0x74, 0xec, 0x93, 0x15, + 0xc1, 0xf2, 0xb9, 0x09, 0x9e, 0xb5, 0xd0, 0x2d, 0xe4, 0x7c, 0x99, 0x6c, 0x14, 0x39, 0xcb, 0x27, + 0x32, 0xe4, 0x04, 0x56, 0x2a, 0x1e, 0x84, 0xe4, 0x02, 0x8c, 0x7e, 0x2d, 0x32, 0xda, 0x3b, 0x50, + 0x64, 0xba, 0x49, 0x91, 0xa9, 0xe7, 0xfb, 0x9a, 0xa9, 0x8c, 0xd5, 0xf9, 0x3a, 0xf8, 0x01, 0xac, + 0x57, 0xbf, 0xd1, 0x20, 0xcf, 0xeb, 0x3f, 0x8d, 0x31, 0xee, 0x0d, 0xc7, 0x68, 0xee, 0xcf, 0x23, + 0xf7, 0x2d, 0xda, 0xe6, 0xdc, 0x13, 0xa4, 0x51, 0x25, 0xc0, 0x29, 0x16, 0x5a, 0xd9, 0xcf, 0x13, + 0xc8, 0x75, 0x43, 0xa7, 0x95, 0xaf, 0x38, 0xda, 0x37, 0xc6, 0x60, 0xd8, 0xce, 0x91, 0xac, 0x49, + 0x9d, 0x63, 0x4d, 0xbf, 0x7e, 0xe7, 0x20, 0x3d, 0x40, 0x5e, 0xfe, 0x6f, 0x79, 0x80, 0xd2, 0x8b, + 0x06, 0xcb, 0x03, 0x94, 0x1f, 0x19, 0x94, 0x3c, 0x00, 0x32, 0xc3, 0x07, 0x07, 0xe4, 0x23, 0x5c, + 0x19, 0xb2, 0xca, 0xaf, 0x55, 0x74, 0x24, 0x69, 0xd5, 0xca, 0xb0, 0xeb, 0xf8, 0x4a, 0x8e, 0x58, + 0x14, 0x0f, 0x72, 0xed, 0xb9, 0x30, 0xab, 0xd0, 0xc9, 0x46, 0x91, 0x80, 0xa2, 0x5c, 0x59, 0x91, + 0x4d, 0x37, 0x90, 0xe8, 0x25, 0x3a, 0x6f, 0x12, 0xe5, 0x34, 0xbb, 0x30, 0x67, 0x54, 0x1f, 0x13, + 0xed, 0xc2, 0xcb, 0xc5, 0xd6, 0xed, 0x2b, 0x95, 0x6d, 0xb6, 0xa3, 0xa2, 0x4b, 0x9c, 0x41, 0x8a, + 0x08, 0x9a, 0xc7, 0x77, 0x61, 0xc1, 0x2a, 0x00, 0xce, 0x95, 0x5f, 0x55, 0xa2, 0x9c, 0x2b, 0xbf, + 0xb2, 0x6a, 0x58, 0x85, 0xab, 0x14, 0x95, 0x9f, 0x4a, 0x14, 0xcd, 0xeb, 0x63, 0x68, 0xea, 0xba, + 0xdb, 0x5c, 0xff, 0xc5, 0x52, 0xdc, 0xf3, 0x78, 0x58, 0x73, 0x70, 0xca, 0x3b, 0x77, 0xe3, 0x7e, + 0x57, 0xd0, 0x9f, 0x33, 0xaa, 0x68, 0x73, 0x7d, 0x95, 0x4b, 0x6b, 0x47, 0x2f, 0x16, 0x4b, 0x57, + 0x3d, 0xec, 0xa8, 0xe5, 0x4f, 0x60, 0xa9, 0x50, 0xe0, 0x99, 0x07, 0x29, 0xd5, 0xe5, 0xac, 0x79, + 0x90, 0x32, 0xa2, 0x32, 0xd4, 0x0e, 0x03, 0x05, 0x3f, 0x2f, 0x0c, 0x73, 0xbb, 0x12, 0xde, 0x5c, + 0x54, 0x76, 0x58, 0x36, 0x6b, 0xd5, 0x79, 0x5a, 0x36, 0x6b, 0xd7, 0x4a, 0x96, 0xbc, 0xb9, 0xb8, + 0x50, 0x21, 0x1f, 0xc0, 0xac, 0xaa, 0xbb, 0xcb, 0x0d, 0xb6, 0x50, 0x71, 0xd8, 0x6e, 0x95, 0x1b, + 0x24, 0x55, 0xcb, 0x68, 0x3d, 0xdf, 0x47, 0xaa, 0x72, 0x12, 0x8c, 0x5a, 0xbd, 0x7c, 0x12, 0xca, + 0x05, 0x7c, 0x13, 0x4e, 0x82, 0xf0, 0x58, 0x9a, 0xfe, 0x5f, 0x38, 0x78, 0xd1, 0x37, 0xbe, 0xae, + 0x8e, 0xbc, 0x7a, 0x81, 0x12, 0x3c, 0x21, 0xcc, 0x6b, 0x17, 0x2e, 0xda, 0xa3, 0xb7, 0x50, 0x4c, + 0x4a, 0xaf, 0xaa, 0x7d, 0x12, 0xbb, 0xf9, 0x02, 0x5d, 0x57, 0xf0, 0x71, 0xa1, 0xff, 0xdc, 0x11, + 0x7f, 0x73, 0x6c, 0x0c, 0x5d, 0xb2, 0x3d, 0xa1, 0x00, 0x4a, 0xe0, 0x3b, 0x13, 0xe3, 0x4b, 0x71, + 0x6f, 0xa2, 0xb8, 0xd7, 0xe9, 0x95, 0x31, 0xe2, 0x72, 0x61, 0x43, 0xb8, 0x64, 0xd6, 0xdf, 0xbd, + 0x3b, 0x8c, 0x7c, 0xe3, 0x4c, 0x55, 0x51, 0x9a, 0x97, 0x9b, 0x49, 0xb1, 0x5a, 0x4c, 0x05, 0x2c, + 0x14, 0x5d, 0xff, 0xa9, 0x6c, 0x3d, 0x0c, 0xbc, 0xec, 0x90, 0x53, 0xe5, 0xdc, 0x7e, 0xec, 0xe4, + 0xa5, 0x5f, 0xf6, 0x30, 0x04, 0xe3, 0xab, 0x45, 0xda, 0x56, 0x85, 0xdd, 0x18, 0xd6, 0xaf, 0x23, + 0xeb, 0x57, 0xe8, 0x2d, 0x93, 0xb5, 0xfc, 0x4f, 0x0c, 0x1d, 0x65, 0xb0, 0xa5, 0xf9, 0x91, 0x51, + 0x7c, 0x68, 0x14, 0xa2, 0xe5, 0xdb, 0xff, 0xe8, 0x9a, 0xb6, 0x3c, 0xfe, 0x18, 0x53, 0xc9, 0x66, + 0x87, 0x02, 0xa7, 0x1a, 0x11, 0xcd, 0xbb, 0x7b, 0x16, 0xf8, 0x5c, 0x88, 0x9f, 0x39, 0xa5, 0xda, + 0x39, 0xa3, 0xaa, 0x8b, 0xdc, 0x1e, 0xc1, 0xa7, 0x5c, 0xdb, 0xd6, 0x7e, 0x71, 0x12, 0xd4, 0x0b, + 0x48, 0xf6, 0x87, 0x56, 0x8d, 0x92, 0x59, 0xea, 0x96, 0x47, 0x29, 0x63, 0x4b, 0xe1, 0x2e, 0x24, + 0x91, 0x3c, 0xfd, 0xd3, 0xcb, 0x95, 0x12, 0xf9, 0x5e, 0x26, 0x0f, 0xca, 0xcb, 0xc5, 0xb2, 0x17, + 0x33, 0xe1, 0x52, 0x59, 0xa0, 0xd2, 0xbe, 0x3e, 0x1a, 0xa1, 0x2a, 0xf3, 0x72, 0xc4, 0x32, 0x51, + 0xc1, 0xe2, 0x4b, 0x06, 0x27, 0xb0, 0xbc, 0x3f, 0x92, 0xe9, 0xfe, 0xa7, 0x66, 0x2a, 0xa3, 0x53, + 0x8a, 0x4c, 0xd3, 0x02, 0x53, 0x3e, 0xd8, 0x13, 0x51, 0xe3, 0x6f, 0x16, 0xa8, 0x90, 0xad, 0xd1, + 0xa5, 0x2b, 0x65, 0xbe, 0x95, 0xb5, 0x2d, 0x36, 0x5f, 0xe3, 0xa8, 0x8c, 0x55, 0x1f, 0x22, 0x4c, + 0x58, 0x2a, 0x54, 0x9e, 0xe4, 0x5b, 0x5f, 0x75, 0x49, 0xca, 0x84, 0x99, 0x8f, 0xd4, 0x66, 0xc6, + 0x79, 0x65, 0x98, 0x84, 0x28, 0x54, 0x70, 0x90, 0x1b, 0x55, 0x07, 0x3f, 0xab, 0x40, 0x62, 0xdc, + 0x11, 0x54, 0xf2, 0x24, 0xeb, 0xa5, 0x73, 0xa1, 0x3a, 0x36, 0xfd, 0x9e, 0xb8, 0x71, 0x1f, 0x51, + 0x40, 0x42, 0x6e, 0x57, 0x65, 0x1b, 0x2e, 0x2c, 0x86, 0x74, 0xc1, 0xe4, 0x5a, 0x31, 0x25, 0x51, + 0x12, 0xe7, 0x18, 0xd3, 0x3f, 0x66, 0x19, 0x88, 0x95, 0x10, 0xa9, 0xa8, 0x0f, 0x19, 0x99, 0x3d, + 0x28, 0xe6, 0x41, 0xe4, 0xf1, 0x5e, 0x71, 0xfa, 0xa1, 0xfd, 0xa7, 0xe6, 0x2c, 0x96, 0x37, 0x2b, + 0x46, 0x7d, 0x11, 0xd6, 0xcf, 0x21, 0xeb, 0xab, 0xe4, 0x4a, 0x61, 0xbc, 0x05, 0x11, 0xc4, 0x09, + 0xc0, 0xb8, 0x98, 0x37, 0x4f, 0x00, 0xa5, 0x9a, 0x16, 0xeb, 0x04, 0x50, 0x2e, 0x2b, 0x29, 0x9d, + 0x00, 0x3c, 0x8e, 0x82, 0x4e, 0x83, 0x3c, 0x81, 0xe5, 0xe2, 0x05, 0xb9, 0xb1, 0x7c, 0xaa, 0xaf, + 0xce, 0xcf, 0x4d, 0xfa, 0xc8, 0x73, 0x4d, 0x2f, 0x13, 0x29, 0xfd, 0x3b, 0xf2, 0x0d, 0x07, 0x79, + 0x02, 0x4b, 0x85, 0x3b, 0x6b, 0x63, 0x0a, 0x2b, 0x2f, 0xb3, 0x47, 0xb3, 0xb2, 0x17, 0xa8, 0x66, + 0x35, 0xc4, 0xde, 0x7c, 0xd1, 0x3c, 0x85, 0x95, 0x8a, 0x6b, 0x67, 0xe3, 0xdc, 0x3c, 0xf2, 0x4e, + 0xba, 0x5d, 0x16, 0xca, 0xba, 0x7e, 0xb5, 0x73, 0x5b, 0x39, 0xef, 0x84, 0x09, 0xce, 0x03, 0x63, + 0x98, 0xf2, 0x6f, 0xc9, 0x96, 0x29, 0x5a, 0x37, 0xfd, 0xed, 0xad, 0x91, 0xed, 0x95, 0xce, 0x57, + 0xb3, 0x94, 0xb7, 0xb1, 0x21, 0x2c, 0xda, 0xa2, 0x1a, 0x69, 0x95, 0xaa, 0x1b, 0xf3, 0x73, 0x47, + 0x68, 0xaf, 0x10, 0xcd, 0xee, 0x13, 0xa4, 0xcd, 0x60, 0xc1, 0xaa, 0x65, 0x30, 0x8c, 0xb3, 0xa2, + 0x4a, 0x62, 0xc2, 0x14, 0xa1, 0x39, 0xa6, 0x78, 0xc0, 0xd5, 0x68, 0x9a, 0xa6, 0x2c, 0x99, 0x20, + 0x5b, 0x95, 0x9c, 0xf2, 0xba, 0x88, 0x4f, 0xcd, 0x2c, 0x35, 0x98, 0xc9, 0x52, 0x8b, 0x0a, 0x66, + 0x76, 0x11, 0xc6, 0xf9, 0xb3, 0x76, 0x0e, 0xd3, 0x53, 0xa3, 0x80, 0x40, 0x15, 0x23, 0x1c, 0xc4, + 0x47, 0x47, 0x21, 0x33, 0xd2, 0x0c, 0x23, 0xaa, 0x15, 0x46, 0x8f, 0xf4, 0x06, 0x32, 0xbd, 0x42, + 0xd7, 0x6d, 0xa6, 0xde, 0x30, 0x8b, 0xd5, 0xda, 0xf8, 0x3e, 0x6e, 0x28, 0x85, 0x5a, 0x2a, 0x6b, + 0x43, 0xa9, 0x2e, 0x2b, 0x6b, 0xd3, 0x71, 0x28, 0x23, 0x76, 0x96, 0x63, 0x89, 0xd7, 0x93, 0x6c, + 0xbe, 0x8b, 0x91, 0x82, 0x75, 0xe9, 0x6d, 0x45, 0x0a, 0x55, 0x05, 0x04, 0x13, 0xde, 0x07, 0x19, + 0x7b, 0xa7, 0xb8, 0xe7, 0x4c, 0x61, 0x65, 0x9f, 0xf1, 0x29, 0xb3, 0x03, 0x04, 0x5a, 0xc5, 0xce, + 0x2e, 0x25, 0x38, 0xd7, 0xf3, 0x88, 0x84, 0x59, 0xca, 0x32, 0x2f, 0x0c, 0xad, 0xe8, 0x80, 0xfc, + 0xbe, 0x03, 0x9b, 0xe3, 0x2a, 0x0a, 0xc8, 0x4b, 0x8a, 0xf4, 0x04, 0x75, 0x07, 0xa3, 0xe5, 0x90, + 0x87, 0x2d, 0x72, 0x9d, 0xcb, 0x21, 0x6a, 0xf1, 0xf5, 0xe5, 0x8c, 0xa2, 0x24, 0x04, 0x12, 0x89, + 0x2c, 0xbb, 0xcc, 0x80, 0x54, 0xc5, 0x40, 0x56, 0xe5, 0x82, 0x95, 0xc8, 0xaa, 0xae, 0x51, 0x28, + 0x25, 0xb2, 0x2c, 0xed, 0xa7, 0x7c, 0x55, 0x15, 0x4b, 0x0e, 0xf2, 0xa9, 0x1e, 0x51, 0xc4, 0x90, + 0x07, 0x67, 0xa3, 0xaa, 0x15, 0xec, 0x39, 0x3f, 0x55, 0x58, 0xea, 0x0a, 0x35, 0x85, 0x95, 0x8a, + 0x2b, 0x7d, 0xe3, 0xc0, 0x32, 0xf2, 0xbe, 0x7f, 0xc2, 0x39, 0xd7, 0x1c, 0xb9, 0xb9, 0x49, 0xea, + 0x3f, 0x73, 0xe0, 0xf2, 0xc8, 0x8b, 0x73, 0x72, 0xab, 0x6a, 0x48, 0x55, 0x95, 0x01, 0xed, 0xdb, + 0x13, 0x60, 0xda, 0x59, 0x4c, 0x72, 0xb5, 0xa8, 0x05, 0xeb, 0x2e, 0x9d, 0xf4, 0xe1, 0x52, 0xe9, + 0x2e, 0x9d, 0x5c, 0xaf, 0x52, 0x86, 0x79, 0xcd, 0x3e, 0xe1, 0x1e, 0x6f, 0xaa, 0x02, 0x2f, 0xdb, + 0xc9, 0x11, 0x2c, 0x15, 0x2e, 0xdb, 0xf3, 0xcd, 0xaf, 0xfa, 0x16, 0x7e, 0xc2, 0x6b, 0x65, 0x93, + 0xd5, 0x30, 0x09, 0xbb, 0xd3, 0xf8, 0x24, 0xe5, 0xf5, 0xff, 0x0d, 0x00, 0x00, 0xff, 0xff, 0xfa, + 0x35, 0xc5, 0x70, 0x58, 0x5f, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -10951,16 +7634,16 @@ const _ = grpc.SupportPackageIsVersion6 type GoCryptoTraderClient interface { GetInfo(ctx context.Context, in *GetInfoRequest, opts ...grpc.CallOption) (*GetInfoResponse, error) GetSubsystems(ctx context.Context, in *GetSubsystemsRequest, opts ...grpc.CallOption) (*GetSusbsytemsResponse, error) - EnableSubsystem(ctx context.Context, in *GenericSubsystemRequest, opts ...grpc.CallOption) (*GenericSubsystemResponse, error) - DisableSubsystem(ctx context.Context, in *GenericSubsystemRequest, opts ...grpc.CallOption) (*GenericSubsystemResponse, error) + EnableSubsystem(ctx context.Context, in *GenericSubsystemRequest, opts ...grpc.CallOption) (*GenericResponse, error) + DisableSubsystem(ctx context.Context, in *GenericSubsystemRequest, opts ...grpc.CallOption) (*GenericResponse, error) GetRPCEndpoints(ctx context.Context, in *GetRPCEndpointsRequest, opts ...grpc.CallOption) (*GetRPCEndpointsResponse, error) GetCommunicationRelayers(ctx context.Context, in *GetCommunicationRelayersRequest, opts ...grpc.CallOption) (*GetCommunicationRelayersResponse, error) GetExchanges(ctx context.Context, in *GetExchangesRequest, opts ...grpc.CallOption) (*GetExchangesResponse, error) - DisableExchange(ctx context.Context, in *GenericExchangeNameRequest, opts ...grpc.CallOption) (*GenericExchangeNameResponse, error) + DisableExchange(ctx context.Context, in *GenericExchangeNameRequest, opts ...grpc.CallOption) (*GenericResponse, error) GetExchangeInfo(ctx context.Context, in *GenericExchangeNameRequest, opts ...grpc.CallOption) (*GetExchangeInfoResponse, error) GetExchangeOTPCode(ctx context.Context, in *GenericExchangeNameRequest, opts ...grpc.CallOption) (*GetExchangeOTPReponse, error) GetExchangeOTPCodes(ctx context.Context, in *GetExchangeOTPsRequest, opts ...grpc.CallOption) (*GetExchangeOTPsResponse, error) - EnableExchange(ctx context.Context, in *GenericExchangeNameRequest, opts ...grpc.CallOption) (*GenericExchangeNameResponse, error) + EnableExchange(ctx context.Context, in *GenericExchangeNameRequest, opts ...grpc.CallOption) (*GenericResponse, error) GetTicker(ctx context.Context, in *GetTickerRequest, opts ...grpc.CallOption) (*TickerResponse, error) GetTickers(ctx context.Context, in *GetTickersRequest, opts ...grpc.CallOption) (*GetTickersResponse, error) GetOrderbook(ctx context.Context, in *GetOrderbookRequest, opts ...grpc.CallOption) (*OrderbookResponse, error) @@ -10970,8 +7653,8 @@ type GoCryptoTraderClient interface { GetConfig(ctx context.Context, in *GetConfigRequest, opts ...grpc.CallOption) (*GetConfigResponse, error) GetPortfolio(ctx context.Context, in *GetPortfolioRequest, opts ...grpc.CallOption) (*GetPortfolioResponse, error) GetPortfolioSummary(ctx context.Context, in *GetPortfolioSummaryRequest, opts ...grpc.CallOption) (*GetPortfolioSummaryResponse, error) - AddPortfolioAddress(ctx context.Context, in *AddPortfolioAddressRequest, opts ...grpc.CallOption) (*AddPortfolioAddressResponse, error) - RemovePortfolioAddress(ctx context.Context, in *RemovePortfolioAddressRequest, opts ...grpc.CallOption) (*RemovePortfolioAddressResponse, error) + AddPortfolioAddress(ctx context.Context, in *AddPortfolioAddressRequest, opts ...grpc.CallOption) (*GenericResponse, error) + RemovePortfolioAddress(ctx context.Context, in *RemovePortfolioAddressRequest, opts ...grpc.CallOption) (*GenericResponse, error) GetForexProviders(ctx context.Context, in *GetForexProvidersRequest, opts ...grpc.CallOption) (*GetForexProvidersResponse, error) GetForexRates(ctx context.Context, in *GetForexRatesRequest, opts ...grpc.CallOption) (*GetForexRatesResponse, error) GetOrders(ctx context.Context, in *GetOrdersRequest, opts ...grpc.CallOption) (*GetOrdersResponse, error) @@ -10979,11 +7662,11 @@ type GoCryptoTraderClient interface { SubmitOrder(ctx context.Context, in *SubmitOrderRequest, opts ...grpc.CallOption) (*SubmitOrderResponse, error) SimulateOrder(ctx context.Context, in *SimulateOrderRequest, opts ...grpc.CallOption) (*SimulateOrderResponse, error) WhaleBomb(ctx context.Context, in *WhaleBombRequest, opts ...grpc.CallOption) (*SimulateOrderResponse, error) - CancelOrder(ctx context.Context, in *CancelOrderRequest, opts ...grpc.CallOption) (*CancelOrderResponse, error) + CancelOrder(ctx context.Context, in *CancelOrderRequest, opts ...grpc.CallOption) (*GenericResponse, error) CancelAllOrders(ctx context.Context, in *CancelAllOrdersRequest, opts ...grpc.CallOption) (*CancelAllOrdersResponse, error) GetEvents(ctx context.Context, in *GetEventsRequest, opts ...grpc.CallOption) (*GetEventsResponse, error) AddEvent(ctx context.Context, in *AddEventRequest, opts ...grpc.CallOption) (*AddEventResponse, error) - RemoveEvent(ctx context.Context, in *RemoveEventRequest, opts ...grpc.CallOption) (*RemoveEventResponse, error) + RemoveEvent(ctx context.Context, in *RemoveEventRequest, opts ...grpc.CallOption) (*GenericResponse, error) GetCryptocurrencyDepositAddresses(ctx context.Context, in *GetCryptocurrencyDepositAddressesRequest, opts ...grpc.CallOption) (*GetCryptocurrencyDepositAddressesResponse, error) GetCryptocurrencyDepositAddress(ctx context.Context, in *GetCryptocurrencyDepositAddressRequest, opts ...grpc.CallOption) (*GetCryptocurrencyDepositAddressResponse, error) WithdrawFiatFunds(ctx context.Context, in *WithdrawFiatRequest, opts ...grpc.CallOption) (*WithdrawResponse, error) @@ -10994,23 +7677,31 @@ type GoCryptoTraderClient interface { GetLoggerDetails(ctx context.Context, in *GetLoggerDetailsRequest, opts ...grpc.CallOption) (*GetLoggerDetailsResponse, error) SetLoggerDetails(ctx context.Context, in *SetLoggerDetailsRequest, opts ...grpc.CallOption) (*GetLoggerDetailsResponse, error) GetExchangePairs(ctx context.Context, in *GetExchangePairsRequest, opts ...grpc.CallOption) (*GetExchangePairsResponse, error) - EnableExchangePair(ctx context.Context, in *ExchangePairRequest, opts ...grpc.CallOption) (*GenericExchangeNameResponse, error) - DisableExchangePair(ctx context.Context, in *ExchangePairRequest, opts ...grpc.CallOption) (*GenericExchangeNameResponse, error) + SetExchangePair(ctx context.Context, in *SetExchangePairRequest, opts ...grpc.CallOption) (*GenericResponse, error) GetOrderbookStream(ctx context.Context, in *GetOrderbookStreamRequest, opts ...grpc.CallOption) (GoCryptoTrader_GetOrderbookStreamClient, error) GetExchangeOrderbookStream(ctx context.Context, in *GetExchangeOrderbookStreamRequest, opts ...grpc.CallOption) (GoCryptoTrader_GetExchangeOrderbookStreamClient, error) GetTickerStream(ctx context.Context, in *GetTickerStreamRequest, opts ...grpc.CallOption) (GoCryptoTrader_GetTickerStreamClient, error) GetExchangeTickerStream(ctx context.Context, in *GetExchangeTickerStreamRequest, opts ...grpc.CallOption) (GoCryptoTrader_GetExchangeTickerStreamClient, error) GetAuditEvent(ctx context.Context, in *GetAuditEventRequest, opts ...grpc.CallOption) (*GetAuditEventResponse, error) - GCTScriptExecute(ctx context.Context, in *GCTScriptExecuteRequest, opts ...grpc.CallOption) (*GCTScriptGenericResponse, error) - GCTScriptUpload(ctx context.Context, in *GCTScriptUploadRequest, opts ...grpc.CallOption) (*GCTScriptGenericResponse, error) + GCTScriptExecute(ctx context.Context, in *GCTScriptExecuteRequest, opts ...grpc.CallOption) (*GenericResponse, error) + GCTScriptUpload(ctx context.Context, in *GCTScriptUploadRequest, opts ...grpc.CallOption) (*GenericResponse, error) GCTScriptReadScript(ctx context.Context, in *GCTScriptReadScriptRequest, opts ...grpc.CallOption) (*GCTScriptQueryResponse, error) GCTScriptStatus(ctx context.Context, in *GCTScriptStatusRequest, opts ...grpc.CallOption) (*GCTScriptStatusResponse, error) GCTScriptQuery(ctx context.Context, in *GCTScriptQueryRequest, opts ...grpc.CallOption) (*GCTScriptQueryResponse, error) - GCTScriptStop(ctx context.Context, in *GCTScriptStopRequest, opts ...grpc.CallOption) (*GCTScriptGenericResponse, error) - GCTScriptStopAll(ctx context.Context, in *GCTScriptStopAllRequest, opts ...grpc.CallOption) (*GCTScriptGenericResponse, error) + GCTScriptStop(ctx context.Context, in *GCTScriptStopRequest, opts ...grpc.CallOption) (*GenericResponse, error) + GCTScriptStopAll(ctx context.Context, in *GCTScriptStopAllRequest, opts ...grpc.CallOption) (*GenericResponse, error) GCTScriptListAll(ctx context.Context, in *GCTScriptListAllRequest, opts ...grpc.CallOption) (*GCTScriptStatusResponse, error) - GCTScriptAutoLoadToggle(ctx context.Context, in *GCTScriptAutoLoadRequest, opts ...grpc.CallOption) (*GCTScriptGenericResponse, error) + GCTScriptAutoLoadToggle(ctx context.Context, in *GCTScriptAutoLoadRequest, opts ...grpc.CallOption) (*GenericResponse, error) GetHistoricCandles(ctx context.Context, in *GetHistoricCandlesRequest, opts ...grpc.CallOption) (*GetHistoricCandlesResponse, error) + SetExchangeAsset(ctx context.Context, in *SetExchangeAssetRequest, opts ...grpc.CallOption) (*GenericResponse, error) + SetAllExchangePairs(ctx context.Context, in *SetExchangeAllPairsRequest, opts ...grpc.CallOption) (*GenericResponse, error) + UpdateExchangeSupportedPairs(ctx context.Context, in *UpdateExchangeSupportedPairsRequest, opts ...grpc.CallOption) (*GenericResponse, error) + GetExchangeAssets(ctx context.Context, in *GetExchangeAssetsRequest, opts ...grpc.CallOption) (*GetExchangeAssetsResponse, error) + WebsocketGetInfo(ctx context.Context, in *WebsocketGetInfoRequest, opts ...grpc.CallOption) (*WebsocketGetInfoResponse, error) + WebsocketSetEnabled(ctx context.Context, in *WebsocketSetEnabledRequest, opts ...grpc.CallOption) (*GenericResponse, error) + WebsocketGetSubscriptions(ctx context.Context, in *WebsocketGetSubscriptionsRequest, opts ...grpc.CallOption) (*WebsocketGetSubscriptionsResponse, error) + WebsocketSetProxy(ctx context.Context, in *WebsocketSetProxyRequest, opts ...grpc.CallOption) (*GenericResponse, error) + WebsocketSetURL(ctx context.Context, in *WebsocketSetURLRequest, opts ...grpc.CallOption) (*GenericResponse, error) } type goCryptoTraderClient struct { @@ -11039,8 +7730,8 @@ func (c *goCryptoTraderClient) GetSubsystems(ctx context.Context, in *GetSubsyst return out, nil } -func (c *goCryptoTraderClient) EnableSubsystem(ctx context.Context, in *GenericSubsystemRequest, opts ...grpc.CallOption) (*GenericSubsystemResponse, error) { - out := new(GenericSubsystemResponse) +func (c *goCryptoTraderClient) EnableSubsystem(ctx context.Context, in *GenericSubsystemRequest, opts ...grpc.CallOption) (*GenericResponse, error) { + out := new(GenericResponse) err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/EnableSubsystem", in, out, opts...) if err != nil { return nil, err @@ -11048,8 +7739,8 @@ func (c *goCryptoTraderClient) EnableSubsystem(ctx context.Context, in *GenericS return out, nil } -func (c *goCryptoTraderClient) DisableSubsystem(ctx context.Context, in *GenericSubsystemRequest, opts ...grpc.CallOption) (*GenericSubsystemResponse, error) { - out := new(GenericSubsystemResponse) +func (c *goCryptoTraderClient) DisableSubsystem(ctx context.Context, in *GenericSubsystemRequest, opts ...grpc.CallOption) (*GenericResponse, error) { + out := new(GenericResponse) err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/DisableSubsystem", in, out, opts...) if err != nil { return nil, err @@ -11084,8 +7775,8 @@ func (c *goCryptoTraderClient) GetExchanges(ctx context.Context, in *GetExchange return out, nil } -func (c *goCryptoTraderClient) DisableExchange(ctx context.Context, in *GenericExchangeNameRequest, opts ...grpc.CallOption) (*GenericExchangeNameResponse, error) { - out := new(GenericExchangeNameResponse) +func (c *goCryptoTraderClient) DisableExchange(ctx context.Context, in *GenericExchangeNameRequest, opts ...grpc.CallOption) (*GenericResponse, error) { + out := new(GenericResponse) err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/DisableExchange", in, out, opts...) if err != nil { return nil, err @@ -11120,8 +7811,8 @@ func (c *goCryptoTraderClient) GetExchangeOTPCodes(ctx context.Context, in *GetE return out, nil } -func (c *goCryptoTraderClient) EnableExchange(ctx context.Context, in *GenericExchangeNameRequest, opts ...grpc.CallOption) (*GenericExchangeNameResponse, error) { - out := new(GenericExchangeNameResponse) +func (c *goCryptoTraderClient) EnableExchange(ctx context.Context, in *GenericExchangeNameRequest, opts ...grpc.CallOption) (*GenericResponse, error) { + out := new(GenericResponse) err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/EnableExchange", in, out, opts...) if err != nil { return nil, err @@ -11233,8 +7924,8 @@ func (c *goCryptoTraderClient) GetPortfolioSummary(ctx context.Context, in *GetP return out, nil } -func (c *goCryptoTraderClient) AddPortfolioAddress(ctx context.Context, in *AddPortfolioAddressRequest, opts ...grpc.CallOption) (*AddPortfolioAddressResponse, error) { - out := new(AddPortfolioAddressResponse) +func (c *goCryptoTraderClient) AddPortfolioAddress(ctx context.Context, in *AddPortfolioAddressRequest, opts ...grpc.CallOption) (*GenericResponse, error) { + out := new(GenericResponse) err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/AddPortfolioAddress", in, out, opts...) if err != nil { return nil, err @@ -11242,8 +7933,8 @@ func (c *goCryptoTraderClient) AddPortfolioAddress(ctx context.Context, in *AddP return out, nil } -func (c *goCryptoTraderClient) RemovePortfolioAddress(ctx context.Context, in *RemovePortfolioAddressRequest, opts ...grpc.CallOption) (*RemovePortfolioAddressResponse, error) { - out := new(RemovePortfolioAddressResponse) +func (c *goCryptoTraderClient) RemovePortfolioAddress(ctx context.Context, in *RemovePortfolioAddressRequest, opts ...grpc.CallOption) (*GenericResponse, error) { + out := new(GenericResponse) err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/RemovePortfolioAddress", in, out, opts...) if err != nil { return nil, err @@ -11314,8 +8005,8 @@ func (c *goCryptoTraderClient) WhaleBomb(ctx context.Context, in *WhaleBombReque return out, nil } -func (c *goCryptoTraderClient) CancelOrder(ctx context.Context, in *CancelOrderRequest, opts ...grpc.CallOption) (*CancelOrderResponse, error) { - out := new(CancelOrderResponse) +func (c *goCryptoTraderClient) CancelOrder(ctx context.Context, in *CancelOrderRequest, opts ...grpc.CallOption) (*GenericResponse, error) { + out := new(GenericResponse) err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/CancelOrder", in, out, opts...) if err != nil { return nil, err @@ -11350,8 +8041,8 @@ func (c *goCryptoTraderClient) AddEvent(ctx context.Context, in *AddEventRequest return out, nil } -func (c *goCryptoTraderClient) RemoveEvent(ctx context.Context, in *RemoveEventRequest, opts ...grpc.CallOption) (*RemoveEventResponse, error) { - out := new(RemoveEventResponse) +func (c *goCryptoTraderClient) RemoveEvent(ctx context.Context, in *RemoveEventRequest, opts ...grpc.CallOption) (*GenericResponse, error) { + out := new(GenericResponse) err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/RemoveEvent", in, out, opts...) if err != nil { return nil, err @@ -11449,18 +8140,9 @@ func (c *goCryptoTraderClient) GetExchangePairs(ctx context.Context, in *GetExch return out, nil } -func (c *goCryptoTraderClient) EnableExchangePair(ctx context.Context, in *ExchangePairRequest, opts ...grpc.CallOption) (*GenericExchangeNameResponse, error) { - out := new(GenericExchangeNameResponse) - err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/EnableExchangePair", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *goCryptoTraderClient) DisableExchangePair(ctx context.Context, in *ExchangePairRequest, opts ...grpc.CallOption) (*GenericExchangeNameResponse, error) { - out := new(GenericExchangeNameResponse) - err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/DisableExchangePair", in, out, opts...) +func (c *goCryptoTraderClient) SetExchangePair(ctx context.Context, in *SetExchangePairRequest, opts ...grpc.CallOption) (*GenericResponse, error) { + out := new(GenericResponse) + err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/SetExchangePair", in, out, opts...) if err != nil { return nil, err } @@ -11604,8 +8286,8 @@ func (c *goCryptoTraderClient) GetAuditEvent(ctx context.Context, in *GetAuditEv return out, nil } -func (c *goCryptoTraderClient) GCTScriptExecute(ctx context.Context, in *GCTScriptExecuteRequest, opts ...grpc.CallOption) (*GCTScriptGenericResponse, error) { - out := new(GCTScriptGenericResponse) +func (c *goCryptoTraderClient) GCTScriptExecute(ctx context.Context, in *GCTScriptExecuteRequest, opts ...grpc.CallOption) (*GenericResponse, error) { + out := new(GenericResponse) err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/GCTScriptExecute", in, out, opts...) if err != nil { return nil, err @@ -11613,8 +8295,8 @@ func (c *goCryptoTraderClient) GCTScriptExecute(ctx context.Context, in *GCTScri return out, nil } -func (c *goCryptoTraderClient) GCTScriptUpload(ctx context.Context, in *GCTScriptUploadRequest, opts ...grpc.CallOption) (*GCTScriptGenericResponse, error) { - out := new(GCTScriptGenericResponse) +func (c *goCryptoTraderClient) GCTScriptUpload(ctx context.Context, in *GCTScriptUploadRequest, opts ...grpc.CallOption) (*GenericResponse, error) { + out := new(GenericResponse) err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/GCTScriptUpload", in, out, opts...) if err != nil { return nil, err @@ -11649,8 +8331,8 @@ func (c *goCryptoTraderClient) GCTScriptQuery(ctx context.Context, in *GCTScript return out, nil } -func (c *goCryptoTraderClient) GCTScriptStop(ctx context.Context, in *GCTScriptStopRequest, opts ...grpc.CallOption) (*GCTScriptGenericResponse, error) { - out := new(GCTScriptGenericResponse) +func (c *goCryptoTraderClient) GCTScriptStop(ctx context.Context, in *GCTScriptStopRequest, opts ...grpc.CallOption) (*GenericResponse, error) { + out := new(GenericResponse) err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/GCTScriptStop", in, out, opts...) if err != nil { return nil, err @@ -11658,8 +8340,8 @@ func (c *goCryptoTraderClient) GCTScriptStop(ctx context.Context, in *GCTScriptS return out, nil } -func (c *goCryptoTraderClient) GCTScriptStopAll(ctx context.Context, in *GCTScriptStopAllRequest, opts ...grpc.CallOption) (*GCTScriptGenericResponse, error) { - out := new(GCTScriptGenericResponse) +func (c *goCryptoTraderClient) GCTScriptStopAll(ctx context.Context, in *GCTScriptStopAllRequest, opts ...grpc.CallOption) (*GenericResponse, error) { + out := new(GenericResponse) err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/GCTScriptStopAll", in, out, opts...) if err != nil { return nil, err @@ -11676,8 +8358,8 @@ func (c *goCryptoTraderClient) GCTScriptListAll(ctx context.Context, in *GCTScri return out, nil } -func (c *goCryptoTraderClient) GCTScriptAutoLoadToggle(ctx context.Context, in *GCTScriptAutoLoadRequest, opts ...grpc.CallOption) (*GCTScriptGenericResponse, error) { - out := new(GCTScriptGenericResponse) +func (c *goCryptoTraderClient) GCTScriptAutoLoadToggle(ctx context.Context, in *GCTScriptAutoLoadRequest, opts ...grpc.CallOption) (*GenericResponse, error) { + out := new(GenericResponse) err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/GCTScriptAutoLoadToggle", in, out, opts...) if err != nil { return nil, err @@ -11694,20 +8376,101 @@ func (c *goCryptoTraderClient) GetHistoricCandles(ctx context.Context, in *GetHi return out, nil } +func (c *goCryptoTraderClient) SetExchangeAsset(ctx context.Context, in *SetExchangeAssetRequest, opts ...grpc.CallOption) (*GenericResponse, error) { + out := new(GenericResponse) + err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/SetExchangeAsset", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *goCryptoTraderClient) SetAllExchangePairs(ctx context.Context, in *SetExchangeAllPairsRequest, opts ...grpc.CallOption) (*GenericResponse, error) { + out := new(GenericResponse) + err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/SetAllExchangePairs", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *goCryptoTraderClient) UpdateExchangeSupportedPairs(ctx context.Context, in *UpdateExchangeSupportedPairsRequest, opts ...grpc.CallOption) (*GenericResponse, error) { + out := new(GenericResponse) + err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/UpdateExchangeSupportedPairs", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *goCryptoTraderClient) GetExchangeAssets(ctx context.Context, in *GetExchangeAssetsRequest, opts ...grpc.CallOption) (*GetExchangeAssetsResponse, error) { + out := new(GetExchangeAssetsResponse) + err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/GetExchangeAssets", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *goCryptoTraderClient) WebsocketGetInfo(ctx context.Context, in *WebsocketGetInfoRequest, opts ...grpc.CallOption) (*WebsocketGetInfoResponse, error) { + out := new(WebsocketGetInfoResponse) + err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/WebsocketGetInfo", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *goCryptoTraderClient) WebsocketSetEnabled(ctx context.Context, in *WebsocketSetEnabledRequest, opts ...grpc.CallOption) (*GenericResponse, error) { + out := new(GenericResponse) + err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/WebsocketSetEnabled", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *goCryptoTraderClient) WebsocketGetSubscriptions(ctx context.Context, in *WebsocketGetSubscriptionsRequest, opts ...grpc.CallOption) (*WebsocketGetSubscriptionsResponse, error) { + out := new(WebsocketGetSubscriptionsResponse) + err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/WebsocketGetSubscriptions", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *goCryptoTraderClient) WebsocketSetProxy(ctx context.Context, in *WebsocketSetProxyRequest, opts ...grpc.CallOption) (*GenericResponse, error) { + out := new(GenericResponse) + err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/WebsocketSetProxy", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *goCryptoTraderClient) WebsocketSetURL(ctx context.Context, in *WebsocketSetURLRequest, opts ...grpc.CallOption) (*GenericResponse, error) { + out := new(GenericResponse) + err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/WebsocketSetURL", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // GoCryptoTraderServer is the server API for GoCryptoTrader service. type GoCryptoTraderServer interface { GetInfo(context.Context, *GetInfoRequest) (*GetInfoResponse, error) GetSubsystems(context.Context, *GetSubsystemsRequest) (*GetSusbsytemsResponse, error) - EnableSubsystem(context.Context, *GenericSubsystemRequest) (*GenericSubsystemResponse, error) - DisableSubsystem(context.Context, *GenericSubsystemRequest) (*GenericSubsystemResponse, error) + EnableSubsystem(context.Context, *GenericSubsystemRequest) (*GenericResponse, error) + DisableSubsystem(context.Context, *GenericSubsystemRequest) (*GenericResponse, error) GetRPCEndpoints(context.Context, *GetRPCEndpointsRequest) (*GetRPCEndpointsResponse, error) GetCommunicationRelayers(context.Context, *GetCommunicationRelayersRequest) (*GetCommunicationRelayersResponse, error) GetExchanges(context.Context, *GetExchangesRequest) (*GetExchangesResponse, error) - DisableExchange(context.Context, *GenericExchangeNameRequest) (*GenericExchangeNameResponse, error) + DisableExchange(context.Context, *GenericExchangeNameRequest) (*GenericResponse, error) GetExchangeInfo(context.Context, *GenericExchangeNameRequest) (*GetExchangeInfoResponse, error) GetExchangeOTPCode(context.Context, *GenericExchangeNameRequest) (*GetExchangeOTPReponse, error) GetExchangeOTPCodes(context.Context, *GetExchangeOTPsRequest) (*GetExchangeOTPsResponse, error) - EnableExchange(context.Context, *GenericExchangeNameRequest) (*GenericExchangeNameResponse, error) + EnableExchange(context.Context, *GenericExchangeNameRequest) (*GenericResponse, error) GetTicker(context.Context, *GetTickerRequest) (*TickerResponse, error) GetTickers(context.Context, *GetTickersRequest) (*GetTickersResponse, error) GetOrderbook(context.Context, *GetOrderbookRequest) (*OrderbookResponse, error) @@ -11717,8 +8480,8 @@ type GoCryptoTraderServer interface { GetConfig(context.Context, *GetConfigRequest) (*GetConfigResponse, error) GetPortfolio(context.Context, *GetPortfolioRequest) (*GetPortfolioResponse, error) GetPortfolioSummary(context.Context, *GetPortfolioSummaryRequest) (*GetPortfolioSummaryResponse, error) - AddPortfolioAddress(context.Context, *AddPortfolioAddressRequest) (*AddPortfolioAddressResponse, error) - RemovePortfolioAddress(context.Context, *RemovePortfolioAddressRequest) (*RemovePortfolioAddressResponse, error) + AddPortfolioAddress(context.Context, *AddPortfolioAddressRequest) (*GenericResponse, error) + RemovePortfolioAddress(context.Context, *RemovePortfolioAddressRequest) (*GenericResponse, error) GetForexProviders(context.Context, *GetForexProvidersRequest) (*GetForexProvidersResponse, error) GetForexRates(context.Context, *GetForexRatesRequest) (*GetForexRatesResponse, error) GetOrders(context.Context, *GetOrdersRequest) (*GetOrdersResponse, error) @@ -11726,11 +8489,11 @@ type GoCryptoTraderServer interface { SubmitOrder(context.Context, *SubmitOrderRequest) (*SubmitOrderResponse, error) SimulateOrder(context.Context, *SimulateOrderRequest) (*SimulateOrderResponse, error) WhaleBomb(context.Context, *WhaleBombRequest) (*SimulateOrderResponse, error) - CancelOrder(context.Context, *CancelOrderRequest) (*CancelOrderResponse, error) + CancelOrder(context.Context, *CancelOrderRequest) (*GenericResponse, error) CancelAllOrders(context.Context, *CancelAllOrdersRequest) (*CancelAllOrdersResponse, error) GetEvents(context.Context, *GetEventsRequest) (*GetEventsResponse, error) AddEvent(context.Context, *AddEventRequest) (*AddEventResponse, error) - RemoveEvent(context.Context, *RemoveEventRequest) (*RemoveEventResponse, error) + RemoveEvent(context.Context, *RemoveEventRequest) (*GenericResponse, error) GetCryptocurrencyDepositAddresses(context.Context, *GetCryptocurrencyDepositAddressesRequest) (*GetCryptocurrencyDepositAddressesResponse, error) GetCryptocurrencyDepositAddress(context.Context, *GetCryptocurrencyDepositAddressRequest) (*GetCryptocurrencyDepositAddressResponse, error) WithdrawFiatFunds(context.Context, *WithdrawFiatRequest) (*WithdrawResponse, error) @@ -11741,215 +8504,247 @@ type GoCryptoTraderServer interface { GetLoggerDetails(context.Context, *GetLoggerDetailsRequest) (*GetLoggerDetailsResponse, error) SetLoggerDetails(context.Context, *SetLoggerDetailsRequest) (*GetLoggerDetailsResponse, error) GetExchangePairs(context.Context, *GetExchangePairsRequest) (*GetExchangePairsResponse, error) - EnableExchangePair(context.Context, *ExchangePairRequest) (*GenericExchangeNameResponse, error) - DisableExchangePair(context.Context, *ExchangePairRequest) (*GenericExchangeNameResponse, error) + SetExchangePair(context.Context, *SetExchangePairRequest) (*GenericResponse, error) GetOrderbookStream(*GetOrderbookStreamRequest, GoCryptoTrader_GetOrderbookStreamServer) error GetExchangeOrderbookStream(*GetExchangeOrderbookStreamRequest, GoCryptoTrader_GetExchangeOrderbookStreamServer) error GetTickerStream(*GetTickerStreamRequest, GoCryptoTrader_GetTickerStreamServer) error GetExchangeTickerStream(*GetExchangeTickerStreamRequest, GoCryptoTrader_GetExchangeTickerStreamServer) error GetAuditEvent(context.Context, *GetAuditEventRequest) (*GetAuditEventResponse, error) - GCTScriptExecute(context.Context, *GCTScriptExecuteRequest) (*GCTScriptGenericResponse, error) - GCTScriptUpload(context.Context, *GCTScriptUploadRequest) (*GCTScriptGenericResponse, error) + GCTScriptExecute(context.Context, *GCTScriptExecuteRequest) (*GenericResponse, error) + GCTScriptUpload(context.Context, *GCTScriptUploadRequest) (*GenericResponse, error) GCTScriptReadScript(context.Context, *GCTScriptReadScriptRequest) (*GCTScriptQueryResponse, error) GCTScriptStatus(context.Context, *GCTScriptStatusRequest) (*GCTScriptStatusResponse, error) GCTScriptQuery(context.Context, *GCTScriptQueryRequest) (*GCTScriptQueryResponse, error) - GCTScriptStop(context.Context, *GCTScriptStopRequest) (*GCTScriptGenericResponse, error) - GCTScriptStopAll(context.Context, *GCTScriptStopAllRequest) (*GCTScriptGenericResponse, error) + GCTScriptStop(context.Context, *GCTScriptStopRequest) (*GenericResponse, error) + GCTScriptStopAll(context.Context, *GCTScriptStopAllRequest) (*GenericResponse, error) GCTScriptListAll(context.Context, *GCTScriptListAllRequest) (*GCTScriptStatusResponse, error) - GCTScriptAutoLoadToggle(context.Context, *GCTScriptAutoLoadRequest) (*GCTScriptGenericResponse, error) + GCTScriptAutoLoadToggle(context.Context, *GCTScriptAutoLoadRequest) (*GenericResponse, error) GetHistoricCandles(context.Context, *GetHistoricCandlesRequest) (*GetHistoricCandlesResponse, error) + SetExchangeAsset(context.Context, *SetExchangeAssetRequest) (*GenericResponse, error) + SetAllExchangePairs(context.Context, *SetExchangeAllPairsRequest) (*GenericResponse, error) + UpdateExchangeSupportedPairs(context.Context, *UpdateExchangeSupportedPairsRequest) (*GenericResponse, error) + GetExchangeAssets(context.Context, *GetExchangeAssetsRequest) (*GetExchangeAssetsResponse, error) + WebsocketGetInfo(context.Context, *WebsocketGetInfoRequest) (*WebsocketGetInfoResponse, error) + WebsocketSetEnabled(context.Context, *WebsocketSetEnabledRequest) (*GenericResponse, error) + WebsocketGetSubscriptions(context.Context, *WebsocketGetSubscriptionsRequest) (*WebsocketGetSubscriptionsResponse, error) + WebsocketSetProxy(context.Context, *WebsocketSetProxyRequest) (*GenericResponse, error) + WebsocketSetURL(context.Context, *WebsocketSetURLRequest) (*GenericResponse, error) } // UnimplementedGoCryptoTraderServer can be embedded to have forward compatible implementations. type UnimplementedGoCryptoTraderServer struct { } -func (*UnimplementedGoCryptoTraderServer) GetInfo(context.Context, *GetInfoRequest) (*GetInfoResponse, error) { +func (*UnimplementedGoCryptoTraderServer) GetInfo(ctx context.Context, req *GetInfoRequest) (*GetInfoResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetInfo not implemented") } -func (*UnimplementedGoCryptoTraderServer) GetSubsystems(context.Context, *GetSubsystemsRequest) (*GetSusbsytemsResponse, error) { +func (*UnimplementedGoCryptoTraderServer) GetSubsystems(ctx context.Context, req *GetSubsystemsRequest) (*GetSusbsytemsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetSubsystems not implemented") } -func (*UnimplementedGoCryptoTraderServer) EnableSubsystem(context.Context, *GenericSubsystemRequest) (*GenericSubsystemResponse, error) { +func (*UnimplementedGoCryptoTraderServer) EnableSubsystem(ctx context.Context, req *GenericSubsystemRequest) (*GenericResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method EnableSubsystem not implemented") } -func (*UnimplementedGoCryptoTraderServer) DisableSubsystem(context.Context, *GenericSubsystemRequest) (*GenericSubsystemResponse, error) { +func (*UnimplementedGoCryptoTraderServer) DisableSubsystem(ctx context.Context, req *GenericSubsystemRequest) (*GenericResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method DisableSubsystem not implemented") } -func (*UnimplementedGoCryptoTraderServer) GetRPCEndpoints(context.Context, *GetRPCEndpointsRequest) (*GetRPCEndpointsResponse, error) { +func (*UnimplementedGoCryptoTraderServer) GetRPCEndpoints(ctx context.Context, req *GetRPCEndpointsRequest) (*GetRPCEndpointsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetRPCEndpoints not implemented") } -func (*UnimplementedGoCryptoTraderServer) GetCommunicationRelayers(context.Context, *GetCommunicationRelayersRequest) (*GetCommunicationRelayersResponse, error) { +func (*UnimplementedGoCryptoTraderServer) GetCommunicationRelayers(ctx context.Context, req *GetCommunicationRelayersRequest) (*GetCommunicationRelayersResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetCommunicationRelayers not implemented") } -func (*UnimplementedGoCryptoTraderServer) GetExchanges(context.Context, *GetExchangesRequest) (*GetExchangesResponse, error) { +func (*UnimplementedGoCryptoTraderServer) GetExchanges(ctx context.Context, req *GetExchangesRequest) (*GetExchangesResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetExchanges not implemented") } -func (*UnimplementedGoCryptoTraderServer) DisableExchange(context.Context, *GenericExchangeNameRequest) (*GenericExchangeNameResponse, error) { +func (*UnimplementedGoCryptoTraderServer) DisableExchange(ctx context.Context, req *GenericExchangeNameRequest) (*GenericResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method DisableExchange not implemented") } -func (*UnimplementedGoCryptoTraderServer) GetExchangeInfo(context.Context, *GenericExchangeNameRequest) (*GetExchangeInfoResponse, error) { +func (*UnimplementedGoCryptoTraderServer) GetExchangeInfo(ctx context.Context, req *GenericExchangeNameRequest) (*GetExchangeInfoResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetExchangeInfo not implemented") } -func (*UnimplementedGoCryptoTraderServer) GetExchangeOTPCode(context.Context, *GenericExchangeNameRequest) (*GetExchangeOTPReponse, error) { +func (*UnimplementedGoCryptoTraderServer) GetExchangeOTPCode(ctx context.Context, req *GenericExchangeNameRequest) (*GetExchangeOTPReponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetExchangeOTPCode not implemented") } -func (*UnimplementedGoCryptoTraderServer) GetExchangeOTPCodes(context.Context, *GetExchangeOTPsRequest) (*GetExchangeOTPsResponse, error) { +func (*UnimplementedGoCryptoTraderServer) GetExchangeOTPCodes(ctx context.Context, req *GetExchangeOTPsRequest) (*GetExchangeOTPsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetExchangeOTPCodes not implemented") } -func (*UnimplementedGoCryptoTraderServer) EnableExchange(context.Context, *GenericExchangeNameRequest) (*GenericExchangeNameResponse, error) { +func (*UnimplementedGoCryptoTraderServer) EnableExchange(ctx context.Context, req *GenericExchangeNameRequest) (*GenericResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method EnableExchange not implemented") } -func (*UnimplementedGoCryptoTraderServer) GetTicker(context.Context, *GetTickerRequest) (*TickerResponse, error) { +func (*UnimplementedGoCryptoTraderServer) GetTicker(ctx context.Context, req *GetTickerRequest) (*TickerResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetTicker not implemented") } -func (*UnimplementedGoCryptoTraderServer) GetTickers(context.Context, *GetTickersRequest) (*GetTickersResponse, error) { +func (*UnimplementedGoCryptoTraderServer) GetTickers(ctx context.Context, req *GetTickersRequest) (*GetTickersResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetTickers not implemented") } -func (*UnimplementedGoCryptoTraderServer) GetOrderbook(context.Context, *GetOrderbookRequest) (*OrderbookResponse, error) { +func (*UnimplementedGoCryptoTraderServer) GetOrderbook(ctx context.Context, req *GetOrderbookRequest) (*OrderbookResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetOrderbook not implemented") } -func (*UnimplementedGoCryptoTraderServer) GetOrderbooks(context.Context, *GetOrderbooksRequest) (*GetOrderbooksResponse, error) { +func (*UnimplementedGoCryptoTraderServer) GetOrderbooks(ctx context.Context, req *GetOrderbooksRequest) (*GetOrderbooksResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetOrderbooks not implemented") } -func (*UnimplementedGoCryptoTraderServer) GetAccountInfo(context.Context, *GetAccountInfoRequest) (*GetAccountInfoResponse, error) { +func (*UnimplementedGoCryptoTraderServer) GetAccountInfo(ctx context.Context, req *GetAccountInfoRequest) (*GetAccountInfoResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetAccountInfo not implemented") } -func (*UnimplementedGoCryptoTraderServer) GetAccountInfoStream(*GetAccountInfoRequest, GoCryptoTrader_GetAccountInfoStreamServer) error { +func (*UnimplementedGoCryptoTraderServer) GetAccountInfoStream(req *GetAccountInfoRequest, srv GoCryptoTrader_GetAccountInfoStreamServer) error { return status.Errorf(codes.Unimplemented, "method GetAccountInfoStream not implemented") } -func (*UnimplementedGoCryptoTraderServer) GetConfig(context.Context, *GetConfigRequest) (*GetConfigResponse, error) { +func (*UnimplementedGoCryptoTraderServer) GetConfig(ctx context.Context, req *GetConfigRequest) (*GetConfigResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetConfig not implemented") } -func (*UnimplementedGoCryptoTraderServer) GetPortfolio(context.Context, *GetPortfolioRequest) (*GetPortfolioResponse, error) { +func (*UnimplementedGoCryptoTraderServer) GetPortfolio(ctx context.Context, req *GetPortfolioRequest) (*GetPortfolioResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetPortfolio not implemented") } -func (*UnimplementedGoCryptoTraderServer) GetPortfolioSummary(context.Context, *GetPortfolioSummaryRequest) (*GetPortfolioSummaryResponse, error) { +func (*UnimplementedGoCryptoTraderServer) GetPortfolioSummary(ctx context.Context, req *GetPortfolioSummaryRequest) (*GetPortfolioSummaryResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetPortfolioSummary not implemented") } -func (*UnimplementedGoCryptoTraderServer) AddPortfolioAddress(context.Context, *AddPortfolioAddressRequest) (*AddPortfolioAddressResponse, error) { +func (*UnimplementedGoCryptoTraderServer) AddPortfolioAddress(ctx context.Context, req *AddPortfolioAddressRequest) (*GenericResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method AddPortfolioAddress not implemented") } -func (*UnimplementedGoCryptoTraderServer) RemovePortfolioAddress(context.Context, *RemovePortfolioAddressRequest) (*RemovePortfolioAddressResponse, error) { +func (*UnimplementedGoCryptoTraderServer) RemovePortfolioAddress(ctx context.Context, req *RemovePortfolioAddressRequest) (*GenericResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method RemovePortfolioAddress not implemented") } -func (*UnimplementedGoCryptoTraderServer) GetForexProviders(context.Context, *GetForexProvidersRequest) (*GetForexProvidersResponse, error) { +func (*UnimplementedGoCryptoTraderServer) GetForexProviders(ctx context.Context, req *GetForexProvidersRequest) (*GetForexProvidersResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetForexProviders not implemented") } -func (*UnimplementedGoCryptoTraderServer) GetForexRates(context.Context, *GetForexRatesRequest) (*GetForexRatesResponse, error) { +func (*UnimplementedGoCryptoTraderServer) GetForexRates(ctx context.Context, req *GetForexRatesRequest) (*GetForexRatesResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetForexRates not implemented") } -func (*UnimplementedGoCryptoTraderServer) GetOrders(context.Context, *GetOrdersRequest) (*GetOrdersResponse, error) { +func (*UnimplementedGoCryptoTraderServer) GetOrders(ctx context.Context, req *GetOrdersRequest) (*GetOrdersResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetOrders not implemented") } -func (*UnimplementedGoCryptoTraderServer) GetOrder(context.Context, *GetOrderRequest) (*OrderDetails, error) { +func (*UnimplementedGoCryptoTraderServer) GetOrder(ctx context.Context, req *GetOrderRequest) (*OrderDetails, error) { return nil, status.Errorf(codes.Unimplemented, "method GetOrder not implemented") } -func (*UnimplementedGoCryptoTraderServer) SubmitOrder(context.Context, *SubmitOrderRequest) (*SubmitOrderResponse, error) { +func (*UnimplementedGoCryptoTraderServer) SubmitOrder(ctx context.Context, req *SubmitOrderRequest) (*SubmitOrderResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method SubmitOrder not implemented") } -func (*UnimplementedGoCryptoTraderServer) SimulateOrder(context.Context, *SimulateOrderRequest) (*SimulateOrderResponse, error) { +func (*UnimplementedGoCryptoTraderServer) SimulateOrder(ctx context.Context, req *SimulateOrderRequest) (*SimulateOrderResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method SimulateOrder not implemented") } -func (*UnimplementedGoCryptoTraderServer) WhaleBomb(context.Context, *WhaleBombRequest) (*SimulateOrderResponse, error) { +func (*UnimplementedGoCryptoTraderServer) WhaleBomb(ctx context.Context, req *WhaleBombRequest) (*SimulateOrderResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method WhaleBomb not implemented") } -func (*UnimplementedGoCryptoTraderServer) CancelOrder(context.Context, *CancelOrderRequest) (*CancelOrderResponse, error) { +func (*UnimplementedGoCryptoTraderServer) CancelOrder(ctx context.Context, req *CancelOrderRequest) (*GenericResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method CancelOrder not implemented") } -func (*UnimplementedGoCryptoTraderServer) CancelAllOrders(context.Context, *CancelAllOrdersRequest) (*CancelAllOrdersResponse, error) { +func (*UnimplementedGoCryptoTraderServer) CancelAllOrders(ctx context.Context, req *CancelAllOrdersRequest) (*CancelAllOrdersResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method CancelAllOrders not implemented") } -func (*UnimplementedGoCryptoTraderServer) GetEvents(context.Context, *GetEventsRequest) (*GetEventsResponse, error) { +func (*UnimplementedGoCryptoTraderServer) GetEvents(ctx context.Context, req *GetEventsRequest) (*GetEventsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetEvents not implemented") } -func (*UnimplementedGoCryptoTraderServer) AddEvent(context.Context, *AddEventRequest) (*AddEventResponse, error) { +func (*UnimplementedGoCryptoTraderServer) AddEvent(ctx context.Context, req *AddEventRequest) (*AddEventResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method AddEvent not implemented") } -func (*UnimplementedGoCryptoTraderServer) RemoveEvent(context.Context, *RemoveEventRequest) (*RemoveEventResponse, error) { +func (*UnimplementedGoCryptoTraderServer) RemoveEvent(ctx context.Context, req *RemoveEventRequest) (*GenericResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method RemoveEvent not implemented") } -func (*UnimplementedGoCryptoTraderServer) GetCryptocurrencyDepositAddresses(context.Context, *GetCryptocurrencyDepositAddressesRequest) (*GetCryptocurrencyDepositAddressesResponse, error) { +func (*UnimplementedGoCryptoTraderServer) GetCryptocurrencyDepositAddresses(ctx context.Context, req *GetCryptocurrencyDepositAddressesRequest) (*GetCryptocurrencyDepositAddressesResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetCryptocurrencyDepositAddresses not implemented") } -func (*UnimplementedGoCryptoTraderServer) GetCryptocurrencyDepositAddress(context.Context, *GetCryptocurrencyDepositAddressRequest) (*GetCryptocurrencyDepositAddressResponse, error) { +func (*UnimplementedGoCryptoTraderServer) GetCryptocurrencyDepositAddress(ctx context.Context, req *GetCryptocurrencyDepositAddressRequest) (*GetCryptocurrencyDepositAddressResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetCryptocurrencyDepositAddress not implemented") } -func (*UnimplementedGoCryptoTraderServer) WithdrawFiatFunds(context.Context, *WithdrawFiatRequest) (*WithdrawResponse, error) { +func (*UnimplementedGoCryptoTraderServer) WithdrawFiatFunds(ctx context.Context, req *WithdrawFiatRequest) (*WithdrawResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method WithdrawFiatFunds not implemented") } -func (*UnimplementedGoCryptoTraderServer) WithdrawCryptocurrencyFunds(context.Context, *WithdrawCryptoRequest) (*WithdrawResponse, error) { +func (*UnimplementedGoCryptoTraderServer) WithdrawCryptocurrencyFunds(ctx context.Context, req *WithdrawCryptoRequest) (*WithdrawResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method WithdrawCryptocurrencyFunds not implemented") } -func (*UnimplementedGoCryptoTraderServer) WithdrawalEventByID(context.Context, *WithdrawalEventByIDRequest) (*WithdrawalEventByIDResponse, error) { +func (*UnimplementedGoCryptoTraderServer) WithdrawalEventByID(ctx context.Context, req *WithdrawalEventByIDRequest) (*WithdrawalEventByIDResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method WithdrawalEventByID not implemented") } -func (*UnimplementedGoCryptoTraderServer) WithdrawalEventsByExchange(context.Context, *WithdrawalEventsByExchangeRequest) (*WithdrawalEventsByExchangeResponse, error) { +func (*UnimplementedGoCryptoTraderServer) WithdrawalEventsByExchange(ctx context.Context, req *WithdrawalEventsByExchangeRequest) (*WithdrawalEventsByExchangeResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method WithdrawalEventsByExchange not implemented") } -func (*UnimplementedGoCryptoTraderServer) WithdrawalEventsByDate(context.Context, *WithdrawalEventsByDateRequest) (*WithdrawalEventsByExchangeResponse, error) { +func (*UnimplementedGoCryptoTraderServer) WithdrawalEventsByDate(ctx context.Context, req *WithdrawalEventsByDateRequest) (*WithdrawalEventsByExchangeResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method WithdrawalEventsByDate not implemented") } -func (*UnimplementedGoCryptoTraderServer) GetLoggerDetails(context.Context, *GetLoggerDetailsRequest) (*GetLoggerDetailsResponse, error) { +func (*UnimplementedGoCryptoTraderServer) GetLoggerDetails(ctx context.Context, req *GetLoggerDetailsRequest) (*GetLoggerDetailsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetLoggerDetails not implemented") } -func (*UnimplementedGoCryptoTraderServer) SetLoggerDetails(context.Context, *SetLoggerDetailsRequest) (*GetLoggerDetailsResponse, error) { +func (*UnimplementedGoCryptoTraderServer) SetLoggerDetails(ctx context.Context, req *SetLoggerDetailsRequest) (*GetLoggerDetailsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method SetLoggerDetails not implemented") } -func (*UnimplementedGoCryptoTraderServer) GetExchangePairs(context.Context, *GetExchangePairsRequest) (*GetExchangePairsResponse, error) { +func (*UnimplementedGoCryptoTraderServer) GetExchangePairs(ctx context.Context, req *GetExchangePairsRequest) (*GetExchangePairsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetExchangePairs not implemented") } -func (*UnimplementedGoCryptoTraderServer) EnableExchangePair(context.Context, *ExchangePairRequest) (*GenericExchangeNameResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method EnableExchangePair not implemented") +func (*UnimplementedGoCryptoTraderServer) SetExchangePair(ctx context.Context, req *SetExchangePairRequest) (*GenericResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SetExchangePair not implemented") } -func (*UnimplementedGoCryptoTraderServer) DisableExchangePair(context.Context, *ExchangePairRequest) (*GenericExchangeNameResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method DisableExchangePair not implemented") -} -func (*UnimplementedGoCryptoTraderServer) GetOrderbookStream(*GetOrderbookStreamRequest, GoCryptoTrader_GetOrderbookStreamServer) error { +func (*UnimplementedGoCryptoTraderServer) GetOrderbookStream(req *GetOrderbookStreamRequest, srv GoCryptoTrader_GetOrderbookStreamServer) error { return status.Errorf(codes.Unimplemented, "method GetOrderbookStream not implemented") } -func (*UnimplementedGoCryptoTraderServer) GetExchangeOrderbookStream(*GetExchangeOrderbookStreamRequest, GoCryptoTrader_GetExchangeOrderbookStreamServer) error { +func (*UnimplementedGoCryptoTraderServer) GetExchangeOrderbookStream(req *GetExchangeOrderbookStreamRequest, srv GoCryptoTrader_GetExchangeOrderbookStreamServer) error { return status.Errorf(codes.Unimplemented, "method GetExchangeOrderbookStream not implemented") } -func (*UnimplementedGoCryptoTraderServer) GetTickerStream(*GetTickerStreamRequest, GoCryptoTrader_GetTickerStreamServer) error { +func (*UnimplementedGoCryptoTraderServer) GetTickerStream(req *GetTickerStreamRequest, srv GoCryptoTrader_GetTickerStreamServer) error { return status.Errorf(codes.Unimplemented, "method GetTickerStream not implemented") } -func (*UnimplementedGoCryptoTraderServer) GetExchangeTickerStream(*GetExchangeTickerStreamRequest, GoCryptoTrader_GetExchangeTickerStreamServer) error { +func (*UnimplementedGoCryptoTraderServer) GetExchangeTickerStream(req *GetExchangeTickerStreamRequest, srv GoCryptoTrader_GetExchangeTickerStreamServer) error { return status.Errorf(codes.Unimplemented, "method GetExchangeTickerStream not implemented") } -func (*UnimplementedGoCryptoTraderServer) GetAuditEvent(context.Context, *GetAuditEventRequest) (*GetAuditEventResponse, error) { +func (*UnimplementedGoCryptoTraderServer) GetAuditEvent(ctx context.Context, req *GetAuditEventRequest) (*GetAuditEventResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetAuditEvent not implemented") } -func (*UnimplementedGoCryptoTraderServer) GCTScriptExecute(context.Context, *GCTScriptExecuteRequest) (*GCTScriptGenericResponse, error) { +func (*UnimplementedGoCryptoTraderServer) GCTScriptExecute(ctx context.Context, req *GCTScriptExecuteRequest) (*GenericResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GCTScriptExecute not implemented") } -func (*UnimplementedGoCryptoTraderServer) GCTScriptUpload(context.Context, *GCTScriptUploadRequest) (*GCTScriptGenericResponse, error) { +func (*UnimplementedGoCryptoTraderServer) GCTScriptUpload(ctx context.Context, req *GCTScriptUploadRequest) (*GenericResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GCTScriptUpload not implemented") } -func (*UnimplementedGoCryptoTraderServer) GCTScriptReadScript(context.Context, *GCTScriptReadScriptRequest) (*GCTScriptQueryResponse, error) { +func (*UnimplementedGoCryptoTraderServer) GCTScriptReadScript(ctx context.Context, req *GCTScriptReadScriptRequest) (*GCTScriptQueryResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GCTScriptReadScript not implemented") } -func (*UnimplementedGoCryptoTraderServer) GCTScriptStatus(context.Context, *GCTScriptStatusRequest) (*GCTScriptStatusResponse, error) { +func (*UnimplementedGoCryptoTraderServer) GCTScriptStatus(ctx context.Context, req *GCTScriptStatusRequest) (*GCTScriptStatusResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GCTScriptStatus not implemented") } -func (*UnimplementedGoCryptoTraderServer) GCTScriptQuery(context.Context, *GCTScriptQueryRequest) (*GCTScriptQueryResponse, error) { +func (*UnimplementedGoCryptoTraderServer) GCTScriptQuery(ctx context.Context, req *GCTScriptQueryRequest) (*GCTScriptQueryResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GCTScriptQuery not implemented") } -func (*UnimplementedGoCryptoTraderServer) GCTScriptStop(context.Context, *GCTScriptStopRequest) (*GCTScriptGenericResponse, error) { +func (*UnimplementedGoCryptoTraderServer) GCTScriptStop(ctx context.Context, req *GCTScriptStopRequest) (*GenericResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GCTScriptStop not implemented") } -func (*UnimplementedGoCryptoTraderServer) GCTScriptStopAll(context.Context, *GCTScriptStopAllRequest) (*GCTScriptGenericResponse, error) { +func (*UnimplementedGoCryptoTraderServer) GCTScriptStopAll(ctx context.Context, req *GCTScriptStopAllRequest) (*GenericResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GCTScriptStopAll not implemented") } -func (*UnimplementedGoCryptoTraderServer) GCTScriptListAll(context.Context, *GCTScriptListAllRequest) (*GCTScriptStatusResponse, error) { +func (*UnimplementedGoCryptoTraderServer) GCTScriptListAll(ctx context.Context, req *GCTScriptListAllRequest) (*GCTScriptStatusResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GCTScriptListAll not implemented") } -func (*UnimplementedGoCryptoTraderServer) GCTScriptAutoLoadToggle(context.Context, *GCTScriptAutoLoadRequest) (*GCTScriptGenericResponse, error) { +func (*UnimplementedGoCryptoTraderServer) GCTScriptAutoLoadToggle(ctx context.Context, req *GCTScriptAutoLoadRequest) (*GenericResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GCTScriptAutoLoadToggle not implemented") } -func (*UnimplementedGoCryptoTraderServer) GetHistoricCandles(context.Context, *GetHistoricCandlesRequest) (*GetHistoricCandlesResponse, error) { +func (*UnimplementedGoCryptoTraderServer) GetHistoricCandles(ctx context.Context, req *GetHistoricCandlesRequest) (*GetHistoricCandlesResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetHistoricCandles not implemented") } +func (*UnimplementedGoCryptoTraderServer) SetExchangeAsset(ctx context.Context, req *SetExchangeAssetRequest) (*GenericResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SetExchangeAsset not implemented") +} +func (*UnimplementedGoCryptoTraderServer) SetAllExchangePairs(ctx context.Context, req *SetExchangeAllPairsRequest) (*GenericResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SetAllExchangePairs not implemented") +} +func (*UnimplementedGoCryptoTraderServer) UpdateExchangeSupportedPairs(ctx context.Context, req *UpdateExchangeSupportedPairsRequest) (*GenericResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateExchangeSupportedPairs not implemented") +} +func (*UnimplementedGoCryptoTraderServer) GetExchangeAssets(ctx context.Context, req *GetExchangeAssetsRequest) (*GetExchangeAssetsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetExchangeAssets not implemented") +} +func (*UnimplementedGoCryptoTraderServer) WebsocketGetInfo(ctx context.Context, req *WebsocketGetInfoRequest) (*WebsocketGetInfoResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method WebsocketGetInfo not implemented") +} +func (*UnimplementedGoCryptoTraderServer) WebsocketSetEnabled(ctx context.Context, req *WebsocketSetEnabledRequest) (*GenericResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method WebsocketSetEnabled not implemented") +} +func (*UnimplementedGoCryptoTraderServer) WebsocketGetSubscriptions(ctx context.Context, req *WebsocketGetSubscriptionsRequest) (*WebsocketGetSubscriptionsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method WebsocketGetSubscriptions not implemented") +} +func (*UnimplementedGoCryptoTraderServer) WebsocketSetProxy(ctx context.Context, req *WebsocketSetProxyRequest) (*GenericResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method WebsocketSetProxy not implemented") +} +func (*UnimplementedGoCryptoTraderServer) WebsocketSetURL(ctx context.Context, req *WebsocketSetURLRequest) (*GenericResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method WebsocketSetURL not implemented") +} func RegisterGoCryptoTraderServer(s *grpc.Server, srv GoCryptoTraderServer) { s.RegisterService(&_GoCryptoTrader_serviceDesc, srv) @@ -12768,38 +9563,20 @@ func _GoCryptoTrader_GetExchangePairs_Handler(srv interface{}, ctx context.Conte return interceptor(ctx, in, info, handler) } -func _GoCryptoTrader_EnableExchangePair_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(ExchangePairRequest) +func _GoCryptoTrader_SetExchangePair_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SetExchangePairRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(GoCryptoTraderServer).EnableExchangePair(ctx, in) + return srv.(GoCryptoTraderServer).SetExchangePair(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/gctrpc.GoCryptoTrader/EnableExchangePair", + FullMethod: "/gctrpc.GoCryptoTrader/SetExchangePair", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(GoCryptoTraderServer).EnableExchangePair(ctx, req.(*ExchangePairRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _GoCryptoTrader_DisableExchangePair_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(ExchangePairRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(GoCryptoTraderServer).DisableExchangePair(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/gctrpc.GoCryptoTrader/DisableExchangePair", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(GoCryptoTraderServer).DisableExchangePair(ctx, req.(*ExchangePairRequest)) + return srv.(GoCryptoTraderServer).SetExchangePair(ctx, req.(*SetExchangePairRequest)) } return interceptor(ctx, in, info, handler) } @@ -13086,6 +9863,168 @@ func _GoCryptoTrader_GetHistoricCandles_Handler(srv interface{}, ctx context.Con return interceptor(ctx, in, info, handler) } +func _GoCryptoTrader_SetExchangeAsset_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SetExchangeAssetRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GoCryptoTraderServer).SetExchangeAsset(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gctrpc.GoCryptoTrader/SetExchangeAsset", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GoCryptoTraderServer).SetExchangeAsset(ctx, req.(*SetExchangeAssetRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GoCryptoTrader_SetAllExchangePairs_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SetExchangeAllPairsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GoCryptoTraderServer).SetAllExchangePairs(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gctrpc.GoCryptoTrader/SetAllExchangePairs", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GoCryptoTraderServer).SetAllExchangePairs(ctx, req.(*SetExchangeAllPairsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GoCryptoTrader_UpdateExchangeSupportedPairs_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateExchangeSupportedPairsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GoCryptoTraderServer).UpdateExchangeSupportedPairs(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gctrpc.GoCryptoTrader/UpdateExchangeSupportedPairs", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GoCryptoTraderServer).UpdateExchangeSupportedPairs(ctx, req.(*UpdateExchangeSupportedPairsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GoCryptoTrader_GetExchangeAssets_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetExchangeAssetsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GoCryptoTraderServer).GetExchangeAssets(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gctrpc.GoCryptoTrader/GetExchangeAssets", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GoCryptoTraderServer).GetExchangeAssets(ctx, req.(*GetExchangeAssetsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GoCryptoTrader_WebsocketGetInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(WebsocketGetInfoRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GoCryptoTraderServer).WebsocketGetInfo(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gctrpc.GoCryptoTrader/WebsocketGetInfo", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GoCryptoTraderServer).WebsocketGetInfo(ctx, req.(*WebsocketGetInfoRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GoCryptoTrader_WebsocketSetEnabled_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(WebsocketSetEnabledRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GoCryptoTraderServer).WebsocketSetEnabled(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gctrpc.GoCryptoTrader/WebsocketSetEnabled", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GoCryptoTraderServer).WebsocketSetEnabled(ctx, req.(*WebsocketSetEnabledRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GoCryptoTrader_WebsocketGetSubscriptions_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(WebsocketGetSubscriptionsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GoCryptoTraderServer).WebsocketGetSubscriptions(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gctrpc.GoCryptoTrader/WebsocketGetSubscriptions", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GoCryptoTraderServer).WebsocketGetSubscriptions(ctx, req.(*WebsocketGetSubscriptionsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GoCryptoTrader_WebsocketSetProxy_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(WebsocketSetProxyRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GoCryptoTraderServer).WebsocketSetProxy(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gctrpc.GoCryptoTrader/WebsocketSetProxy", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GoCryptoTraderServer).WebsocketSetProxy(ctx, req.(*WebsocketSetProxyRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GoCryptoTrader_WebsocketSetURL_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(WebsocketSetURLRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GoCryptoTraderServer).WebsocketSetURL(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gctrpc.GoCryptoTrader/WebsocketSetURL", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GoCryptoTraderServer).WebsocketSetURL(ctx, req.(*WebsocketSetURLRequest)) + } + return interceptor(ctx, in, info, handler) +} + var _GoCryptoTrader_serviceDesc = grpc.ServiceDesc{ ServiceName: "gctrpc.GoCryptoTrader", HandlerType: (*GoCryptoTraderServer)(nil), @@ -13267,12 +10206,8 @@ var _GoCryptoTrader_serviceDesc = grpc.ServiceDesc{ Handler: _GoCryptoTrader_GetExchangePairs_Handler, }, { - MethodName: "EnableExchangePair", - Handler: _GoCryptoTrader_EnableExchangePair_Handler, - }, - { - MethodName: "DisableExchangePair", - Handler: _GoCryptoTrader_DisableExchangePair_Handler, + MethodName: "SetExchangePair", + Handler: _GoCryptoTrader_SetExchangePair_Handler, }, { MethodName: "GetAuditEvent", @@ -13318,6 +10253,42 @@ var _GoCryptoTrader_serviceDesc = grpc.ServiceDesc{ MethodName: "GetHistoricCandles", Handler: _GoCryptoTrader_GetHistoricCandles_Handler, }, + { + MethodName: "SetExchangeAsset", + Handler: _GoCryptoTrader_SetExchangeAsset_Handler, + }, + { + MethodName: "SetAllExchangePairs", + Handler: _GoCryptoTrader_SetAllExchangePairs_Handler, + }, + { + MethodName: "UpdateExchangeSupportedPairs", + Handler: _GoCryptoTrader_UpdateExchangeSupportedPairs_Handler, + }, + { + MethodName: "GetExchangeAssets", + Handler: _GoCryptoTrader_GetExchangeAssets_Handler, + }, + { + MethodName: "WebsocketGetInfo", + Handler: _GoCryptoTrader_WebsocketGetInfo_Handler, + }, + { + MethodName: "WebsocketSetEnabled", + Handler: _GoCryptoTrader_WebsocketSetEnabled_Handler, + }, + { + MethodName: "WebsocketGetSubscriptions", + Handler: _GoCryptoTrader_WebsocketGetSubscriptions_Handler, + }, + { + MethodName: "WebsocketSetProxy", + Handler: _GoCryptoTrader_WebsocketSetProxy_Handler, + }, + { + MethodName: "WebsocketSetURL", + Handler: _GoCryptoTrader_WebsocketSetURL_Handler, + }, }, Streams: []grpc.StreamDesc{ { diff --git a/gctrpc/rpc.pb.gw.go b/gctrpc/rpc.pb.gw.go index 97f3615a..ae8bcfd6 100644 --- a/gctrpc/rpc.pb.gw.go +++ b/gctrpc/rpc.pb.gw.go @@ -91,10 +91,7 @@ func local_request_GoCryptoTrader_EnableSubsystem_0(ctx context.Context, marshal var protoReq GenericSubsystemRequest var metadata runtime.ServerMetadata - if err := req.ParseForm(); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_EnableSubsystem_0); err != nil { + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_EnableSubsystem_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -127,10 +124,7 @@ func local_request_GoCryptoTrader_DisableSubsystem_0(ctx context.Context, marsha var protoReq GenericSubsystemRequest var metadata runtime.ServerMetadata - if err := req.ParseForm(); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_DisableSubsystem_0); err != nil { + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_DisableSubsystem_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -199,10 +193,7 @@ func local_request_GoCryptoTrader_GetExchanges_0(ctx context.Context, marshaler var protoReq GetExchangesRequest var metadata runtime.ServerMetadata - if err := req.ParseForm(); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_GetExchanges_0); err != nil { + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_GetExchanges_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -269,10 +260,7 @@ func local_request_GoCryptoTrader_GetExchangeInfo_0(ctx context.Context, marshal var protoReq GenericExchangeNameRequest var metadata runtime.ServerMetadata - if err := req.ParseForm(); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_GetExchangeInfo_0); err != nil { + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_GetExchangeInfo_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -305,10 +293,7 @@ func local_request_GoCryptoTrader_GetExchangeOTPCode_0(ctx context.Context, mars var protoReq GenericExchangeNameRequest var metadata runtime.ServerMetadata - if err := req.ParseForm(); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_GetExchangeOTPCode_0); err != nil { + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_GetExchangeOTPCode_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -497,10 +482,7 @@ func local_request_GoCryptoTrader_GetAccountInfo_0(ctx context.Context, marshale var protoReq GetAccountInfoRequest var metadata runtime.ServerMetadata - if err := req.ParseForm(); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_GetAccountInfo_0); err != nil { + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_GetAccountInfo_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -1281,10 +1263,7 @@ func local_request_GoCryptoTrader_GetLoggerDetails_0(ctx context.Context, marsha var protoReq GetLoggerDetailsRequest var metadata runtime.ServerMetadata - if err := req.ParseForm(); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_GetLoggerDetails_0); err != nil { + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_GetLoggerDetails_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -1361,8 +1340,8 @@ func local_request_GoCryptoTrader_GetExchangePairs_0(ctx context.Context, marsha } -func request_GoCryptoTrader_EnableExchangePair_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq ExchangePairRequest +func request_GoCryptoTrader_SetExchangePair_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq SetExchangePairRequest var metadata runtime.ServerMetadata newReader, berr := utilities.IOReaderFactory(req.Body) @@ -1373,13 +1352,13 @@ func request_GoCryptoTrader_EnableExchangePair_0(ctx context.Context, marshaler return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - msg, err := client.EnableExchangePair(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + msg, err := client.SetExchangePair(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } -func local_request_GoCryptoTrader_EnableExchangePair_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq ExchangePairRequest +func local_request_GoCryptoTrader_SetExchangePair_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq SetExchangePairRequest var metadata runtime.ServerMetadata newReader, berr := utilities.IOReaderFactory(req.Body) @@ -1390,41 +1369,7 @@ func local_request_GoCryptoTrader_EnableExchangePair_0(ctx context.Context, mars return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - msg, err := server.EnableExchangePair(ctx, &protoReq) - return msg, metadata, err - -} - -func request_GoCryptoTrader_DisableExchangePair_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq ExchangePairRequest - var metadata runtime.ServerMetadata - - newReader, berr := utilities.IOReaderFactory(req.Body) - if berr != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) - } - if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - - msg, err := client.DisableExchangePair(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) - return msg, metadata, err - -} - -func local_request_GoCryptoTrader_DisableExchangePair_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq ExchangePairRequest - var metadata runtime.ServerMetadata - - newReader, berr := utilities.IOReaderFactory(req.Body) - if berr != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) - } - if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - - msg, err := server.DisableExchangePair(ctx, &protoReq) + msg, err := server.SetExchangePair(ctx, &protoReq) return msg, metadata, err } @@ -1565,10 +1510,7 @@ func local_request_GoCryptoTrader_GetAuditEvent_0(ctx context.Context, marshaler var protoReq GetAuditEventRequest var metadata runtime.ServerMetadata - if err := req.ParseForm(); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_GetAuditEvent_0); err != nil { + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_GetAuditEvent_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -1601,10 +1543,7 @@ func local_request_GoCryptoTrader_GCTScriptExecute_0(ctx context.Context, marsha var protoReq GCTScriptExecuteRequest var metadata runtime.ServerMetadata - if err := req.ParseForm(); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_GCTScriptExecute_0); err != nil { + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_GCTScriptExecute_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -1723,10 +1662,7 @@ func local_request_GoCryptoTrader_GCTScriptQuery_0(ctx context.Context, marshale var protoReq GCTScriptQueryRequest var metadata runtime.ServerMetadata - if err := req.ParseForm(); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_GCTScriptQuery_0); err != nil { + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_GCTScriptQuery_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -1895,10 +1831,7 @@ func local_request_GoCryptoTrader_GetHistoricCandles_0(ctx context.Context, mars var protoReq GetHistoricCandlesRequest var metadata runtime.ServerMetadata - if err := req.ParseForm(); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_GetHistoricCandles_0); err != nil { + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_GetHistoricCandles_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -1907,6 +1840,303 @@ func local_request_GoCryptoTrader_GetHistoricCandles_0(ctx context.Context, mars } +var ( + filter_GoCryptoTrader_SetExchangeAsset_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_GoCryptoTrader_SetExchangeAsset_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq SetExchangeAssetRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_SetExchangeAsset_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.SetExchangeAsset(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_GoCryptoTrader_SetExchangeAsset_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq SetExchangeAssetRequest + var metadata runtime.ServerMetadata + + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_SetExchangeAsset_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.SetExchangeAsset(ctx, &protoReq) + return msg, metadata, err + +} + +var ( + filter_GoCryptoTrader_SetAllExchangePairs_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_GoCryptoTrader_SetAllExchangePairs_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq SetExchangeAllPairsRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_SetAllExchangePairs_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.SetAllExchangePairs(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_GoCryptoTrader_SetAllExchangePairs_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq SetExchangeAllPairsRequest + var metadata runtime.ServerMetadata + + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_SetAllExchangePairs_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.SetAllExchangePairs(ctx, &protoReq) + return msg, metadata, err + +} + +var ( + filter_GoCryptoTrader_UpdateExchangeSupportedPairs_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_GoCryptoTrader_UpdateExchangeSupportedPairs_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq UpdateExchangeSupportedPairsRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_UpdateExchangeSupportedPairs_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.UpdateExchangeSupportedPairs(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_GoCryptoTrader_UpdateExchangeSupportedPairs_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq UpdateExchangeSupportedPairsRequest + var metadata runtime.ServerMetadata + + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_UpdateExchangeSupportedPairs_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.UpdateExchangeSupportedPairs(ctx, &protoReq) + return msg, metadata, err + +} + +var ( + filter_GoCryptoTrader_GetExchangeAssets_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_GoCryptoTrader_GetExchangeAssets_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetExchangeAssetsRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_GetExchangeAssets_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.GetExchangeAssets(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_GoCryptoTrader_GetExchangeAssets_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetExchangeAssetsRequest + var metadata runtime.ServerMetadata + + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_GetExchangeAssets_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.GetExchangeAssets(ctx, &protoReq) + return msg, metadata, err + +} + +var ( + filter_GoCryptoTrader_WebsocketGetInfo_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_GoCryptoTrader_WebsocketGetInfo_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq WebsocketGetInfoRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_WebsocketGetInfo_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.WebsocketGetInfo(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_GoCryptoTrader_WebsocketGetInfo_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq WebsocketGetInfoRequest + var metadata runtime.ServerMetadata + + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_WebsocketGetInfo_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.WebsocketGetInfo(ctx, &protoReq) + return msg, metadata, err + +} + +var ( + filter_GoCryptoTrader_WebsocketSetEnabled_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_GoCryptoTrader_WebsocketSetEnabled_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq WebsocketSetEnabledRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_WebsocketSetEnabled_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.WebsocketSetEnabled(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_GoCryptoTrader_WebsocketSetEnabled_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq WebsocketSetEnabledRequest + var metadata runtime.ServerMetadata + + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_WebsocketSetEnabled_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.WebsocketSetEnabled(ctx, &protoReq) + return msg, metadata, err + +} + +var ( + filter_GoCryptoTrader_WebsocketGetSubscriptions_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_GoCryptoTrader_WebsocketGetSubscriptions_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq WebsocketGetSubscriptionsRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_WebsocketGetSubscriptions_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.WebsocketGetSubscriptions(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_GoCryptoTrader_WebsocketGetSubscriptions_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq WebsocketGetSubscriptionsRequest + var metadata runtime.ServerMetadata + + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_WebsocketGetSubscriptions_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.WebsocketGetSubscriptions(ctx, &protoReq) + return msg, metadata, err + +} + +var ( + filter_GoCryptoTrader_WebsocketSetProxy_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_GoCryptoTrader_WebsocketSetProxy_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq WebsocketSetProxyRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_WebsocketSetProxy_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.WebsocketSetProxy(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_GoCryptoTrader_WebsocketSetProxy_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq WebsocketSetProxyRequest + var metadata runtime.ServerMetadata + + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_WebsocketSetProxy_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.WebsocketSetProxy(ctx, &protoReq) + return msg, metadata, err + +} + +var ( + filter_GoCryptoTrader_WebsocketSetURL_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_GoCryptoTrader_WebsocketSetURL_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq WebsocketSetURLRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_WebsocketSetURL_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.WebsocketSetURL(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_GoCryptoTrader_WebsocketSetURL_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq WebsocketSetURLRequest + var metadata runtime.ServerMetadata + + if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_WebsocketSetURL_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.WebsocketSetURL(ctx, &protoReq) + return msg, metadata, err + +} + // RegisterGoCryptoTraderHandlerServer registers the http handlers for service GoCryptoTrader to "mux". // UnaryRPC :call GoCryptoTraderServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. @@ -2799,7 +3029,7 @@ func RegisterGoCryptoTraderHandlerServer(ctx context.Context, mux *runtime.Serve }) - mux.Handle("POST", pattern_GoCryptoTrader_EnableExchangePair_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("POST", pattern_GoCryptoTrader_SetExchangePair_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) @@ -2808,34 +3038,14 @@ func RegisterGoCryptoTraderHandlerServer(ctx context.Context, mux *runtime.Serve runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTrader_EnableExchangePair_0(rctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTrader_SetExchangePair_0(rctx, inboundMarshaler, server, req, pathParams) ctx = runtime.NewServerMetadataContext(ctx, md) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTrader_EnableExchangePair_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - - mux.Handle("POST", pattern_GoCryptoTrader_DisableExchangePair_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_GoCryptoTrader_DisableExchangePair_0(rctx, inboundMarshaler, server, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - - forward_GoCryptoTrader_DisableExchangePair_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTrader_SetExchangePair_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -3087,6 +3297,186 @@ func RegisterGoCryptoTraderHandlerServer(ctx context.Context, mux *runtime.Serve }) + mux.Handle("GET", pattern_GoCryptoTrader_SetExchangeAsset_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_SetExchangeAsset_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_SetExchangeAsset_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_SetAllExchangePairs_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_SetAllExchangePairs_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_SetAllExchangePairs_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_UpdateExchangeSupportedPairs_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_UpdateExchangeSupportedPairs_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_UpdateExchangeSupportedPairs_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetExchangeAssets_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_GetExchangeAssets_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetExchangeAssets_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_WebsocketGetInfo_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_WebsocketGetInfo_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_WebsocketGetInfo_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_WebsocketSetEnabled_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_WebsocketSetEnabled_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_WebsocketSetEnabled_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_WebsocketGetSubscriptions_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_WebsocketGetSubscriptions_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_WebsocketGetSubscriptions_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_WebsocketSetProxy_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_WebsocketSetProxy_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_WebsocketSetProxy_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_WebsocketSetURL_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GoCryptoTrader_WebsocketSetURL_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_WebsocketSetURL_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -4028,7 +4418,7 @@ func RegisterGoCryptoTraderHandlerClient(ctx context.Context, mux *runtime.Serve }) - mux.Handle("POST", pattern_GoCryptoTrader_EnableExchangePair_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("POST", pattern_GoCryptoTrader_SetExchangePair_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) @@ -4037,34 +4427,14 @@ func RegisterGoCryptoTraderHandlerClient(ctx context.Context, mux *runtime.Serve runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTrader_EnableExchangePair_0(rctx, inboundMarshaler, client, req, pathParams) + resp, md, err := request_GoCryptoTrader_SetExchangePair_0(rctx, inboundMarshaler, client, req, pathParams) ctx = runtime.NewServerMetadataContext(ctx, md) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTrader_EnableExchangePair_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - - mux.Handle("POST", pattern_GoCryptoTrader_DisableExchangePair_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := request_GoCryptoTrader_DisableExchangePair_0(rctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - - forward_GoCryptoTrader_DisableExchangePair_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTrader_SetExchangePair_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -4368,6 +4738,186 @@ func RegisterGoCryptoTraderHandlerClient(ctx context.Context, mux *runtime.Serve }) + mux.Handle("GET", pattern_GoCryptoTrader_SetExchangeAsset_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GoCryptoTrader_SetExchangeAsset_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_SetExchangeAsset_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_SetAllExchangePairs_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GoCryptoTrader_SetAllExchangePairs_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_SetAllExchangePairs_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_UpdateExchangeSupportedPairs_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GoCryptoTrader_UpdateExchangeSupportedPairs_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_UpdateExchangeSupportedPairs_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_GetExchangeAssets_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GoCryptoTrader_GetExchangeAssets_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_GetExchangeAssets_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_WebsocketGetInfo_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GoCryptoTrader_WebsocketGetInfo_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_WebsocketGetInfo_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_WebsocketSetEnabled_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GoCryptoTrader_WebsocketSetEnabled_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_WebsocketSetEnabled_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_WebsocketGetSubscriptions_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GoCryptoTrader_WebsocketGetSubscriptions_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_WebsocketGetSubscriptions_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_WebsocketSetProxy_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GoCryptoTrader_WebsocketSetProxy_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_WebsocketSetProxy_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_GoCryptoTrader_WebsocketSetURL_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GoCryptoTrader_WebsocketSetURL_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_GoCryptoTrader_WebsocketSetURL_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -4462,9 +5012,7 @@ var ( pattern_GoCryptoTrader_GetExchangePairs_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getexchangepairs"}, "", runtime.AssumeColonVerbOpt(true))) - pattern_GoCryptoTrader_EnableExchangePair_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "enableexchangepair"}, "", runtime.AssumeColonVerbOpt(true))) - - pattern_GoCryptoTrader_DisableExchangePair_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "disableexchangepair"}, "", runtime.AssumeColonVerbOpt(true))) + pattern_GoCryptoTrader_SetExchangePair_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "setexchangepair"}, "", runtime.AssumeColonVerbOpt(true))) pattern_GoCryptoTrader_GetOrderbookStream_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getorderbookstream"}, "", runtime.AssumeColonVerbOpt(true))) @@ -4495,6 +5043,24 @@ var ( pattern_GoCryptoTrader_GCTScriptAutoLoadToggle_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "gctscript", "autoload"}, "", runtime.AssumeColonVerbOpt(true))) pattern_GoCryptoTrader_GetHistoricCandles_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "gethistoriccandles"}, "", runtime.AssumeColonVerbOpt(true))) + + pattern_GoCryptoTrader_SetExchangeAsset_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "setexchangeasset"}, "", runtime.AssumeColonVerbOpt(true))) + + pattern_GoCryptoTrader_SetAllExchangePairs_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "setallexchangepairs"}, "", runtime.AssumeColonVerbOpt(true))) + + pattern_GoCryptoTrader_UpdateExchangeSupportedPairs_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "updateexchangesupportedpairs"}, "", runtime.AssumeColonVerbOpt(true))) + + pattern_GoCryptoTrader_GetExchangeAssets_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getexchangeassets"}, "", runtime.AssumeColonVerbOpt(true))) + + pattern_GoCryptoTrader_WebsocketGetInfo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "websocketgetinfo"}, "", runtime.AssumeColonVerbOpt(true))) + + pattern_GoCryptoTrader_WebsocketSetEnabled_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "websocketsetenabled"}, "", runtime.AssumeColonVerbOpt(true))) + + pattern_GoCryptoTrader_WebsocketGetSubscriptions_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "websocketgetsubscriptions"}, "", runtime.AssumeColonVerbOpt(true))) + + pattern_GoCryptoTrader_WebsocketSetProxy_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "websocketsetproxy"}, "", runtime.AssumeColonVerbOpt(true))) + + pattern_GoCryptoTrader_WebsocketSetURL_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "websocketseturl"}, "", runtime.AssumeColonVerbOpt(true))) ) var ( @@ -4588,9 +5154,7 @@ var ( forward_GoCryptoTrader_GetExchangePairs_0 = runtime.ForwardResponseMessage - forward_GoCryptoTrader_EnableExchangePair_0 = runtime.ForwardResponseMessage - - forward_GoCryptoTrader_DisableExchangePair_0 = runtime.ForwardResponseMessage + forward_GoCryptoTrader_SetExchangePair_0 = runtime.ForwardResponseMessage forward_GoCryptoTrader_GetOrderbookStream_0 = runtime.ForwardResponseStream @@ -4621,4 +5185,22 @@ var ( forward_GoCryptoTrader_GCTScriptAutoLoadToggle_0 = runtime.ForwardResponseMessage forward_GoCryptoTrader_GetHistoricCandles_0 = runtime.ForwardResponseMessage + + forward_GoCryptoTrader_SetExchangeAsset_0 = runtime.ForwardResponseMessage + + forward_GoCryptoTrader_SetAllExchangePairs_0 = runtime.ForwardResponseMessage + + forward_GoCryptoTrader_UpdateExchangeSupportedPairs_0 = runtime.ForwardResponseMessage + + forward_GoCryptoTrader_GetExchangeAssets_0 = runtime.ForwardResponseMessage + + forward_GoCryptoTrader_WebsocketGetInfo_0 = runtime.ForwardResponseMessage + + forward_GoCryptoTrader_WebsocketSetEnabled_0 = runtime.ForwardResponseMessage + + forward_GoCryptoTrader_WebsocketGetSubscriptions_0 = runtime.ForwardResponseMessage + + forward_GoCryptoTrader_WebsocketSetProxy_0 = runtime.ForwardResponseMessage + + forward_GoCryptoTrader_WebsocketSetURL_0 = runtime.ForwardResponseMessage ) diff --git a/gctrpc/rpc.proto b/gctrpc/rpc.proto index b5e0e3c7..fe9292ce 100644 --- a/gctrpc/rpc.proto +++ b/gctrpc/rpc.proto @@ -32,8 +32,6 @@ message GenericSubsystemRequest { string subsystem = 1; } -message GenericSubsystemResponse {} - message GetSubsystemsRequest {} message GetSusbsytemsResponse { @@ -55,8 +53,6 @@ message GenericExchangeNameRequest { string exchange = 1; } -message GenericExchangeNameResponse {} - message GetExchangesRequest { bool enabled = 1; } @@ -249,16 +245,12 @@ message AddPortfolioAddressRequest { bool cold_storage = 6; } -message AddPortfolioAddressResponse {} - message RemovePortfolioAddressRequest { string address = 1; string coin_type = 2; string description = 3; } -message RemovePortfolioAddressResponse {} - message GetForexProvidersRequest {} message ForexProvider { @@ -379,8 +371,6 @@ message CancelOrderRequest { string side = 7; } -message CancelOrderResponse {} - message CancelAllOrdersRequest { string exchange = 1; } @@ -431,8 +421,6 @@ message RemoveEventRequest { int64 id = 1; } -message RemoveEventResponse {} - message GetCryptocurrencyDepositAddressesRequest { string exchange = 1; } @@ -561,10 +549,11 @@ message GetExchangePairsResponse { map supported_assets = 1; } -message ExchangePairRequest { +message SetExchangePairRequest { string exchange = 1; string asset_type = 2; - CurrencyPair pair = 3; + repeated CurrencyPair pairs = 3; + bool enable = 4; } message GetOrderbookStreamRequest { @@ -685,11 +674,79 @@ message GCTScriptQueryResponse{ string data = 3; } -message GCTScriptGenericResponse { +message GenericResponse { string status = 1; string data = 2; } +message SetExchangeAssetRequest { + string exchange = 1; + string asset = 2; + bool enable = 3; +} + +message SetExchangeAllPairsRequest { + string exchange = 1; + bool enable = 2; +} + +message UpdateExchangeSupportedPairsRequest { + string exchange = 1; +} + +message GetExchangeAssetsRequest { + string exchange = 1; +} + +message GetExchangeAssetsResponse { + string assets = 1; +} + +message WebsocketGetInfoRequest { + string exchange = 1; +} + +message WebsocketGetInfoResponse { + string exchange = 1; + bool supported = 2; + bool enabled = 3; + bool authenticated_supported = 4; + bool authenticated = 5; + string running_url = 6; + string proxy_address = 7; +} + +message WebsocketSetEnabledRequest { + string exchange = 1; + bool enable = 2; +} + +message WebsocketGetSubscriptionsRequest { + string exchange = 1; +} + +message WebsocketSubscription { + string channel = 1; + string currency = 2; + string asset = 3; + string params = 4; +} + +message WebsocketGetSubscriptionsResponse { + string exchange = 1; + repeated WebsocketSubscription subscriptions = 2; +} + +message WebsocketSetProxyRequest { + string exchange = 1; + string proxy = 2; +} + +message WebsocketSetURLRequest { + string exchange = 1; + string url = 2; +} + service GoCryptoTrader { rpc GetInfo (GetInfoRequest) returns (GetInfoResponse) { option (google.api.http) = { @@ -703,13 +760,13 @@ service GoCryptoTrader { }; } - rpc EnableSubsystem (GenericSubsystemRequest) returns (GenericSubsystemResponse) { + rpc EnableSubsystem (GenericSubsystemRequest) returns (GenericResponse) { option (google.api.http) = { get: "/v1/enablesubsystem" }; } - rpc DisableSubsystem (GenericSubsystemRequest) returns (GenericSubsystemResponse) { + rpc DisableSubsystem (GenericSubsystemRequest) returns (GenericResponse) { option (google.api.http) = { get: "/v1/disablesubsystem" }; @@ -733,7 +790,7 @@ service GoCryptoTrader { }; } - rpc DisableExchange (GenericExchangeNameRequest) returns (GenericExchangeNameResponse) { + rpc DisableExchange (GenericExchangeNameRequest) returns (GenericResponse) { option (google.api.http) = { post: "/v1/disableexchange" body: "*" @@ -758,7 +815,7 @@ service GoCryptoTrader { }; } - rpc EnableExchange (GenericExchangeNameRequest) returns (GenericExchangeNameResponse) { + rpc EnableExchange (GenericExchangeNameRequest) returns (GenericResponse) { option (google.api.http) = { post: "/v1/enableexchange" body: "*" @@ -822,14 +879,14 @@ service GoCryptoTrader { } - rpc AddPortfolioAddress (AddPortfolioAddressRequest) returns (AddPortfolioAddressResponse) { + rpc AddPortfolioAddress (AddPortfolioAddressRequest) returns (GenericResponse) { option (google.api.http) = { post: "/v1/addportfolioaddress" body: "*" }; } - rpc RemovePortfolioAddress (RemovePortfolioAddressRequest) returns (RemovePortfolioAddressResponse) { + rpc RemovePortfolioAddress (RemovePortfolioAddressRequest) returns (GenericResponse) { option (google.api.http) = { post: "/v1/removeportfolioaddress" body: "*" @@ -883,7 +940,7 @@ service GoCryptoTrader { }; } - rpc CancelOrder (CancelOrderRequest) returns (CancelOrderResponse) { + rpc CancelOrder (CancelOrderRequest) returns (GenericResponse) { option (google.api.http) = { post: "/v1/cancelorder" body: "*" @@ -910,7 +967,7 @@ service GoCryptoTrader { }; } - rpc RemoveEvent(RemoveEventRequest) returns (RemoveEventResponse) { + rpc RemoveEvent(RemoveEventRequest) returns (GenericResponse) { option (google.api.http) = { post: "/v1/removeevent" body: "*" @@ -987,16 +1044,9 @@ service GoCryptoTrader { }; } - rpc EnableExchangePair(ExchangePairRequest) returns (GenericExchangeNameResponse) { + rpc SetExchangePair(SetExchangePairRequest) returns (GenericResponse) { option (google.api.http) = { - post: "/v1/enableexchangepair", - body: "*" - }; - } - - rpc DisableExchangePair(ExchangePairRequest) returns (GenericExchangeNameResponse) { - option (google.api.http) = { - post: "/v1/disableexchangepair", + post: "/v1/setexchangepair", body: "*" }; } @@ -1031,13 +1081,13 @@ service GoCryptoTrader { }; } - rpc GCTScriptExecute(GCTScriptExecuteRequest) returns (GCTScriptGenericResponse) { + rpc GCTScriptExecute(GCTScriptExecuteRequest) returns (GenericResponse) { option (google.api.http) = { get: "/v1/gctscript/execute", }; } - rpc GCTScriptUpload(GCTScriptUploadRequest) returns (GCTScriptGenericResponse) { + rpc GCTScriptUpload(GCTScriptUploadRequest) returns (GenericResponse) { option (google.api.http) = { post: "/v1/gctscript/upload", body: "*" @@ -1063,14 +1113,14 @@ service GoCryptoTrader { }; } - rpc GCTScriptStop(GCTScriptStopRequest) returns (GCTScriptGenericResponse) { + rpc GCTScriptStop(GCTScriptStopRequest) returns (GenericResponse) { option (google.api.http) = { post: "/v1/gctscript/stop", body: "*" }; } - rpc GCTScriptStopAll(GCTScriptStopAllRequest) returns (GCTScriptGenericResponse) { + rpc GCTScriptStopAll(GCTScriptStopAllRequest) returns (GenericResponse) { option (google.api.http) = { post: "/v1/gctscript/stop", body: "*" @@ -1083,7 +1133,7 @@ service GoCryptoTrader { body: "*" }; } - rpc GCTScriptAutoLoadToggle(GCTScriptAutoLoadRequest) returns (GCTScriptGenericResponse) { + rpc GCTScriptAutoLoadToggle(GCTScriptAutoLoadRequest) returns (GenericResponse) { option (google.api.http) = { post: "/v1/gctscript/autoload", body: "*" @@ -1095,4 +1145,58 @@ service GoCryptoTrader { get: "/v1/gethistoriccandles" }; } -} + + rpc SetExchangeAsset(SetExchangeAssetRequest) returns (GenericResponse) { + option (google.api.http) = { + get: "/v1/setexchangeasset" + }; + } + + rpc SetAllExchangePairs(SetExchangeAllPairsRequest) returns (GenericResponse) { + option (google.api.http) = { + get: "/v1/setallexchangepairs" + }; + } + + rpc UpdateExchangeSupportedPairs(UpdateExchangeSupportedPairsRequest) returns (GenericResponse) { + option (google.api.http) = { + get: "/v1/updateexchangesupportedpairs" + }; + } + + rpc GetExchangeAssets(GetExchangeAssetsRequest) returns (GetExchangeAssetsResponse) { + option (google.api.http) = { + get: "/v1/getexchangeassets" + }; + } + + rpc WebsocketGetInfo(WebsocketGetInfoRequest) returns (WebsocketGetInfoResponse) { + option (google.api.http) = { + get: "/v1/websocketgetinfo" + }; + } + + rpc WebsocketSetEnabled(WebsocketSetEnabledRequest) returns (GenericResponse) { + option (google.api.http) = { + get: "/v1/websocketsetenabled" + }; + } + + rpc WebsocketGetSubscriptions(WebsocketGetSubscriptionsRequest) returns (WebsocketGetSubscriptionsResponse) { + option (google.api.http) = { + get: "/v1/websocketgetsubscriptions" + }; + } + + rpc WebsocketSetProxy(WebsocketSetProxyRequest) returns (GenericResponse) { + option (google.api.http) = { + get: "/v1/websocketsetproxy" + }; + } + + rpc WebsocketSetURL(WebsocketSetURLRequest) returns (GenericResponse) { + option (google.api.http) = { + get: "/v1/websocketseturl" + }; + } +} \ No newline at end of file diff --git a/gctrpc/rpc.swagger.json b/gctrpc/rpc.swagger.json index 057a7351..45b0f0f6 100644 --- a/gctrpc/rpc.swagger.json +++ b/gctrpc/rpc.swagger.json @@ -13,7 +13,7 @@ "paths": { "/v1/addevent": { "post": { - "operationId": "GoCryptoTrader_AddEvent", + "operationId": "AddEvent", "responses": { "200": { "description": "A successful response.", @@ -45,12 +45,12 @@ }, "/v1/addportfolioaddress": { "post": { - "operationId": "GoCryptoTrader_AddPortfolioAddress", + "operationId": "AddPortfolioAddress", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/gctrpcAddPortfolioAddressResponse" + "$ref": "#/definitions/gctrpcGenericResponse" } }, "default": { @@ -77,7 +77,7 @@ }, "/v1/cancelallorders": { "post": { - "operationId": "GoCryptoTrader_CancelAllOrders", + "operationId": "CancelAllOrders", "responses": { "200": { "description": "A successful response.", @@ -109,12 +109,12 @@ }, "/v1/cancelorder": { "post": { - "operationId": "GoCryptoTrader_CancelOrder", + "operationId": "CancelOrder", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/gctrpcCancelOrderResponse" + "$ref": "#/definitions/gctrpcGenericResponse" } }, "default": { @@ -141,12 +141,12 @@ }, "/v1/disableexchange": { "post": { - "operationId": "GoCryptoTrader_DisableExchange", + "operationId": "DisableExchange", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/gctrpcGenericExchangeNameResponse" + "$ref": "#/definitions/gctrpcGenericResponse" } }, "default": { @@ -171,46 +171,14 @@ ] } }, - "/v1/disableexchangepair": { - "post": { - "operationId": "GoCryptoTrader_DisableExchangePair", - "responses": { - "200": { - "description": "A successful response.", - "schema": { - "$ref": "#/definitions/gctrpcGenericExchangeNameResponse" - } - }, - "default": { - "description": "An unexpected error response", - "schema": { - "$ref": "#/definitions/runtimeError" - } - } - }, - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/gctrpcExchangePairRequest" - } - } - ], - "tags": [ - "GoCryptoTrader" - ] - } - }, "/v1/disablesubsystem": { "get": { - "operationId": "GoCryptoTrader_DisableSubsystem", + "operationId": "DisableSubsystem", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/gctrpcGenericSubsystemResponse" + "$ref": "#/definitions/gctrpcGenericResponse" } }, "default": { @@ -235,12 +203,12 @@ }, "/v1/enableexchange": { "post": { - "operationId": "GoCryptoTrader_EnableExchange", + "operationId": "EnableExchange", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/gctrpcGenericExchangeNameResponse" + "$ref": "#/definitions/gctrpcGenericResponse" } }, "default": { @@ -265,46 +233,14 @@ ] } }, - "/v1/enableexchangepair": { - "post": { - "operationId": "GoCryptoTrader_EnableExchangePair", - "responses": { - "200": { - "description": "A successful response.", - "schema": { - "$ref": "#/definitions/gctrpcGenericExchangeNameResponse" - } - }, - "default": { - "description": "An unexpected error response", - "schema": { - "$ref": "#/definitions/runtimeError" - } - } - }, - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/gctrpcExchangePairRequest" - } - } - ], - "tags": [ - "GoCryptoTrader" - ] - } - }, "/v1/enablesubsystem": { "get": { - "operationId": "GoCryptoTrader_EnableSubsystem", + "operationId": "EnableSubsystem", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/gctrpcGenericSubsystemResponse" + "$ref": "#/definitions/gctrpcGenericResponse" } }, "default": { @@ -329,12 +265,12 @@ }, "/v1/gctscript/autoload": { "post": { - "operationId": "GoCryptoTrader_GCTScriptAutoLoadToggle", + "operationId": "GCTScriptAutoLoadToggle", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/gctrpcGCTScriptGenericResponse" + "$ref": "#/definitions/gctrpcGenericResponse" } }, "default": { @@ -361,12 +297,12 @@ }, "/v1/gctscript/execute": { "get": { - "operationId": "GoCryptoTrader_GCTScriptExecute", + "operationId": "GCTScriptExecute", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/gctrpcGCTScriptGenericResponse" + "$ref": "#/definitions/gctrpcGenericResponse" } }, "default": { @@ -409,7 +345,7 @@ }, "/v1/gctscript/query": { "get": { - "operationId": "GoCryptoTrader_GCTScriptQuery", + "operationId": "GCTScriptQuery", "responses": { "200": { "description": "A successful response.", @@ -457,7 +393,7 @@ }, "/v1/gctscript/read": { "post": { - "operationId": "GoCryptoTrader_GCTScriptReadScript", + "operationId": "GCTScriptReadScript", "responses": { "200": { "description": "A successful response.", @@ -489,7 +425,7 @@ }, "/v1/gctscript/status": { "get": { - "operationId": "GoCryptoTrader_GCTScriptStatus", + "operationId": "GCTScriptStatus", "responses": { "200": { "description": "A successful response.", @@ -511,7 +447,7 @@ }, "/v1/gctscript/stop": { "post": { - "operationId": "GoCryptoTrader_GCTScriptListAll", + "operationId": "GCTScriptListAll", "responses": { "200": { "description": "A successful response.", @@ -543,12 +479,12 @@ }, "/v1/gctscript/upload": { "post": { - "operationId": "GoCryptoTrader_GCTScriptUpload", + "operationId": "GCTScriptUpload", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/gctrpcGCTScriptGenericResponse" + "$ref": "#/definitions/gctrpcGenericResponse" } }, "default": { @@ -575,7 +511,7 @@ }, "/v1/getaccountinfo": { "get": { - "operationId": "GoCryptoTrader_GetAccountInfo", + "operationId": "GetAccountInfo", "responses": { "200": { "description": "A successful response.", @@ -605,7 +541,7 @@ }, "/v1/getaccountinfostream": { "get": { - "operationId": "GoCryptoTrader_GetAccountInfoStream", + "operationId": "GetAccountInfoStream", "responses": { "200": { "description": "A successful response.(streaming responses)", @@ -644,7 +580,7 @@ }, "/v1/getauditevent": { "get": { - "operationId": "GoCryptoTrader_GetAuditEvent", + "operationId": "GetAuditEvent", "responses": { "200": { "description": "A successful response.", @@ -700,7 +636,7 @@ }, "/v1/getcommunicationrelayers": { "get": { - "operationId": "GoCryptoTrader_GetCommunicationRelayers", + "operationId": "GetCommunicationRelayers", "responses": { "200": { "description": "A successful response.", @@ -722,7 +658,7 @@ }, "/v1/getconfig": { "get": { - "operationId": "GoCryptoTrader_GetConfig", + "operationId": "GetConfig", "responses": { "200": { "description": "A successful response.", @@ -744,7 +680,7 @@ }, "/v1/getcryptodepositaddress": { "post": { - "operationId": "GoCryptoTrader_GetCryptocurrencyDepositAddress", + "operationId": "GetCryptocurrencyDepositAddress", "responses": { "200": { "description": "A successful response.", @@ -776,7 +712,7 @@ }, "/v1/getcryptodepositaddresses": { "post": { - "operationId": "GoCryptoTrader_GetCryptocurrencyDepositAddresses", + "operationId": "GetCryptocurrencyDepositAddresses", "responses": { "200": { "description": "A successful response.", @@ -808,7 +744,7 @@ }, "/v1/getevents": { "get": { - "operationId": "GoCryptoTrader_GetEvents", + "operationId": "GetEvents", "responses": { "200": { "description": "A successful response.", @@ -828,9 +764,39 @@ ] } }, + "/v1/getexchangeassets": { + "get": { + "operationId": "GetExchangeAssets", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/gctrpcGetExchangeAssetsResponse" + } + }, + "default": { + "description": "An unexpected error response", + "schema": { + "$ref": "#/definitions/runtimeError" + } + } + }, + "parameters": [ + { + "name": "exchange", + "in": "query", + "required": false, + "type": "string" + } + ], + "tags": [ + "GoCryptoTrader" + ] + } + }, "/v1/getexchangeinfo": { "get": { - "operationId": "GoCryptoTrader_GetExchangeInfo", + "operationId": "GetExchangeInfo", "responses": { "200": { "description": "A successful response.", @@ -860,7 +826,7 @@ }, "/v1/getexchangeorderbookstream": { "get": { - "operationId": "GoCryptoTrader_GetExchangeOrderbookStream", + "operationId": "GetExchangeOrderbookStream", "responses": { "200": { "description": "A successful response.(streaming responses)", @@ -899,7 +865,7 @@ }, "/v1/getexchangeotp": { "get": { - "operationId": "GoCryptoTrader_GetExchangeOTPCode", + "operationId": "GetExchangeOTPCode", "responses": { "200": { "description": "A successful response.", @@ -929,7 +895,7 @@ }, "/v1/getexchangeotps": { "get": { - "operationId": "GoCryptoTrader_GetExchangeOTPCodes", + "operationId": "GetExchangeOTPCodes", "responses": { "200": { "description": "A successful response.", @@ -951,7 +917,7 @@ }, "/v1/getexchangepairs": { "post": { - "operationId": "GoCryptoTrader_GetExchangePairs", + "operationId": "GetExchangePairs", "responses": { "200": { "description": "A successful response.", @@ -983,7 +949,7 @@ }, "/v1/getexchanges": { "get": { - "operationId": "GoCryptoTrader_GetExchanges", + "operationId": "GetExchanges", "responses": { "200": { "description": "A successful response.", @@ -1014,7 +980,7 @@ }, "/v1/getexchangetickerstream": { "get": { - "operationId": "GoCryptoTrader_GetExchangeTickerStream", + "operationId": "GetExchangeTickerStream", "responses": { "200": { "description": "A successful response.(streaming responses)", @@ -1053,7 +1019,7 @@ }, "/v1/getforexproviders": { "get": { - "operationId": "GoCryptoTrader_GetForexProviders", + "operationId": "GetForexProviders", "responses": { "200": { "description": "A successful response.", @@ -1075,7 +1041,7 @@ }, "/v1/getforexrates": { "get": { - "operationId": "GoCryptoTrader_GetForexRates", + "operationId": "GetForexRates", "responses": { "200": { "description": "A successful response.", @@ -1097,7 +1063,7 @@ }, "/v1/gethistoriccandles": { "get": { - "operationId": "GoCryptoTrader_GetHistoricCandles", + "operationId": "GetHistoricCandles", "responses": { "200": { "description": "A successful response.", @@ -1179,7 +1145,7 @@ }, "/v1/getinfo": { "get": { - "operationId": "GoCryptoTrader_GetInfo", + "operationId": "GetInfo", "responses": { "200": { "description": "A successful response.", @@ -1201,7 +1167,7 @@ }, "/v1/getloggerdetails": { "get": { - "operationId": "GoCryptoTrader_GetLoggerDetails", + "operationId": "GetLoggerDetails", "responses": { "200": { "description": "A successful response.", @@ -1231,7 +1197,7 @@ }, "/v1/getorder": { "post": { - "operationId": "GoCryptoTrader_GetOrder", + "operationId": "GetOrder", "responses": { "200": { "description": "A successful response.", @@ -1263,7 +1229,7 @@ }, "/v1/getorderbook": { "post": { - "operationId": "GoCryptoTrader_GetOrderbook", + "operationId": "GetOrderbook", "responses": { "200": { "description": "A successful response.", @@ -1295,7 +1261,7 @@ }, "/v1/getorderbooks": { "get": { - "operationId": "GoCryptoTrader_GetOrderbooks", + "operationId": "GetOrderbooks", "responses": { "200": { "description": "A successful response.", @@ -1317,7 +1283,7 @@ }, "/v1/getorderbookstream": { "get": { - "operationId": "GoCryptoTrader_GetOrderbookStream", + "operationId": "GetOrderbookStream", "responses": { "200": { "description": "A successful response.(streaming responses)", @@ -1380,7 +1346,7 @@ }, "/v1/getorders": { "post": { - "operationId": "GoCryptoTrader_GetOrders", + "operationId": "GetOrders", "responses": { "200": { "description": "A successful response.", @@ -1412,7 +1378,7 @@ }, "/v1/getportfolio": { "get": { - "operationId": "GoCryptoTrader_GetPortfolio", + "operationId": "GetPortfolio", "responses": { "200": { "description": "A successful response.", @@ -1434,7 +1400,7 @@ }, "/v1/getportfoliosummary": { "get": { - "operationId": "GoCryptoTrader_GetPortfolioSummary", + "operationId": "GetPortfolioSummary", "responses": { "200": { "description": "A successful response.", @@ -1456,7 +1422,7 @@ }, "/v1/getrpcendpoints": { "get": { - "operationId": "GoCryptoTrader_GetRPCEndpoints", + "operationId": "GetRPCEndpoints", "responses": { "200": { "description": "A successful response.", @@ -1478,7 +1444,7 @@ }, "/v1/getsubsystems": { "get": { - "operationId": "GoCryptoTrader_GetSubsystems", + "operationId": "GetSubsystems", "responses": { "200": { "description": "A successful response.", @@ -1500,7 +1466,7 @@ }, "/v1/getticker": { "post": { - "operationId": "GoCryptoTrader_GetTicker", + "operationId": "GetTicker", "responses": { "200": { "description": "A successful response.", @@ -1532,7 +1498,7 @@ }, "/v1/gettickers": { "get": { - "operationId": "GoCryptoTrader_GetTickers", + "operationId": "GetTickers", "responses": { "200": { "description": "A successful response.", @@ -1554,7 +1520,7 @@ }, "/v1/gettickerstream": { "get": { - "operationId": "GoCryptoTrader_GetTickerStream", + "operationId": "GetTickerStream", "responses": { "200": { "description": "A successful response.(streaming responses)", @@ -1617,12 +1583,12 @@ }, "/v1/removeevent": { "post": { - "operationId": "GoCryptoTrader_RemoveEvent", + "operationId": "RemoveEvent", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/gctrpcRemoveEventResponse" + "$ref": "#/definitions/gctrpcGenericResponse" } }, "default": { @@ -1649,12 +1615,12 @@ }, "/v1/removeportfolioaddress": { "post": { - "operationId": "GoCryptoTrader_RemovePortfolioAddress", + "operationId": "RemovePortfolioAddress", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/gctrpcRemovePortfolioAddressResponse" + "$ref": "#/definitions/gctrpcGenericResponse" } }, "default": { @@ -1679,9 +1645,121 @@ ] } }, + "/v1/setallexchangepairs": { + "get": { + "operationId": "SetAllExchangePairs", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/gctrpcGenericResponse" + } + }, + "default": { + "description": "An unexpected error response", + "schema": { + "$ref": "#/definitions/runtimeError" + } + } + }, + "parameters": [ + { + "name": "exchange", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "enable", + "in": "query", + "required": false, + "type": "boolean", + "format": "boolean" + } + ], + "tags": [ + "GoCryptoTrader" + ] + } + }, + "/v1/setexchangeasset": { + "get": { + "operationId": "SetExchangeAsset", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/gctrpcGenericResponse" + } + }, + "default": { + "description": "An unexpected error response", + "schema": { + "$ref": "#/definitions/runtimeError" + } + } + }, + "parameters": [ + { + "name": "exchange", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "asset", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "enable", + "in": "query", + "required": false, + "type": "boolean", + "format": "boolean" + } + ], + "tags": [ + "GoCryptoTrader" + ] + } + }, + "/v1/setexchangepair": { + "post": { + "operationId": "SetExchangePair", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/gctrpcGenericResponse" + } + }, + "default": { + "description": "An unexpected error response", + "schema": { + "$ref": "#/definitions/runtimeError" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/gctrpcSetExchangePairRequest" + } + } + ], + "tags": [ + "GoCryptoTrader" + ] + } + }, "/v1/setloggerdetails": { "post": { - "operationId": "GoCryptoTrader_SetLoggerDetails", + "operationId": "SetLoggerDetails", "responses": { "200": { "description": "A successful response.", @@ -1713,7 +1791,7 @@ }, "/v1/simulateorder": { "post": { - "operationId": "GoCryptoTrader_SimulateOrder", + "operationId": "SimulateOrder", "responses": { "200": { "description": "A successful response.", @@ -1745,7 +1823,7 @@ }, "/v1/submitorder": { "post": { - "operationId": "GoCryptoTrader_SubmitOrder", + "operationId": "SubmitOrder", "responses": { "200": { "description": "A successful response.", @@ -1775,9 +1853,208 @@ ] } }, + "/v1/updateexchangesupportedpairs": { + "get": { + "operationId": "UpdateExchangeSupportedPairs", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/gctrpcGenericResponse" + } + }, + "default": { + "description": "An unexpected error response", + "schema": { + "$ref": "#/definitions/runtimeError" + } + } + }, + "parameters": [ + { + "name": "exchange", + "in": "query", + "required": false, + "type": "string" + } + ], + "tags": [ + "GoCryptoTrader" + ] + } + }, + "/v1/websocketgetinfo": { + "get": { + "operationId": "WebsocketGetInfo", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/gctrpcWebsocketGetInfoResponse" + } + }, + "default": { + "description": "An unexpected error response", + "schema": { + "$ref": "#/definitions/runtimeError" + } + } + }, + "parameters": [ + { + "name": "exchange", + "in": "query", + "required": false, + "type": "string" + } + ], + "tags": [ + "GoCryptoTrader" + ] + } + }, + "/v1/websocketgetsubscriptions": { + "get": { + "operationId": "WebsocketGetSubscriptions", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/gctrpcWebsocketGetSubscriptionsResponse" + } + }, + "default": { + "description": "An unexpected error response", + "schema": { + "$ref": "#/definitions/runtimeError" + } + } + }, + "parameters": [ + { + "name": "exchange", + "in": "query", + "required": false, + "type": "string" + } + ], + "tags": [ + "GoCryptoTrader" + ] + } + }, + "/v1/websocketsetenabled": { + "get": { + "operationId": "WebsocketSetEnabled", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/gctrpcGenericResponse" + } + }, + "default": { + "description": "An unexpected error response", + "schema": { + "$ref": "#/definitions/runtimeError" + } + } + }, + "parameters": [ + { + "name": "exchange", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "enable", + "in": "query", + "required": false, + "type": "boolean", + "format": "boolean" + } + ], + "tags": [ + "GoCryptoTrader" + ] + } + }, + "/v1/websocketsetproxy": { + "get": { + "operationId": "WebsocketSetProxy", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/gctrpcGenericResponse" + } + }, + "default": { + "description": "An unexpected error response", + "schema": { + "$ref": "#/definitions/runtimeError" + } + } + }, + "parameters": [ + { + "name": "exchange", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "proxy", + "in": "query", + "required": false, + "type": "string" + } + ], + "tags": [ + "GoCryptoTrader" + ] + } + }, + "/v1/websocketseturl": { + "get": { + "operationId": "WebsocketSetURL", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/gctrpcGenericResponse" + } + }, + "default": { + "description": "An unexpected error response", + "schema": { + "$ref": "#/definitions/runtimeError" + } + } + }, + "parameters": [ + { + "name": "exchange", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "url", + "in": "query", + "required": false, + "type": "string" + } + ], + "tags": [ + "GoCryptoTrader" + ] + } + }, "/v1/whalebomb": { "post": { - "operationId": "GoCryptoTrader_WhaleBomb", + "operationId": "WhaleBomb", "responses": { "200": { "description": "A successful response.", @@ -1809,7 +2086,7 @@ }, "/v1/withdrawaleventbydate": { "post": { - "operationId": "GoCryptoTrader_WithdrawalEventsByDate", + "operationId": "WithdrawalEventsByDate", "responses": { "200": { "description": "A successful response.", @@ -1841,7 +2118,7 @@ }, "/v1/withdrawaleventbyid": { "post": { - "operationId": "GoCryptoTrader_WithdrawalEventsByExchange", + "operationId": "WithdrawalEventsByExchange", "responses": { "200": { "description": "A successful response.", @@ -1873,7 +2150,7 @@ }, "/v1/withdrawfiatfunds": { "post": { - "operationId": "GoCryptoTrader_WithdrawFiatFunds", + "operationId": "WithdrawFiatFunds", "responses": { "200": { "description": "A successful response.", @@ -1905,7 +2182,7 @@ }, "/v1/withdrawithdrawcryptofundswfiatfunds": { "post": { - "operationId": "GoCryptoTrader_WithdrawCryptocurrencyFunds", + "operationId": "WithdrawCryptocurrencyFunds", "responses": { "200": { "description": "A successful response.", @@ -2038,9 +2315,6 @@ } } }, - "gctrpcAddPortfolioAddressResponse": { - "type": "object" - }, "gctrpcAuditEvent": { "type": "object", "properties": { @@ -2103,9 +2377,6 @@ } } }, - "gctrpcCancelOrderResponse": { - "type": "object" - }, "gctrpcCandle": { "type": "object", "properties": { @@ -2220,20 +2491,6 @@ } } }, - "gctrpcExchangePairRequest": { - "type": "object", - "properties": { - "exchange": { - "type": "string" - }, - "asset_type": { - "type": "string" - }, - "pair": { - "$ref": "#/definitions/gctrpcCurrencyPair" - } - } - }, "gctrpcFiatWithdrawalEvent": { "type": "object", "properties": { @@ -2335,17 +2592,6 @@ } } }, - "gctrpcGCTScriptGenericResponse": { - "type": "object", - "properties": { - "status": { - "type": "string" - }, - "data": { - "type": "string" - } - } - }, "gctrpcGCTScriptListAllRequest": { "type": "object" }, @@ -2427,11 +2673,16 @@ } } }, - "gctrpcGenericExchangeNameResponse": { - "type": "object" - }, - "gctrpcGenericSubsystemResponse": { - "type": "object" + "gctrpcGenericResponse": { + "type": "object", + "properties": { + "status": { + "type": "string" + }, + "data": { + "type": "string" + } + } }, "gctrpcGetAccountInfoResponse": { "type": "object", @@ -2544,6 +2795,14 @@ } } }, + "gctrpcGetExchangeAssetsResponse": { + "type": "object", + "properties": { + "assets": { + "type": "string" + } + } + }, "gctrpcGetExchangeInfoResponse": { "type": "object", "properties": { @@ -3113,9 +3372,6 @@ } } }, - "gctrpcRemoveEventResponse": { - "type": "object" - }, "gctrpcRemovePortfolioAddressRequest": { "type": "object", "properties": { @@ -3130,8 +3386,26 @@ } } }, - "gctrpcRemovePortfolioAddressResponse": { - "type": "object" + "gctrpcSetExchangePairRequest": { + "type": "object", + "properties": { + "exchange": { + "type": "string" + }, + "asset_type": { + "type": "string" + }, + "pairs": { + "type": "array", + "items": { + "$ref": "#/definitions/gctrpcCurrencyPair" + } + }, + "enable": { + "type": "boolean", + "format": "boolean" + } + } }, "gctrpcSetLoggerDetailsRequest": { "type": "object", @@ -3322,6 +3596,67 @@ } } }, + "gctrpcWebsocketGetInfoResponse": { + "type": "object", + "properties": { + "exchange": { + "type": "string" + }, + "supported": { + "type": "boolean", + "format": "boolean" + }, + "enabled": { + "type": "boolean", + "format": "boolean" + }, + "authenticated_supported": { + "type": "boolean", + "format": "boolean" + }, + "authenticated": { + "type": "boolean", + "format": "boolean" + }, + "running_url": { + "type": "string" + }, + "proxy_address": { + "type": "string" + } + } + }, + "gctrpcWebsocketGetSubscriptionsResponse": { + "type": "object", + "properties": { + "exchange": { + "type": "string" + }, + "subscriptions": { + "type": "array", + "items": { + "$ref": "#/definitions/gctrpcWebsocketSubscription" + } + } + } + }, + "gctrpcWebsocketSubscription": { + "type": "object", + "properties": { + "channel": { + "type": "string" + }, + "currency": { + "type": "string" + }, + "asset": { + "type": "string" + }, + "params": { + "type": "string" + } + } + }, "gctrpcWhaleBombRequest": { "type": "object", "properties": { diff --git a/gctscript/README.md b/gctscript/README.md index 53ea2d41..e0e383cc 100644 --- a/gctscript/README.md +++ b/gctscript/README.md @@ -78,8 +78,8 @@ With an example configuration being: ``` - Start/Execute: ```shell script - gctcli gctscript execute - gctcli gctscript execute "timer.gct" "~/gctscript" + gctcli script execute + gctcli script execute "timer.gct" "~/gctscript" { "status": "ok", @@ -88,8 +88,8 @@ With an example configuration being: ``` - Stop: ```shell script - gctcli gctscript stop - gctcli gctscript stop 821bd73e-02b1-4974-9463-874cb49f130d + gctcli script stop + gctcli script stop 821bd73e-02b1-4974-9463-874cb49f130d { "status": "ok", @@ -98,7 +98,7 @@ With an example configuration being: ``` - Status: ```shell script - gctcli gctscript status + gctcli script status { "status": "ok", @@ -113,8 +113,8 @@ With an example configuration being: ``` - Read file: ```shell script - gctcli gctscript read - gctcli gctscript read "timer.gct" + gctcli script read + gctcli script read "timer.gct" { "status": "ok", @@ -127,8 +127,8 @@ With an example configuration being: ``` - Query running script: ```shell script - gctcli gctscript query - gctcli gctscript query 821bd73e-02b1-4974-9463-874cb49f130d + gctcli script query + gctcli script query 821bd73e-02b1-4974-9463-874cb49f130d { "status": "ok", "script": { @@ -143,7 +143,7 @@ With an example configuration being: ``` - Add script to autoload: ```shell script - gctcli gctscript autoload add timer + gctcli script autoload add timer { "status": "success", "data": "script timer added to autoload list" @@ -151,7 +151,7 @@ With an example configuration being: ``` - Remove script from autoload: ```shell script - gctcli gctscript autoload remove timer + gctcli script autoload remove timer { "status": "success", "data": "script timer removed from autoload list" diff --git a/gctscript/modules/gct/exchange.go b/gctscript/modules/gct/exchange.go index 5b2d5d4d..c108f30c 100644 --- a/gctscript/modules/gct/exchange.go +++ b/gctscript/modules/gct/exchange.go @@ -54,10 +54,13 @@ func ExchangeOrderbook(args ...objects.Object) (objects.Object, error) { return nil, fmt.Errorf(ErrParameterConvertFailed, assetTypeParam) } - pairs := currency.NewPairDelimiter(currencyPair, delimiter) + pair, err := currency.NewPairDelimiter(currencyPair, delimiter) + if err != nil { + return nil, err + } assetType := asset.Item(assetTypeParam) - ob, err := wrappers.GetWrapper().Orderbook(exchangeName, pairs, assetType) + ob, err := wrappers.GetWrapper().Orderbook(exchangeName, pair, assetType) if err != nil { return nil, err } @@ -113,10 +116,14 @@ func ExchangeTicker(args ...objects.Object) (objects.Object, error) { return nil, fmt.Errorf(ErrParameterConvertFailed, assetTypeParam) } - pairs := currency.NewPairDelimiter(currencyPair, delimiter) + pair, err := currency.NewPairDelimiter(currencyPair, delimiter) + if err != nil { + return nil, err + } + assetType := asset.Item(assetTypeParam) - tx, err := wrappers.GetWrapper().Ticker(exchangeName, pairs, assetType) + tx, err := wrappers.GetWrapper().Ticker(exchangeName, pair, assetType) if err != nil { return nil, err } @@ -188,8 +195,9 @@ func ExchangePairs(args ...objects.Object) (objects.Object, error) { } r := objects.Array{} - for x := range rtnValue.Slice() { - r.Value = append(r.Value, &objects.String{Value: rtnValue.Slice()[x].String()}) + pairs := *(*[]currency.Pair)(rtnValue) + for x := range pairs { + r.Value = append(r.Value, &objects.String{Value: pairs[x].String()}) } return &r, nil } @@ -346,7 +354,10 @@ func ExchangeOrderSubmit(args ...objects.Object) (objects.Object, error) { if !ok { return nil, fmt.Errorf(ErrParameterConvertFailed, orderClientID) } - pair := currency.NewPairDelimiter(currencyPair, delimiter) + pair, err := currency.NewPairDelimiter(currencyPair, delimiter) + if err != nil { + return nil, err + } tempSubmit := &order.Submit{ Pair: pair, @@ -357,7 +368,7 @@ func ExchangeOrderSubmit(args ...objects.Object) (objects.Object, error) { ClientID: orderClientID, } - err := tempSubmit.Validate() + err = tempSubmit.Validate() if err != nil { return nil, err } @@ -550,10 +561,13 @@ func exchangeOHLCV(args ...objects.Object) (objects.Object, error) { if err != nil { return nil, err } - pairs := currency.NewPairDelimiter(currencyPair, delimiter) + pair, err := currency.NewPairDelimiter(currencyPair, delimiter) + if err != nil { + return nil, err + } assetType := asset.Item(assetTypeParam) - ret, err := wrappers.GetWrapper().OHLCV(exchangeName, pairs, assetType, startTime, endTime, kline.Interval(interval)) + ret, err := wrappers.GetWrapper().OHLCV(exchangeName, pair, assetType, startTime, endTime, kline.Interval(interval)) if err != nil { return nil, err } diff --git a/gctscript/wrappers/gct/exchange/exchange.go b/gctscript/wrappers/gct/exchange/exchange.go index 8e8ac49d..b46996e6 100644 --- a/gctscript/wrappers/gct/exchange/exchange.go +++ b/gctscript/wrappers/gct/exchange/exchange.go @@ -70,10 +70,15 @@ func (e Exchange) Pairs(exch string, enabledOnly bool, item asset.Item) (*curren return nil, err } - if enabledOnly { - return &x.CurrencyPairs.Get(item).Enabled, nil + ps, err := x.CurrencyPairs.Get(item) + if err != nil { + return nil, err } - return &x.CurrencyPairs.Get(item).Available, nil + + if enabledOnly { + return &ps.Enabled, nil + } + return &ps.Available, nil } // QueryOrder returns details of a valid exchange order diff --git a/gctscript/wrappers/gct/exchange/exchange_test.go b/gctscript/wrappers/gct/exchange/exchange_test.go index c846b33c..16a4cb69 100644 --- a/gctscript/wrappers/gct/exchange/exchange_test.go +++ b/gctscript/wrappers/gct/exchange/exchange_test.go @@ -89,8 +89,11 @@ func TestExchange_IsEnabled(t *testing.T) { func TestExchange_Ticker(t *testing.T) { t.Parallel() - c := currency.NewPairDelimiter(pairs, delimiter) - _, err := exchangeTest.Ticker(exchName, c, assetType) + c, err := currency.NewPairDelimiter(pairs, delimiter) + if err != nil { + t.Fatal(err) + } + _, err = exchangeTest.Ticker(exchName, c, assetType) if err != nil { t.Fatal(err) } @@ -98,8 +101,11 @@ func TestExchange_Ticker(t *testing.T) { func TestExchange_Orderbook(t *testing.T) { t.Parallel() - c := currency.NewPairDelimiter(pairs, delimiter) - _, err := exchangeTest.Orderbook(exchName, c, assetType) + c, err := currency.NewPairDelimiter(pairs, delimiter) + if err != nil { + t.Fatal(err) + } + _, err = exchangeTest.Orderbook(exchName, c, assetType) if err != nil { t.Fatal(err) } @@ -141,8 +147,13 @@ func TestExchange_SubmitOrder(t *testing.T) { if !configureExchangeKeys() { t.Skip("no exchange configured test skipped") } + + c, err := currency.NewPairDelimiter(pairs, delimiter) + if err != nil { + t.Fatal(err) + } tempOrder := &order.Submit{ - Pair: currency.NewPairDelimiter(pairs, delimiter), + Pair: c, Type: orderType, Side: orderSide, TriggerPrice: 0, @@ -152,7 +163,7 @@ func TestExchange_SubmitOrder(t *testing.T) { ClientID: orderClientID, Exchange: exchName, } - _, err := exchangeTest.SubmitOrder(tempOrder) + _, err = exchangeTest.SubmitOrder(tempOrder) if err != nil { t.Fatal(err) } diff --git a/gctscript/wrappers/validator/validator.go b/gctscript/wrappers/validator/validator.go index c8f63fb8..228947b2 100644 --- a/gctscript/wrappers/validator/validator.go +++ b/gctscript/wrappers/validator/validator.go @@ -96,7 +96,12 @@ func (w Wrapper) Pairs(exch string, _ bool, _ asset.Item) (*currency.Pairs, erro return nil, errTestFailed } - pairs := currency.NewPairsFromStrings([]string{"btc_usd", "btc_aud", "btc_ltc"}) + pairs, err := currency.NewPairsFromStrings([]string{"btc_usd", + "btc_aud", + "btc_ltc"}) + if err != nil { + return nil, err + } return &pairs, nil } @@ -105,11 +110,17 @@ func (w Wrapper) QueryOrder(exch, _ string) (*order.Detail, error) { if exch == exchError.String() { return nil, errTestFailed } + + pair, err := currency.NewPairFromString("BTCAUD") + if err != nil { + return nil, err + } + return &order.Detail{ Exchange: exch, AccountID: "hello", ID: "1", - Pair: currency.NewPairFromString("BTCAUD"), + Pair: pair, Side: "ask", Type: "limit", Date: time.Now(), @@ -121,7 +132,6 @@ func (w Wrapper) QueryOrder(exch, _ string) (*order.Detail, error) { Fee: 0, Trades: []order.TradeHistory{ { - Timestamp: time.Now(), TID: "", Price: 1, Amount: 2, @@ -179,12 +189,11 @@ func (w Wrapper) AccountInformation(exch string) (account.Holdings, error) { { CurrencyName: currency.Code{ Item: ¤cy.Item{ - ID: 0, - FullName: "Bitcoin", - Symbol: "BTC", - Role: 1, - AssocChain: "", - AssocExchange: nil, + ID: 0, + FullName: "Bitcoin", + Symbol: "BTC", + Role: 1, + AssocChain: "", }, }, TotalValue: 100, diff --git a/gctscript/wrappers/validator/validator_test.go b/gctscript/wrappers/validator/validator_test.go index 679f22dc..eaa457d5 100644 --- a/gctscript/wrappers/validator/validator_test.go +++ b/gctscript/wrappers/validator/validator_test.go @@ -27,8 +27,8 @@ const ( ) var ( - currencyPair = currency.NewPairFromString("BTCAUD") - testWrapper = Wrapper{} + currencyPair, _ = currency.NewPairFromString("BTCAUD") + testWrapper = Wrapper{} ) func TestWrapper_Exchanges(t *testing.T) { @@ -101,8 +101,11 @@ func TestWrapper_DepositAddress(t *testing.T) { func TestWrapper_Orderbook(t *testing.T) { t.Parallel() - c := currency.NewPairDelimiter(pairs, delimiter) - _, err := testWrapper.Orderbook(exchName, c, assetType) + c, err := currency.NewPairDelimiter(pairs, delimiter) + if err != nil { + t.Fatal(err) + } + _, err = testWrapper.Orderbook(exchName, c, assetType) if err != nil { t.Fatal(err) } @@ -146,9 +149,12 @@ func TestWrapper_QueryOrder(t *testing.T) { func TestWrapper_SubmitOrder(t *testing.T) { t.Parallel() - + c, err := currency.NewPairDelimiter(pairs, delimiter) + if err != nil { + t.Fatal(err) + } tempOrder := &order.Submit{ - Pair: currency.NewPairDelimiter(pairs, delimiter), + Pair: c, Type: orderType, Side: orderSide, TriggerPrice: 0, @@ -158,7 +164,7 @@ func TestWrapper_SubmitOrder(t *testing.T) { ClientID: orderClientID, Exchange: "true", } - _, err := testWrapper.SubmitOrder(tempOrder) + _, err = testWrapper.SubmitOrder(tempOrder) if err != nil { t.Fatal(err) } @@ -171,8 +177,11 @@ func TestWrapper_SubmitOrder(t *testing.T) { func TestWrapper_Ticker(t *testing.T) { t.Parallel() - c := currency.NewPairDelimiter(pairs, delimiter) - _, err := testWrapper.Ticker(exchName, c, assetType) + c, err := currency.NewPairDelimiter(pairs, delimiter) + if err != nil { + t.Fatal(err) + } + _, err = testWrapper.Ticker(exchName, c, assetType) if err != nil { t.Fatal(err) } @@ -208,8 +217,11 @@ func TestWrapper_WithdrawalFiatFunds(t *testing.T) { } func TestWrapper_OHLCV(t *testing.T) { - c := currency.NewPairDelimiter(pairs, delimiter) - _, err := testWrapper.OHLCV("test", c, asset.Spot, time.Now().Add(-24*time.Hour), time.Now(), kline.OneDay) + c, err := currency.NewPairDelimiter(pairs, delimiter) + if err != nil { + t.Fatal(err) + } + _, err = testWrapper.OHLCV("test", c, asset.Spot, time.Now().Add(-24*time.Hour), time.Now(), kline.OneDay) if err != nil { t.Fatal(err) } diff --git a/go.mod b/go.mod index ae5b296e..ccbc52a2 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,12 @@ module github.com/thrasher-corp/gocryptotrader -go 1.12 +go 1.13 require ( + github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect github.com/d5/tengo/v2 v2.6.0 + github.com/friendsofgo/errors v0.9.2 // indirect + github.com/fsnotify/fsnotify v1.4.9 // indirect github.com/gofrs/uuid v3.3.0+incompatible github.com/golang/protobuf v1.4.2 github.com/google/go-querystring v1.0.0 @@ -14,8 +17,12 @@ require ( github.com/kat-co/vala v0.0.0-20170210184112-42e1d8b61f12 github.com/lib/pq v1.7.1 github.com/mattn/go-sqlite3 v1.14.0 + github.com/mitchellh/mapstructure v1.3.2 // indirect + github.com/pelletier/go-toml v1.8.0 // indirect github.com/pkg/errors v0.9.1 github.com/pquerna/otp v1.2.0 + github.com/spf13/afero v1.3.1 // indirect + github.com/spf13/cast v1.3.1 // indirect github.com/spf13/viper v1.7.0 github.com/stretchr/testify v1.5.1 // indirect github.com/thrasher-corp/gct-ta v0.0.0-20200623072738-f2b55b7f9f41 @@ -23,13 +30,17 @@ require ( github.com/thrasher-corp/sqlboiler v1.0.1-0.20191001234224-71e17f37a85e github.com/toorop/go-pusher v0.0.0-20180521062818-4521e2eb39fb github.com/urfave/cli v1.22.4 + github.com/volatiletech/inflect v0.0.1 // indirect github.com/volatiletech/null v8.0.0+incompatible + github.com/volatiletech/sqlboiler v3.7.1+incompatible // indirect golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4 golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e + golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae // indirect + golang.org/x/text v0.3.3 // indirect golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 google.golang.org/grpc v1.30.0 - google.golang.org/protobuf v1.25.0 + google.golang.org/protobuf v1.25.0 // indirect gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect - gopkg.in/yaml.v2 v2.2.7 // indirect + gopkg.in/ini.v1 v1.57.0 // indirect ) diff --git a/go.sum b/go.sum index a19e2446..1561db47 100644 --- a/go.sum +++ b/go.sum @@ -53,6 +53,8 @@ github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/d5/tengo/v2 v2.6.0 h1:D0cJtpiBzaLJ/Smv6nnUc/LIfO46oKwDx85NZtIRNRI= github.com/d5/tengo/v2 v2.6.0/go.mod h1:XRGjEs5I9jYIKTxly6HCF8oiiilk5E/RYXOZ5b0DZC8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -67,8 +69,12 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/ericlagergren/decimal v0.0.0-20180907214518-0bb163153a5d/go.mod h1:1yj25TwtUlJ+pfOu9apAVaM1RWfZGg+aFpd4hPQZekQ= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/friendsofgo/errors v0.9.2 h1:X6NYxef4efCBdwI7BgS820zFaN7Cphrmb+Pljdzjtgk= +github.com/friendsofgo/errors v0.9.2/go.mod h1:yCvFW5AkDIL9qn7suHVLiI/gH228n7PC4Pn44IGoTOI= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= @@ -108,6 +114,7 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= @@ -152,6 +159,7 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= @@ -166,6 +174,7 @@ github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -174,8 +183,6 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.7.0 h1:h93mCPfUSkaul3Ka/VG8uZdmW1uMHDGxzu0NWHuJmHY= -github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.7.1 h1:FvD5XTVTDt+KON6oIoOmHq6B6HzGuYEhuTMpEG0yuBQ= github.com/lib/pq v1.7.1/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= @@ -196,6 +203,8 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.3.2 h1:mRS76wmkOn3KkKAyXDu42V+6ebnXWIztFSYGN7GeoRg= +github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= @@ -205,10 +214,13 @@ github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FI github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.4.0 h1:u3Z1r+oOXJIkxqw34zVhyPgjBsm6X2wn21NWs/HfSeg= github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= +github.com/pelletier/go-toml v1.8.0 h1:Keo9qb7iRJs2voHvunFtuuYFsbWeOBh8/P9v/kVMFtw= +github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= @@ -248,8 +260,13 @@ github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasO github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/afero v1.3.1 h1:GPTpEAuNr98px18yNQ66JllNil98wfRZ/5Ukny8FeQA= +github.com/spf13/afero v1.3.1/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= @@ -285,10 +302,14 @@ github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA= github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/volatiletech/inflect v0.0.0-20170731032912-e7201282ae8d h1:gI4/tqP6lCY5k6Sg+4k9qSoBXmPwG+xXgMpK7jivD4M= github.com/volatiletech/inflect v0.0.0-20170731032912-e7201282ae8d/go.mod h1:jspfvgf53t5NLUT4o9L1IX0kIBNKamGq1tWc/MgWK9Q= +github.com/volatiletech/inflect v0.0.1 h1:2a6FcMQyhmPZcLa+uet3VJ8gLn/9svWhJxJYwvE8KsU= +github.com/volatiletech/inflect v0.0.1/go.mod h1:IBti31tG6phkHitLlr5j7shC5SOo//x0AjDzaJU1PLA= github.com/volatiletech/null v8.0.0+incompatible h1:7wP8m5d/gZ6kW/9GnrLtMCRre2dlEnaQ9Km5OXlK4zg= github.com/volatiletech/null v8.0.0+incompatible/go.mod h1:0wD98JzdqB+rLyZ70fN05VDbXbafIb0KU0MdVhCzmOQ= github.com/volatiletech/sqlboiler v3.5.0+incompatible h1:n160O7UQLpZVRnJY6VH5eRNkt7sQdQBZGCCZ3CUy1+g= github.com/volatiletech/sqlboiler v3.5.0+incompatible/go.mod h1:jLfDkkHWPbS2cWRLkyC20vQWaIQsASEY7gM7zSo11Yw= +github.com/volatiletech/sqlboiler v3.7.1+incompatible h1:dm9/NjDskQVwAarmpeZ2UqLn1NKE8M3WHSHBS4jw2x8= +github.com/volatiletech/sqlboiler v3.7.1+incompatible/go.mod h1:jLfDkkHWPbS2cWRLkyC20vQWaIQsASEY7gM7zSo11Yw= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= @@ -304,6 +325,7 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4 h1:QmwruyY+bKbDDL0BaglrbZABEali68eoMFhTZpCjYVA= golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -370,12 +392,17 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190927073244-c990c680b611/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae h1:Ih9Yo4hSPImZOpfGuA4bR/ORKTAbhZo2AbWNRCnevdo= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -421,6 +448,7 @@ google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBr google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884 h1:fiNLklpBwWK1mth30Hlwk+fcdBmIALlgF5iy77O37Ig= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= @@ -453,14 +481,17 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww= +gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3 h1:fvjTMHxHEw/mxHbtzPi3JCcKXQRAnQTBRo6YCJSVHKI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo= -gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/testdata/configtest.json b/testdata/configtest.json index b452a883..95cc256c 100644 --- a/testdata/configtest.json +++ b/testdata/configtest.json @@ -507,8 +507,8 @@ ], "pairs": { "spot": { - "enabled": "BTCKRW,ETHKRW,DASHKRW,LTCKRW,ETCKRW,XRPKRW,BCHKRW,XMRKRW,ZECKRW,QTUMKRW,BTGKRW,EOSKRW", - "available": "BHPKRW,STEEMKRW,GTOKRW,ETCKRW,STRATKRW,FXKRW,LTCKRW,MIXKRW,THETAKRW,QTUMKRW,ADAKRW,MCOKRW,INSKRW,RDNKRW,CONKRW,FABKRW,ETHKRW,HDACKRW,BTCKRW,POWRKRW,CMTKRW,LBAKRW,ETHOSKRW,HCKRW,ETZKRW,PPTKRW,XVGKRW,WTCKRW,TMTGKRW,LOOMKRW,WETKRW,ABTKRW,ITCKRW,GXCKRW,ORBSKRW,ICXKRW,BSVKRW,MXCKRW,MITHKRW,ZECKRW,AEKRW,SALTKRW,ARNKRW,TRUEKRW,ENJKRW,GNTKRW,PLYKRW,XMRKRW,REPKRW,ZRXKRW,BTGKRW,APISKRW,QKCKRW,LRCKRW,DVPKRW,DADKRW,CHRKRW,BCHKRW,NPXSKRW,PIVXKRW,AMOKRW,RNTKRW,XEMKRW,FCTKRW,WOMKRW,WAXPKRW,DACKRW,OMGKRW,PCMKRW,CROKRW,FNBKRW,ANKRKRW,EOSKRW,KNCKRW,OCNKRW,MTLKRW,XSRKRW,VALORKRW,TRVKRW,AUTOKRW,HYCKRW,AOAKRW,BTTKRW,MBLKRW,VETKRW,XRPKRW,ZILKRW,ELFKRW,LAMBKRW,POLYKRW,IOSTKRW,BZNTKRW,DASHKRW,CTXCKRW,BATKRW,FZZKRW,PAYKRW,BCDKRW,SNTKRW,WAVESKRW,XLMKRW,LINKKRW,OGOKRW,WICCKRW,TRXKRW" + "enabled": "BTCKRW,ETHKRW,DASHKRW,LTCKRW,ETCKRW,XRPKRW,BCHKRW,ZECKRW,QTUMKRW,BTGKRW,EOSKRW", + "available": "BHPKRW,STEEMKRW,GTOKRW,ETCKRW,STRATKRW,FXKRW,LTCKRW,MIXKRW,THETAKRW,QTUMKRW,ADAKRW,MCOKRW,INSKRW,RDNKRW,CONKRW,FABKRW,ETHKRW,HDACKRW,BTCKRW,POWRKRW,CMTKRW,LBAKRW,ETHOSKRW,HCKRW,ETZKRW,PPTKRW,XVGKRW,WTCKRW,TMTGKRW,LOOMKRW,WETKRW,ABTKRW,ITCKRW,GXCKRW,ORBSKRW,ICXKRW,BSVKRW,MXCKRW,MITHKRW,ZECKRW,AEKRW,SALTKRW,ARNKRW,TRUEKRW,ENJKRW,GNTKRW,PLYKRW,REPKRW,ZRXKRW,BTGKRW,APISKRW,QKCKRW,LRCKRW,DVPKRW,DADKRW,CHRKRW,BCHKRW,NPXSKRW,PIVXKRW,AMOKRW,RNTKRW,XEMKRW,FCTKRW,WOMKRW,WAXPKRW,DACKRW,OMGKRW,PCMKRW,CROKRW,FNBKRW,ANKRKRW,EOSKRW,KNCKRW,OCNKRW,MTLKRW,XSRKRW,VALORKRW,TRVKRW,AUTOKRW,HYCKRW,AOAKRW,BTTKRW,MBLKRW,VETKRW,XRPKRW,ZILKRW,ELFKRW,LAMBKRW,POLYKRW,IOSTKRW,BZNTKRW,DASHKRW,CTXCKRW,BATKRW,FZZKRW,PAYKRW,BCDKRW,SNTKRW,WAVESKRW,XLMKRW,LINKKRW,OGOKRW,WICCKRW,TRXKRW" } } },