From a32d16e1f54d9792c746bd71e3723690781f80d9 Mon Sep 17 00:00:00 2001 From: Ryan O'Hara-Reid Date: Fri, 31 Jan 2020 12:09:24 +1100 Subject: [PATCH] Expose auth validator functionality for wrapper (#416) * expose auth validator functionality for wrapper * Add REST validation after keys set, package account types for future syncing * Add transient error checking for initial creddemtial validation * fix command types * Addressed nits from glorious person * Amalgamate body within error when not between 2xx status, added btcmarket specific auth error check * nit fix for glorious person * Format fix * removed unused code * check transient first then validate if its an exchange specific authentication error, all others will be disregarded * Addressed glorious nits * Addressed glorious nits * Moved account processing to updateaccountinfo func and added in fetch account info * Add GRPC Account streaming (NOTE: could not complete until sync item added) * RM exchange check * Address xtda nits * RM comment code * Fix linter issues * used most recent protoc version * lbank linter issues fixed * Addressed nits and changed len check to range in for loops * Fixed timeout issue * thrasher nits addressed * add string holdings --- cmd/exchange_wrapper_coverage/main.go | 2 +- cmd/exchange_wrapper_issues/main.go | 7 +- cmd/gctcli/commands.go | 61 ++ cmd/gctcli/main.go | 1 + engine/exchange.go | 75 +- engine/exchange_test.go | 8 +- engine/helpers.go | 70 +- engine/helpers_test.go | 66 +- engine/restful_types.go | 4 +- engine/rpcserver.go | 84 +- exchanges/account/account.go | 86 ++ exchanges/account/account_test.go | 129 +++ exchanges/account/account_types.go | 47 ++ exchanges/alphapoint/alphapoint_test.go | 2 +- exchanges/alphapoint/alphapoint_wrapper.go | 50 +- exchanges/binance/binance_test.go | 2 +- exchanges/binance/binance_wrapper.go | 47 +- exchanges/bitfinex/bitfinex_test.go | 2 +- exchanges/bitfinex/bitfinex_wrapper.go | 33 +- exchanges/bitflyer/bitflyer_wrapper.go | 24 +- exchanges/bithumb/bithumb_test.go | 4 +- exchanges/bithumb/bithumb_wrapper.go | 35 +- exchanges/bitmex/bitmex_test.go | 4 +- exchanges/bitmex/bitmex_wrapper.go | 35 +- exchanges/bitstamp/bitstamp_wrapper.go | 35 +- exchanges/bittrex/bittrex_wrapper.go | 37 +- exchanges/btcmarkets/btcmarkets_test.go | 10 +- exchanges/btcmarkets/btcmarkets_wrapper.go | 52 +- exchanges/btse/btse_wrapper.go | 36 +- exchanges/coinbasepro/coinbasepro_wrapper.go | 37 +- exchanges/coinbene/coinbene_test.go | 2 +- exchanges/coinbene/coinbene_wrapper.go | 46 +- exchanges/coinut/coinut_test.go | 4 +- exchanges/coinut/coinut_wrapper.go | 33 +- exchanges/exchange.go | 50 +- exchanges/exchange_types.go | 20 - exchanges/exmo/exmo_wrapper.go | 35 +- exchanges/gateio/gateio_test.go | 4 +- exchanges/gateio/gateio_wrapper.go | 42 +- exchanges/gemini/gemini_websocket.go | 2 +- exchanges/gemini/gemini_wrapper.go | 37 +- exchanges/hitbtc/hitbtc_wrapper.go | 40 +- exchanges/huobi/huobi_test.go | 4 +- exchanges/huobi/huobi_wrapper.go | 44 +- exchanges/interfaces.go | 5 +- exchanges/itbit/itbit_test.go | 2 +- exchanges/itbit/itbit_wrapper.go | 35 +- exchanges/kraken/kraken_test.go | 4 +- exchanges/kraken/kraken_wrapper.go | 35 +- exchanges/lakebtc/lakebtc_websocket.go | 6 +- exchanges/lakebtc/lakebtc_wrapper.go | 35 +- exchanges/lbank/lbank_test.go | 2 +- exchanges/lbank/lbank_wrapper.go | 53 +- .../localbitcoins/localbitcoins_wrapper.go | 36 +- exchanges/okcoin/okcoin_test.go | 3 +- exchanges/okex/okex_test.go | 2 +- exchanges/okgroup/okgroup_wrapper.go | 58 +- exchanges/order/order_test.go | 6 +- exchanges/poloniex/poloniex_wrapper.go | 35 +- exchanges/request/request.go | 13 +- exchanges/websocket/wshandler/wshandler.go | 4 +- .../websocket/wshandler/wshandler_test.go | 4 +- .../websocket/wsorderbook/wsorderbook.go | 2 +- .../websocket/wsorderbook/wsorderbook_test.go | 12 +- exchanges/yobit/yobit_test.go | 2 +- exchanges/yobit/yobit_wrapper.go | 35 +- exchanges/zb/zb_test.go | 4 +- exchanges/zb/zb_wrapper.go | 35 +- gctrpc/rpc.pb.go | 760 ++++++++++-------- gctrpc/rpc.pb.gw.go | 59 ++ gctrpc/rpc.proto | 8 +- gctrpc/rpc.swagger.json | 103 ++- gctscript/modules/wrapper_types.go | 23 +- gctscript/wrappers/gct/exchange/exchange.go | 24 +- gctscript/wrappers/validator/validator.go | 12 +- 75 files changed, 2009 insertions(+), 856 deletions(-) create mode 100644 exchanges/account/account.go create mode 100644 exchanges/account/account_test.go create mode 100644 exchanges/account/account_types.go diff --git a/cmd/exchange_wrapper_coverage/main.go b/cmd/exchange_wrapper_coverage/main.go index f2d08f37e9d..36f41b55e0d 100644 --- a/cmd/exchange_wrapper_coverage/main.go +++ b/cmd/exchange_wrapper_coverage/main.go @@ -111,7 +111,7 @@ func testWrappers(e exchange.IBotExchange) []string { funcs = append(funcs, "UpdateTradablePairs") } - _, err = e.GetAccountInfo() + _, err = e.FetchAccountInfo() if err == common.ErrNotYetImplemented { funcs = append(funcs, "GetAccountInfo") } diff --git a/cmd/exchange_wrapper_issues/main.go b/cmd/exchange_wrapper_issues/main.go index 76d5661075f..52a1b99a0d7 100644 --- a/cmd/exchange_wrapper_issues/main.go +++ b/cmd/exchange_wrapper_issues/main.go @@ -18,6 +18,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/engine" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/account" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" @@ -398,15 +399,15 @@ func testWrappers(e exchange.IBotExchange, base *exchange.Base, config *Config) }) } - var r7 exchange.AccountInfo - r7, err = e.GetAccountInfo() + var r7 account.Holdings + r7, err = e.FetchAccountInfo() msg = "" if err != nil { msg = err.Error() responseContainer.ErrorCount++ } responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{ - Function: "GetAccountInfo", + Function: "FetchAccountInfo", Error: msg, Response: jsonifyInterface([]interface{}{r7}), }) diff --git a/cmd/gctcli/commands.go b/cmd/gctcli/commands.go index 94989487eff..cc5941f8a97 100644 --- a/cmd/gctcli/commands.go +++ b/cmd/gctcli/commands.go @@ -770,6 +770,67 @@ func getAccountInfo(c *cli.Context) error { return nil } +var getAccountInfoStreamCommand = cli.Command{ + Name: "getaccountinfostream", + Usage: "gets the account info stream for a specific exchange", + ArgsUsage: "", + Action: getAccountInfoStream, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "exchange", + Usage: "the exchange to get the account info stream from", + }, + }, +} + +func getAccountInfoStream(c *cli.Context) error { + if c.NArg() == 0 && c.NumFlags() == 0 { + cli.ShowCommandHelp(c, "getaccountinfostream") + return nil + } + + var exchangeName string + + if c.IsSet("exchange") { + exchangeName = c.String("exchange") + } else { + exchangeName = c.Args().First() + } + + if !validExchange(exchangeName) { + return errInvalidExchange + } + + conn, err := setupClient() + if err != nil { + return err + } + defer conn.Close() + + client := gctrpc.NewGoCryptoTraderClient(conn) + result, err := client.GetAccountInfoStream(context.Background(), + &gctrpc.GetAccountInfoRequest{Exchange: exchangeName}) + if err != nil { + return err + } + + for { + resp, err := result.Recv() + if err != nil { + return err + } + + err = clearScreen() + if err != nil { + return err + } + + fmt.Printf("Account balance stream for %s:\n\n", exchangeName) + + fmt.Printf("%+v", resp) + } +} + var getConfigCommand = cli.Command{ Name: "getconfig", Usage: "gets the config", diff --git a/cmd/gctcli/main.go b/cmd/gctcli/main.go index a353c5c15a1..0fff7ca961d 100644 --- a/cmd/gctcli/main.go +++ b/cmd/gctcli/main.go @@ -102,6 +102,7 @@ func main() { getOrderbookCommand, getOrderbooksCommand, getAccountInfoCommand, + getAccountInfoStreamCommand, getConfigCommand, getPortfolioCommand, getPortfolioSummaryCommand, diff --git a/engine/exchange.go b/engine/exchange.go index bf4dcde4f64..823efad31c4 100644 --- a/engine/exchange.go +++ b/engine/exchange.go @@ -59,17 +59,6 @@ func dryrunParamInteraction(param string) { } } -// CheckExchangeExists returns true whether or not an exchange has already -// been loaded -func CheckExchangeExists(exchName string) bool { - for x := range Bot.Exchanges { - if strings.EqualFold(Bot.Exchanges[x].GetName(), exchName) { - return true - } - } - return false -} - // GetExchangeByName returns an exchange given an exchange name func GetExchangeByName(exchName string) exchange.IBotExchange { for x := range Bot.Exchanges { @@ -86,7 +75,8 @@ func ReloadExchange(name string) error { return ErrNoExchangesLoaded } - if !CheckExchangeExists(name) { + e := GetExchangeByName(name) + if e == nil { return ErrExchangeNotFound } @@ -95,7 +85,6 @@ func ReloadExchange(name string) error { return err } - e := GetExchangeByName(name) e.Setup(exchCfg) log.Debugf(log.ExchangeSys, "%s exchange reloaded successfully.\n", name) return nil @@ -107,7 +96,7 @@ func UnloadExchange(name string) error { return ErrNoExchangesLoaded } - if !CheckExchangeExists(name) { + if GetExchangeByName(name) == nil { return ErrExchangeNotFound } @@ -139,7 +128,7 @@ func LoadExchange(name string, useWG bool, wg *sync.WaitGroup) error { var exch exchange.IBotExchange if len(Bot.Exchanges) > 0 { - if CheckExchangeExists(name) { + if GetExchangeByName(name) != nil { return ErrExchangeAlreadyLoaded } } @@ -288,55 +277,65 @@ func LoadExchange(name string, useWG bool, wg *sync.WaitGroup) error { Bot.Exchanges = append(Bot.Exchanges, exch) + base := exch.GetBase() + if base.API.AuthenticatedSupport || + base.API.AuthenticatedWebsocketSupport { + err = exch.ValidateCredentials() + if err != nil { + log.Warnf(log.ExchangeSys, + "%s: Cannot validate credentials, authenticated support has been disabled, Error: %s\n", + base.Name, + err) + base.API.AuthenticatedSupport = false + base.API.AuthenticatedWebsocketSupport = false + exchCfg.API.AuthenticatedSupport = false + exchCfg.API.AuthenticatedWebsocketSupport = false + } + } + if useWG { exch.Start(wg) } else { - wg := sync.WaitGroup{} - exch.Start(&wg) - wg.Wait() + tempWG := sync.WaitGroup{} + exch.Start(&tempWG) + tempWG.Wait() } + return nil } // SetupExchanges sets up the exchanges used by the Bot func SetupExchanges() { var wg sync.WaitGroup - exchanges := Bot.Config.GetAllExchangeConfigs() - for x := range exchanges { - exch := exchanges[x] - if CheckExchangeExists(exch.Name) { - e := GetExchangeByName(exch.Name) - if e == nil { - log.Errorln(log.ExchangeSys, ErrExchangeNotFound) - continue - } - - err := ReloadExchange(exch.Name) + configs := Bot.Config.GetAllExchangeConfigs() + for x := range configs { + if e := GetExchangeByName(configs[x].Name); e != nil { + err := ReloadExchange(configs[x].Name) if err != nil { - log.Errorf(log.ExchangeSys, "ReloadExchange %s failed: %s\n", exch.Name, err) + log.Errorf(log.ExchangeSys, "ReloadExchange %s failed: %s\n", configs[x].Name, err) continue } if !e.IsEnabled() { - UnloadExchange(exch.Name) + UnloadExchange(configs[x].Name) continue } return } - if !exch.Enabled && !Bot.Settings.EnableAllExchanges { - log.Debugf(log.ExchangeSys, "%s: Exchange support: Disabled\n", exch.Name) + if !configs[x].Enabled && !Bot.Settings.EnableAllExchanges { + log.Debugf(log.ExchangeSys, "%s: Exchange support: Disabled\n", configs[x].Name) continue } - err := LoadExchange(exch.Name, true, &wg) + err := LoadExchange(configs[x].Name, true, &wg) if err != nil { - log.Errorf(log.ExchangeSys, "LoadExchange %s failed: %s\n", exch.Name, err) + log.Errorf(log.ExchangeSys, "LoadExchange %s failed: %s\n", configs[x].Name, err) continue } log.Debugf(log.ExchangeSys, "%s: Exchange support: Enabled (Authenticated API support: %s - Verbose mode: %s).\n", - exch.Name, - common.IsEnabled(exch.API.AuthenticatedSupport), - common.IsEnabled(exch.Verbose), + configs[x].Name, + common.IsEnabled(configs[x].API.AuthenticatedSupport), + common.IsEnabled(configs[x].Verbose), ) } wg.Wait() diff --git a/engine/exchange_test.go b/engine/exchange_test.go index 18f08032f66..2f63d4d45b6 100644 --- a/engine/exchange_test.go +++ b/engine/exchange_test.go @@ -21,7 +21,7 @@ func SetupTest(t *testing.T) { testSetup = true } - if CheckExchangeExists(testExchange) { + if GetExchangeByName(testExchange) != nil { return } err := LoadExchange(testExchange, false, nil) @@ -31,7 +31,7 @@ func SetupTest(t *testing.T) { } func CleanupTest(t *testing.T) { - if !CheckExchangeExists(testExchange) { + if GetExchangeByName(testExchange) == nil { return } @@ -45,11 +45,11 @@ func CleanupTest(t *testing.T) { func TestCheckExchangeExists(t *testing.T) { SetupTest(t) - if !CheckExchangeExists(testExchange) { + if GetExchangeByName(testExchange) == nil { t.Errorf("TestGetExchangeExists: Unable to find exchange") } - if CheckExchangeExists("Asdsad") { + if GetExchangeByName("Asdsad") != nil { t.Errorf("TestGetExchangeExists: Non-existent exchange found") } diff --git a/engine/helpers.go b/engine/helpers.go index 93387fbfb7d..edaf3a0581a 100644 --- a/engine/helpers.go +++ b/engine/helpers.go @@ -22,6 +22,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/dispatch" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/account" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/stats" @@ -447,18 +448,21 @@ func GetSpecificTicker(p currency.Pair, exchangeName string, assetType asset.Ite // GetCollatedExchangeAccountInfoByCoin collates individual exchange account // information and turns into into a map string of // exchange.AccountCurrencyInfo -func GetCollatedExchangeAccountInfoByCoin(exchAccounts []exchange.AccountInfo) map[currency.Code]exchange.AccountCurrencyInfo { - result := make(map[currency.Code]exchange.AccountCurrencyInfo) - for _, accounts := range exchAccounts { - for _, account := range accounts.Accounts { - for _, accountCurrencyInfo := range account.Currencies { - currencyName := accountCurrencyInfo.CurrencyName - avail := accountCurrencyInfo.TotalValue - onHold := accountCurrencyInfo.Hold - +func GetCollatedExchangeAccountInfoByCoin(accounts []account.Holdings) map[currency.Code]account.Balance { + result := make(map[currency.Code]account.Balance) + for x := range accounts { + for y := range accounts[x].Accounts { + for z := range accounts[x].Accounts[y].Currencies { + currencyName := accounts[x].Accounts[y].Currencies[z].CurrencyName + avail := accounts[x].Accounts[y].Currencies[z].TotalValue + onHold := accounts[x].Accounts[y].Currencies[z].Hold info, ok := result[currencyName] if !ok { - accountInfo := exchange.AccountCurrencyInfo{CurrencyName: currencyName, Hold: onHold, TotalValue: avail} + accountInfo := account.Balance{ + CurrencyName: currencyName, + Hold: onHold, + TotalValue: avail, + } result[currencyName] = accountInfo } else { info.Hold += onHold @@ -471,16 +475,6 @@ func GetCollatedExchangeAccountInfoByCoin(exchAccounts []exchange.AccountInfo) m return result } -// GetAccountCurrencyInfoByExchangeName returns info for an exchange -func GetAccountCurrencyInfoByExchangeName(accounts []exchange.AccountInfo, exchangeName string) (exchange.AccountInfo, error) { - for i := 0; i < len(accounts); i++ { - if accounts[i].Exchange == exchangeName { - return accounts[i], nil - } - } - return exchange.AccountInfo{}, ErrExchangeNotFound -} - // 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) { @@ -504,23 +498,23 @@ func GetExchangeLowestPriceByCurrencyPair(p currency.Pair, assetType asset.Item) } // SeedExchangeAccountInfo seeds account info -func SeedExchangeAccountInfo(data []exchange.AccountInfo) { - if len(data) == 0 { +func SeedExchangeAccountInfo(accounts []account.Holdings) { + if len(accounts) == 0 { return } port := portfolio.GetPortfolio() - for _, exchangeData := range data { - exchangeName := exchangeData.Exchange - var currencies []exchange.AccountCurrencyInfo - for _, account := range exchangeData.Accounts { - for _, info := range account.Currencies { + for x := range accounts { + exchangeName := accounts[x].Exchange + var currencies []account.Balance + for y := range accounts[x].Accounts { + for z := range accounts[x].Accounts[y].Currencies { var update bool for i := range currencies { - if info.CurrencyName == currencies[i].CurrencyName { - currencies[i].Hold += info.Hold - currencies[i].TotalValue += info.TotalValue + if accounts[x].Accounts[y].Currencies[z].CurrencyName == currencies[i].CurrencyName { + currencies[i].Hold += accounts[x].Accounts[y].Currencies[z].Hold + currencies[i].TotalValue += accounts[x].Accounts[y].Currencies[z].TotalValue update = true } } @@ -529,17 +523,17 @@ func SeedExchangeAccountInfo(data []exchange.AccountInfo) { continue } - currencies = append(currencies, exchange.AccountCurrencyInfo{ - CurrencyName: info.CurrencyName, - TotalValue: info.TotalValue, - Hold: info.Hold, + currencies = append(currencies, account.Balance{ + CurrencyName: accounts[x].Accounts[y].Currencies[z].CurrencyName, + TotalValue: accounts[x].Accounts[y].Currencies[z].TotalValue, + Hold: accounts[x].Accounts[y].Currencies[z].Hold, }) } } - for _, total := range currencies { - currencyName := total.CurrencyName - total := total.TotalValue + for x := range currencies { + currencyName := currencies[x].CurrencyName + total := currencies[x].TotalValue if !port.ExchangeAddressExists(exchangeName, currencyName) { if total <= 0 { @@ -768,7 +762,7 @@ func GetAllEnabledExchangeAccountInfo() AllEnabledExchangeAccounts { } continue } - individualExchange, err := individualBot.GetAccountInfo() + individualExchange, err := individualBot.FetchAccountInfo() if err != nil { log.Errorf(log.ExchangeSys, "Error encountered retrieving exchange account info for %s. Error %s\n", individualBot.GetName(), err) diff --git a/engine/helpers_test.go b/engine/helpers_test.go index 35d6d3927d7..f4322d8935c 100644 --- a/engine/helpers_test.go +++ b/engine/helpers_test.go @@ -7,7 +7,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" - exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/account" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/stats" @@ -453,13 +453,13 @@ func TestGetSpecificTicker(t *testing.T) { func TestGetCollatedExchangeAccountInfoByCoin(t *testing.T) { SetupTestHelpers(t) - var exchangeInfo []exchange.AccountInfo - var info exchange.AccountInfo + var exchangeInfo []account.Holdings - info.Exchange = "Bitfinex" - info.Accounts = append(info.Accounts, - exchange.Account{ - Currencies: []exchange.AccountCurrencyInfo{ + var bitfinexHoldings account.Holdings + bitfinexHoldings.Exchange = "Bitfinex" + bitfinexHoldings.Accounts = append(bitfinexHoldings.Accounts, + account.SubAccount{ + Currencies: []account.Balance{ { CurrencyName: currency.BTC, TotalValue: 100, @@ -468,21 +468,27 @@ func TestGetCollatedExchangeAccountInfoByCoin(t *testing.T) { }, }) - exchangeInfo = append(exchangeInfo, info) + exchangeInfo = append(exchangeInfo, bitfinexHoldings) - info.Exchange = "Bitstamp" - info.Accounts = append(info.Accounts, - exchange.Account{ - Currencies: []exchange.AccountCurrencyInfo{ + var bitstampHoldings account.Holdings + bitstampHoldings.Exchange = "Bitstamp" + bitstampHoldings.Accounts = append(bitstampHoldings.Accounts, + account.SubAccount{ + Currencies: []account.Balance{ { CurrencyName: currency.LTC, TotalValue: 100, Hold: 0, }, + { + CurrencyName: currency.BTC, + TotalValue: 100, + Hold: 0, + }, }, }) - exchangeInfo = append(exchangeInfo, info) + exchangeInfo = append(exchangeInfo, bitstampHoldings) result := GetCollatedExchangeAccountInfoByCoin(exchangeInfo) if len(result) == 0 { @@ -504,40 +510,6 @@ func TestGetCollatedExchangeAccountInfoByCoin(t *testing.T) { } } -func TestGetAccountCurrencyInfoByExchangeName(t *testing.T) { - SetupTestHelpers(t) - - var exchangeInfo []exchange.AccountInfo - var info exchange.AccountInfo - info.Exchange = "Bitfinex" - info.Accounts = append(info.Accounts, - exchange.Account{ - Currencies: []exchange.AccountCurrencyInfo{ - { - CurrencyName: currency.BTC, - TotalValue: 100, - Hold: 0, - }, - }, - }) - - exchangeInfo = append(exchangeInfo, info) - - result, err := GetAccountCurrencyInfoByExchangeName(exchangeInfo, "Bitfinex") - if err != nil { - t.Fatal(err) - } - - if result.Exchange != "Bitfinex" { - t.Fatal("Unexepcted result") - } - - _, err = GetAccountCurrencyInfoByExchangeName(exchangeInfo, "ASDF") - if err != ErrExchangeNotFound { - t.Fatal("Unexepcted result") - } -} - func TestGetExchangeHighestPriceByCurrencyPair(t *testing.T) { SetupTestHelpers(t) diff --git a/engine/restful_types.go b/engine/restful_types.go index ba84a39d8ac..31a3ec3ee67 100644 --- a/engine/restful_types.go +++ b/engine/restful_types.go @@ -3,7 +3,7 @@ package engine import ( "net/http" - exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/account" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" ) @@ -42,5 +42,5 @@ type EnabledExchangeCurrencies struct { // AllEnabledExchangeAccounts holds all enabled accounts info type AllEnabledExchangeAccounts struct { - Data []exchange.AccountInfo `json:"data"` + Data []account.Holdings `json:"data"` } diff --git a/engine/rpcserver.go b/engine/rpcserver.go index 9d335fee667..7f300e86df4 100644 --- a/engine/rpcserver.go +++ b/engine/rpcserver.go @@ -24,6 +24,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/database/models/postgres" "github.com/thrasher-corp/gocryptotrader/database/models/sqlite3" "github.com/thrasher-corp/gocryptotrader/database/repository/audit" + "github.com/thrasher-corp/gocryptotrader/exchanges/account" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" @@ -448,7 +449,7 @@ func (s *RPCServer) GetAccountInfo(ctx context.Context, r *gctrpc.GetAccountInfo return nil, errors.New("exchange is not loaded/doesn't exist") } - resp, err := exch.GetAccountInfo() + resp, err := exch.FetchAccountInfo() if err != nil { return nil, err } @@ -470,6 +471,87 @@ func (s *RPCServer) GetAccountInfo(ctx context.Context, r *gctrpc.GetAccountInfo return &gctrpc.GetAccountInfoResponse{Exchange: r.Exchange, Accounts: accounts}, nil } +// GetAccountInfoStream streams an account balance for a specific exchange +func (s *RPCServer) GetAccountInfoStream(r *gctrpc.GetAccountInfoRequest, stream gctrpc.GoCryptoTrader_GetAccountInfoStreamServer) error { + if r.Exchange == "" { + return errors.New(errExchangeNameUnset) + } + + exch := GetExchangeByName(r.Exchange) + if exch == nil { + return errors.New("exchange is not loaded/doesn't exist") + } + + initAcc, err := exch.FetchAccountInfo() + if err != nil { + return err + } + + var accounts []*gctrpc.Account + for x := range initAcc.Accounts { + var subAccounts []*gctrpc.AccountCurrencyInfo + for y := range initAcc.Accounts[x].Currencies { + subAccounts = append(subAccounts, &gctrpc.AccountCurrencyInfo{ + Currency: initAcc.Accounts[x].Currencies[y].CurrencyName.String(), + TotalValue: initAcc.Accounts[x].Currencies[y].TotalValue, + Hold: initAcc.Accounts[x].Currencies[y].Hold, + }) + } + accounts = append(accounts, &gctrpc.Account{ + Id: initAcc.Accounts[x].ID, + Currencies: subAccounts, + }) + } + + err = stream.Send(&gctrpc.GetAccountInfoResponse{ + Exchange: initAcc.Exchange, + Accounts: accounts, + }) + if err != nil { + return err + } + + pipe, err := account.SubscribeToExchangeAccount(r.Exchange) + if err != nil { + return err + } + + defer pipe.Release() + + for { + data, ok := <-pipe.C + if !ok { + return errors.New(errDispatchSystem) + } + + acc := (*data.(*interface{})).(account.Holdings) + + var accounts []*gctrpc.Account + for x := range acc.Accounts { + var subAccounts []*gctrpc.AccountCurrencyInfo + for y := range acc.Accounts[x].Currencies { + subAccounts = append(subAccounts, &gctrpc.AccountCurrencyInfo{ + Currency: acc.Accounts[x].Currencies[y].CurrencyName.String(), + TotalValue: acc.Accounts[x].Currencies[y].TotalValue, + Hold: acc.Accounts[x].Currencies[y].Hold, + }) + } + accounts = append(accounts, &gctrpc.Account{ + Id: acc.Accounts[x].ID, + Currencies: subAccounts, + }) + } + + err := stream.Send(&gctrpc.GetAccountInfoResponse{ + Exchange: acc.Exchange, + Accounts: accounts, + }) + if err != nil { + return err + } + } +} + // GetConfig returns the bots config func (s *RPCServer) GetConfig(ctx context.Context, r *gctrpc.GetConfigRequest) (*gctrpc.GetConfigResponse, error) { return &gctrpc.GetConfigResponse{}, common.ErrNotYetImplemented diff --git a/exchanges/account/account.go b/exchanges/account/account.go new file mode 100644 index 00000000000..79d59441444 --- /dev/null +++ b/exchanges/account/account.go @@ -0,0 +1,86 @@ +package account + +import ( + "errors" + "fmt" + "strings" + + "github.com/gofrs/uuid" + "github.com/thrasher-corp/gocryptotrader/dispatch" +) + +func init() { + service = new(Service) + service.accounts = make(map[string]*Account) + service.mux = dispatch.GetNewMux() +} + +// SubscribeToExchangeAccount subcribes to your exchange account +func SubscribeToExchangeAccount(exchange string) (dispatch.Pipe, error) { + exchange = strings.ToLower(exchange) + service.Lock() + + acc, ok := service.accounts[exchange] + if !ok { + service.Unlock() + return dispatch.Pipe{}, + fmt.Errorf("%s exchange account holdings not found", exchange) + } + + defer service.Unlock() + return service.mux.Subscribe(acc.ID) +} + +// Process processes new account holdings updates +func Process(h *Holdings) error { + if h == nil { + return errors.New("cannot be nil") + } + + if h.Exchange == "" { + return errors.New("exchange name unset") + } + + return service.Update(h) +} + +// GetHoldings returns full holdings for an exchange +func GetHoldings(exch string) (Holdings, error) { + if exch == "" { + return Holdings{}, errors.New("exchange name unset") + } + + exch = strings.ToLower(exch) + + service.Lock() + h, ok := service.accounts[exch] + if !ok { + service.Unlock() + return Holdings{}, errors.New("exchange account holdings not found") + } + defer service.Unlock() + return *h.h, nil +} + +// Update updates holdings with new account info +func (s *Service) Update(a *Holdings) error { + exch := strings.ToLower(a.Exchange) + s.Lock() + acc, ok := s.accounts[exch] + if !ok { + id, err := s.mux.GetID() + if err != nil { + s.Unlock() + return err + } + + s.accounts[exch] = &Account{h: a, ID: id} + s.Unlock() + return nil + } + + acc.h.Accounts = a.Accounts + defer s.Unlock() + + return s.mux.Publish([]uuid.UUID{acc.ID}, acc.h) +} diff --git a/exchanges/account/account_test.go b/exchanges/account/account_test.go new file mode 100644 index 00000000000..85eecaa4235 --- /dev/null +++ b/exchanges/account/account_test.go @@ -0,0 +1,129 @@ +package account + +import ( + "sync" + "testing" + "time" + + "github.com/thrasher-corp/gocryptotrader/currency" + "github.com/thrasher-corp/gocryptotrader/dispatch" +) + +func TestHoldings(t *testing.T) { + err := dispatch.Start(1, 1) + if err != nil { + t.Fatal(err) + } + err = Process(nil) + if err == nil { + t.Error("error cannot be nil") + } + + err = Process(&Holdings{}) + if err == nil { + t.Error("error cannot be nil") + } + + holdings := Holdings{ + Exchange: "Test", + } + + err = Process(&holdings) + if err != nil { + t.Error(err) + } + + err = Process(&Holdings{ + Exchange: "Test", + Accounts: []SubAccount{{ + ID: "1337", + Currencies: []Balance{ + { + CurrencyName: currency.BTC, + TotalValue: 100, + Hold: 20, + }, + }, + }}, + }) + if err != nil { + t.Error(err) + } + + _, err = GetHoldings("") + if err == nil { + t.Error("error cannot be nil") + } + + _, err = GetHoldings("bla") + if err == nil { + t.Error("error cannot be nil") + } + + u, err := GetHoldings("Test") + if err != nil { + t.Error(err) + } + + if u.Accounts[0].ID != "1337" { + t.Errorf("expecting 1337 but receieved %s", u.Accounts[0].ID) + } + + if u.Accounts[0].Currencies[0].CurrencyName != currency.BTC { + t.Errorf("expecting BTC but receieved %s", + u.Accounts[0].Currencies[0].CurrencyName) + } + + if u.Accounts[0].Currencies[0].TotalValue != 100 { + t.Errorf("expecting 100 but receieved %f", + u.Accounts[0].Currencies[0].TotalValue) + } + + if u.Accounts[0].Currencies[0].Hold != 20 { + t.Errorf("expecting 20 but receieved %f", + u.Accounts[0].Currencies[0].Hold) + } + + _, err = SubscribeToExchangeAccount("nonsense") + if err == nil { + t.Fatal("error cannot be nil") + } + + p, err := SubscribeToExchangeAccount("Test") + if err != nil { + t.Fatal(err) + } + + var wg sync.WaitGroup + wg.Add(1) + go func(p dispatch.Pipe, wg *sync.WaitGroup) { + for i := 0; i < 2; i++ { + c := time.NewTimer(time.Second) + select { + case <-p.C: + case <-c.C: + } + } + + wg.Done() + }(p, &wg) + + err = Process(&Holdings{ + Exchange: "Test", + Accounts: []SubAccount{{ + ID: "1337", + Currencies: []Balance{ + { + CurrencyName: currency.BTC, + TotalValue: 100000, + Hold: 20, + }, + }, + }}, + }) + if err != nil { + t.Error(err) + } + + wg.Wait() +} diff --git a/exchanges/account/account_types.go b/exchanges/account/account_types.go new file mode 100644 index 00000000000..aa25b0397d2 --- /dev/null +++ b/exchanges/account/account_types.go @@ -0,0 +1,47 @@ +package account + +import ( + "sync" + + "github.com/gofrs/uuid" + "github.com/thrasher-corp/gocryptotrader/currency" + "github.com/thrasher-corp/gocryptotrader/dispatch" +) + +// Vars for the ticker package +var ( + service *Service +) + +// Service holds ticker information for each individual exchange +type Service struct { + accounts map[string]*Account + mux *dispatch.Mux + sync.Mutex +} + +// Account holds a stream ID and a pointer to the exchange holdings +type Account struct { + h *Holdings + ID uuid.UUID +} + +// Holdings is a generic type to hold each exchange's holdings for all enabled +// currencies +type Holdings struct { + Exchange string + Accounts []SubAccount +} + +// SubAccount defines a singular account type with asocciated currency balances +type SubAccount struct { + ID string + Currencies []Balance +} + +// Balance is a sub type to store currency name and individual totals +type Balance struct { + CurrencyName currency.Code + TotalValue float64 + Hold float64 +} diff --git a/exchanges/alphapoint/alphapoint_test.go b/exchanges/alphapoint/alphapoint_test.go index d299389720b..562ece4552a 100644 --- a/exchanges/alphapoint/alphapoint_test.go +++ b/exchanges/alphapoint/alphapoint_test.go @@ -324,7 +324,7 @@ func TestGetAccountInfo(t *testing.T) { t.Skip("API keys not set, skipping") } - _, err := a.GetAccountInfo() + _, err := a.UpdateAccountInfo() if err == nil { t.Error("GetUserInfo() Expected error") } diff --git a/exchanges/alphapoint/alphapoint_wrapper.go b/exchanges/alphapoint/alphapoint_wrapper.go index a80b68f1948..442a0a4f46a 100644 --- a/exchanges/alphapoint/alphapoint_wrapper.go +++ b/exchanges/alphapoint/alphapoint_wrapper.go @@ -9,6 +9,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/account" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" @@ -85,33 +86,49 @@ func (a *Alphapoint) UpdateTradablePairs(forceUpdate bool) error { return common.ErrFunctionNotSupported } -// GetAccountInfo retrieves balances for all enabled currencies on the +// UpdateAccountInfo retrieves balances for all enabled currencies on the // Alphapoint exchange -func (a *Alphapoint) GetAccountInfo() (exchange.AccountInfo, error) { - var response exchange.AccountInfo +func (a *Alphapoint) UpdateAccountInfo() (account.Holdings, error) { + var response account.Holdings response.Exchange = a.Name - account, err := a.GetAccountInformation() + acc, err := a.GetAccountInformation() if err != nil { return response, err } - var currencies []exchange.AccountCurrencyInfo - for i := 0; i < len(account.Currencies); i++ { - var exchangeCurrency exchange.AccountCurrencyInfo - exchangeCurrency.CurrencyName = currency.NewCode(account.Currencies[i].Name) - exchangeCurrency.TotalValue = float64(account.Currencies[i].Balance) - exchangeCurrency.Hold = float64(account.Currencies[i].Hold) + var balances []account.Balance + for i := range acc.Currencies { + var balance account.Balance + balance.CurrencyName = currency.NewCode(acc.Currencies[i].Name) + balance.TotalValue = float64(acc.Currencies[i].Balance) + balance.Hold = float64(acc.Currencies[i].Hold) - currencies = append(currencies, exchangeCurrency) + balances = append(balances, balance) } - response.Accounts = append(response.Accounts, exchange.Account{ - Currencies: currencies, + response.Accounts = append(response.Accounts, account.SubAccount{ + Currencies: balances, }) + err = account.Process(&response) + if err != nil { + return account.Holdings{}, err + } + return response, nil } +// FetchAccountInfo retrieves balances for all enabled currencies on the +// Alphapoint exchange +func (a *Alphapoint) FetchAccountInfo() (account.Holdings, error) { + acc, err := account.GetHoldings(a.Name) + if err != nil { + return a.UpdateAccountInfo() + } + + return acc, nil +} + // 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) @@ -412,3 +429,10 @@ func (a *Alphapoint) GetSubscriptions() ([]wshandler.WebsocketChannelSubscriptio func (a *Alphapoint) AuthenticateWebsocket() error { return common.ErrFunctionNotSupported } + +// ValidateCredentials validates current credentials used for wrapper +// functionality +func (a *Alphapoint) ValidateCredentials() error { + _, err := a.UpdateAccountInfo() + return a.CheckTransientError(err) +} diff --git a/exchanges/binance/binance_test.go b/exchanges/binance/binance_test.go index 6bdcc3e5888..7a87fb54a79 100644 --- a/exchanges/binance/binance_test.go +++ b/exchanges/binance/binance_test.go @@ -427,7 +427,7 @@ func TestCancelAllExchangeOrders(t *testing.T) { func TestGetAccountInfo(t *testing.T) { t.Parallel() - _, err := b.GetAccountInfo() + _, err := b.UpdateAccountInfo() switch { case areTestAPIKeysSet() && err != nil: t.Error("GetAccountInfo() error", err) diff --git a/exchanges/binance/binance_wrapper.go b/exchanges/binance/binance_wrapper.go index 182e1e0fed0..87e9fa3c104 100644 --- a/exchanges/binance/binance_wrapper.go +++ b/exchanges/binance/binance_wrapper.go @@ -11,6 +11,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/account" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" @@ -345,28 +346,28 @@ func (b *Binance) UpdateOrderbook(p currency.Pair, assetType asset.Item) (*order return orderbook.Get(b.Name, p, assetType) } -// GetAccountInfo retrieves balances for all enabled currencies for the +// UpdateAccountInfo retrieves balances for all enabled currencies for the // Bithumb exchange -func (b *Binance) GetAccountInfo() (exchange.AccountInfo, error) { - var info exchange.AccountInfo +func (b *Binance) UpdateAccountInfo() (account.Holdings, error) { + var info account.Holdings raw, err := b.GetAccount() if err != nil { return info, err } - var currencyBalance []exchange.AccountCurrencyInfo + var currencyBalance []account.Balance for i := range raw.Balances { - freeCurrency, err := strconv.ParseFloat(raw.Balances[i].Free, 64) - if err != nil { - return info, err + freeCurrency, parseErr := strconv.ParseFloat(raw.Balances[i].Free, 64) + if parseErr != nil { + return info, parseErr } - lockedCurrency, err := strconv.ParseFloat(raw.Balances[i].Locked, 64) - if err != nil { - return info, err + lockedCurrency, parseErr := strconv.ParseFloat(raw.Balances[i].Locked, 64) + if parseErr != nil { + return info, parseErr } - currencyBalance = append(currencyBalance, exchange.AccountCurrencyInfo{ + currencyBalance = append(currencyBalance, account.Balance{ CurrencyName: currency.NewCode(raw.Balances[i].Asset), TotalValue: freeCurrency + lockedCurrency, Hold: freeCurrency, @@ -374,13 +375,28 @@ func (b *Binance) GetAccountInfo() (exchange.AccountInfo, error) { } info.Exchange = b.Name - info.Accounts = append(info.Accounts, exchange.Account{ + info.Accounts = append(info.Accounts, account.SubAccount{ Currencies: currencyBalance, }) + err = account.Process(&info) + if err != nil { + return account.Holdings{}, err + } + return info, nil } +// FetchAccountInfo retrieves balances for all enabled currencies +func (b *Binance) FetchAccountInfo() (account.Holdings, error) { + acc, err := account.GetHoldings(b.Name) + if err != nil { + return b.UpdateAccountInfo() + } + + return acc, nil +} + // GetFundingHistory returns funding history, deposits and // withdrawals func (b *Binance) GetFundingHistory() ([]exchange.FundHistory, error) { @@ -636,3 +652,10 @@ func (b *Binance) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, func (b *Binance) AuthenticateWebsocket() error { return common.ErrFunctionNotSupported } + +// ValidateCredentials validates current credentials used for wrapper +// functionality +func (b *Binance) ValidateCredentials() error { + _, err := b.UpdateAccountInfo() + return b.CheckTransientError(err) +} diff --git a/exchanges/bitfinex/bitfinex_test.go b/exchanges/bitfinex/bitfinex_test.go index 99972950f4f..e37147ac8ef 100644 --- a/exchanges/bitfinex/bitfinex_test.go +++ b/exchanges/bitfinex/bitfinex_test.go @@ -277,7 +277,7 @@ func TestGetAccountInfo(t *testing.T) { } t.Parallel() - _, err := b.GetAccountInfo() + _, err := b.UpdateAccountInfo() if err != nil { t.Error("GetAccountInfo error", err) } diff --git a/exchanges/bitfinex/bitfinex_wrapper.go b/exchanges/bitfinex/bitfinex_wrapper.go index db9adc905ba..10606bcd526 100644 --- a/exchanges/bitfinex/bitfinex_wrapper.go +++ b/exchanges/bitfinex/bitfinex_wrapper.go @@ -12,6 +12,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/account" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" @@ -329,10 +330,10 @@ func (b *Bitfinex) UpdateOrderbook(p currency.Pair, assetType asset.Item) (*orde return orderbook.Get(b.Name, p, assetType) } -// GetAccountInfo retrieves balances for all enabled currencies on the +// UpdateAccountInfo retrieves balances for all enabled currencies on the // Bitfinex exchange -func (b *Bitfinex) GetAccountInfo() (exchange.AccountInfo, error) { - var response exchange.AccountInfo +func (b *Bitfinex) UpdateAccountInfo() (account.Holdings, error) { + var response account.Holdings response.Exchange = b.Name accountBalance, err := b.GetAccountBalance() @@ -340,7 +341,7 @@ func (b *Bitfinex) GetAccountInfo() (exchange.AccountInfo, error) { return response, err } - var Accounts = []exchange.Account{ + var Accounts = []account.SubAccount{ {ID: "deposit"}, {ID: "exchange"}, {ID: "trading"}, @@ -350,7 +351,7 @@ func (b *Bitfinex) GetAccountInfo() (exchange.AccountInfo, error) { for i := range Accounts { if Accounts[i].ID == accountBalance[x].Type { Accounts[i].Currencies = append(Accounts[i].Currencies, - exchange.AccountCurrencyInfo{ + account.Balance{ CurrencyName: currency.NewCode(accountBalance[x].Currency), TotalValue: accountBalance[x].Amount, Hold: accountBalance[x].Amount - accountBalance[x].Available, @@ -360,9 +361,24 @@ func (b *Bitfinex) GetAccountInfo() (exchange.AccountInfo, error) { } response.Accounts = Accounts + err = account.Process(&response) + if err != nil { + return account.Holdings{}, err + } + return response, nil } +// FetchAccountInfo retrieves balances for all enabled currencies +func (b *Bitfinex) FetchAccountInfo() (account.Holdings, error) { + acc, err := account.GetHoldings(b.Name) + if err != nil { + return b.UpdateAccountInfo() + } + + return acc, nil +} + // GetFundingHistory returns funding history, deposits and // withdrawals func (b *Bitfinex) GetFundingHistory() ([]exchange.FundHistory, error) { @@ -719,3 +735,10 @@ func (b *Bitfinex) appendOptionalDelimiter(p *currency.Pair) { p.Delimiter = ":" } } + +// ValidateCredentials validates current credentials used for wrapper +// functionality +func (b *Bitfinex) ValidateCredentials() error { + _, err := b.UpdateAccountInfo() + return b.CheckTransientError(err) +} diff --git a/exchanges/bitflyer/bitflyer_wrapper.go b/exchanges/bitflyer/bitflyer_wrapper.go index d598cde515c..aa915e7e6a4 100644 --- a/exchanges/bitflyer/bitflyer_wrapper.go +++ b/exchanges/bitflyer/bitflyer_wrapper.go @@ -9,6 +9,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/account" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" @@ -257,10 +258,20 @@ func (b *Bitflyer) UpdateOrderbook(p currency.Pair, assetType asset.Item) (*orde return orderbook.Get(b.Name, p, assetType) } -// GetAccountInfo retrieves balances for all enabled currencies on the +// UpdateAccountInfo retrieves balances for all enabled currencies on the // Bitflyer exchange -func (b *Bitflyer) GetAccountInfo() (exchange.AccountInfo, error) { - return exchange.AccountInfo{}, common.ErrNotYetImplemented +func (b *Bitflyer) UpdateAccountInfo() (account.Holdings, error) { + return account.Holdings{}, common.ErrNotYetImplemented +} + +// FetchAccountInfo retrieves balances for all enabled currencies +func (b *Bitflyer) FetchAccountInfo() (account.Holdings, error) { + acc, err := account.GetHoldings(b.Name) + if err != nil { + return b.UpdateAccountInfo() + } + + return acc, nil } // GetFundingHistory returns funding history, deposits and @@ -372,3 +383,10 @@ func (b *Bitflyer) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, func (b *Bitflyer) AuthenticateWebsocket() error { return common.ErrFunctionNotSupported } + +// ValidateCredentials validates current credentials used for wrapper +// functionality +func (b *Bitflyer) ValidateCredentials() error { + _, err := b.UpdateAccountInfo() + return b.CheckTransientError(err) +} diff --git a/exchanges/bithumb/bithumb_test.go b/exchanges/bithumb/bithumb_test.go index b1efe9bb88c..4537e53f6bf 100644 --- a/exchanges/bithumb/bithumb_test.go +++ b/exchanges/bithumb/bithumb_test.go @@ -417,12 +417,12 @@ func TestCancelAllExchangeOrders(t *testing.T) { func TestGetAccountInfo(t *testing.T) { t.Parallel() if areTestAPIKeysSet() { - _, err := b.GetAccountInfo() + _, err := b.UpdateAccountInfo() if err != nil { t.Error("Bithumb GetAccountInfo() error", err) } } else { - _, err := b.GetAccountInfo() + _, err := b.UpdateAccountInfo() if err == nil { t.Error("Bithumb GetAccountInfo() Expected error") } diff --git a/exchanges/bithumb/bithumb_wrapper.go b/exchanges/bithumb/bithumb_wrapper.go index ff9e1d103ef..7c9ffa8d9f8 100644 --- a/exchanges/bithumb/bithumb_wrapper.go +++ b/exchanges/bithumb/bithumb_wrapper.go @@ -12,6 +12,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/account" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" @@ -257,16 +258,16 @@ func (b *Bithumb) UpdateOrderbook(p currency.Pair, assetType asset.Item) (*order return orderbook.Get(b.Name, p, assetType) } -// GetAccountInfo retrieves balances for all enabled currencies for the +// UpdateAccountInfo retrieves balances for all enabled currencies for the // Bithumb exchange -func (b *Bithumb) GetAccountInfo() (exchange.AccountInfo, error) { - var info exchange.AccountInfo +func (b *Bithumb) UpdateAccountInfo() (account.Holdings, error) { + var info account.Holdings bal, err := b.GetAccountBalance("ALL") if err != nil { return info, err } - var exchangeBalances []exchange.AccountCurrencyInfo + var exchangeBalances []account.Balance for key, totalAmount := range bal.Total { hold, ok := bal.InUse[key] if !ok { @@ -274,21 +275,36 @@ func (b *Bithumb) GetAccountInfo() (exchange.AccountInfo, error) { key) } - exchangeBalances = append(exchangeBalances, exchange.AccountCurrencyInfo{ + exchangeBalances = append(exchangeBalances, account.Balance{ CurrencyName: currency.NewCode(key), TotalValue: totalAmount, Hold: hold, }) } - info.Accounts = append(info.Accounts, exchange.Account{ + info.Accounts = append(info.Accounts, account.SubAccount{ Currencies: exchangeBalances, }) info.Exchange = b.Name + err = account.Process(&info) + if err != nil { + return account.Holdings{}, err + } + return info, nil } +// FetchAccountInfo retrieves balances for all enabled currencies +func (b *Bithumb) FetchAccountInfo() (account.Holdings, error) { + acc, err := account.GetHoldings(b.Name) + if err != nil { + return b.UpdateAccountInfo() + } + + return acc, nil +} + // GetFundingHistory returns funding history, deposits and // withdrawals func (b *Bithumb) GetFundingHistory() ([]exchange.FundHistory, error) { @@ -562,3 +578,10 @@ func (b *Bithumb) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, func (b *Bithumb) AuthenticateWebsocket() error { return common.ErrFunctionNotSupported } + +// ValidateCredentials validates current credentials used for wrapper +// functionality +func (b *Bithumb) ValidateCredentials() error { + _, err := b.UpdateAccountInfo() + return b.CheckTransientError(err) +} diff --git a/exchanges/bitmex/bitmex_test.go b/exchanges/bitmex/bitmex_test.go index f43da283ccd..b201bc572b5 100644 --- a/exchanges/bitmex/bitmex_test.go +++ b/exchanges/bitmex/bitmex_test.go @@ -584,12 +584,12 @@ func TestCancelAllExchangeOrders(t *testing.T) { func TestGetAccountInfo(t *testing.T) { if areTestAPIKeysSet() { - _, err := b.GetAccountInfo() + _, err := b.UpdateAccountInfo() if err != nil { t.Error("GetAccountInfo() error", err) } } else { - _, err := b.GetAccountInfo() + _, err := b.UpdateAccountInfo() if err == nil { t.Error("GetAccountInfo() error") } diff --git a/exchanges/bitmex/bitmex_wrapper.go b/exchanges/bitmex/bitmex_wrapper.go index 36a26d5a41c..ae96c4c2031 100644 --- a/exchanges/bitmex/bitmex_wrapper.go +++ b/exchanges/bitmex/bitmex_wrapper.go @@ -11,6 +11,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/account" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" @@ -370,10 +371,10 @@ func (b *Bitmex) UpdateOrderbook(p currency.Pair, assetType asset.Item) (*orderb return orderbook.Get(b.Name, p, assetType) } -// GetAccountInfo retrieves balances for all enabled currencies for the +// UpdateAccountInfo retrieves balances for all enabled currencies for the // Bitmex exchange -func (b *Bitmex) GetAccountInfo() (exchange.AccountInfo, error) { - var info exchange.AccountInfo +func (b *Bitmex) UpdateAccountInfo() (account.Holdings, error) { + var info account.Holdings bal, err := b.GetAllUserMargin() if err != nil { @@ -381,22 +382,37 @@ func (b *Bitmex) GetAccountInfo() (exchange.AccountInfo, error) { } // Need to update to add Margin/Liquidity availibilty - var balances []exchange.AccountCurrencyInfo + var balances []account.Balance for i := range bal { - balances = append(balances, exchange.AccountCurrencyInfo{ + balances = append(balances, account.Balance{ CurrencyName: currency.NewCode(bal[i].Currency), TotalValue: float64(bal[i].WalletBalance), }) } info.Exchange = b.Name - info.Accounts = append(info.Accounts, exchange.Account{ + info.Accounts = append(info.Accounts, account.SubAccount{ Currencies: balances, }) + err = account.Process(&info) + if err != nil { + return account.Holdings{}, err + } + return info, nil } +// FetchAccountInfo retrieves balances for all enabled currencies +func (b *Bitmex) FetchAccountInfo() (account.Holdings, error) { + acc, err := account.GetHoldings(b.Name) + if err != nil { + return b.UpdateAccountInfo() + } + + return acc, nil +} + // GetFundingHistory returns funding history, deposits and // withdrawals func (b *Bitmex) GetFundingHistory() ([]exchange.FundHistory, error) { @@ -660,3 +676,10 @@ func (b *Bitmex) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, e func (b *Bitmex) AuthenticateWebsocket() error { return b.websocketSendAuth() } + +// ValidateCredentials validates current credentials used for wrapper +// functionality +func (b *Bitmex) ValidateCredentials() error { + _, err := b.UpdateAccountInfo() + return b.CheckTransientError(err) +} diff --git a/exchanges/bitstamp/bitstamp_wrapper.go b/exchanges/bitstamp/bitstamp_wrapper.go index e8d3ebe904f..59cf2751def 100644 --- a/exchanges/bitstamp/bitstamp_wrapper.go +++ b/exchanges/bitstamp/bitstamp_wrapper.go @@ -11,6 +11,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/account" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" @@ -315,31 +316,46 @@ func (b *Bitstamp) UpdateOrderbook(p currency.Pair, assetType asset.Item) (*orde return orderbook.Get(b.Name, p, assetType) } -// GetAccountInfo retrieves balances for all enabled currencies for the +// UpdateAccountInfo retrieves balances for all enabled currencies for the // Bitstamp exchange -func (b *Bitstamp) GetAccountInfo() (exchange.AccountInfo, error) { - var response exchange.AccountInfo +func (b *Bitstamp) UpdateAccountInfo() (account.Holdings, error) { + var response account.Holdings response.Exchange = b.Name accountBalance, err := b.GetBalance() if err != nil { return response, err } - var currencies []exchange.AccountCurrencyInfo + var currencies []account.Balance for k, v := range accountBalance { - currencies = append(currencies, exchange.AccountCurrencyInfo{ + currencies = append(currencies, account.Balance{ CurrencyName: currency.NewCode(k), TotalValue: v.Available, Hold: v.Reserved, }) } - response.Accounts = append(response.Accounts, exchange.Account{ + response.Accounts = append(response.Accounts, account.SubAccount{ Currencies: currencies, }) + err = account.Process(&response) + if err != nil { + return account.Holdings{}, err + } + return response, nil } +// FetchAccountInfo retrieves balances for all enabled currencies +func (b *Bitstamp) FetchAccountInfo() (account.Holdings, error) { + acc, err := account.GetHoldings(b.Name) + if err != nil { + return b.UpdateAccountInfo() + } + + return acc, nil +} + // GetFundingHistory returns funding history, deposits and // withdrawals func (b *Bitstamp) GetFundingHistory() ([]exchange.FundHistory, error) { @@ -644,3 +660,10 @@ func (b *Bitstamp) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, func (b *Bitstamp) AuthenticateWebsocket() error { return common.ErrFunctionNotSupported } + +// ValidateCredentials validates current credentials used for wrapper +// functionality +func (b *Bitstamp) ValidateCredentials() error { + _, err := b.UpdateAccountInfo() + return b.CheckTransientError(err) +} diff --git a/exchanges/bittrex/bittrex_wrapper.go b/exchanges/bittrex/bittrex_wrapper.go index b833be8135a..bde4686e4ca 100644 --- a/exchanges/bittrex/bittrex_wrapper.go +++ b/exchanges/bittrex/bittrex_wrapper.go @@ -10,6 +10,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/account" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" @@ -195,32 +196,47 @@ func (b *Bittrex) UpdateTradablePairs(forceUpdate bool) error { return b.UpdatePairs(currency.NewPairsFromStrings(pairs), asset.Spot, false, forceUpdate) } -// GetAccountInfo Retrieves balances for all enabled currencies for the +// UpdateAccountInfo Retrieves balances for all enabled currencies for the // Bittrex exchange -func (b *Bittrex) GetAccountInfo() (exchange.AccountInfo, error) { - var response exchange.AccountInfo +func (b *Bittrex) UpdateAccountInfo() (account.Holdings, error) { + var response account.Holdings response.Exchange = b.Name accountBalance, err := b.GetAccountBalances() if err != nil { return response, err } - var currencies []exchange.AccountCurrencyInfo - for i := 0; i < len(accountBalance.Result); i++ { - var exchangeCurrency exchange.AccountCurrencyInfo + var currencies []account.Balance + for i := range accountBalance.Result { + var exchangeCurrency account.Balance exchangeCurrency.CurrencyName = currency.NewCode(accountBalance.Result[i].Currency) exchangeCurrency.TotalValue = accountBalance.Result[i].Balance exchangeCurrency.Hold = accountBalance.Result[i].Balance - accountBalance.Result[i].Available currencies = append(currencies, exchangeCurrency) } - response.Accounts = append(response.Accounts, exchange.Account{ + response.Accounts = append(response.Accounts, account.SubAccount{ Currencies: currencies, }) + err = account.Process(&response) + if err != nil { + return account.Holdings{}, err + } + return response, nil } +// FetchAccountInfo retrieves balances for all enabled currencies +func (b *Bittrex) FetchAccountInfo() (account.Holdings, error) { + acc, err := account.GetHoldings(b.Name) + if err != nil { + return b.UpdateAccountInfo() + } + + return acc, nil +} + // 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) @@ -561,3 +577,10 @@ func (b *Bittrex) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, func (b *Bittrex) AuthenticateWebsocket() error { return common.ErrFunctionNotSupported } + +// ValidateCredentials validates current credentials used for wrapper +// functionality +func (b *Bittrex) ValidateCredentials() error { + _, err := b.UpdateAccountInfo() + return b.CheckTransientError(err) +} diff --git a/exchanges/btcmarkets/btcmarkets_test.go b/exchanges/btcmarkets/btcmarkets_test.go index 55437a4e021..8f5d8d12e54 100644 --- a/exchanges/btcmarkets/btcmarkets_test.go +++ b/exchanges/btcmarkets/btcmarkets_test.go @@ -1,6 +1,7 @@ package btcmarkets import ( + "fmt" "log" "os" "testing" @@ -45,6 +46,13 @@ func TestMain(m *testing.M) { log.Fatal(err) } + 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()) } @@ -443,7 +451,7 @@ func TestGetAccountInfo(t *testing.T) { if !areTestAPIKeysSet() { t.Skip("API keys required but not set, skipping test") } - _, err := b.GetAccountInfo() + _, err := b.UpdateAccountInfo() if err != nil { t.Error(err) } diff --git a/exchanges/btcmarkets/btcmarkets_wrapper.go b/exchanges/btcmarkets/btcmarkets_wrapper.go index 5d3ee75bac8..e6b58018884 100644 --- a/exchanges/btcmarkets/btcmarkets_wrapper.go +++ b/exchanges/btcmarkets/btcmarkets_wrapper.go @@ -11,6 +11,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/account" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" @@ -314,28 +315,44 @@ func (b *BTCMarkets) UpdateOrderbook(p currency.Pair, assetType asset.Item) (*or return orderbook.Get(b.Name, p, assetType) } -// GetAccountInfo retrieves balances for all enabled currencies -func (b *BTCMarkets) GetAccountInfo() (exchange.AccountInfo, error) { - var resp exchange.AccountInfo +// UpdateAccountInfo retrieves balances for all enabled currencies +func (b *BTCMarkets) UpdateAccountInfo() (account.Holdings, error) { + var resp account.Holdings data, err := b.GetAccountBalance() if err != nil { return resp, err } - var account exchange.Account + var acc account.SubAccount for key := range data { c := currency.NewCode(data[key].AssetName) hold := data[key].Locked total := data[key].Balance - account.Currencies = append(account.Currencies, - exchange.AccountCurrencyInfo{CurrencyName: c, + acc.Currencies = append(acc.Currencies, + account.Balance{CurrencyName: c, TotalValue: total, Hold: hold}) } - resp.Accounts = append(resp.Accounts, account) + resp.Accounts = append(resp.Accounts, acc) resp.Exchange = b.Name + + err = account.Process(&resp) + if err != nil { + return account.Holdings{}, err + } + return resp, nil } +// FetchAccountInfo retrieves balances for all enabled currencies +func (b *BTCMarkets) FetchAccountInfo() (account.Holdings, error) { + acc, err := account.GetHoldings(b.Name) + if err != nil { + return b.UpdateAccountInfo() + } + + return acc, nil +} + // GetFundingHistory returns funding history, deposits and // withdrawals func (b *BTCMarkets) GetFundingHistory() ([]exchange.FundHistory, error) { @@ -688,3 +705,24 @@ func (b *BTCMarkets) GetSubscriptions() ([]wshandler.WebsocketChannelSubscriptio func (b *BTCMarkets) AuthenticateWebsocket() error { return common.ErrFunctionNotSupported } + +// ValidateCredentials validates current credentials used for wrapper +// functionality +func (b *BTCMarkets) ValidateCredentials() error { + _, err := b.UpdateAccountInfo() + if err != nil { + if b.CheckTransientError(err) == nil { + return nil + } + // Check for specific auth errors; all other errors can be disregarded + // as this does not affect authenticated requests. + if strings.Contains(err.Error(), "InvalidAPIKey") || + strings.Contains(err.Error(), "InvalidAuthTimestamp") || + strings.Contains(err.Error(), "InvalidAuthSignature") || + strings.Contains(err.Error(), "InsufficientAPIPermission") { + return err + } + } + + return nil +} diff --git a/exchanges/btse/btse_wrapper.go b/exchanges/btse/btse_wrapper.go index 6575aa82fde..23661d158e0 100644 --- a/exchanges/btse/btse_wrapper.go +++ b/exchanges/btse/btse_wrapper.go @@ -12,6 +12,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/account" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" @@ -296,19 +297,19 @@ func (b *BTSE) UpdateOrderbook(p currency.Pair, assetType asset.Item) (*orderboo return orderbook.Get(b.Name, p, assetType) } -// GetAccountInfo retrieves balances for all enabled currencies for the +// UpdateAccountInfo retrieves balances for all enabled currencies for the // BTSE exchange -func (b *BTSE) GetAccountInfo() (exchange.AccountInfo, error) { - var a exchange.AccountInfo +func (b *BTSE) UpdateAccountInfo() (account.Holdings, error) { + var a account.Holdings balance, err := b.GetAccountBalance() if err != nil { return a, err } - var currencies []exchange.AccountCurrencyInfo + var currencies []account.Balance for b := range balance { currencies = append(currencies, - exchange.AccountCurrencyInfo{ + account.Balance{ CurrencyName: currency.NewCode(balance[b].Currency), TotalValue: balance[b].Total, Hold: balance[b].Available, @@ -316,14 +317,30 @@ func (b *BTSE) GetAccountInfo() (exchange.AccountInfo, error) { ) } a.Exchange = b.Name - a.Accounts = []exchange.Account{ + a.Accounts = []account.SubAccount{ { Currencies: currencies, }, } + + err = account.Process(&a) + if err != nil { + return account.Holdings{}, err + } + return a, nil } +// FetchAccountInfo retrieves balances for all enabled currencies +func (b *BTSE) FetchAccountInfo() (account.Holdings, error) { + acc, err := account.GetHoldings(b.Name) + if err != nil { + return b.UpdateAccountInfo() + } + + return acc, nil +} + // GetFundingHistory returns funding history, deposits and // withdrawals func (b *BTSE) GetFundingHistory() ([]exchange.FundHistory, error) { @@ -626,3 +643,10 @@ func (b *BTSE) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, err func (b *BTSE) AuthenticateWebsocket() error { return common.ErrFunctionNotSupported } + +// ValidateCredentials validates current credentials used for wrapper +// functionality +func (b *BTSE) ValidateCredentials() error { + _, err := b.UpdateAccountInfo() + return b.CheckTransientError(err) +} diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index cd24943054f..857a0288373 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -11,6 +11,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/account" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" @@ -254,19 +255,19 @@ func (c *CoinbasePro) UpdateTradablePairs(forceUpdate bool) error { return c.UpdatePairs(currency.NewPairsFromStrings(pairs), asset.Spot, false, forceUpdate) } -// GetAccountInfo retrieves balances for all enabled currencies for the +// UpdateAccountInfo retrieves balances for all enabled currencies for the // coinbasepro exchange -func (c *CoinbasePro) GetAccountInfo() (exchange.AccountInfo, error) { - var response exchange.AccountInfo +func (c *CoinbasePro) UpdateAccountInfo() (account.Holdings, error) { + var response account.Holdings response.Exchange = c.Name accountBalance, err := c.GetAccounts() if err != nil { return response, err } - var currencies []exchange.AccountCurrencyInfo - for i := 0; i < len(accountBalance); i++ { - var exchangeCurrency exchange.AccountCurrencyInfo + var currencies []account.Balance + for i := range accountBalance { + var exchangeCurrency account.Balance exchangeCurrency.CurrencyName = currency.NewCode(accountBalance[i].Currency) exchangeCurrency.TotalValue = accountBalance[i].Available exchangeCurrency.Hold = accountBalance[i].Hold @@ -274,13 +275,28 @@ func (c *CoinbasePro) GetAccountInfo() (exchange.AccountInfo, error) { currencies = append(currencies, exchangeCurrency) } - response.Accounts = append(response.Accounts, exchange.Account{ + response.Accounts = append(response.Accounts, account.SubAccount{ Currencies: currencies, }) + err = account.Process(&response) + if err != nil { + return account.Holdings{}, err + } + return response, nil } +// FetchAccountInfo retrieves balances for all enabled currencies +func (c *CoinbasePro) FetchAccountInfo() (account.Holdings, error) { + acc, err := account.GetHoldings(c.Name) + if err != nil { + return c.UpdateAccountInfo() + } + + return acc, nil +} + // 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()) @@ -639,3 +655,10 @@ func (c *CoinbasePro) GetHistoricCandles(p currency.Pair, rangesize, granularity } return candles, nil } + +// ValidateCredentials validates current credentials used for wrapper +// functionality +func (c *CoinbasePro) ValidateCredentials() error { + _, err := c.UpdateAccountInfo() + return c.CheckTransientError(err) +} diff --git a/exchanges/coinbene/coinbene_test.go b/exchanges/coinbene/coinbene_test.go index 79280c2c3c6..b740401ef9b 100644 --- a/exchanges/coinbene/coinbene_test.go +++ b/exchanges/coinbene/coinbene_test.go @@ -239,7 +239,7 @@ func TestGetAccountInfo(t *testing.T) { if !areTestAPIKeysSet() { t.Skip("API keys required but not set, skipping test") } - _, err := c.GetAccountInfo() + _, err := c.UpdateAccountInfo() if err != nil { t.Error(err) } diff --git a/exchanges/coinbene/coinbene_wrapper.go b/exchanges/coinbene/coinbene_wrapper.go index 4567e20d7d4..5876f2c9f41 100644 --- a/exchanges/coinbene/coinbene_wrapper.go +++ b/exchanges/coinbene/coinbene_wrapper.go @@ -10,6 +10,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/account" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" @@ -411,29 +412,47 @@ func (c *Coinbene) UpdateOrderbook(p currency.Pair, assetType asset.Item) (*orde return orderbook.Get(c.Name, p, assetType) } -// GetAccountInfo retrieves balances for all enabled currencies for the +// UpdateAccountInfo retrieves balances for all enabled currencies for the // Coinbene exchange -func (c *Coinbene) GetAccountInfo() (exchange.AccountInfo, error) { - var info exchange.AccountInfo +func (c *Coinbene) UpdateAccountInfo() (account.Holdings, error) { + var info account.Holdings balance, err := c.GetAccountBalances() if err != nil { return info, err } - var account exchange.Account + var acc account.SubAccount for key := range balance { c := currency.NewCode(balance[key].Asset) hold := balance[key].Reserved available := balance[key].Available - account.Currencies = append(account.Currencies, - exchange.AccountCurrencyInfo{CurrencyName: c, - TotalValue: hold + available, - Hold: hold}) - } - info.Accounts = append(info.Accounts, account) + acc.Currencies = append(acc.Currencies, + account.Balance{ + CurrencyName: c, + TotalValue: hold + available, + Hold: hold, + }) + } + info.Accounts = append(info.Accounts, acc) info.Exchange = c.Name + + err = account.Process(&info) + if err != nil { + return account.Holdings{}, err + } + return info, nil } +// FetchAccountInfo retrieves balances for all enabled currencies +func (c *Coinbene) FetchAccountInfo() (account.Holdings, error) { + acc, err := account.GetHoldings(c.Name) + if err != nil { + return c.UpdateAccountInfo() + } + + return acc, nil +} + // GetFundingHistory returns funding history, deposits and // withdrawals func (c *Coinbene) GetFundingHistory() ([]exchange.FundHistory, error) { @@ -717,3 +736,10 @@ func (c *Coinbene) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, func (c *Coinbene) AuthenticateWebsocket() error { return c.Login() } + +// ValidateCredentials validates current credentials used for wrapper +// functionality +func (c *Coinbene) ValidateCredentials() error { + _, err := c.UpdateAccountInfo() + return c.CheckTransientError(err) +} diff --git a/exchanges/coinut/coinut_test.go b/exchanges/coinut/coinut_test.go index c00e81743db..d71e7b5f407 100644 --- a/exchanges/coinut/coinut_test.go +++ b/exchanges/coinut/coinut_test.go @@ -360,12 +360,12 @@ func TestCancelAllExchangeOrders(t *testing.T) { func TestGetAccountInfo(t *testing.T) { if apiKey != "" || clientID != "" { - _, err := c.GetAccountInfo() + _, err := c.UpdateAccountInfo() if err != nil { t.Error("GetAccountInfo() error", err) } } else { - _, err := c.GetAccountInfo() + _, err := c.UpdateAccountInfo() if err == nil { t.Error("GetAccountInfo() Expected error") } diff --git a/exchanges/coinut/coinut_wrapper.go b/exchanges/coinut/coinut_wrapper.go index 1ca12d6d9c5..9bd46dfb7f4 100644 --- a/exchanges/coinut/coinut_wrapper.go +++ b/exchanges/coinut/coinut_wrapper.go @@ -12,6 +12,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/account" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" @@ -260,10 +261,10 @@ func (c *COINUT) UpdateTradablePairs(forceUpdate bool) error { asset.Spot, false, forceUpdate) } -// GetAccountInfo retrieves balances for all enabled currencies for the +// UpdateAccountInfo retrieves balances for all enabled currencies for the // COINUT exchange -func (c *COINUT) GetAccountInfo() (exchange.AccountInfo, error) { - var info exchange.AccountInfo +func (c *COINUT) UpdateAccountInfo() (account.Holdings, error) { + var info account.Holdings var bal *UserBalance var err error if c.Websocket.CanUseAuthenticatedWebsocketForWrapper() { @@ -280,7 +281,7 @@ func (c *COINUT) GetAccountInfo() (exchange.AccountInfo, error) { } } - var balances = []exchange.AccountCurrencyInfo{ + var balances = []account.Balance{ { CurrencyName: currency.BCH, TotalValue: bal.BCH, @@ -339,13 +340,28 @@ func (c *COINUT) GetAccountInfo() (exchange.AccountInfo, error) { }, } info.Exchange = c.Name - info.Accounts = append(info.Accounts, exchange.Account{ + info.Accounts = append(info.Accounts, account.SubAccount{ Currencies: balances, }) + err = account.Process(&info) + if err != nil { + return account.Holdings{}, err + } + return info, nil } +// FetchAccountInfo retrieves balances for all enabled currencies +func (c *COINUT) FetchAccountInfo() (account.Holdings, error) { + acc, err := account.GetHoldings(c.Name) + if err != nil { + return c.UpdateAccountInfo() + } + + return acc, nil +} + // 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) @@ -887,3 +903,10 @@ func (c *COINUT) loadInstrumentsIfNotLoaded() error { } return nil } + +// ValidateCredentials validates current credentials used for wrapper +// functionality +func (c *COINUT) ValidateCredentials() error { + _, err := c.UpdateAccountInfo() + return c.CheckTransientError(err) +} diff --git a/exchanges/exchange.go b/exchanges/exchange.go index ff8f91ff510..98ea6bfd81f 100644 --- a/exchanges/exchange.go +++ b/exchanges/exchange.go @@ -3,6 +3,7 @@ package exchange import ( "errors" "fmt" + "net" "net/http" "net/url" "strings" @@ -203,7 +204,8 @@ func (e *Base) SupportsRESTTickerBatchUpdates() bool { // SupportsAutoPairUpdates returns whether or not the exchange supports // auto currency pair updating func (e *Base) SupportsAutoPairUpdates() bool { - if e.Features.Supports.RESTCapabilities.AutoPairUpdates || e.Features.Supports.WebsocketCapabilities.AutoPairUpdates { + if e.Features.Supports.RESTCapabilities.AutoPairUpdates || + e.Features.Supports.WebsocketCapabilities.AutoPairUpdates { return true } return false @@ -423,7 +425,9 @@ func (e *Base) SetAPIKeys(apiKey, apiSecret, clientID string) { if err != nil { e.API.AuthenticatedSupport = false e.API.AuthenticatedWebsocketSupport = false - log.Warnf(log.ExchangeSys, warningBase64DecryptSecretKeyFailed, e.Name) + log.Warnf(log.ExchangeSys, + warningBase64DecryptSecretKeyFailed, + e.Name) return } e.API.Credentials.Secret = string(result) @@ -442,7 +446,9 @@ func (e *Base) SetupDefaults(exch *config.ExchangeConfig) error { e.API.AuthenticatedSupport = exch.API.AuthenticatedSupport e.API.AuthenticatedWebsocketSupport = exch.API.AuthenticatedWebsocketSupport if e.API.AuthenticatedSupport || e.API.AuthenticatedWebsocketSupport { - e.SetAPIKeys(exch.API.Credentials.Key, exch.API.Credentials.Secret, exch.API.Credentials.ClientID) + e.SetAPIKeys(exch.API.Credentials.Key, + exch.API.Credentials.Secret, + exch.API.Credentials.ClientID) } if exch.HTTPTimeout <= time.Duration(0) { @@ -475,33 +481,28 @@ func (e *Base) SetupDefaults(exch *config.ExchangeConfig) error { return nil } -// AllowAuthenticatedRequest checks to see if the required fields have been set before sending an authenticated -// API request +// AllowAuthenticatedRequest checks to see if the required fields have been set +// before sending an authenticated API request func (e *Base) AllowAuthenticatedRequest() bool { - // Skip auth check if e.SkipAuthCheck { return true } // Individual package usage, allow request if API credentials are valid a // and without needing to set AuthenticatedSupport to true - if !e.LoadedByConfig && !e.ValidateAPICredentials() { - return false - } - - // Bot usage, AuthenticatedSupport can be disabled by user if desired, so don't - // allow authenticated requests. - if (!e.API.AuthenticatedSupport && !e.API.AuthenticatedWebsocketSupport) && e.LoadedByConfig { - return false + if !e.LoadedByConfig { + return e.ValidateAPICredentials() } - // Check to see if the user has enabled AuthenticatedSupport, but has invalid - // API credentials set and loaded by config - if (e.API.AuthenticatedSupport || e.API.AuthenticatedWebsocketSupport) && e.LoadedByConfig && !e.ValidateAPICredentials() { + // Bot usage, AuthenticatedSupport can be disabled by user if desired, so + // don't allow authenticated requests. + if !e.API.AuthenticatedSupport && !e.API.AuthenticatedWebsocketSupport { return false } - return true + // Check to see if the user has enabled AuthenticatedSupport, but has + // invalid API credentials set and loaded by config + return e.ValidateAPICredentials() } // ValidateAPICredentials validates the exchanges API credentials @@ -781,3 +782,16 @@ func (e *Base) PrintEnabledPairs() { // GetBase returns the exchange base func (e *Base) GetBase() *Base { return e } + +// CheckTransientError catches transient errors and returns nil if found, used +// for validation of API credentials +func (e *Base) CheckTransientError(err error) error { + if _, ok := err.(net.Error); ok { + log.Warnf(log.ExchangeSys, + "%s net error captured, will not disable authentication %s", + e.Name, + err) + return nil + } + return err +} diff --git a/exchanges/exchange_types.go b/exchanges/exchange_types.go index 8e478678f56..f5a1ee044d4 100644 --- a/exchanges/exchange_types.go +++ b/exchanges/exchange_types.go @@ -107,26 +107,6 @@ type FeeBuilder struct { Amount float64 } -// AccountInfo is a Generic type to hold each exchange's holdings in -// all enabled currencies -type AccountInfo struct { - Exchange string - Accounts []Account -} - -// Account defines a singular account type with asocciated currencies -type Account struct { - ID string - Currencies []AccountCurrencyInfo -} - -// AccountCurrencyInfo is a sub type to store currency name and value -type AccountCurrencyInfo struct { - CurrencyName currency.Code - TotalValue float64 - Hold float64 -} - // TradeHistory holds exchange history data type TradeHistory struct { Timestamp time.Time diff --git a/exchanges/exmo/exmo_wrapper.go b/exchanges/exmo/exmo_wrapper.go index d48ae845889..e12189a74bd 100644 --- a/exchanges/exmo/exmo_wrapper.go +++ b/exchanges/exmo/exmo_wrapper.go @@ -12,6 +12,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/account" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" @@ -279,19 +280,19 @@ func (e *EXMO) UpdateOrderbook(p currency.Pair, assetType asset.Item) (*orderboo return orderbook.Get(e.Name, p, assetType) } -// GetAccountInfo retrieves balances for all enabled currencies for the +// UpdateAccountInfo retrieves balances for all enabled currencies for the // Exmo exchange -func (e *EXMO) GetAccountInfo() (exchange.AccountInfo, error) { - var response exchange.AccountInfo +func (e *EXMO) UpdateAccountInfo() (account.Holdings, error) { + var response account.Holdings response.Exchange = e.Name result, err := e.GetUserInfo() if err != nil { return response, err } - var currencies []exchange.AccountCurrencyInfo + var currencies []account.Balance for x, y := range result.Balances { - var exchangeCurrency exchange.AccountCurrencyInfo + var exchangeCurrency account.Balance exchangeCurrency.CurrencyName = currency.NewCode(x) for z, w := range result.Reserved { if z == x { @@ -304,13 +305,28 @@ func (e *EXMO) GetAccountInfo() (exchange.AccountInfo, error) { currencies = append(currencies, exchangeCurrency) } - response.Accounts = append(response.Accounts, exchange.Account{ + response.Accounts = append(response.Accounts, account.SubAccount{ Currencies: currencies, }) + err = account.Process(&response) + if err != nil { + return account.Holdings{}, err + } + return response, nil } +// FetchAccountInfo retrieves balances for all enabled currencies +func (e *EXMO) FetchAccountInfo() (account.Holdings, error) { + acc, err := account.GetHoldings(e.Name) + if err != nil { + return e.UpdateAccountInfo() + } + + return acc, nil +} + // GetFundingHistory returns funding history, deposits and // withdrawals func (e *EXMO) GetFundingHistory() ([]exchange.FundHistory, error) { @@ -542,3 +558,10 @@ func (e *EXMO) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, err func (e *EXMO) AuthenticateWebsocket() error { return common.ErrFunctionNotSupported } + +// ValidateCredentials validates current credentials used for wrapper +// functionality +func (e *EXMO) ValidateCredentials() error { + _, err := e.UpdateAccountInfo() + return e.CheckTransientError(err) +} diff --git a/exchanges/gateio/gateio_test.go b/exchanges/gateio/gateio_test.go index 3037673405f..ac92153e50d 100644 --- a/exchanges/gateio/gateio_test.go +++ b/exchanges/gateio/gateio_test.go @@ -383,12 +383,12 @@ func TestCancelAllExchangeOrders(t *testing.T) { func TestGetAccountInfo(t *testing.T) { if apiSecret == "" || apiKey == "" { - _, err := g.GetAccountInfo() + _, err := g.UpdateAccountInfo() if err == nil { t.Error("GetAccountInfo() Expected error") } } else { - _, err := g.GetAccountInfo() + _, err := g.UpdateAccountInfo() if err != nil { t.Error("GetAccountInfo() error", err) } diff --git a/exchanges/gateio/gateio_wrapper.go b/exchanges/gateio/gateio_wrapper.go index 86be3473525..12ba56d8d16 100644 --- a/exchanges/gateio/gateio_wrapper.go +++ b/exchanges/gateio/gateio_wrapper.go @@ -13,6 +13,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/account" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" @@ -306,26 +307,26 @@ func (g *Gateio) UpdateOrderbook(p currency.Pair, assetType asset.Item) (*orderb return orderbook.Get(g.Name, p, assetType) } -// GetAccountInfo retrieves balances for all enabled currencies for the +// UpdateAccountInfo retrieves balances for all enabled currencies for the // ZB exchange -func (g *Gateio) GetAccountInfo() (exchange.AccountInfo, error) { - var info exchange.AccountInfo - var balances []exchange.AccountCurrencyInfo +func (g *Gateio) UpdateAccountInfo() (account.Holdings, error) { + var info account.Holdings + var balances []account.Balance if g.Websocket.CanUseAuthenticatedWebsocketForWrapper() { resp, err := g.wsGetBalance([]string{}) if err != nil { return info, err } - var currData []exchange.AccountCurrencyInfo + var currData []account.Balance for k := range resp.Result { - currData = append(currData, exchange.AccountCurrencyInfo{ + currData = append(currData, account.Balance{ CurrencyName: currency.NewCode(k), TotalValue: resp.Result[k].Available + resp.Result[k].Freeze, Hold: resp.Result[k].Freeze, }) } - info.Accounts = append(info.Accounts, exchange.Account{ + info.Accounts = append(info.Accounts, account.SubAccount{ Currencies: currData, }) } else { @@ -342,7 +343,7 @@ func (g *Gateio) GetAccountInfo() (exchange.AccountInfo, error) { return info, err } - balances = append(balances, exchange.AccountCurrencyInfo{ + balances = append(balances, account.Balance{ CurrencyName: currency.NewCode(x), Hold: lockedF, }) @@ -368,7 +369,7 @@ func (g *Gateio) GetAccountInfo() (exchange.AccountInfo, error) { } } if !updated { - balances = append(balances, exchange.AccountCurrencyInfo{ + balances = append(balances, account.Balance{ CurrencyName: currency.NewCode(x), TotalValue: availAmount, }) @@ -378,16 +379,30 @@ func (g *Gateio) GetAccountInfo() (exchange.AccountInfo, error) { break } - info.Accounts = append(info.Accounts, exchange.Account{ + info.Accounts = append(info.Accounts, account.SubAccount{ Currencies: balances, }) } info.Exchange = g.Name + err := account.Process(&info) + if err != nil { + return account.Holdings{}, err + } return info, nil } +// FetchAccountInfo retrieves balances for all enabled currencies +func (g *Gateio) FetchAccountInfo() (account.Holdings, error) { + acc, err := account.GetHoldings(g.Name) + if err != nil { + return g.UpdateAccountInfo() + } + + return acc, nil +} + // GetFundingHistory returns funding history, deposits and // withdrawals func (g *Gateio) GetFundingHistory() ([]exchange.FundHistory, error) { @@ -693,3 +708,10 @@ func (g *Gateio) AuthenticateWebsocket() error { _, err := g.wsServerSignIn() return err } + +// ValidateCredentials validates current credentials used for wrapper +// functionality +func (g *Gateio) ValidateCredentials() error { + _, err := g.UpdateAccountInfo() + return g.CheckTransientError(err) +} diff --git a/exchanges/gemini/gemini_websocket.go b/exchanges/gemini/gemini_websocket.go index 545ef6df4c4..87e041016e6 100644 --- a/exchanges/gemini/gemini_websocket.go +++ b/exchanges/gemini/gemini_websocket.go @@ -294,7 +294,7 @@ func (g *Gemini) wsProcessUpdate(result WsMarketUpdateResponse, pair currency.Pa Exchange: g.Name} } else { var asks, bids []orderbook.Item - for i := 0; i < len(result.Events); i++ { + for i := range result.Events { if result.Events[i].Type == "trade" { g.Websocket.DataHandler <- wshandler.TradeData{ Timestamp: time.Unix(0, result.Timestamp), diff --git a/exchanges/gemini/gemini_wrapper.go b/exchanges/gemini/gemini_wrapper.go index d821ebc19a5..0baef1366f4 100644 --- a/exchanges/gemini/gemini_wrapper.go +++ b/exchanges/gemini/gemini_wrapper.go @@ -12,6 +12,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/account" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" @@ -209,32 +210,47 @@ func (g *Gemini) UpdateTradablePairs(forceUpdate bool) error { return g.UpdatePairs(currency.NewPairsFromStrings(pairs), asset.Spot, false, forceUpdate) } -// GetAccountInfo Retrieves balances for all enabled currencies for the +// UpdateAccountInfo Retrieves balances for all enabled currencies for the // Gemini exchange -func (g *Gemini) GetAccountInfo() (exchange.AccountInfo, error) { - var response exchange.AccountInfo +func (g *Gemini) UpdateAccountInfo() (account.Holdings, error) { + var response account.Holdings response.Exchange = g.Name accountBalance, err := g.GetBalances() if err != nil { return response, err } - var currencies []exchange.AccountCurrencyInfo - for i := 0; i < len(accountBalance); i++ { - var exchangeCurrency exchange.AccountCurrencyInfo + var currencies []account.Balance + for i := range accountBalance { + var exchangeCurrency account.Balance exchangeCurrency.CurrencyName = currency.NewCode(accountBalance[i].Currency) exchangeCurrency.TotalValue = accountBalance[i].Amount exchangeCurrency.Hold = accountBalance[i].Available currencies = append(currencies, exchangeCurrency) } - response.Accounts = append(response.Accounts, exchange.Account{ + response.Accounts = append(response.Accounts, account.SubAccount{ Currencies: currencies, }) + err = account.Process(&response) + if err != nil { + return account.Holdings{}, err + } + return response, nil } +// FetchAccountInfo retrieves balances for all enabled currencies +func (g *Gemini) FetchAccountInfo() (account.Holdings, error) { + acc, err := account.GetHoldings(g.Name) + if err != nil { + return g.UpdateAccountInfo() + } + + return acc, nil +} + // 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) @@ -545,3 +561,10 @@ func (g *Gemini) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, e func (g *Gemini) AuthenticateWebsocket() error { return common.ErrFunctionNotSupported } + +// ValidateCredentials validates current credentials used for wrapper +// functionality +func (g *Gemini) ValidateCredentials() error { + _, err := g.UpdateAccountInfo() + return g.CheckTransientError(err) +} diff --git a/exchanges/hitbtc/hitbtc_wrapper.go b/exchanges/hitbtc/hitbtc_wrapper.go index f28a49180bd..9f9a903c72a 100644 --- a/exchanges/hitbtc/hitbtc_wrapper.go +++ b/exchanges/hitbtc/hitbtc_wrapper.go @@ -12,6 +12,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/account" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" @@ -336,33 +337,47 @@ func (h *HitBTC) UpdateOrderbook(currencyPair currency.Pair, assetType asset.Ite return orderbook.Get(h.Name, currencyPair, assetType) } -// GetAccountInfo retrieves balances for all enabled currencies for the +// UpdateAccountInfo retrieves balances for all enabled currencies for the // HitBTC exchange -func (h *HitBTC) GetAccountInfo() (exchange.AccountInfo, error) { - var response exchange.AccountInfo +func (h *HitBTC) UpdateAccountInfo() (account.Holdings, error) { + var response account.Holdings response.Exchange = h.Name accountBalance, err := h.GetBalances() if err != nil { return response, err } - var currencies []exchange.AccountCurrencyInfo + var currencies []account.Balance for i := range accountBalance { - var exchangeCurrency exchange.AccountCurrencyInfo + var exchangeCurrency account.Balance exchangeCurrency.CurrencyName = currency.NewCode(accountBalance[i].Currency) exchangeCurrency.TotalValue = accountBalance[i].Available exchangeCurrency.Hold = accountBalance[i].Reserved currencies = append(currencies, exchangeCurrency) } - response.Accounts = append(response.Accounts, - exchange.Account{ - Currencies: currencies, - }) + response.Accounts = append(response.Accounts, account.SubAccount{ + Currencies: currencies, + }) + + err = account.Process(&response) + if err != nil { + return account.Holdings{}, err + } return response, nil } +// FetchAccountInfo retrieves balances for all enabled currencies +func (h *HitBTC) FetchAccountInfo() (account.Holdings, error) { + acc, err := account.GetHoldings(h.Name) + if err != nil { + return h.UpdateAccountInfo() + } + + return acc, nil +} + // GetFundingHistory returns funding history, deposits and // withdrawals func (h *HitBTC) GetFundingHistory() ([]exchange.FundHistory, error) { @@ -598,3 +613,10 @@ func (h *HitBTC) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, e func (h *HitBTC) AuthenticateWebsocket() error { return h.wsLogin() } + +// ValidateCredentials validates current credentials used for wrapper +// functionality +func (h *HitBTC) ValidateCredentials() error { + _, err := h.UpdateAccountInfo() + return h.CheckTransientError(err) +} diff --git a/exchanges/huobi/huobi_test.go b/exchanges/huobi/huobi_test.go index 1f6b6508341..d29b6312767 100644 --- a/exchanges/huobi/huobi_test.go +++ b/exchanges/huobi/huobi_test.go @@ -565,12 +565,12 @@ func TestCancelAllExchangeOrders(t *testing.T) { func TestGetAccountInfo(t *testing.T) { if !areTestAPIKeysSet() { - _, err := h.GetAccountInfo() + _, err := h.UpdateAccountInfo() if err == nil { t.Error("GetAccountInfo() Expected error") } } else { - _, err := h.GetAccountInfo() + _, err := h.UpdateAccountInfo() if err != nil { t.Error("GetAccountInfo() error", err) } diff --git a/exchanges/huobi/huobi_wrapper.go b/exchanges/huobi/huobi_wrapper.go index 357d5b261d9..89fb29a1626 100644 --- a/exchanges/huobi/huobi_wrapper.go +++ b/exchanges/huobi/huobi_wrapper.go @@ -12,6 +12,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/account" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" @@ -391,22 +392,22 @@ func (h *HUOBI) GetAccountID() ([]Account, error) { return acc, nil } -// GetAccountInfo retrieves balances for all enabled currencies for the +// UpdateAccountInfo retrieves balances for all enabled currencies for the // HUOBI exchange - to-do -func (h *HUOBI) GetAccountInfo() (exchange.AccountInfo, error) { - var info exchange.AccountInfo +func (h *HUOBI) UpdateAccountInfo() (account.Holdings, error) { + var info account.Holdings info.Exchange = h.Name if h.Websocket.CanUseAuthenticatedWebsocketForWrapper() { resp, err := h.wsGetAccountsList() if err != nil { return info, err } - var currencyDetails []exchange.AccountCurrencyInfo + var currencyDetails []account.Balance for i := range resp.Data { if len(resp.Data[i].List) == 0 { continue } - currData := exchange.AccountCurrencyInfo{ + currData := account.Balance{ CurrencyName: currency.NewCode(resp.Data[i].List[0].Currency), TotalValue: resp.Data[i].List[0].Balance, } @@ -415,7 +416,7 @@ func (h *HUOBI) GetAccountInfo() (exchange.AccountInfo, error) { } currencyDetails = append(currencyDetails, currData) } - var acc exchange.Account + var acc account.SubAccount acc.Currencies = currencyDetails info.Accounts = append(info.Accounts, acc) } else { @@ -424,14 +425,14 @@ func (h *HUOBI) GetAccountInfo() (exchange.AccountInfo, error) { return info, err } for i := range accounts { - var acc exchange.Account + var acc account.SubAccount acc.ID = strconv.FormatInt(accounts[i].ID, 10) balances, err := h.GetAccountBalance(acc.ID) if err != nil { return info, err } - var currencyDetails []exchange.AccountCurrencyInfo + var currencyDetails []account.Balance for j := range balances { var frozen bool if balances[j].Type == "frozen" { @@ -456,13 +457,13 @@ func (h *HUOBI) GetAccountInfo() (exchange.AccountInfo, error) { if frozen { currencyDetails = append(currencyDetails, - exchange.AccountCurrencyInfo{ + account.Balance{ CurrencyName: currency.NewCode(balances[j].Currency), Hold: balances[j].Balance, }) } else { currencyDetails = append(currencyDetails, - exchange.AccountCurrencyInfo{ + account.Balance{ CurrencyName: currency.NewCode(balances[j].Currency), TotalValue: balances[j].Balance, }) @@ -473,9 +474,25 @@ func (h *HUOBI) GetAccountInfo() (exchange.AccountInfo, error) { info.Accounts = append(info.Accounts, acc) } } + + err := account.Process(&info) + if err != nil { + return account.Holdings{}, err + } + return info, nil } +// FetchAccountInfo retrieves balances for all enabled currencies +func (h *HUOBI) FetchAccountInfo() (account.Holdings, error) { + acc, err := account.GetHoldings(h.Name) + if err != nil { + return h.UpdateAccountInfo() + } + + return acc, nil +} + // GetFundingHistory returns funding history, deposits and // withdrawals func (h *HUOBI) GetFundingHistory() ([]exchange.FundHistory, error) { @@ -845,3 +862,10 @@ func (h *HUOBI) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, er func (h *HUOBI) AuthenticateWebsocket() error { return h.wsLogin() } + +// ValidateCredentials validates current credentials used for wrapper +// functionality +func (h *HUOBI) ValidateCredentials() error { + _, err := h.UpdateAccountInfo() + return h.CheckTransientError(err) +} diff --git a/exchanges/interfaces.go b/exchanges/interfaces.go index 0a2661e64ff..70bed3d7171 100644 --- a/exchanges/interfaces.go +++ b/exchanges/interfaces.go @@ -5,6 +5,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" + "github.com/thrasher-corp/gocryptotrader/exchanges/account" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" @@ -22,6 +23,7 @@ type IBotExchange interface { GetName() string IsEnabled() bool SetEnabled(bool) + ValidateCredentials() error FetchTicker(currency currency.Pair, assetType asset.Item) (*ticker.Price, error) UpdateTicker(currency currency.Pair, assetType asset.Item) (*ticker.Price, error) FetchOrderbook(currency currency.Pair, assetType asset.Item) (*orderbook.Base, error) @@ -30,7 +32,8 @@ type IBotExchange interface { UpdateTradablePairs(forceUpdate bool) error GetEnabledPairs(assetType asset.Item) currency.Pairs GetAvailablePairs(assetType asset.Item) currency.Pairs - GetAccountInfo() (AccountInfo, error) + FetchAccountInfo() (account.Holdings, error) + UpdateAccountInfo() (account.Holdings, error) GetAuthenticatedAPISupport(endpoint uint8) bool SetPairs(pairs currency.Pairs, assetType asset.Item, enabled bool) error GetAssetTypes() asset.Items diff --git a/exchanges/itbit/itbit_test.go b/exchanges/itbit/itbit_test.go index d97301a4e1f..c02f9d1c02f 100644 --- a/exchanges/itbit/itbit_test.go +++ b/exchanges/itbit/itbit_test.go @@ -372,7 +372,7 @@ func TestCancelAllExchangeOrders(t *testing.T) { func TestGetAccountInfo(t *testing.T) { if areTestAPIKeysSet() { - _, err := i.GetAccountInfo() + _, err := i.UpdateAccountInfo() if err == nil { t.Error("GetAccountInfo() Expected error") } diff --git a/exchanges/itbit/itbit_wrapper.go b/exchanges/itbit/itbit_wrapper.go index 26d33f66232..70799c13047 100644 --- a/exchanges/itbit/itbit_wrapper.go +++ b/exchanges/itbit/itbit_wrapper.go @@ -12,6 +12,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/account" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" @@ -239,9 +240,9 @@ func (i *ItBit) UpdateOrderbook(p currency.Pair, assetType asset.Item) (*orderbo return orderbook.Get(i.Name, p, assetType) } -// GetAccountInfo retrieves balances for all enabled currencies -func (i *ItBit) GetAccountInfo() (exchange.AccountInfo, error) { - var info exchange.AccountInfo +// UpdateAccountInfo retrieves balances for all enabled currencies +func (i *ItBit) UpdateAccountInfo() (account.Holdings, error) { + var info account.Holdings info.Exchange = i.Name wallets, err := i.GetWallets(url.Values{}) @@ -267,22 +268,37 @@ func (i *ItBit) GetAccountInfo() (exchange.AccountInfo, error) { } } - var fullBalance []exchange.AccountCurrencyInfo + var fullBalance []account.Balance for key := range amounts { - fullBalance = append(fullBalance, exchange.AccountCurrencyInfo{ + fullBalance = append(fullBalance, account.Balance{ CurrencyName: currency.NewCode(key), TotalValue: amounts[key].TotalValue, Hold: amounts[key].Hold, }) } - info.Accounts = append(info.Accounts, exchange.Account{ + info.Accounts = append(info.Accounts, account.SubAccount{ Currencies: fullBalance, }) + err = account.Process(&info) + if err != nil { + return account.Holdings{}, err + } + return info, nil } +// FetchAccountInfo retrieves balances for all enabled currencies +func (i *ItBit) FetchAccountInfo() (account.Holdings, error) { + acc, err := account.GetHoldings(i.Name) + if err != nil { + return i.UpdateAccountInfo() + } + + return acc, nil +} + // GetFundingHistory returns funding history, deposits and // withdrawals func (i *ItBit) GetFundingHistory() ([]exchange.FundHistory, error) { @@ -547,3 +563,10 @@ func (i *ItBit) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, er func (i *ItBit) AuthenticateWebsocket() error { return common.ErrFunctionNotSupported } + +// ValidateCredentials validates current credentials used for wrapper +// functionality +func (i *ItBit) ValidateCredentials() error { + _, err := i.UpdateAccountInfo() + return i.CheckTransientError(err) +} diff --git a/exchanges/kraken/kraken_test.go b/exchanges/kraken/kraken_test.go index 09a7ef8a170..2f8bd9d5485 100644 --- a/exchanges/kraken/kraken_test.go +++ b/exchanges/kraken/kraken_test.go @@ -505,12 +505,12 @@ func TestCancelAllExchangeOrders(t *testing.T) { // TestGetAccountInfo wrapper test func TestGetAccountInfo(t *testing.T) { if areTestAPIKeysSet() || clientID != "" { - _, err := k.GetAccountInfo() + _, err := k.UpdateAccountInfo() if err != nil { t.Error("GetAccountInfo() error", err) } } else { - _, err := k.GetAccountInfo() + _, err := k.UpdateAccountInfo() if err == nil { t.Error("GetAccountInfo() Expected error") } diff --git a/exchanges/kraken/kraken_wrapper.go b/exchanges/kraken/kraken_wrapper.go index a1a9bfa5990..8a5b2146ba7 100644 --- a/exchanges/kraken/kraken_wrapper.go +++ b/exchanges/kraken/kraken_wrapper.go @@ -12,6 +12,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/account" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" @@ -373,10 +374,10 @@ func (k *Kraken) UpdateOrderbook(p currency.Pair, assetType asset.Item) (*orderb return orderbook.Get(k.Name, p, assetType) } -// GetAccountInfo retrieves balances for all enabled currencies for the +// UpdateAccountInfo retrieves balances for all enabled currencies for the // Kraken exchange - to-do -func (k *Kraken) GetAccountInfo() (exchange.AccountInfo, error) { - var info exchange.AccountInfo +func (k *Kraken) UpdateAccountInfo() (account.Holdings, error) { + var info account.Holdings info.Exchange = k.Name bal, err := k.GetBalance() @@ -384,21 +385,36 @@ func (k *Kraken) GetAccountInfo() (exchange.AccountInfo, error) { return info, err } - var balances []exchange.AccountCurrencyInfo + var balances []account.Balance for key := range bal { - balances = append(balances, exchange.AccountCurrencyInfo{ + balances = append(balances, account.Balance{ CurrencyName: currency.NewCode(key), TotalValue: bal[key], }) } - info.Accounts = append(info.Accounts, exchange.Account{ + info.Accounts = append(info.Accounts, account.SubAccount{ Currencies: balances, }) + err = account.Process(&info) + if err != nil { + return account.Holdings{}, err + } + return info, nil } +// FetchAccountInfo retrieves balances for all enabled currencies +func (k *Kraken) FetchAccountInfo() (account.Holdings, error) { + acc, err := account.GetHoldings(k.Name) + if err != nil { + return k.UpdateAccountInfo() + } + + return acc, nil +} + // GetFundingHistory returns funding history, deposits and // withdrawals func (k *Kraken) GetFundingHistory() ([]exchange.FundHistory, error) { @@ -705,3 +721,10 @@ func (k *Kraken) AuthenticateWebsocket() error { } return err } + +// ValidateCredentials validates current credentials used for wrapper +// functionality +func (k *Kraken) ValidateCredentials() error { + _, err := k.UpdateAccountInfo() + return k.CheckTransientError(err) +} diff --git a/exchanges/lakebtc/lakebtc_websocket.go b/exchanges/lakebtc/lakebtc_websocket.go index ba512b48759..621482aa1a4 100644 --- a/exchanges/lakebtc/lakebtc_websocket.go +++ b/exchanges/lakebtc/lakebtc_websocket.go @@ -146,7 +146,7 @@ func (l *LakeBTC) processTrades(data, channel string) error { return err } curr := l.getCurrencyFromChannel(channel) - for i := 0; i < len(tradeData.Trades); i++ { + for i := range tradeData.Trades { l.Websocket.DataHandler <- wshandler.TradeData{ Timestamp: time.Unix(tradeData.Trades[i].Date, 0), CurrencyPair: curr, @@ -177,7 +177,7 @@ func (l *LakeBTC) processOrderbook(obUpdate, channel string) error { ExchangeName: l.Name, } - for i := 0; i < len(update.Asks); i++ { + for i := range update.Asks { var amount, price float64 amount, err = strconv.ParseFloat(update.Asks[i][1], 64) if err != nil { @@ -195,7 +195,7 @@ func (l *LakeBTC) processOrderbook(obUpdate, channel string) error { }) } - for i := 0; i < len(update.Bids); i++ { + for i := range update.Bids { var amount, price float64 amount, err = strconv.ParseFloat(update.Bids[i][1], 64) if err != nil { diff --git a/exchanges/lakebtc/lakebtc_wrapper.go b/exchanges/lakebtc/lakebtc_wrapper.go index 09abfcd2b05..d3233adb5dd 100644 --- a/exchanges/lakebtc/lakebtc_wrapper.go +++ b/exchanges/lakebtc/lakebtc_wrapper.go @@ -12,6 +12,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/account" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" @@ -283,23 +284,23 @@ func (l *LakeBTC) UpdateOrderbook(p currency.Pair, assetType asset.Item) (*order return orderbook.Get(l.Name, p, assetType) } -// GetAccountInfo retrieves balances for all enabled currencies for the +// UpdateAccountInfo retrieves balances for all enabled currencies for the // LakeBTC exchange -func (l *LakeBTC) GetAccountInfo() (exchange.AccountInfo, error) { - var response exchange.AccountInfo +func (l *LakeBTC) UpdateAccountInfo() (account.Holdings, error) { + var response account.Holdings response.Exchange = l.Name accountInfo, err := l.GetAccountInformation() if err != nil { return response, err } - var currencies []exchange.AccountCurrencyInfo + var currencies []account.Balance for x, y := range accountInfo.Balance { for z, w := range accountInfo.Locked { if z != x { continue } - var exchangeCurrency exchange.AccountCurrencyInfo + var exchangeCurrency account.Balance exchangeCurrency.CurrencyName = currency.NewCode(x) exchangeCurrency.TotalValue, _ = strconv.ParseFloat(y, 64) exchangeCurrency.Hold, _ = strconv.ParseFloat(w, 64) @@ -307,13 +308,28 @@ func (l *LakeBTC) GetAccountInfo() (exchange.AccountInfo, error) { } } - response.Accounts = append(response.Accounts, exchange.Account{ + response.Accounts = append(response.Accounts, account.SubAccount{ Currencies: currencies, }) + err = account.Process(&response) + if err != nil { + return account.Holdings{}, err + } + return response, nil } +// FetchAccountInfo retrieves balances for all enabled currencies +func (l *LakeBTC) FetchAccountInfo() (account.Holdings, error) { + acc, err := account.GetHoldings(l.Name) + if err != nil { + return l.UpdateAccountInfo() + } + + return acc, nil +} + // GetFundingHistory returns funding history, deposits and // withdrawals func (l *LakeBTC) GetFundingHistory() ([]exchange.FundHistory, error) { @@ -534,3 +550,10 @@ func (l *LakeBTC) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, func (l *LakeBTC) AuthenticateWebsocket() error { return common.ErrFunctionNotSupported } + +// ValidateCredentials validates current credentials used for wrapper +// functionality +func (l *LakeBTC) ValidateCredentials() error { + _, err := l.UpdateAccountInfo() + return l.CheckTransientError(err) +} diff --git a/exchanges/lbank/lbank_test.go b/exchanges/lbank/lbank_test.go index 4aa2c4a78a5..7eacaba914a 100644 --- a/exchanges/lbank/lbank_test.go +++ b/exchanges/lbank/lbank_test.go @@ -388,7 +388,7 @@ func TestGetAccountInfo(t *testing.T) { if !areTestAPIKeysSet() { t.Skip("API keys required but not set, skipping test") } - _, err := l.GetAccountInfo() + _, err := l.UpdateAccountInfo() if err != nil { t.Error(err) } diff --git a/exchanges/lbank/lbank_wrapper.go b/exchanges/lbank/lbank_wrapper.go index 8bf64587c1a..2e0f285a67d 100644 --- a/exchanges/lbank/lbank_wrapper.go +++ b/exchanges/lbank/lbank_wrapper.go @@ -11,6 +11,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/account" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" @@ -249,40 +250,55 @@ func (l *Lbank) UpdateOrderbook(p currency.Pair, assetType asset.Item) (*orderbo return orderbook.Get(l.Name, p, assetType) } -// GetAccountInfo retrieves balances for all enabled currencies for the +// UpdateAccountInfo retrieves balances for all enabled currencies for the // Lbank exchange -func (l *Lbank) GetAccountInfo() (exchange.AccountInfo, error) { - var info exchange.AccountInfo +func (l *Lbank) UpdateAccountInfo() (account.Holdings, error) { + var info account.Holdings data, err := l.GetUserInfo() if err != nil { return info, err } - var account exchange.Account + var acc account.SubAccount for key, val := range data.Info.Asset { c := currency.NewCode(key) hold, ok := data.Info.Freeze[key] if !ok { return info, fmt.Errorf("hold data not found with %s", key) } - totalVal, err := strconv.ParseFloat(val, 64) - if err != nil { - return info, err + totalVal, parseErr := strconv.ParseFloat(val, 64) + if parseErr != nil { + return info, parseErr } - totalHold, err := strconv.ParseFloat(hold, 64) - if err != nil { - return info, err + totalHold, parseErr := strconv.ParseFloat(hold, 64) + if parseErr != nil { + return info, parseErr } - account.Currencies = append(account.Currencies, - exchange.AccountCurrencyInfo{CurrencyName: c, - TotalValue: totalVal, - Hold: totalHold}) + acc.Currencies = append(acc.Currencies, account.Balance{ + CurrencyName: c, + TotalValue: totalVal, + Hold: totalHold}) } - info.Accounts = append(info.Accounts, account) + info.Accounts = append(info.Accounts, acc) info.Exchange = l.Name + + err = account.Process(&info) + if err != nil { + return account.Holdings{}, err + } return info, nil } +// FetchAccountInfo retrieves balances for all enabled currencies +func (l *Lbank) FetchAccountInfo() (account.Holdings, error) { + acc, err := account.GetHoldings(l.Name) + if err != nil { + return l.UpdateAccountInfo() + } + + return acc, nil +} + // GetFundingHistory returns funding history, deposits and // withdrawals func (l *Lbank) GetFundingHistory() ([]exchange.FundHistory, error) { @@ -690,3 +706,10 @@ func (l *Lbank) AuthenticateWebsocket() error { func (l *Lbank) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) { return nil, common.ErrNotYetImplemented } + +// ValidateCredentials validates current credentials used for wrapper +// functionality +func (l *Lbank) ValidateCredentials() error { + _, err := l.UpdateAccountInfo() + return l.CheckTransientError(err) +} diff --git a/exchanges/localbitcoins/localbitcoins_wrapper.go b/exchanges/localbitcoins/localbitcoins_wrapper.go index dfb57c5fcc9..8afc1835330 100644 --- a/exchanges/localbitcoins/localbitcoins_wrapper.go +++ b/exchanges/localbitcoins/localbitcoins_wrapper.go @@ -13,6 +13,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/account" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" @@ -244,25 +245,41 @@ func (l *LocalBitcoins) UpdateOrderbook(p currency.Pair, assetType asset.Item) ( return orderbook.Get(l.Name, p, assetType) } -// GetAccountInfo retrieves balances for all enabled currencies for the +// UpdateAccountInfo retrieves balances for all enabled currencies for the // LocalBitcoins exchange -func (l *LocalBitcoins) GetAccountInfo() (exchange.AccountInfo, error) { - var response exchange.AccountInfo +func (l *LocalBitcoins) UpdateAccountInfo() (account.Holdings, error) { + var response account.Holdings response.Exchange = l.Name accountBalance, err := l.GetWalletBalance() if err != nil { return response, err } - var exchangeCurrency exchange.AccountCurrencyInfo + var exchangeCurrency account.Balance exchangeCurrency.CurrencyName = currency.BTC exchangeCurrency.TotalValue = accountBalance.Total.Balance - response.Accounts = append(response.Accounts, exchange.Account{ - Currencies: []exchange.AccountCurrencyInfo{exchangeCurrency}, + response.Accounts = append(response.Accounts, account.SubAccount{ + Currencies: []account.Balance{exchangeCurrency}, }) + + err = account.Process(&response) + if err != nil { + return account.Holdings{}, err + } + return response, nil } +// FetchAccountInfo retrieves balances for all enabled currencies +func (l *LocalBitcoins) FetchAccountInfo() (account.Holdings, error) { + acc, err := account.GetHoldings(l.Name) + if err != nil { + return l.UpdateAccountInfo() + } + + return acc, nil +} + // GetFundingHistory returns funding history, deposits and // withdrawals func (l *LocalBitcoins) GetFundingHistory() ([]exchange.FundHistory, error) { @@ -571,3 +588,10 @@ func (l *LocalBitcoins) GetSubscriptions() ([]wshandler.WebsocketChannelSubscrip func (l *LocalBitcoins) AuthenticateWebsocket() error { return common.ErrFunctionNotSupported } + +// ValidateCredentials validates current credentials used for wrapper +// functionality +func (l *LocalBitcoins) ValidateCredentials() error { + _, err := l.UpdateAccountInfo() + return l.CheckTransientError(err) +} diff --git a/exchanges/okcoin/okcoin_test.go b/exchanges/okcoin/okcoin_test.go index 8350cb8e155..32714f3675b 100644 --- a/exchanges/okcoin/okcoin_test.go +++ b/exchanges/okcoin/okcoin_test.go @@ -83,6 +83,7 @@ func areTestAPIKeysSet() bool { } func testStandardErrorHandling(t *testing.T, err error) { + t.Helper() if !areTestAPIKeysSet() && err == nil { t.Error("Expecting an error when no keys are set") } @@ -1042,7 +1043,7 @@ func TestCancelAllExchangeOrders(t *testing.T) { // TestGetAccountInfo Wrapper test func TestGetAccountInfo(t *testing.T) { - _, err := o.GetAccountInfo() + _, err := o.UpdateAccountInfo() testStandardErrorHandling(t, err) } diff --git a/exchanges/okex/okex_test.go b/exchanges/okex/okex_test.go index 563c9b58440..b6fa1a68e25 100644 --- a/exchanges/okex/okex_test.go +++ b/exchanges/okex/okex_test.go @@ -1732,7 +1732,7 @@ func TestCancelAllExchangeOrders(t *testing.T) { // TestGetAccountInfo Wrapper test func TestGetAccountInfo(t *testing.T) { - _, err := o.GetAccountInfo() + _, err := o.UpdateAccountInfo() testStandardErrorHandling(t, err) } diff --git a/exchanges/okgroup/okgroup_wrapper.go b/exchanges/okgroup/okgroup_wrapper.go index 1a4ac02a355..8545b78c497 100644 --- a/exchanges/okgroup/okgroup_wrapper.go +++ b/exchanges/okgroup/okgroup_wrapper.go @@ -10,12 +10,12 @@ import ( "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/account" "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/withdraw" - log "github.com/thrasher-corp/gocryptotrader/logger" ) // Note: GoCryptoTrader wrapper funcs currently only support SPOT trades. @@ -170,27 +170,28 @@ func (o *OKGroup) UpdateOrderbook(p currency.Pair, a asset.Item) (*orderbook.Bas return orderbook.Get(o.Name, p, a) } -// GetAccountInfo retrieves balances for all enabled currencies -func (o *OKGroup) GetAccountInfo() (resp exchange.AccountInfo, err error) { - resp.Exchange = o.Name +// UpdateAccountInfo retrieves balances for all enabled currencies +func (o *OKGroup) UpdateAccountInfo() (account.Holdings, error) { currencies, err := o.GetSpotTradingAccounts() - currencyAccount := exchange.Account{} + if err != nil { + return account.Holdings{}, err + } + + var resp account.Holdings + resp.Exchange = o.Name + currencyAccount := account.SubAccount{} for i := range currencies { - hold, err := strconv.ParseFloat(currencies[i].Hold, 64) - if err != nil { - log.Errorf(log.ExchangeSys, - "Could not convert %v to float64", - currencies[i].Hold) + hold, parseErr := strconv.ParseFloat(currencies[i].Hold, 64) + if parseErr != nil { + return resp, parseErr } - totalValue, err := strconv.ParseFloat(currencies[i].Balance, 64) - if err != nil { - log.Errorf(log.ExchangeSys, - "Could not convert %v to float64", - currencies[i].Balance) + totalValue, parseErr := strconv.ParseFloat(currencies[i].Balance, 64) + if parseErr != nil { + return resp, parseErr } currencyAccount.Currencies = append(currencyAccount.Currencies, - exchange.AccountCurrencyInfo{ + account.Balance{ CurrencyName: currency.NewCode(currencies[i].Currency), Hold: hold, TotalValue: totalValue, @@ -198,7 +199,23 @@ func (o *OKGroup) GetAccountInfo() (resp exchange.AccountInfo, err error) { } resp.Accounts = append(resp.Accounts, currencyAccount) - return + + err = account.Process(&resp) + if err != nil { + return resp, err + } + + return resp, nil +} + +// FetchAccountInfo retrieves balances for all enabled currencies +func (o *OKGroup) FetchAccountInfo() (account.Holdings, error) { + acc, err := account.GetHoldings(o.Name) + if err != nil { + return o.UpdateAccountInfo() + } + + return acc, nil } // GetFundingHistory returns funding history, deposits and @@ -503,3 +520,10 @@ func (o *OKGroup) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, func (o *OKGroup) AuthenticateWebsocket() error { return o.WsLogin() } + +// ValidateCredentials validates current credentials used for wrapper +// functionality +func (o *OKGroup) ValidateCredentials() error { + _, err := o.UpdateAccountInfo() + return o.CheckTransientError(err) +} diff --git a/exchanges/order/order_test.go b/exchanges/order/order_test.go index d2061f5d221..86d0b7e7f49 100644 --- a/exchanges/order/order_test.go +++ b/exchanges/order/order_test.go @@ -424,7 +424,7 @@ var stringsToOrderSide = []struct { } func TestStringToOrderSide(t *testing.T) { - for i := 0; i < len(stringsToOrderSide); i++ { + for i := range stringsToOrderSide { testData := &stringsToOrderSide[i] t.Run(testData.in, func(t *testing.T) { out, err := StringToOrderSide(testData.in) @@ -466,7 +466,7 @@ var stringsToOrderType = []struct { } func TestStringToOrderType(t *testing.T) { - for i := 0; i < len(stringsToOrderType); i++ { + for i := range stringsToOrderType { testData := &stringsToOrderType[i] t.Run(testData.in, func(t *testing.T) { out, err := StringToOrderType(testData.in) @@ -520,7 +520,7 @@ var stringsToOrderStatus = []struct { } func TestStringToOrderStatus(t *testing.T) { - for i := 0; i < len(stringsToOrderStatus); i++ { + for i := range stringsToOrderStatus { testData := &stringsToOrderStatus[i] t.Run(testData.in, func(t *testing.T) { out, err := StringToOrderStatus(testData.in) diff --git a/exchanges/poloniex/poloniex_wrapper.go b/exchanges/poloniex/poloniex_wrapper.go index a596753ea56..91848ec2d31 100644 --- a/exchanges/poloniex/poloniex_wrapper.go +++ b/exchanges/poloniex/poloniex_wrapper.go @@ -11,6 +11,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/account" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" @@ -321,31 +322,46 @@ func (p *Poloniex) UpdateOrderbook(currencyPair currency.Pair, assetType asset.I return orderbook.Get(p.Name, currencyPair, assetType) } -// GetAccountInfo retrieves balances for all enabled currencies for the +// UpdateAccountInfo retrieves balances for all enabled currencies for the // Poloniex exchange -func (p *Poloniex) GetAccountInfo() (exchange.AccountInfo, error) { - var response exchange.AccountInfo +func (p *Poloniex) UpdateAccountInfo() (account.Holdings, error) { + var response account.Holdings response.Exchange = p.Name accountBalance, err := p.GetBalances() if err != nil { return response, err } - var currencies []exchange.AccountCurrencyInfo + var currencies []account.Balance for x, y := range accountBalance.Currency { - var exchangeCurrency exchange.AccountCurrencyInfo + var exchangeCurrency account.Balance exchangeCurrency.CurrencyName = currency.NewCode(x) exchangeCurrency.TotalValue = y currencies = append(currencies, exchangeCurrency) } - response.Accounts = append(response.Accounts, exchange.Account{ + response.Accounts = append(response.Accounts, account.SubAccount{ Currencies: currencies, }) + err = account.Process(&response) + if err != nil { + return account.Holdings{}, err + } + return response, nil } +// FetchAccountInfo retrieves balances for all enabled currencies +func (p *Poloniex) FetchAccountInfo() (account.Holdings, error) { + acc, err := account.GetHoldings(p.Name) + if err != nil { + return p.UpdateAccountInfo() + } + + return acc, nil +} + // GetFundingHistory returns funding history, deposits and // withdrawals func (p *Poloniex) GetFundingHistory() ([]exchange.FundHistory, error) { @@ -606,3 +622,10 @@ func (p *Poloniex) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, func (p *Poloniex) AuthenticateWebsocket() error { return common.ErrFunctionNotSupported } + +// ValidateCredentials validates current credentials used for wrapper +// functionality +func (p *Poloniex) ValidateCredentials() error { + _, err := p.UpdateAccountInfo() + return p.CheckTransientError(err) +} diff --git a/exchanges/request/request.go b/exchanges/request/request.go index ed2cfd9a234..a50dafa22a0 100644 --- a/exchanges/request/request.go +++ b/exchanges/request/request.go @@ -302,14 +302,11 @@ func (r *Requester) DoRequest(req *http.Request, path string, body io.Reader, re } } - if resp.StatusCode != 200 && resp.StatusCode != 201 && resp.StatusCode != 202 { - err = fmt.Errorf("unsuccessful HTTP status code: %d", resp.StatusCode) - if verbose { - err = fmt.Errorf("%s\n%s", err.Error(), - fmt.Sprintf("%s exchange raw response: %s", r.Name, string(contents))) - } - - return err + if resp.StatusCode < http.StatusOK || + resp.StatusCode > http.StatusAccepted { + return fmt.Errorf("unsuccessful HTTP status code: %d body: %s", + resp.StatusCode, + string(contents)) } if httpDebug { diff --git a/exchanges/websocket/wshandler/wshandler.go b/exchanges/websocket/wshandler/wshandler.go index 278a67357cb..9cff9843e77 100644 --- a/exchanges/websocket/wshandler/wshandler.go +++ b/exchanges/websocket/wshandler/wshandler.go @@ -479,7 +479,7 @@ func (w *Websocket) manageSubscriptions() { func (w *Websocket) appendSubscribedChannels() error { w.subscriptionMutex.Lock() defer w.subscriptionMutex.Unlock() - for i := 0; i < len(w.channelsToSubscribe); i++ { + for i := range w.channelsToSubscribe { channelIsSubscribed := false for j := 0; j < len(w.subscribedChannels); j++ { if w.subscribedChannels[j].Equal(&w.channelsToSubscribe[i]) { @@ -506,7 +506,7 @@ func (w *Websocket) appendSubscribedChannels() error { func (w *Websocket) unsubscribeToChannels() error { w.subscriptionMutex.Lock() defer w.subscriptionMutex.Unlock() - for i := 0; i < len(w.subscribedChannels); i++ { + for i := range w.subscribedChannels { subscriptionFound := false for j := 0; j < len(w.channelsToSubscribe); j++ { if w.channelsToSubscribe[j].Equal(&w.subscribedChannels[i]) { diff --git a/exchanges/websocket/wshandler/wshandler_test.go b/exchanges/websocket/wshandler/wshandler_test.go index b12c95b759a..3365ebd6164 100644 --- a/exchanges/websocket/wshandler/wshandler_test.go +++ b/exchanges/websocket/wshandler/wshandler_test.go @@ -542,7 +542,7 @@ func TestDial(t *testing.T) { {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 := 0; i < len(testCases); i++ { + for i := range testCases { testData := &testCases[i] t.Run(testData.WC.ExchangeName, func(t *testing.T) { if testData.WC.ProxyURL != "" && !useProxyTests { @@ -566,7 +566,7 @@ func TestSendMessage(t *testing.T) { {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 := 0; i < len(testCases); i++ { + for i := range testCases { testData := &testCases[i] t.Run(testData.WC.ExchangeName, func(t *testing.T) { if testData.WC.ProxyURL != "" && !useProxyTests { diff --git a/exchanges/websocket/wsorderbook/wsorderbook.go b/exchanges/websocket/wsorderbook/wsorderbook.go index 906bc4e8379..1f609202416 100644 --- a/exchanges/websocket/wsorderbook/wsorderbook.go +++ b/exchanges/websocket/wsorderbook/wsorderbook.go @@ -86,7 +86,7 @@ func (w *WebsocketOrderbookLocal) processBufferUpdate(o *orderbook.Base, u *Webs }) } } - for i := 0; i < len(bufferLookup); i++ { + for i := range bufferLookup { w.processObUpdate(o, bufferLookup[i]) } w.buffer[u.Pair][u.Asset] = bufferLookup diff --git a/exchanges/websocket/wsorderbook/wsorderbook_test.go b/exchanges/websocket/wsorderbook/wsorderbook_test.go index 9c2cae51dd0..95f6b7f2546 100644 --- a/exchanges/websocket/wsorderbook/wsorderbook_test.go +++ b/exchanges/websocket/wsorderbook/wsorderbook_test.go @@ -280,7 +280,7 @@ func TestHittingTheBuffer(t *testing.T) { } obl.bufferEnabled = true obl.obBufferLimit = 5 - for i := 0; i < len(itemArray); i++ { + for i := range itemArray { asks := itemArray[i] bids := itemArray[i] err = obl.Update(&WebsocketOrderbookUpdate{ @@ -314,7 +314,7 @@ func TestInsertWithIDs(t *testing.T) { obl.bufferEnabled = true obl.updateEntriesByID = true obl.obBufferLimit = 5 - for i := 0; i < len(itemArray); i++ { + for i := range itemArray { asks := itemArray[i] bids := itemArray[i] err = obl.Update(&WebsocketOrderbookUpdate{ @@ -349,7 +349,7 @@ func TestSortIDs(t *testing.T) { obl.sortBufferByUpdateIDs = true obl.sortBuffer = true obl.obBufferLimit = 5 - for i := 0; i < len(itemArray); i++ { + for i := range itemArray { asks := itemArray[i] bids := itemArray[i] err = obl.Update(&WebsocketOrderbookUpdate{ @@ -394,7 +394,7 @@ func TestDeleteWithIDs(t *testing.T) { itemArray[1][0]) obl.updateEntriesByID = true - for i := 0; i < len(itemArray); i++ { + for i := range itemArray { asks := itemArray[i] bids := itemArray[i] err = obl.Update(&WebsocketOrderbookUpdate{ @@ -427,7 +427,7 @@ func TestUpdateWithIDs(t *testing.T) { t.Fatal(err) } obl.updateEntriesByID = true - for i := 0; i < len(itemArray); i++ { + for i := range itemArray { asks := itemArray[i] bids := itemArray[i] err = obl.Update(&WebsocketOrderbookUpdate{ @@ -467,7 +467,7 @@ func TestOutOfOrderIDs(t *testing.T) { obl.bufferEnabled = true obl.sortBuffer = true obl.obBufferLimit = 5 - for i := 0; i < len(itemArray); i++ { + for i := range itemArray { asks := itemArray[i] err = obl.Update(&WebsocketOrderbookUpdate{ Asks: asks, diff --git a/exchanges/yobit/yobit_test.go b/exchanges/yobit/yobit_test.go index 02df1b73442..3fb435b1e9d 100644 --- a/exchanges/yobit/yobit_test.go +++ b/exchanges/yobit/yobit_test.go @@ -89,7 +89,7 @@ func TestGetTrades(t *testing.T) { func TestGetAccountInfo(t *testing.T) { t.Parallel() - _, err := y.GetAccountInfo() + _, err := y.UpdateAccountInfo() if err == nil { t.Error("GetAccountInfo() Expected error") } diff --git a/exchanges/yobit/yobit_wrapper.go b/exchanges/yobit/yobit_wrapper.go index 9c172189876..fb126c8de86 100644 --- a/exchanges/yobit/yobit_wrapper.go +++ b/exchanges/yobit/yobit_wrapper.go @@ -12,6 +12,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/account" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" @@ -267,19 +268,19 @@ func (y *Yobit) UpdateOrderbook(p currency.Pair, assetType asset.Item) (*orderbo return orderbook.Get(y.Name, p, assetType) } -// GetAccountInfo retrieves balances for all enabled currencies for the +// UpdateAccountInfo retrieves balances for all enabled currencies for the // Yobit exchange -func (y *Yobit) GetAccountInfo() (exchange.AccountInfo, error) { - var response exchange.AccountInfo +func (y *Yobit) UpdateAccountInfo() (account.Holdings, error) { + var response account.Holdings response.Exchange = y.Name accountBalance, err := y.GetAccountInformation() if err != nil { return response, err } - var currencies []exchange.AccountCurrencyInfo + var currencies []account.Balance for x, y := range accountBalance.FundsInclOrders { - var exchangeCurrency exchange.AccountCurrencyInfo + var exchangeCurrency account.Balance exchangeCurrency.CurrencyName = currency.NewCode(x) exchangeCurrency.TotalValue = y exchangeCurrency.Hold = 0 @@ -292,13 +293,28 @@ func (y *Yobit) GetAccountInfo() (exchange.AccountInfo, error) { currencies = append(currencies, exchangeCurrency) } - response.Accounts = append(response.Accounts, exchange.Account{ + response.Accounts = append(response.Accounts, account.SubAccount{ Currencies: currencies, }) + err = account.Process(&response) + if err != nil { + return account.Holdings{}, err + } + return response, nil } +// FetchAccountInfo retrieves balances for all enabled currencies +func (y *Yobit) FetchAccountInfo() (account.Holdings, error) { + acc, err := account.GetHoldings(y.Name) + if err != nil { + return y.UpdateAccountInfo() + } + + return acc, nil +} + // GetFundingHistory returns funding history, deposits and // withdrawals func (y *Yobit) GetFundingHistory() ([]exchange.FundHistory, error) { @@ -540,3 +556,10 @@ func (y *Yobit) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, er func (y *Yobit) AuthenticateWebsocket() error { return common.ErrFunctionNotSupported } + +// ValidateCredentials validates current credentials used for wrapper +// functionality +func (y *Yobit) ValidateCredentials() error { + _, err := y.UpdateAccountInfo() + return y.CheckTransientError(err) +} diff --git a/exchanges/zb/zb_test.go b/exchanges/zb/zb_test.go index f1f066b2157..0bbd12cf393 100644 --- a/exchanges/zb/zb_test.go +++ b/exchanges/zb/zb_test.go @@ -391,12 +391,12 @@ func TestCancelAllExchangeOrders(t *testing.T) { func TestGetAccountInfo(t *testing.T) { if z.ValidateAPICredentials() { - _, err := z.GetAccountInfo() + _, err := z.UpdateAccountInfo() if err != nil { t.Error("GetAccountInfo() error", err) } } else { - _, err := z.GetAccountInfo() + _, err := z.UpdateAccountInfo() if err == nil { t.Error("GetAccountInfo() Expected error") } diff --git a/exchanges/zb/zb_wrapper.go b/exchanges/zb/zb_wrapper.go index 7b385a85651..9ec2a61dd83 100644 --- a/exchanges/zb/zb_wrapper.go +++ b/exchanges/zb/zb_wrapper.go @@ -12,6 +12,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/account" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" @@ -303,11 +304,11 @@ func (z *ZB) UpdateOrderbook(p currency.Pair, assetType asset.Item) (*orderbook. return orderbook.Get(z.Name, p, assetType) } -// GetAccountInfo retrieves balances for all enabled currencies for the +// UpdateAccountInfo retrieves balances for all enabled currencies for the // ZB exchange -func (z *ZB) GetAccountInfo() (exchange.AccountInfo, error) { - var info exchange.AccountInfo - var balances []exchange.AccountCurrencyInfo +func (z *ZB) UpdateAccountInfo() (account.Holdings, error) { + var info account.Holdings + var balances []account.Balance var coins []AccountsResponseCoin if z.Websocket.CanUseAuthenticatedWebsocketForWrapper() { resp, err := z.wsGetAccountInfoRequest() @@ -334,7 +335,7 @@ func (z *ZB) GetAccountInfo() (exchange.AccountInfo, error) { return info, err } - balances = append(balances, exchange.AccountCurrencyInfo{ + balances = append(balances, account.Balance{ CurrencyName: currency.NewCode(coins[i].EnName), TotalValue: hold + avail, Hold: hold, @@ -342,13 +343,28 @@ func (z *ZB) GetAccountInfo() (exchange.AccountInfo, error) { } info.Exchange = z.Name - info.Accounts = append(info.Accounts, exchange.Account{ + info.Accounts = append(info.Accounts, account.SubAccount{ Currencies: balances, }) + err := account.Process(&info) + if err != nil { + return account.Holdings{}, err + } + return info, nil } +// FetchAccountInfo retrieves balances for all enabled currencies +func (z *ZB) FetchAccountInfo() (account.Holdings, error) { + acc, err := account.GetHoldings(z.Name) + if err != nil { + return z.UpdateAccountInfo() + } + + return acc, nil +} + // GetFundingHistory returns funding history, deposits and // withdrawals func (z *ZB) GetFundingHistory() ([]exchange.FundHistory, error) { @@ -666,3 +682,10 @@ func (z *ZB) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error func (z *ZB) AuthenticateWebsocket() error { return common.ErrFunctionNotSupported } + +// ValidateCredentials validates current credentials used for wrapper +// functionality +func (z *ZB) ValidateCredentials() error { + _, err := z.UpdateAccountInfo() + return z.CheckTransientError(err) +} diff --git a/gctrpc/rpc.pb.go b/gctrpc/rpc.pb.go index 80261705f9a..2c6b43ad20c 100644 --- a/gctrpc/rpc.pb.go +++ b/gctrpc/rpc.pb.go @@ -5972,350 +5972,351 @@ func init() { func init() { proto.RegisterFile("rpc.proto", fileDescriptor_77a6da22d6a3feb1) } var fileDescriptor_77a6da22d6a3feb1 = []byte{ - // 5474 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x3c, 0x4d, 0x6f, 0x1c, 0x47, - 0x76, 0x98, 0x21, 0x45, 0x72, 0x1e, 0xbf, 0x86, 0xc5, 0xaf, 0xd1, 0x88, 0x12, 0xa5, 0xf6, 0x5a, - 0x96, 0xfc, 0x41, 0xd9, 0xb2, 0x92, 0x75, 0xd6, 0xce, 0x6e, 0x28, 0x4a, 0xa6, 0xb5, 0xd6, 0x5a, - 0xdc, 0x26, 0x2d, 0x03, 0xde, 0xc0, 0x93, 0xe6, 0x74, 0x71, 0xd8, 0x51, 0x4f, 0x77, 0xbb, 0xbb, - 0x86, 0x14, 0xbd, 0x09, 0xb2, 0x30, 0x92, 0x20, 0x87, 0x20, 0x39, 0x18, 0x01, 0x12, 0x20, 0x97, - 0xe4, 0x14, 0x04, 0xc8, 0x25, 0xc8, 0x29, 0x87, 0x45, 0xae, 0x41, 0x8e, 0xb9, 0xe4, 0x07, 0x04, - 0xb9, 0x25, 0x01, 0x02, 0xe4, 0x92, 0x53, 0x50, 0xaf, 0x3e, 0xba, 0xaa, 0xbb, 0x67, 0x38, 0xb4, - 0xb5, 0xde, 0x8b, 0x34, 0xfd, 0xea, 0xd5, 0x7b, 0xaf, 0x5e, 0xbd, 0xaa, 0x7a, 0xaf, 0xde, 0x2b, - 0x42, 0x23, 0x4d, 0xba, 0x5b, 0x49, 0x1a, 0xb3, 0x98, 0x4c, 0xf5, 0xba, 0x2c, 0x4d, 0xba, 0xed, - 0x8d, 0x5e, 0x1c, 0xf7, 0x42, 0x7a, 0xc7, 0x4b, 0x82, 0x3b, 0x5e, 0x14, 0xc5, 0xcc, 0x63, 0x41, - 0x1c, 0x65, 0x02, 0xcb, 0x69, 0xc2, 0xc2, 0x2e, 0x65, 0x8f, 0xa2, 0xa3, 0xd8, 0xa5, 0x9f, 0x0f, - 0x68, 0xc6, 0x9c, 0x7f, 0x98, 0x84, 0x45, 0x0d, 0xca, 0x92, 0x38, 0xca, 0x28, 0x59, 0x83, 0xa9, - 0x41, 0xc2, 0x82, 0x3e, 0x6d, 0xd5, 0xae, 0xd7, 0x6e, 0x35, 0x5c, 0xf9, 0x45, 0xee, 0xc0, 0xb2, - 0x77, 0xe2, 0x05, 0xa1, 0x77, 0x18, 0xd2, 0x0e, 0x7d, 0xde, 0x3d, 0xf6, 0xa2, 0x1e, 0xcd, 0x5a, - 0xf5, 0xeb, 0xb5, 0x5b, 0x13, 0x2e, 0xd1, 0x4d, 0x0f, 0x55, 0x0b, 0x79, 0x0d, 0x96, 0x68, 0xc4, - 0x41, 0xbe, 0x81, 0x3e, 0x81, 0xe8, 0x4d, 0xd9, 0x90, 0x23, 0xdf, 0x83, 0x35, 0x9f, 0x1e, 0x79, - 0x83, 0x90, 0x75, 0x8e, 0xe2, 0x94, 0x3e, 0xef, 0x24, 0x69, 0x7c, 0x12, 0xf8, 0x34, 0x6d, 0x4d, - 0xa2, 0x14, 0x2b, 0xb2, 0xf5, 0x7d, 0xde, 0xb8, 0x27, 0xdb, 0xc8, 0x5d, 0x58, 0xd5, 0xbd, 0x02, - 0x8f, 0x75, 0xba, 0x83, 0x34, 0xa5, 0x51, 0xf7, 0xac, 0x75, 0x09, 0x3b, 0x2d, 0xab, 0x4e, 0x81, - 0xc7, 0x76, 0x64, 0x13, 0xf9, 0x04, 0x9a, 0xd9, 0xe0, 0x30, 0x3b, 0xcb, 0x18, 0xed, 0x77, 0x32, - 0xe6, 0xb1, 0x41, 0xd6, 0x9a, 0xba, 0x3e, 0x71, 0x6b, 0xf6, 0xee, 0xeb, 0x5b, 0x42, 0x8d, 0x5b, - 0x05, 0x95, 0x6c, 0xed, 0x2b, 0xfc, 0x7d, 0x44, 0x7f, 0x18, 0xb1, 0xf4, 0xcc, 0x5d, 0xcc, 0x6c, - 0x28, 0xf9, 0x08, 0xe6, 0xd3, 0xa4, 0xdb, 0xa1, 0x91, 0x9f, 0xc4, 0x41, 0xc4, 0xb2, 0xd6, 0x34, - 0x52, 0xbd, 0x3d, 0x8c, 0xaa, 0x9b, 0x74, 0x1f, 0x2a, 0x5c, 0x41, 0x72, 0x2e, 0x35, 0x40, 0xed, - 0xfb, 0xb0, 0x52, 0xc5, 0x98, 0x34, 0x61, 0xe2, 0x19, 0x3d, 0x93, 0xb3, 0xc3, 0x7f, 0x92, 0x15, - 0xb8, 0x74, 0xe2, 0x85, 0x03, 0x8a, 0x93, 0x31, 0xe3, 0x8a, 0x8f, 0xef, 0xd5, 0xdf, 0xa9, 0xb5, - 0x0f, 0x60, 0xa9, 0xc4, 0xa6, 0x82, 0xc0, 0x6d, 0x93, 0xc0, 0xec, 0xdd, 0x65, 0x25, 0xb2, 0xbb, - 0xb7, 0xa3, 0xfa, 0x1a, 0x54, 0x9d, 0x1b, 0xb0, 0xb9, 0x4b, 0xd9, 0x4e, 0xdc, 0xef, 0x0f, 0xa2, - 0xa0, 0x8b, 0x36, 0xe6, 0xd2, 0xd0, 0x3b, 0xa3, 0x69, 0xa6, 0x2c, 0xeb, 0x23, 0x58, 0xa9, 0x6a, - 0x27, 0x2d, 0x98, 0x96, 0x73, 0x8f, 0xfc, 0x67, 0x5c, 0xf5, 0x49, 0x36, 0xa0, 0xd1, 0x8d, 0xa3, - 0x88, 0x76, 0x19, 0xf5, 0xe5, 0x40, 0x72, 0x80, 0xf3, 0x87, 0x75, 0xb8, 0x3e, 0x9c, 0xa7, 0x34, - 0xdd, 0x2f, 0x60, 0xad, 0x6b, 0x22, 0x74, 0x52, 0x89, 0xd1, 0xaa, 0xe1, 0x54, 0xec, 0x18, 0x53, - 0x31, 0x92, 0xd2, 0x56, 0x65, 0xab, 0x98, 0xa4, 0xd5, 0x6e, 0x55, 0x5b, 0xfb, 0x08, 0xda, 0xc3, - 0x3b, 0x55, 0xa8, 0xfc, 0xae, 0xad, 0xf2, 0x0d, 0x25, 0x5a, 0x15, 0x11, 0x53, 0xf7, 0xdf, 0x85, - 0xf5, 0x5d, 0x1a, 0xd1, 0x34, 0xe8, 0x6a, 0xe3, 0x90, 0x3a, 0xe7, 0x1a, 0xd4, 0x36, 0x29, 0x59, - 0xe5, 0x00, 0xa7, 0x0d, 0xad, 0x72, 0x47, 0x31, 0x5c, 0x67, 0x0d, 0x56, 0x76, 0x29, 0xd3, 0x70, - 0x3d, 0x8b, 0x3f, 0xaf, 0xc1, 0x2a, 0x36, 0x64, 0x87, 0xd9, 0x99, 0x68, 0x90, 0xaa, 0xfe, 0x2d, - 0x58, 0xd2, 0xa4, 0x33, 0xb5, 0x8c, 0x84, 0x96, 0xdf, 0x36, 0xb4, 0x5c, 0xee, 0x99, 0x2f, 0xa6, - 0xcc, 0x5c, 0x4d, 0xf9, 0x9a, 0x94, 0xe0, 0xf6, 0x0e, 0xac, 0x56, 0xa2, 0x5e, 0xc4, 0xfe, 0x9d, - 0x16, 0xac, 0xed, 0x52, 0x66, 0x98, 0xb1, 0x61, 0xa0, 0xb3, 0x06, 0x98, 0xdb, 0x65, 0xc6, 0xbc, - 0x94, 0xe5, 0x76, 0x29, 0x3f, 0xc9, 0xcb, 0xb0, 0x10, 0x06, 0x19, 0xa3, 0x51, 0xc7, 0xf3, 0xfd, - 0x94, 0x66, 0x62, 0xcb, 0x6b, 0xb8, 0xf3, 0x02, 0xba, 0x2d, 0x80, 0xce, 0x3f, 0xd6, 0xf8, 0xc4, - 0x14, 0x58, 0x49, 0x65, 0x3d, 0x86, 0x46, 0xbe, 0x2b, 0x08, 0x25, 0x6d, 0x19, 0x4a, 0xaa, 0xea, - 0xb3, 0x55, 0xd8, 0x1a, 0x72, 0x02, 0xed, 0x1f, 0xc3, 0xc2, 0x8b, 0x5e, 0xd0, 0xef, 0x40, 0x5b, - 0xda, 0x86, 0xda, 0x91, 0x3f, 0xf2, 0xfa, 0x54, 0xd9, 0x55, 0x1b, 0x66, 0xd4, 0x06, 0x2e, 0x79, - 0xe8, 0x6f, 0xe7, 0x2a, 0x5c, 0xa9, 0xec, 0x29, 0x0d, 0xeb, 0x0e, 0x2c, 0xef, 0x52, 0xa6, 0xb7, - 0x79, 0x45, 0x71, 0xe8, 0x2e, 0xe0, 0xdc, 0x43, 0x4b, 0x34, 0x3a, 0x48, 0x15, 0x6e, 0x40, 0x23, - 0x3f, 0x44, 0xa4, 0x6d, 0x6b, 0x80, 0x73, 0x17, 0xcd, 0x54, 0xf5, 0x7a, 0x72, 0xb0, 0xe7, 0x52, - 0xd1, 0xed, 0x32, 0xcc, 0xc4, 0x2c, 0xe9, 0x74, 0x63, 0x5f, 0x89, 0x3e, 0x1d, 0xb3, 0x64, 0x27, - 0xf6, 0xa9, 0x34, 0x0d, 0xa3, 0x8f, 0x36, 0x8d, 0xbf, 0x16, 0x53, 0x69, 0x37, 0x49, 0x39, 0x7e, - 0x08, 0x0d, 0x45, 0x50, 0x4d, 0xe5, 0x1b, 0xc6, 0x54, 0x56, 0xf5, 0xd9, 0x7a, 0x22, 0x38, 0xca, - 0x99, 0x9c, 0x91, 0x02, 0x64, 0xed, 0x77, 0x61, 0xde, 0x6a, 0x3a, 0xcf, 0xb2, 0x1b, 0xe6, 0x94, - 0xdd, 0x83, 0xb5, 0x07, 0x41, 0x66, 0x9e, 0xb8, 0xe3, 0x4c, 0xd7, 0x67, 0xb0, 0xb0, 0xe7, 0x05, - 0x69, 0xb6, 0x3f, 0x48, 0x92, 0x18, 0xcd, 0xfb, 0x15, 0x58, 0xcc, 0x8f, 0xf5, 0x84, 0xb7, 0xc9, - 0x4e, 0x0b, 0x1a, 0x8c, 0x3d, 0xc8, 0x4b, 0x30, 0xaf, 0x8e, 0x73, 0x81, 0x26, 0x44, 0x9a, 0x93, - 0x40, 0x44, 0x72, 0xbe, 0x9c, 0xb4, 0x54, 0x67, 0x39, 0x16, 0x04, 0x26, 0x23, 0x4f, 0xbb, 0x15, - 0xf8, 0xdb, 0x34, 0x84, 0xba, 0x7d, 0x1c, 0xb4, 0x60, 0xfa, 0x84, 0xa6, 0x87, 0x71, 0x46, 0xd1, - 0x67, 0x98, 0x71, 0xd5, 0x27, 0x17, 0x64, 0x90, 0x05, 0x51, 0xaf, 0x93, 0x79, 0x91, 0x7f, 0x18, - 0x3f, 0x47, 0x0f, 0x61, 0xc6, 0x9d, 0x43, 0xe0, 0xbe, 0x80, 0x91, 0x1b, 0x30, 0x77, 0xcc, 0x58, - 0xd2, 0xe1, 0xae, 0x4b, 0x3c, 0x60, 0xd2, 0x21, 0x98, 0xe5, 0xb0, 0x03, 0x01, 0xe2, 0x0b, 0x1b, - 0x51, 0x06, 0x19, 0x4d, 0xbd, 0x1e, 0x8d, 0x58, 0x6b, 0x4a, 0x2c, 0x6c, 0x0e, 0xfd, 0x58, 0x01, - 0xc9, 0x55, 0x00, 0x44, 0x4b, 0xd2, 0xf8, 0xf9, 0x59, 0x6b, 0x5a, 0x98, 0x1e, 0x87, 0xec, 0x71, - 0x00, 0xd7, 0xdf, 0xa1, 0x97, 0x51, 0xe5, 0x7a, 0x04, 0x34, 0x6b, 0xcd, 0x08, 0xfd, 0x71, 0xf0, - 0x8e, 0x86, 0x92, 0x0e, 0xf7, 0x3b, 0xa4, 0xd6, 0x3b, 0x5e, 0x96, 0x51, 0x96, 0xb5, 0x1a, 0x68, - 0x40, 0xf7, 0x2a, 0x0c, 0xa8, 0xe0, 0x7f, 0xc8, 0x7e, 0xdb, 0xd8, 0x4d, 0xfb, 0x1f, 0x16, 0x94, - 0xfb, 0x5b, 0xde, 0x80, 0x1d, 0xd3, 0x88, 0xf1, 0xd3, 0x83, 0x33, 0x49, 0x82, 0x16, 0xa0, 0x6e, - 0x9a, 0x56, 0xc3, 0x76, 0x12, 0xb4, 0x3f, 0xe5, 0xce, 0x45, 0x99, 0x6a, 0x85, 0x09, 0xbe, 0x6e, - 0x6f, 0x25, 0x6b, 0x4a, 0x58, 0xdb, 0x8e, 0x4c, 0xd3, 0x3c, 0x85, 0xe6, 0x2e, 0x65, 0x07, 0x41, - 0xf7, 0x19, 0x4d, 0xc7, 0x30, 0x4a, 0x72, 0x0b, 0x26, 0xb9, 0x45, 0x49, 0x06, 0x2b, 0xfa, 0x24, - 0x94, 0x1e, 0x1b, 0x67, 0xe4, 0x22, 0x06, 0x9f, 0x0b, 0xd4, 0x5c, 0x87, 0x9d, 0x25, 0xc2, 0x2e, - 0x1a, 0x6e, 0x03, 0x21, 0x07, 0x67, 0x09, 0x75, 0x9e, 0xc2, 0x9c, 0xd9, 0x89, 0x6f, 0x1a, 0x3e, - 0x0d, 0x83, 0x7e, 0xc0, 0x68, 0xaa, 0x36, 0x0d, 0x0d, 0xe0, 0xf6, 0xc8, 0xa7, 0x48, 0xda, 0x31, - 0xfe, 0xe6, 0xeb, 0xed, 0xf3, 0x41, 0xcc, 0x14, 0x6d, 0xf1, 0xe1, 0xfc, 0x59, 0x1d, 0x16, 0xd4, - 0x70, 0xa4, 0x31, 0x2b, 0x99, 0x6b, 0xe7, 0xca, 0x7c, 0x03, 0xe6, 0x42, 0x2f, 0x63, 0x9d, 0x41, - 0xe2, 0x7b, 0xca, 0xb5, 0x99, 0x70, 0x67, 0x39, 0xec, 0x63, 0x01, 0xe2, 0x16, 0xad, 0x3c, 0x57, - 0x5c, 0x5b, 0x92, 0xfb, 0x5c, 0xd7, 0x1c, 0x0c, 0x81, 0x49, 0xde, 0x07, 0xad, 0xbd, 0xe6, 0xe2, - 0x6f, 0x0e, 0x3b, 0x0e, 0x7a, 0xc7, 0x68, 0xdd, 0x35, 0x17, 0x7f, 0xf3, 0x19, 0x0c, 0xe3, 0x53, - 0xb4, 0xe5, 0x9a, 0xcb, 0x7f, 0x72, 0xc8, 0x61, 0xe0, 0xa3, 0xe9, 0xd6, 0x5c, 0xfe, 0x93, 0x43, - 0xbc, 0xec, 0x19, 0x1a, 0x6a, 0xcd, 0xe5, 0x3f, 0xb9, 0xd7, 0x7f, 0x12, 0x87, 0x83, 0x3e, 0x6d, - 0x35, 0x10, 0x28, 0xbf, 0xc8, 0x15, 0x68, 0x24, 0x69, 0xd0, 0xa5, 0x1d, 0x8f, 0x1d, 0xa3, 0x31, - 0xd5, 0xdc, 0x19, 0x04, 0x6c, 0xb3, 0x63, 0x67, 0x19, 0x96, 0xf4, 0x44, 0xeb, 0xdd, 0xf3, 0x13, - 0x98, 0x96, 0x90, 0x91, 0x93, 0xfe, 0x26, 0x4c, 0x33, 0x81, 0xd6, 0xaa, 0xe3, 0x2a, 0xd0, 0x86, - 0x65, 0x6b, 0xda, 0x55, 0x68, 0xce, 0x0f, 0x80, 0x98, 0xdc, 0xe4, 0x44, 0xdc, 0xce, 0xe9, 0x88, - 0xed, 0x78, 0xd1, 0xa6, 0x93, 0xe5, 0x04, 0xbe, 0xc0, 0xc3, 0xe8, 0x49, 0xea, 0xf3, 0x8d, 0x24, - 0x7e, 0xf6, 0xad, 0x9a, 0xe6, 0x8f, 0x60, 0x5e, 0x33, 0x7e, 0xc4, 0x68, 0x9f, 0x2b, 0xdc, 0xeb, - 0xc7, 0x83, 0x88, 0x21, 0xcf, 0x9a, 0x2b, 0xbf, 0xb8, 0x05, 0xa2, 0x7e, 0x91, 0x65, 0xcd, 0x15, - 0x1f, 0x64, 0x01, 0xea, 0x81, 0x2f, 0x83, 0xa7, 0x7a, 0xe0, 0x3b, 0xff, 0x57, 0x83, 0x25, 0x63, - 0x20, 0x17, 0x36, 0xca, 0x92, 0xc5, 0xd5, 0x2b, 0x2c, 0xee, 0x36, 0x4c, 0x1e, 0x06, 0x3e, 0x8f, - 0xd9, 0xb8, 0x5e, 0x57, 0x15, 0x39, 0x6b, 0x1c, 0x2e, 0xa2, 0x70, 0x54, 0x2f, 0x7b, 0x96, 0xb5, - 0x26, 0x47, 0xa2, 0x72, 0x94, 0xd2, 0x7a, 0xb8, 0x54, 0x5e, 0x0f, 0xb6, 0x2e, 0xa7, 0x8a, 0xba, - 0x14, 0xde, 0xaa, 0xa6, 0xad, 0x2d, 0xaf, 0x0b, 0x90, 0x03, 0x47, 0x4e, 0xeb, 0xaf, 0x01, 0xc4, - 0x1a, 0x53, 0xda, 0xdf, 0xe5, 0x92, 0xd0, 0xda, 0x04, 0x0d, 0x64, 0xe7, 0x43, 0x74, 0x35, 0x4c, - 0xe6, 0x52, 0xf9, 0x77, 0x2d, 0x9a, 0xc2, 0x16, 0x49, 0x89, 0x66, 0x66, 0x11, 0x7b, 0x1b, 0x89, - 0x6d, 0x77, 0xbb, 0x7c, 0xea, 0x8d, 0xc0, 0x7c, 0xe4, 0x19, 0xfe, 0x14, 0xa6, 0x65, 0x0f, 0x69, - 0x16, 0x02, 0xa1, 0x1e, 0xf8, 0xe4, 0x5d, 0x00, 0xe3, 0x1c, 0x12, 0xe3, 0xba, 0xa2, 0x64, 0x90, - 0x9d, 0x94, 0x35, 0x20, 0x3b, 0x03, 0xdd, 0x39, 0x82, 0xe5, 0x0a, 0x14, 0x2e, 0x8a, 0x0e, 0xab, - 0xa5, 0x28, 0xea, 0x9b, 0x6c, 0xc2, 0x2c, 0x8b, 0x99, 0x17, 0x76, 0xf2, 0x13, 0xa2, 0xe6, 0x02, - 0x82, 0x9e, 0x72, 0x08, 0x6e, 0x50, 0x71, 0x28, 0x2c, 0x97, 0x6f, 0x50, 0x71, 0xe8, 0x3b, 0x1e, - 0x3a, 0x5e, 0xd6, 0xa0, 0xa5, 0x0a, 0x47, 0x4d, 0xd9, 0x6b, 0x30, 0xe3, 0x89, 0x2e, 0x6a, 0x60, - 0x8b, 0x85, 0x81, 0xb9, 0x1a, 0xc1, 0x21, 0x78, 0x02, 0xed, 0xc4, 0xd1, 0x51, 0xd0, 0x53, 0xd6, - 0xf1, 0x0a, 0x6e, 0x56, 0x0a, 0x96, 0xfb, 0x24, 0xbe, 0xc7, 0x3c, 0xe4, 0x36, 0xe7, 0xe2, 0x6f, - 0xe7, 0x0f, 0x6a, 0xd0, 0xdc, 0x8b, 0x53, 0x76, 0x14, 0x87, 0x41, 0x2c, 0xdd, 0x7b, 0xee, 0x8e, - 0x28, 0xf7, 0x5f, 0xfa, 0x91, 0xf2, 0x93, 0xef, 0x90, 0xdd, 0x38, 0x88, 0x84, 0xad, 0xd6, 0xa5, - 0x82, 0xe2, 0x20, 0xe2, 0xa6, 0x4a, 0xae, 0xc3, 0xac, 0x4f, 0xb3, 0x6e, 0x1a, 0x24, 0x3c, 0x9c, - 0x93, 0xdb, 0x82, 0x09, 0xe2, 0x84, 0x0f, 0xbd, 0xd0, 0x8b, 0xba, 0x54, 0xee, 0xec, 0xea, 0xd3, - 0x59, 0xc5, 0xed, 0x4a, 0x4b, 0x62, 0x44, 0xd6, 0x36, 0x58, 0x0e, 0xe5, 0x57, 0xa1, 0x91, 0x28, - 0xa0, 0x34, 0xbf, 0x96, 0x3e, 0xab, 0x0b, 0xc3, 0x71, 0x73, 0x54, 0x67, 0x83, 0xfb, 0xfe, 0x39, - 0xbd, 0xfd, 0x41, 0xbf, 0xef, 0xa5, 0x67, 0x8a, 0x5b, 0x04, 0x93, 0x3b, 0x71, 0x10, 0x71, 0x45, - 0xf1, 0x41, 0x29, 0xe7, 0x8d, 0xff, 0x36, 0x45, 0xaf, 0x5b, 0xa2, 0x9b, 0xda, 0x9a, 0xb0, 0xb5, - 0x75, 0x0d, 0x20, 0xa1, 0x69, 0x97, 0x46, 0xcc, 0xeb, 0xa9, 0x11, 0x1b, 0x10, 0xe7, 0x18, 0xc8, - 0x93, 0xa3, 0xa3, 0x30, 0x88, 0x28, 0x67, 0x2b, 0x85, 0x19, 0xa1, 0xfd, 0xe1, 0x32, 0xd8, 0x9c, - 0x26, 0x4a, 0x9c, 0x7e, 0x04, 0x4b, 0x4f, 0xa2, 0x0a, 0x46, 0x8a, 0x5c, 0x6d, 0x14, 0xb9, 0x7a, - 0x89, 0xdc, 0x07, 0x30, 0x67, 0x08, 0x9e, 0x91, 0x77, 0xa0, 0x21, 0x65, 0xd4, 0x81, 0x42, 0x5b, - 0xef, 0x06, 0xa5, 0x11, 0xba, 0x39, 0xb2, 0xf3, 0xe7, 0x35, 0x98, 0xcd, 0x25, 0xcb, 0xc8, 0x3d, - 0xb8, 0xc4, 0xd5, 0xad, 0xa8, 0x5c, 0xd3, 0x54, 0x72, 0x9c, 0x2d, 0xfc, 0x57, 0xf8, 0x85, 0x02, - 0xb9, 0xbd, 0x0f, 0x90, 0x03, 0x2b, 0xdc, 0xba, 0x3b, 0xb6, 0x5b, 0x77, 0xb9, 0x4c, 0x55, 0x89, - 0x66, 0x78, 0x76, 0xff, 0x32, 0xc9, 0xc3, 0xbd, 0x0a, 0x63, 0x91, 0x36, 0xf8, 0x06, 0xcc, 0x8a, - 0xb5, 0xc0, 0x77, 0x00, 0x25, 0xf0, 0x5c, 0x7e, 0xb5, 0x11, 0x44, 0x2e, 0xe0, 0xda, 0xc0, 0x76, - 0xf2, 0x16, 0xcc, 0xa3, 0xb0, 0x9d, 0x58, 0x28, 0x44, 0x2e, 0x6c, 0xbb, 0xc3, 0x1c, 0xa2, 0x48, - 0x95, 0x91, 0x04, 0x56, 0xad, 0x2e, 0x9d, 0x4c, 0x88, 0x20, 0x0f, 0xa9, 0xf7, 0x0c, 0x57, 0x7a, - 0x98, 0x94, 0x42, 0x59, 0x92, 0xa0, 0x6c, 0x13, 0xaa, 0x5b, 0xee, 0x96, 0x5b, 0xc8, 0x1d, 0x98, - 0x93, 0x1c, 0x51, 0x33, 0xf2, 0x88, 0xb3, 0x65, 0x9c, 0x15, 0x1d, 0x11, 0x81, 0xf4, 0x61, 0xc5, - 0xec, 0xa0, 0x25, 0xbc, 0x84, 0x1d, 0xdf, 0x1d, 0x5f, 0xc2, 0xa8, 0x24, 0x20, 0xe9, 0x96, 0x1a, - 0xda, 0xbf, 0x09, 0xad, 0x61, 0x03, 0xaa, 0x98, 0xf6, 0x57, 0xed, 0x69, 0x5f, 0xa9, 0x30, 0xc9, - 0xcc, 0xbc, 0x40, 0xfc, 0x14, 0xd6, 0x87, 0x08, 0x73, 0x81, 0x5b, 0x07, 0xc3, 0x52, 0x4d, 0x6b, - 0xfa, 0xd3, 0x1a, 0xb4, 0xb7, 0x7d, 0xbf, 0xb4, 0x39, 0xe5, 0x97, 0x04, 0xdf, 0xf6, 0x96, 0x7b, - 0x15, 0xae, 0x54, 0x0a, 0x24, 0x6f, 0x33, 0x9e, 0xc3, 0x55, 0x97, 0xf6, 0xe3, 0x13, 0xfa, 0x6d, - 0x8b, 0xec, 0x5c, 0x87, 0x6b, 0xc3, 0x38, 0x4b, 0xd9, 0xf0, 0x7a, 0xcf, 0xbe, 0x1e, 0xd7, 0x8e, - 0xd1, 0x7f, 0xd6, 0x60, 0xde, 0xbe, 0x38, 0x7f, 0x51, 0xb1, 0xf8, 0xeb, 0x40, 0x52, 0x9a, 0xb1, - 0x4e, 0x12, 0x87, 0x21, 0x0f, 0xc9, 0x7d, 0x1a, 0x7a, 0x67, 0xf2, 0xca, 0xbe, 0xc9, 0x5b, 0xf6, - 0x44, 0xc3, 0x03, 0x0e, 0x27, 0xeb, 0x30, 0xed, 0x25, 0x41, 0x87, 0x5b, 0x8d, 0x88, 0xc7, 0xa7, - 0xbc, 0x24, 0xf8, 0x90, 0x9e, 0x11, 0x07, 0xe6, 0x65, 0x43, 0x27, 0xa4, 0x27, 0x34, 0x44, 0x9f, - 0x6f, 0xc2, 0x9d, 0x15, 0xcd, 0x8f, 0x39, 0x88, 0xdc, 0x86, 0x66, 0x92, 0x06, 0xdc, 0xfc, 0xf2, - 0xdc, 0xc0, 0x34, 0x4a, 0xb3, 0x28, 0xe1, 0x6a, 0x74, 0xce, 0x4f, 0xe0, 0x72, 0x85, 0x2e, 0xe4, - 0x1e, 0xf5, 0x7d, 0x58, 0xb4, 0x33, 0x0c, 0x6a, 0x9f, 0xd2, 0x5e, 0xab, 0xd5, 0xd1, 0x5d, 0x38, - 0xb2, 0xe8, 0x48, 0xef, 0x13, 0x71, 0x5c, 0x8f, 0xe9, 0x3b, 0x2d, 0xe7, 0x73, 0x58, 0xc9, 0x81, - 0x3b, 0x71, 0x74, 0x42, 0xd3, 0x8c, 0x5b, 0x1b, 0x81, 0xc9, 0xa3, 0x34, 0x56, 0x17, 0xb2, 0xf8, - 0x9b, 0xfb, 0x6d, 0x2c, 0x96, 0x66, 0x50, 0x67, 0x31, 0xc7, 0x49, 0x3d, 0xa6, 0x4e, 0x29, 0xfc, - 0xcd, 0xfd, 0xe4, 0x00, 0x89, 0xd0, 0x0e, 0xb6, 0x09, 0x53, 0x9d, 0x95, 0x30, 0xce, 0xc5, 0x79, - 0x8a, 0xee, 0xa3, 0x29, 0x8a, 0x1c, 0xe3, 0xaf, 0xc3, 0xac, 0x18, 0x23, 0xef, 0xa9, 0xc6, 0xb7, - 0x61, 0x8d, 0xaf, 0x20, 0xa6, 0x0b, 0x47, 0x1a, 0xea, 0xfc, 0x77, 0x1d, 0xe6, 0xd0, 0x63, 0x7d, - 0x40, 0x99, 0x17, 0x84, 0xa3, 0x7d, 0x69, 0xe1, 0x83, 0xd6, 0xb5, 0x0f, 0xfa, 0x12, 0xcc, 0x9b, - 0x17, 0x22, 0x67, 0x2a, 0x98, 0x35, 0xae, 0x43, 0xce, 0xc8, 0xcb, 0xb0, 0x80, 0xa1, 0x75, 0x8e, - 0x25, 0x6c, 0x66, 0x1e, 0xa1, 0x1a, 0xcd, 0x0e, 0x04, 0x2e, 0x15, 0x02, 0x01, 0xde, 0x8c, 0xce, - 0x74, 0x27, 0x0b, 0x7c, 0x1d, 0x27, 0x20, 0x64, 0x3f, 0xf0, 0x8d, 0x66, 0xec, 0x3d, 0x6d, 0x34, - 0x63, 0x6f, 0x1e, 0x03, 0xa5, 0x54, 0x24, 0x0a, 0x30, 0xdf, 0x35, 0x83, 0x46, 0x37, 0xa7, 0x80, - 0x07, 0x41, 0x1f, 0xb3, 0x61, 0xf2, 0x72, 0xbb, 0x21, 0x2c, 0x56, 0x7c, 0xe5, 0x61, 0x1a, 0x98, - 0x61, 0x5a, 0x1e, 0xd4, 0xcd, 0x5a, 0x41, 0xdd, 0x26, 0xcc, 0xc6, 0x09, 0x8d, 0x3a, 0x32, 0xc4, - 0x9e, 0x13, 0xde, 0x03, 0x07, 0x3d, 0x45, 0x88, 0xbc, 0x32, 0x41, 0x9d, 0x67, 0xe3, 0xc4, 0xa5, - 0xb6, 0x62, 0xea, 0x45, 0xc5, 0xa8, 0x40, 0x70, 0xe2, 0xbc, 0x40, 0xd0, 0xd9, 0x46, 0xaf, 0x58, - 0x31, 0x96, 0xe6, 0xf3, 0x3a, 0x4c, 0xa1, 0x9a, 0x94, 0xe5, 0xac, 0x58, 0x61, 0x8c, 0x34, 0x0a, - 0x57, 0xe2, 0x38, 0x1f, 0x60, 0x0e, 0x11, 0x9b, 0xc6, 0x11, 0xfd, 0x32, 0xcc, 0x88, 0x59, 0xd1, - 0x56, 0x33, 0x8d, 0xdf, 0x8f, 0x7c, 0xe7, 0xdf, 0x6a, 0x40, 0xf6, 0x07, 0x87, 0xfd, 0x60, 0x7c, - 0x6a, 0xe3, 0x07, 0xe8, 0x04, 0x26, 0xd1, 0x4c, 0x84, 0x39, 0xe2, 0xef, 0x82, 0x85, 0x4c, 0x16, - 0x2d, 0x24, 0x9f, 0xce, 0x4b, 0xd5, 0x31, 0xfa, 0x94, 0x39, 0xf9, 0x7c, 0x8b, 0x0f, 0x03, 0x1a, - 0xb1, 0x8e, 0xbc, 0x6c, 0xe1, 0x5b, 0x3c, 0x02, 0x1e, 0xf9, 0xce, 0x3e, 0x2c, 0x5b, 0x23, 0x93, - 0x9a, 0xbe, 0x01, 0x73, 0x42, 0x80, 0x24, 0xf4, 0xba, 0xfa, 0x36, 0x7c, 0x16, 0x61, 0x7b, 0x08, - 0x1a, 0xa5, 0xaf, 0x3f, 0xaa, 0xc1, 0xca, 0x7e, 0xd0, 0x1f, 0x84, 0x1e, 0xa3, 0xbf, 0x00, 0x8d, - 0xe5, 0xc3, 0x9f, 0xb0, 0x86, 0xaf, 0x34, 0x39, 0x99, 0x6b, 0xd2, 0xf9, 0x9f, 0x1a, 0xac, 0x16, - 0x44, 0xd1, 0x3e, 0xa1, 0x6d, 0x4c, 0x43, 0x2e, 0x07, 0x24, 0x92, 0xc1, 0xb4, 0x6e, 0x31, 0x7d, - 0x09, 0xe6, 0xfb, 0x41, 0x14, 0xf4, 0x07, 0xfd, 0x8e, 0xd0, 0xbd, 0x90, 0x69, 0x4e, 0x02, 0xf7, - 0x70, 0x0a, 0x38, 0x92, 0xf7, 0xdc, 0x40, 0x9a, 0x94, 0x48, 0x02, 0x28, 0x90, 0xde, 0x84, 0x95, - 0xdc, 0x6f, 0xef, 0xf4, 0xbc, 0x20, 0xea, 0x84, 0x71, 0x96, 0xc9, 0x39, 0x26, 0x79, 0xdb, 0xae, - 0x17, 0x44, 0x8f, 0xe3, 0x2c, 0x33, 0x36, 0x81, 0x29, 0x73, 0x13, 0xe0, 0x0e, 0x4c, 0xf3, 0x93, - 0x63, 0x2f, 0xa4, 0xf7, 0xe3, 0xfe, 0xe1, 0x8b, 0xd5, 0xfd, 0x0d, 0x98, 0x13, 0xf7, 0x6e, 0xcc, - 0x4b, 0x7b, 0x54, 0xcd, 0xc0, 0x2c, 0xc2, 0x0e, 0x10, 0x54, 0x39, 0x0d, 0xff, 0x55, 0x03, 0xb2, - 0xc3, 0x5d, 0x99, 0x70, 0x6c, 0x7b, 0xe0, 0x5b, 0x89, 0x88, 0x9b, 0x73, 0x0b, 0x6b, 0x48, 0xc8, - 0x23, 0xdb, 0xfc, 0x26, 0x2c, 0xf3, 0xd3, 0xa3, 0x99, 0xbc, 0xe0, 0xe5, 0x58, 0x69, 0x1f, 0x7f, - 0x19, 0x16, 0x4e, 0xbd, 0x30, 0xa4, 0x4c, 0xa7, 0xd8, 0xe4, 0x4d, 0xbc, 0x80, 0xaa, 0x18, 0x5c, - 0x0d, 0x78, 0xda, 0x18, 0xf0, 0x2a, 0x2c, 0x5b, 0xe3, 0x95, 0xde, 0xd0, 0x3d, 0x58, 0x13, 0xe0, - 0xed, 0x30, 0x1c, 0x7b, 0x57, 0x75, 0xfe, 0xb2, 0x0e, 0xeb, 0xa5, 0x6e, 0xda, 0x6d, 0xb0, 0xcd, - 0xf8, 0xa6, 0x1e, 0x6e, 0x75, 0x87, 0x2d, 0xf9, 0x29, 0x7b, 0xb5, 0xff, 0xa9, 0x06, 0x53, 0x02, - 0x34, 0x72, 0x36, 0x3e, 0x55, 0x1b, 0x82, 0x34, 0x38, 0x11, 0x11, 0x7d, 0x77, 0x3c, 0x66, 0xe2, - 0x3f, 0x33, 0xad, 0x2a, 0x76, 0x12, 0x99, 0x51, 0xfd, 0x3e, 0x34, 0x8b, 0x08, 0x17, 0x4a, 0x39, - 0x89, 0x5b, 0x95, 0x87, 0x27, 0xd4, 0x48, 0xa3, 0xfe, 0xbc, 0x06, 0x8b, 0x3b, 0x71, 0xe4, 0x07, - 0xfc, 0xc4, 0xdc, 0xf3, 0x52, 0xaf, 0x9f, 0xc9, 0x4c, 0xbe, 0x00, 0xa9, 0x6b, 0x77, 0x0d, 0x18, - 0x72, 0xc1, 0x79, 0x15, 0xa0, 0x7b, 0x4c, 0xbb, 0xcf, 0x3a, 0xf2, 0xc6, 0x51, 0xa4, 0xff, 0x39, - 0xe4, 0x7e, 0xe0, 0x67, 0xe4, 0x0d, 0x58, 0xce, 0x9b, 0x3b, 0x5e, 0xe4, 0x77, 0xe4, 0x75, 0x23, - 0x66, 0x37, 0x34, 0xde, 0x76, 0xe4, 0x6f, 0x67, 0xcf, 0x32, 0xee, 0x2b, 0xea, 0x5b, 0xb6, 0x8e, - 0xb5, 0x85, 0x2f, 0x6a, 0xf8, 0x36, 0x82, 0x9d, 0xff, 0xad, 0xe1, 0x09, 0xa8, 0x46, 0x25, 0x67, - 0x3b, 0xbf, 0x58, 0xc3, 0xfb, 0x56, 0x6b, 0xca, 0xea, 0x85, 0x29, 0x23, 0x30, 0x19, 0x30, 0xda, - 0x57, 0x07, 0x0b, 0xff, 0x4d, 0xee, 0x43, 0x53, 0x8f, 0xb8, 0x93, 0xa0, 0x5a, 0xe4, 0x32, 0x59, - 0xcf, 0x03, 0x47, 0x4b, 0x6b, 0xee, 0x62, 0xb7, 0xa0, 0x46, 0xb5, 0xbc, 0x2e, 0x8d, 0xb5, 0x51, - 0x77, 0x51, 0xdb, 0x72, 0x7f, 0x12, 0x5f, 0x42, 0x6a, 0xda, 0x1d, 0x30, 0xea, 0x4b, 0x57, 0x59, - 0x7f, 0x3b, 0xff, 0x51, 0x83, 0xc5, 0x6d, 0xdf, 0xc7, 0x71, 0x8f, 0xb3, 0x4d, 0xa8, 0x51, 0xd6, - 0xcf, 0x19, 0xe5, 0xc4, 0xd7, 0x1c, 0xe5, 0x37, 0xde, 0x44, 0x86, 0x28, 0xc1, 0x71, 0xa0, 0x99, - 0x8f, 0xb3, 0x7a, 0x7a, 0x9d, 0xef, 0x00, 0x11, 0xe1, 0x95, 0xa5, 0x8e, 0x22, 0xd6, 0x2a, 0x2c, - 0x5b, 0x58, 0x72, 0xaf, 0x79, 0x1f, 0x6e, 0xed, 0x52, 0xb6, 0x93, 0x9e, 0x25, 0x2c, 0x56, 0xee, - 0xec, 0x03, 0x9a, 0xc4, 0x59, 0xa0, 0x76, 0x2e, 0x3a, 0xd6, 0xee, 0xf3, 0xcf, 0x35, 0xb8, 0x3d, - 0x06, 0x21, 0x39, 0x84, 0xcf, 0xca, 0xf7, 0x4b, 0xbf, 0x61, 0x96, 0xb7, 0x8c, 0x45, 0x65, 0x4b, - 0x43, 0x64, 0x95, 0x81, 0x26, 0xd9, 0x7e, 0x0f, 0x16, 0xec, 0xc6, 0x0b, 0x6d, 0x15, 0x21, 0xdc, - 0x3c, 0x47, 0x88, 0x71, 0x6c, 0xee, 0x26, 0x2c, 0x74, 0x2d, 0x12, 0x92, 0x51, 0x01, 0xea, 0xec, - 0xc0, 0x2b, 0xe7, 0x72, 0x93, 0x6a, 0x1b, 0x1a, 0xa1, 0x3b, 0x7f, 0x37, 0x09, 0xeb, 0x9f, 0x04, - 0xec, 0xd8, 0x4f, 0xbd, 0x53, 0x65, 0x7d, 0xe3, 0x08, 0x59, 0x08, 0xde, 0xeb, 0xe5, 0xfb, 0x86, - 0x57, 0x61, 0x29, 0x8e, 0x28, 0xc6, 0x18, 0x9d, 0xc4, 0xcb, 0xb2, 0xd3, 0x38, 0x55, 0x67, 0xe9, - 0x62, 0x1c, 0x51, 0x1e, 0x67, 0xec, 0x49, 0x70, 0xe1, 0x34, 0x9e, 0x2c, 0x9e, 0xc6, 0x4d, 0x98, - 0x48, 0x82, 0x48, 0xe6, 0x4c, 0xf8, 0x4f, 0x7e, 0x76, 0xb2, 0xd4, 0xf3, 0x0d, 0xca, 0xf2, 0xec, - 0x44, 0xa8, 0xa6, 0x6b, 0xde, 0xe2, 0x4f, 0x17, 0x6e, 0xf1, 0x0d, 0x9d, 0xcc, 0xd8, 0xb7, 0x16, - 0x9b, 0x30, 0x2b, 0x7f, 0x76, 0x98, 0xd7, 0x93, 0x21, 0x10, 0x48, 0xd0, 0x81, 0xd7, 0x33, 0xbc, - 0x35, 0xb0, 0xbc, 0xb5, 0xab, 0x00, 0x47, 0x94, 0x76, 0xac, 0x60, 0xa8, 0x71, 0x44, 0xa9, 0xd8, - 0x74, 0xb9, 0xab, 0x7c, 0xe8, 0x45, 0xcf, 0x3a, 0x78, 0x07, 0x31, 0x27, 0xc4, 0xe1, 0x80, 0x8f, - 0xbc, 0x3e, 0xfa, 0xc4, 0xd8, 0xa8, 0x64, 0x9a, 0x17, 0x1a, 0xe5, 0xb0, 0xed, 0xfc, 0x36, 0x05, - 0x51, 0xba, 0x01, 0x3b, 0x6b, 0x2d, 0xe4, 0xfd, 0x77, 0x02, 0x76, 0xa6, 0xfb, 0xa3, 0xce, 0xd2, - 0xb3, 0xd6, 0x62, 0xde, 0x7f, 0x47, 0x80, 0xb8, 0x78, 0xd9, 0x69, 0x70, 0x44, 0x45, 0x61, 0x48, - 0x53, 0x96, 0x4a, 0x71, 0xc8, 0x4e, 0xec, 0xa3, 0x1b, 0x79, 0x1a, 0xa4, 0x46, 0x70, 0xba, 0x24, - 0x42, 0x58, 0x0e, 0x54, 0xa6, 0xe1, 0xbc, 0x0a, 0x4d, 0x65, 0x2e, 0x66, 0xed, 0x64, 0x4a, 0xb3, - 0x41, 0xc8, 0x54, 0xed, 0xa4, 0xf8, 0x72, 0xde, 0xc2, 0xaa, 0x88, 0xc7, 0x71, 0xaf, 0x97, 0x87, - 0x4f, 0xd2, 0xb4, 0xd6, 0x60, 0x2a, 0x44, 0xb8, 0xea, 0x22, 0xbe, 0x9c, 0x08, 0xef, 0x73, 0x0a, - 0x5d, 0xf2, 0xac, 0x45, 0x10, 0x1d, 0xc5, 0x32, 0x5a, 0xc0, 0xdf, 0x7c, 0x2d, 0xfa, 0xf4, 0x70, - 0xd0, 0x53, 0x35, 0x50, 0xf8, 0xc1, 0x31, 0x4f, 0xbd, 0x34, 0x92, 0x07, 0x2a, 0xfe, 0xe6, 0x98, - 0x34, 0x4d, 0xe3, 0x54, 0x9e, 0x9e, 0xe2, 0xc3, 0xd9, 0x85, 0xf5, 0xfd, 0x8b, 0x89, 0xc8, 0x09, - 0x89, 0xdb, 0x1a, 0xb9, 0xfc, 0xf1, 0xc3, 0xf9, 0xd0, 0xaa, 0x00, 0xc1, 0x2a, 0x81, 0x71, 0x96, - 0xd1, 0x0a, 0x5c, 0xc2, 0xbd, 0x5c, 0x11, 0xc3, 0x0f, 0x1e, 0x11, 0xb6, 0xca, 0xd4, 0x74, 0x0d, - 0x5a, 0xb9, 0xa2, 0x42, 0xec, 0x84, 0xbf, 0x52, 0x51, 0x51, 0x61, 0xf5, 0x1d, 0xaf, 0xa4, 0xe2, - 0x17, 0x5a, 0x25, 0xf1, 0x05, 0x2c, 0x9b, 0xa2, 0x7d, 0xab, 0x51, 0xff, 0xcf, 0x6a, 0x78, 0x43, - 0xa6, 0x23, 0xb0, 0x7d, 0x96, 0x52, 0xaf, 0xff, 0xad, 0x26, 0xc4, 0x7f, 0x00, 0x37, 0xcc, 0x7a, - 0xa9, 0x0b, 0x4b, 0xe2, 0xfc, 0x2e, 0xa6, 0x11, 0x45, 0x92, 0xff, 0x97, 0x20, 0xff, 0x7b, 0x70, - 0xcd, 0x90, 0xff, 0x82, 0x62, 0x38, 0x7f, 0x51, 0xc3, 0x5b, 0xc4, 0xed, 0x81, 0x1f, 0x30, 0xcb, - 0xe7, 0xe0, 0x3b, 0x13, 0xf3, 0x52, 0xd6, 0xf1, 0x3d, 0x46, 0x75, 0x11, 0x27, 0x87, 0x3c, 0xf0, - 0x18, 0x5e, 0x9e, 0xd0, 0xc8, 0x17, 0x8d, 0xf2, 0x32, 0x80, 0x46, 0xbe, 0x6a, 0x12, 0x91, 0xc3, - 0xe1, 0x99, 0x15, 0xa8, 0xdd, 0xc7, 0x73, 0x1a, 0x8b, 0x5e, 0x70, 0xc5, 0x5f, 0x72, 0xc5, 0x07, - 0x5f, 0xd6, 0xf1, 0xd1, 0x11, 0x5f, 0x72, 0x97, 0x10, 0x2c, 0xbf, 0x9c, 0x1d, 0x91, 0x94, 0x36, - 0x44, 0x93, 0xeb, 0xed, 0x55, 0x98, 0xa2, 0xe8, 0x26, 0x17, 0xb3, 0xdb, 0x06, 0xae, 0xc4, 0x70, - 0xfe, 0x4a, 0x58, 0xd8, 0x07, 0x41, 0xc6, 0xe2, 0x34, 0xe8, 0xee, 0x78, 0x91, 0x1f, 0x8e, 0xe5, - 0x06, 0x5d, 0x60, 0x86, 0x36, 0xa0, 0x91, 0x62, 0xfd, 0x5f, 0xf0, 0x05, 0x95, 0xb5, 0x11, 0x39, - 0x80, 0x9f, 0xcb, 0xbd, 0xd4, 0x8b, 0x06, 0xa1, 0x97, 0xf2, 0x53, 0x62, 0x52, 0xdc, 0x28, 0x1b, - 0x20, 0xe7, 0x01, 0x66, 0x3e, 0x4b, 0x22, 0xca, 0xd1, 0xde, 0x84, 0xa9, 0x2e, 0x82, 0xe4, 0x68, - 0x17, 0x8c, 0x18, 0xcc, 0x0f, 0xa9, 0x2b, 0x5b, 0x9d, 0xdf, 0xaf, 0xc1, 0x94, 0x00, 0xf1, 0xdd, - 0x56, 0x17, 0xce, 0x4f, 0xb8, 0xf8, 0x5b, 0x95, 0xe3, 0xd4, 0xf3, 0x72, 0x1c, 0x55, 0xb4, 0x33, - 0x61, 0x14, 0xed, 0x10, 0x98, 0x8c, 0x13, 0x1a, 0xa9, 0xe2, 0x1e, 0xfe, 0x9b, 0xcf, 0x5a, 0x37, - 0x8c, 0x33, 0x2a, 0x23, 0x17, 0xf1, 0x61, 0x14, 0xea, 0x4c, 0x99, 0x85, 0x3a, 0xce, 0x73, 0x80, - 0x7c, 0x1a, 0x50, 0x12, 0x6e, 0xb6, 0xf2, 0xd2, 0x99, 0xff, 0x26, 0xd7, 0x00, 0x02, 0x9f, 0x46, - 0x2c, 0x38, 0x0a, 0xa8, 0x2a, 0xf8, 0x30, 0x20, 0xdc, 0x0d, 0xe8, 0xd3, 0x2c, 0x53, 0xd9, 0xd2, - 0x86, 0xab, 0x3e, 0xb9, 0xa2, 0xf9, 0x58, 0x32, 0xe6, 0xf5, 0x13, 0xe5, 0x93, 0x68, 0x80, 0x73, - 0x08, 0x8d, 0xdd, 0x9d, 0x83, 0x7d, 0x74, 0x77, 0x38, 0xe3, 0x8f, 0x3f, 0x7e, 0xf4, 0x40, 0x31, - 0xe6, 0xbf, 0x75, 0xb2, 0xa1, 0x6e, 0x24, 0x1b, 0x08, 0x9f, 0x65, 0x76, 0xac, 0x82, 0x26, 0xfe, - 0x9b, 0x5b, 0x70, 0x44, 0x9f, 0xb3, 0x4e, 0x3a, 0x88, 0x24, 0x97, 0x69, 0xfe, 0xed, 0x0e, 0x22, - 0xe7, 0x01, 0xac, 0x6b, 0x1e, 0x0f, 0x45, 0x08, 0xa3, 0x6c, 0xe9, 0x36, 0x4c, 0x09, 0x57, 0x4b, - 0x96, 0xbd, 0x2c, 0xe9, 0xbd, 0x5f, 0x75, 0x70, 0x25, 0x82, 0xb3, 0x0d, 0x2b, 0x1a, 0xb8, 0xcf, - 0xe2, 0xe4, 0x6b, 0x90, 0xb8, 0x6c, 0x08, 0xc2, 0x49, 0x6c, 0x87, 0xa1, 0x0a, 0x85, 0x5b, 0xb0, - 0x66, 0x34, 0xf1, 0x10, 0x5b, 0xb5, 0x98, 0x9d, 0x1e, 0x07, 0x19, 0x33, 0x3a, 0xfd, 0x4d, 0xcd, - 0xe8, 0xf5, 0x71, 0x12, 0xc6, 0x9e, 0xaf, 0xa4, 0xda, 0x84, 0x59, 0xc1, 0xb4, 0x63, 0xa4, 0x6a, - 0x40, 0x80, 0xd0, 0x51, 0xca, 0x11, 0xb0, 0x86, 0xa1, 0x6e, 0x22, 0x3c, 0xf0, 0x98, 0xa7, 0xab, - 0x1b, 0x26, 0xf2, 0xea, 0x06, 0xbe, 0xf4, 0xbc, 0xb4, 0x7b, 0x1c, 0x9c, 0x50, 0x5f, 0x3a, 0x00, - 0xfa, 0x9b, 0xcf, 0x73, 0x7c, 0x42, 0xd3, 0xd3, 0x34, 0x60, 0xc2, 0xea, 0x66, 0xdc, 0x1c, 0xe0, - 0xec, 0x42, 0x3b, 0xd7, 0x07, 0xf5, 0x7c, 0xf5, 0xeb, 0xc2, 0x3a, 0xbc, 0x0f, 0xab, 0x1a, 0xf8, - 0xe3, 0x01, 0xd5, 0xc5, 0x06, 0x17, 0xa1, 0xf1, 0x43, 0x68, 0x69, 0xe0, 0xf6, 0x80, 0xc5, 0x8f, - 0x0d, 0xc5, 0xad, 0x59, 0x64, 0x1a, 0xaa, 0x8f, 0x71, 0x8d, 0x27, 0x7c, 0x24, 0x75, 0x8d, 0xf7, - 0x99, 0x35, 0xa7, 0x62, 0xe2, 0x72, 0x87, 0x4e, 0xd7, 0xb6, 0x9b, 0xd7, 0xff, 0xaf, 0xc1, 0xb4, - 0x20, 0xaa, 0x6e, 0x68, 0x2a, 0x44, 0x55, 0x18, 0x4e, 0x6c, 0x4c, 0xb1, 0x1c, 0xef, 0x39, 0xe4, - 0x73, 0x45, 0xd4, 0xcf, 0x51, 0x84, 0x35, 0xc7, 0x0d, 0x59, 0xc1, 0xf2, 0xbe, 0xa1, 0x1c, 0x59, - 0x9d, 0x7d, 0x2e, 0x4b, 0x45, 0xa7, 0x9e, 0xd3, 0xb9, 0xfb, 0xd5, 0x3d, 0x58, 0xd8, 0x8d, 0x45, - 0x5c, 0x75, 0xc0, 0xc3, 0x89, 0x94, 0x3c, 0x81, 0x69, 0xf9, 0x8e, 0x85, 0xac, 0x95, 0x1e, 0xb6, - 0xa0, 0xfa, 0xdb, 0xeb, 0x43, 0x1e, 0xbc, 0x38, 0xcb, 0x5f, 0xfe, 0xeb, 0xbf, 0x7f, 0x55, 0x9f, - 0x27, 0xb3, 0x77, 0x4e, 0xde, 0xba, 0xd3, 0xa3, 0x0c, 0xfd, 0xd6, 0x1e, 0xcc, 0x5b, 0x4f, 0x0f, - 0xc8, 0x86, 0xf5, 0x7c, 0xa0, 0xf0, 0x22, 0xa1, 0x7d, 0x75, 0xe4, 0xe3, 0x02, 0xe7, 0x32, 0xb2, - 0x58, 0x26, 0x4b, 0x92, 0x45, 0xfe, 0xaa, 0x80, 0x7c, 0x0e, 0x8b, 0x0f, 0x31, 0x9f, 0xa9, 0x89, - 0x92, 0xcd, 0x9c, 0x58, 0xe5, 0x8b, 0x8a, 0xf6, 0xf5, 0xe1, 0x08, 0x92, 0xe1, 0x15, 0x64, 0xb8, - 0x4a, 0x96, 0x39, 0x43, 0x91, 0x2f, 0xd5, 0x3c, 0x49, 0x06, 0x4d, 0x59, 0xa3, 0xfd, 0x42, 0x79, - 0x6e, 0x20, 0xcf, 0x35, 0xb2, 0xc2, 0x79, 0xfa, 0x82, 0x41, 0xce, 0x34, 0xc6, 0x74, 0x8c, 0xf9, - 0xa6, 0x80, 0x5c, 0x1b, 0xfa, 0xd8, 0x40, 0xb0, 0xdc, 0x3c, 0xe7, 0x31, 0x82, 0x3d, 0xca, 0x1e, - 0xe5, 0xb8, 0xfa, 0x3d, 0x02, 0xf9, 0x4a, 0xf8, 0xe8, 0x95, 0xaf, 0x5f, 0xc8, 0x2b, 0xe7, 0x3f, - 0xb9, 0x11, 0x32, 0xdc, 0x1a, 0xf7, 0x6d, 0x8e, 0xf3, 0x1d, 0x14, 0xe6, 0x1a, 0xd9, 0x90, 0xc2, - 0x58, 0xef, 0x71, 0xd4, 0x8b, 0x1f, 0xd2, 0x85, 0x39, 0xf3, 0x21, 0x01, 0xb9, 0x52, 0x11, 0x12, - 0x68, 0xe6, 0x1b, 0xd5, 0x8d, 0x92, 0x61, 0x0b, 0x19, 0x12, 0xd2, 0x94, 0x0c, 0xf5, 0xbb, 0x03, - 0xf2, 0x05, 0x2c, 0x16, 0x8a, 0xf0, 0x89, 0x53, 0x98, 0xbe, 0x8a, 0x07, 0x15, 0xed, 0x97, 0x46, - 0xe2, 0x48, 0xae, 0xd7, 0x90, 0x6b, 0xcb, 0x59, 0x36, 0x66, 0x59, 0x71, 0xfe, 0x5e, 0xed, 0x55, - 0x92, 0xe1, 0x3c, 0x9b, 0xf5, 0xe2, 0x63, 0xf1, 0xde, 0x3c, 0xa7, 0xd8, 0xbc, 0x34, 0xd7, 0x8a, - 0x27, 0xae, 0xd6, 0x0c, 0x6b, 0x70, 0x8d, 0x57, 0x0e, 0x18, 0x2f, 0x8f, 0xc3, 0xf7, 0x6a, 0xf5, - 0x2b, 0x09, 0xf9, 0x50, 0xc3, 0x69, 0x23, 0xd7, 0x15, 0x42, 0x0a, 0x5c, 0x63, 0x96, 0x90, 0xcc, - 0x7a, 0x44, 0x22, 0x99, 0xda, 0x56, 0x5d, 0xf1, 0x8c, 0xa3, 0x72, 0xa4, 0xe6, 0xbb, 0x8c, 0xa1, - 0x23, 0x8d, 0x59, 0x92, 0x91, 0xe7, 0xb0, 0x20, 0xb6, 0x8b, 0x17, 0x3f, 0xb3, 0x57, 0x91, 0xef, - 0xba, 0x43, 0xf2, 0x3d, 0xc3, 0x9c, 0xd8, 0x4f, 0xa0, 0xa1, 0x03, 0x1b, 0xd2, 0x32, 0x06, 0x61, - 0x55, 0xd4, 0xb7, 0x87, 0xd4, 0x4b, 0x2b, 0x6b, 0x75, 0xe6, 0xe5, 0xa8, 0x44, 0xf5, 0x33, 0x27, - 0xfc, 0x13, 0x80, 0xbc, 0x80, 0x9a, 0x5c, 0x2e, 0x51, 0xd6, 0x9a, 0x6b, 0x57, 0x35, 0xa9, 0xa7, - 0x62, 0x48, 0xbe, 0x49, 0x16, 0x2c, 0xf2, 0x6a, 0xbd, 0xe9, 0x38, 0xce, 0x5a, 0x6f, 0xc5, 0x92, - 0xeb, 0xf6, 0xf0, 0x5a, 0x5b, 0x35, 0x29, 0x8e, 0x5a, 0x6c, 0xfa, 0xbe, 0x9e, 0x8f, 0x40, 0x1c, - 0x16, 0x46, 0x91, 0xef, 0x46, 0x15, 0x97, 0xca, 0xc3, 0xa2, 0x5c, 0xb1, 0x5b, 0x3a, 0x2c, 0xf2, - 0xc2, 0x5c, 0xf2, 0x0c, 0x9f, 0xca, 0x1a, 0x35, 0xaa, 0xc4, 0xa4, 0x55, 0x2e, 0xd8, 0x6d, 0x5f, - 0x1b, 0xd6, 0x9c, 0x55, 0xdb, 0xb7, 0xbc, 0xd2, 0xc3, 0x45, 0x25, 0x26, 0x5c, 0x54, 0xa6, 0x5a, - 0x13, 0x6e, 0x15, 0xb0, 0xb6, 0x2f, 0x57, 0xb4, 0x48, 0xea, 0xab, 0x48, 0x7d, 0x91, 0xcc, 0xeb, - 0x2d, 0x11, 0x69, 0x89, 0x39, 0xd1, 0x25, 0x43, 0xd6, 0x9c, 0x14, 0xeb, 0x4a, 0xad, 0x3d, 0xb0, - 0x54, 0x5d, 0x5a, 0xda, 0x03, 0x75, 0xfd, 0x28, 0xf9, 0x3d, 0xbb, 0x4c, 0x55, 0x95, 0xcd, 0x39, - 0x23, 0xeb, 0xdc, 0x4a, 0xab, 0x65, 0x68, 0x2d, 0x9c, 0xb3, 0x89, 0x9c, 0x2f, 0x93, 0xf5, 0x22, - 0x67, 0x59, 0x57, 0x47, 0xbe, 0xac, 0xc1, 0x72, 0x45, 0xd5, 0x56, 0x2e, 0xc1, 0xf0, 0x1a, 0xb3, - 0x5c, 0x82, 0x51, 0x65, 0x5f, 0x0e, 0x4a, 0xb0, 0xe1, 0xa0, 0x04, 0x9e, 0xef, 0x6b, 0x09, 0xe4, - 0x0d, 0x25, 0xb7, 0xcc, 0x3f, 0xa9, 0xc1, 0x5a, 0x75, 0x85, 0x16, 0x79, 0x59, 0x3f, 0xbe, 0x1b, - 0x55, 0x3b, 0xd6, 0xbe, 0x79, 0x1e, 0x9a, 0x94, 0xe6, 0x65, 0x94, 0x66, 0xd3, 0x69, 0x73, 0x69, - 0x52, 0xc4, 0xad, 0x12, 0xe8, 0x14, 0xd3, 0x5a, 0x76, 0x0d, 0x14, 0x31, 0x7c, 0x8b, 0xea, 0x52, - 0xb1, 0xf6, 0x8d, 0x11, 0x18, 0xf6, 0xf6, 0x45, 0x56, 0xe5, 0x84, 0x60, 0xe1, 0x90, 0x2e, 0xa6, - 0x92, 0x6b, 0x34, 0xaf, 0x31, 0xb2, 0xd6, 0x68, 0xa9, 0x6c, 0xca, 0x5a, 0xa3, 0xe5, 0x4a, 0xa6, - 0xd2, 0x1a, 0x45, 0x66, 0x58, 0xd5, 0x44, 0x3e, 0xc5, 0x65, 0x23, 0x73, 0xaa, 0xad, 0xe2, 0x52, - 0xcf, 0xaa, 0x96, 0x8d, 0x9d, 0x35, 0x2d, 0x6d, 0x95, 0x22, 0x55, 0xcb, 0xb5, 0xe7, 0xc2, 0x8c, - 0x42, 0x27, 0xeb, 0x45, 0x02, 0x8a, 0x72, 0x65, 0x59, 0x8c, 0xb3, 0x8e, 0x44, 0x97, 0x9c, 0x39, - 0x93, 0x28, 0xa7, 0x79, 0x08, 0xb3, 0x46, 0x09, 0x08, 0xd1, 0x9b, 0x6c, 0xb9, 0xe2, 0xa5, 0x7d, - 0xa5, 0xb2, 0xcd, 0xde, 0x4a, 0x9c, 0x45, 0xce, 0x20, 0x43, 0x04, 0xcd, 0xe3, 0xb7, 0x61, 0xde, - 0xaa, 0xc2, 0xc8, 0x95, 0x5f, 0x55, 0x27, 0x92, 0x2b, 0xbf, 0xb2, 0x74, 0x43, 0x39, 0x9a, 0x0e, - 0x2a, 0x3f, 0x93, 0x28, 0x9a, 0xd7, 0x67, 0xd0, 0xd0, 0xc5, 0x0f, 0xb9, 0xfe, 0x8b, 0xf5, 0x10, - 0xe7, 0xf1, 0xb0, 0xe6, 0xe0, 0x94, 0x77, 0x3e, 0x8c, 0xfb, 0x87, 0x52, 0x5f, 0x46, 0x6a, 0x3f, - 0xd7, 0x57, 0xb9, 0xbe, 0x21, 0xd7, 0x57, 0x55, 0x2d, 0x80, 0xa5, 0xaf, 0x2e, 0x22, 0xe8, 0x31, - 0xa4, 0xb0, 0x58, 0x48, 0xa9, 0xe7, 0x6e, 0x45, 0x75, 0x01, 0x41, 0xee, 0x56, 0x0c, 0xc9, 0xc5, - 0xdb, 0x8e, 0x9b, 0xe0, 0xe7, 0x85, 0x61, 0x6e, 0x5b, 0x62, 0xbb, 0x17, 0x09, 0x67, 0xcb, 0x6e, - 0xad, 0xcc, 0xba, 0x65, 0xb7, 0x76, 0x76, 0xba, 0xb4, 0xdd, 0x8b, 0x3b, 0x37, 0xf2, 0x14, 0x66, - 0x54, 0xa6, 0x33, 0x37, 0xda, 0x42, 0x8e, 0xb7, 0xdd, 0x2a, 0x37, 0x48, 0xaa, 0x96, 0xe1, 0x7a, - 0xbe, 0x8f, 0x54, 0xe5, 0x44, 0x18, 0x79, 0xcf, 0x7c, 0x22, 0xca, 0x29, 0xd3, 0x7c, 0x22, 0xaa, - 0x12, 0xa5, 0xd6, 0x44, 0x88, 0x9d, 0x4b, 0xf3, 0xf8, 0xfb, 0x1a, 0xde, 0x07, 0x8f, 0x4e, 0x5b, - 0x92, 0x37, 0x2f, 0x90, 0xe1, 0x14, 0x02, 0xbd, 0x75, 0xe1, 0x9c, 0xa8, 0x73, 0x0b, 0xc5, 0x74, - 0x9c, 0xab, 0xea, 0x30, 0xc5, 0x6e, 0xbe, 0x40, 0xd7, 0x09, 0x52, 0x2e, 0xf4, 0xdf, 0xd6, 0xc4, - 0x1f, 0x42, 0x18, 0x41, 0x97, 0x6c, 0x8d, 0x29, 0x80, 0x12, 0xf8, 0xce, 0xd8, 0xf8, 0x52, 0xdc, - 0x9b, 0x28, 0xee, 0x75, 0xe7, 0xca, 0x08, 0x71, 0xb9, 0xb0, 0xbf, 0x03, 0x57, 0x74, 0x7a, 0xd3, - 0xa2, 0xfb, 0xfe, 0x20, 0xf2, 0xb3, 0x3c, 0x2e, 0x1d, 0x92, 0x03, 0xcd, 0x0d, 0xa7, 0x98, 0xf5, - 0xb2, 0xcf, 0xc7, 0x53, 0xd9, 0x2a, 0xc4, 0x38, 0xe2, 0xb4, 0x39, 0xf7, 0x04, 0x96, 0x54, 0xbf, - 0xf7, 0x03, 0x8f, 0x7d, 0x63, 0x9e, 0xd7, 0x91, 0x67, 0xdb, 0x59, 0x35, 0x79, 0x1e, 0x05, 0x1e, - 0xd3, 0x1c, 0x33, 0xac, 0x56, 0xb1, 0x12, 0x5a, 0x66, 0xf0, 0x5d, 0x99, 0xea, 0x32, 0x83, 0xef, - 0xea, 0xdc, 0x9b, 0x1d, 0x7c, 0xf7, 0x28, 0x13, 0xb9, 0x30, 0x5f, 0x32, 0x38, 0x81, 0xe6, 0xfe, - 0x50, 0xa6, 0xfb, 0x5f, 0x9b, 0xa9, 0xf4, 0x81, 0x1c, 0x64, 0x9a, 0x15, 0x98, 0xf2, 0xc1, 0x9e, - 0x88, 0xd2, 0x1c, 0x33, 0xd5, 0x45, 0x36, 0x87, 0x27, 0xc1, 0xca, 0x7c, 0x2b, 0xb3, 0x64, 0x36, - 0x5f, 0x23, 0x42, 0xc2, 0x07, 0xe0, 0x9c, 0xef, 0x19, 0x10, 0x3b, 0x4a, 0xc2, 0x87, 0x83, 0x7a, - 0x17, 0xa8, 0x48, 0x70, 0x8d, 0x17, 0x22, 0xdd, 0x40, 0xc6, 0x57, 0x9c, 0xb5, 0x72, 0x88, 0xc4, - 0x79, 0x73, 0xd6, 0x3f, 0x85, 0xe5, 0x42, 0xec, 0xfd, 0x82, 0x78, 0x5b, 0xe6, 0x5c, 0x08, 0xbc, - 0x15, 0x73, 0x86, 0x71, 0x70, 0x21, 0x6b, 0x45, 0x6e, 0x54, 0xc5, 0x1b, 0x56, 0x52, 0x68, 0x54, - 0xe4, 0x23, 0xcf, 0x0d, 0xb2, 0x56, 0x0a, 0x47, 0x90, 0xc2, 0x9b, 0x35, 0xf2, 0xc7, 0x35, 0xcc, - 0x58, 0x0c, 0x49, 0x9a, 0x91, 0xdb, 0x55, 0x01, 0xef, 0x85, 0xc5, 0x90, 0xfb, 0x09, 0xb9, 0x56, - 0x8c, 0x8a, 0x4b, 0xe2, 0x1c, 0xe3, 0x0d, 0x84, 0x99, 0xfa, 0xb2, 0x62, 0xf2, 0x8a, 0x9c, 0xd8, - 0xd0, 0xa0, 0xb5, 0x18, 0x8a, 0xcb, 0xa8, 0x52, 0x71, 0xfa, 0x99, 0xfd, 0x17, 0x19, 0x2c, 0x96, - 0x37, 0x2b, 0x46, 0x7d, 0x11, 0xd6, 0x2f, 0x21, 0xeb, 0xab, 0xe4, 0x4a, 0x61, 0xbc, 0x05, 0x11, - 0x84, 0x5b, 0x6b, 0xa4, 0x58, 0x4c, 0xb7, 0xb6, 0x94, 0xc7, 0xb3, 0xdc, 0xda, 0x72, 0x2a, 0xad, - 0xe4, 0xd6, 0x7a, 0x1c, 0x05, 0x0f, 0x43, 0xc2, 0xa0, 0x59, 0x4c, 0x75, 0x18, 0x4b, 0xb9, 0x3a, - 0x09, 0x62, 0x2c, 0xe5, 0x21, 0xf7, 0xbe, 0x05, 0xaf, 0xbd, 0xcb, 0xc4, 0xf5, 0xf1, 0x1d, 0x59, - 0x0f, 0x46, 0x18, 0x2c, 0x16, 0xd2, 0x10, 0xc6, 0x5c, 0x56, 0xe6, 0x27, 0xc6, 0xe0, 0x69, 0x6f, - 0x1f, 0x9a, 0xe7, 0x00, 0xc9, 0xf0, 0x65, 0xf4, 0x1c, 0x96, 0x2b, 0x52, 0x0a, 0x46, 0xec, 0x38, - 0x34, 0xdf, 0xd0, 0x2e, 0x4b, 0x67, 0x5d, 0xad, 0xdb, 0x97, 0x2c, 0x39, 0xef, 0x94, 0x0a, 0xce, - 0x89, 0x31, 0x5e, 0xf9, 0xf7, 0x9b, 0xca, 0x14, 0xad, 0x2c, 0x4e, 0x7b, 0x73, 0x68, 0x7b, 0xe5, - 0xd1, 0xa0, 0x59, 0xca, 0x0b, 0xf6, 0x10, 0x16, 0x6c, 0x51, 0x8d, 0x2b, 0x85, 0xaa, 0x6c, 0xc8, - 0xb9, 0x23, 0xb4, 0xd7, 0x8c, 0x66, 0xf7, 0x39, 0xd2, 0x8e, 0x60, 0xde, 0xca, 0x53, 0x19, 0xe6, - 0x5a, 0x91, 0x01, 0x1b, 0xdf, 0x7e, 0x8a, 0xfa, 0xcc, 0x58, 0x9c, 0x88, 0x0d, 0xb1, 0x59, 0xcc, - 0x8b, 0x91, 0xcd, 0x4a, 0x96, 0x79, 0xf2, 0xeb, 0x9b, 0x73, 0xcd, 0x0c, 0xae, 0x32, 0xb1, 0x56, - 0xc1, 0xd5, 0x4e, 0xb9, 0x9d, 0x3f, 0x8f, 0xe7, 0x30, 0xc5, 0xcd, 0xa8, 0x98, 0x7b, 0x3a, 0x88, - 0x7b, 0xbd, 0x90, 0x92, 0xf2, 0x88, 0x0a, 0xc9, 0xa9, 0x31, 0xc6, 0x6c, 0x9d, 0x7d, 0x39, 0x7b, - 0x6f, 0xc0, 0x62, 0xb5, 0x6e, 0x7e, 0x8a, 0xc7, 0x4f, 0x21, 0x73, 0x6d, 0x1d, 0x3f, 0xd5, 0x89, - 0xf7, 0xb6, 0x33, 0x0a, 0x65, 0xc8, 0x39, 0x74, 0x2c, 0xf1, 0x44, 0xbe, 0x3b, 0x3b, 0x9c, 0xc2, - 0xbf, 0x26, 0xf7, 0xf6, 0xff, 0x07, 0x00, 0x00, 0xff, 0xff, 0x6a, 0xb5, 0xd2, 0x87, 0x80, 0x4e, - 0x00, 0x00, + // 5495 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x3c, 0x4d, 0x6f, 0x1c, 0x57, + 0x72, 0x98, 0x21, 0x45, 0x72, 0x8a, 0x5f, 0xc3, 0xc7, 0xaf, 0xd1, 0x88, 0x12, 0xa5, 0xf6, 0x5a, + 0x96, 0xfc, 0x41, 0xd9, 0xb2, 0x36, 0xeb, 0xac, 0x9d, 0xdd, 0x50, 0x94, 0x4c, 0x6b, 0xad, 0xb5, + 0xb8, 0x4d, 0x5a, 0x06, 0xbc, 0x81, 0x27, 0xcd, 0xe9, 0xc7, 0x61, 0x47, 0x3d, 0xdd, 0xed, 0xee, + 0x37, 0xa4, 0xe8, 0x4d, 0x90, 0x85, 0x91, 0x04, 0x39, 0x04, 0x9b, 0xc3, 0x22, 0x40, 0x02, 0xe4, + 0x92, 0x9c, 0x82, 0x00, 0xb9, 0x04, 0x39, 0xe5, 0xb0, 0xc8, 0x35, 0xc8, 0x31, 0x97, 0xfc, 0x80, + 0x20, 0xb7, 0x24, 0x40, 0x80, 0x5c, 0x72, 0x0a, 0x5e, 0xbd, 0x8f, 0x7e, 0xaf, 0xbb, 0x67, 0x38, + 0xb4, 0xb5, 0xde, 0x8b, 0x34, 0x5d, 0xaf, 0x5e, 0x55, 0xbd, 0x7a, 0xd5, 0xf5, 0xaa, 0x5e, 0x55, + 0x13, 0x1a, 0x69, 0xd2, 0xdd, 0x4a, 0xd2, 0x98, 0xc5, 0x64, 0xaa, 0xd7, 0x65, 0x69, 0xd2, 0x6d, + 0x6f, 0xf4, 0xe2, 0xb8, 0x17, 0xd2, 0x3b, 0x5e, 0x12, 0xdc, 0xf1, 0xa2, 0x28, 0x66, 0x1e, 0x0b, + 0xe2, 0x28, 0x13, 0x58, 0x4e, 0x13, 0x16, 0x76, 0x29, 0x7b, 0x14, 0x1d, 0xc5, 0x2e, 0xfd, 0x7c, + 0x40, 0x33, 0xe6, 0xfc, 0xc3, 0x24, 0x2c, 0x6a, 0x50, 0x96, 0xc4, 0x51, 0x46, 0xc9, 0x1a, 0x4c, + 0x0d, 0x12, 0x16, 0xf4, 0x69, 0xab, 0x76, 0xbd, 0x76, 0xab, 0xe1, 0xca, 0x27, 0x72, 0x07, 0x96, + 0xbd, 0x13, 0x2f, 0x08, 0xbd, 0xc3, 0x90, 0x76, 0xe8, 0xf3, 0xee, 0xb1, 0x17, 0xf5, 0x68, 0xd6, + 0xaa, 0x5f, 0xaf, 0xdd, 0x9a, 0x70, 0x89, 0x1e, 0x7a, 0xa8, 0x46, 0xc8, 0x6b, 0xb0, 0x44, 0x23, + 0x0e, 0xf2, 0x0d, 0xf4, 0x09, 0x44, 0x6f, 0xca, 0x81, 0x1c, 0xf9, 0x1e, 0xac, 0xf9, 0xf4, 0xc8, + 0x1b, 0x84, 0xac, 0x73, 0x14, 0xa7, 0xf4, 0x79, 0x27, 0x49, 0xe3, 0x93, 0xc0, 0xa7, 0x69, 0x6b, + 0x12, 0xa5, 0x58, 0x91, 0xa3, 0xef, 0xf3, 0xc1, 0x3d, 0x39, 0x46, 0xee, 0xc2, 0xaa, 0x9e, 0x15, + 0x78, 0xac, 0xd3, 0x1d, 0xa4, 0x29, 0x8d, 0xba, 0x67, 0xad, 0x4b, 0x38, 0x69, 0x59, 0x4d, 0x0a, + 0x3c, 0xb6, 0x23, 0x87, 0xc8, 0x27, 0xd0, 0xcc, 0x06, 0x87, 0xd9, 0x59, 0xc6, 0x68, 0xbf, 0x93, + 0x31, 0x8f, 0x0d, 0xb2, 0xd6, 0xd4, 0xf5, 0x89, 0x5b, 0xb3, 0x77, 0x5f, 0xdf, 0x12, 0x6a, 0xdc, + 0x2a, 0xa8, 0x64, 0x6b, 0x5f, 0xe1, 0xef, 0x23, 0xfa, 0xc3, 0x88, 0xa5, 0x67, 0xee, 0x62, 0x66, + 0x43, 0xc9, 0x47, 0x30, 0x9f, 0x26, 0xdd, 0x0e, 0x8d, 0xfc, 0x24, 0x0e, 0x22, 0x96, 0xb5, 0xa6, + 0x91, 0xea, 0xed, 0x61, 0x54, 0xdd, 0xa4, 0xfb, 0x50, 0xe1, 0x0a, 0x92, 0x73, 0xa9, 0x01, 0x6a, + 0xdf, 0x87, 0x95, 0x2a, 0xc6, 0xa4, 0x09, 0x13, 0xcf, 0xe8, 0x99, 0xdc, 0x1d, 0xfe, 0x93, 0xac, + 0xc0, 0xa5, 0x13, 0x2f, 0x1c, 0x50, 0xdc, 0x8c, 0x19, 0x57, 0x3c, 0x7c, 0xb7, 0xfe, 0x4e, 0xad, + 0x7d, 0x00, 0x4b, 0x25, 0x36, 0x15, 0x04, 0x6e, 0x9b, 0x04, 0x66, 0xef, 0x2e, 0x2b, 0x91, 0xdd, + 0xbd, 0x1d, 0x35, 0xd7, 0xa0, 0xea, 0xdc, 0x80, 0xcd, 0x5d, 0xca, 0x76, 0xe2, 0x7e, 0x7f, 0x10, + 0x05, 0x5d, 0xb4, 0x31, 0x97, 0x86, 0xde, 0x19, 0x4d, 0x33, 0x65, 0x59, 0x1f, 0xc1, 0x4a, 0xd5, + 0x38, 0x69, 0xc1, 0xb4, 0xdc, 0x7b, 0xe4, 0x3f, 0xe3, 0xaa, 0x47, 0xb2, 0x01, 0x8d, 0x6e, 0x1c, + 0x45, 0xb4, 0xcb, 0xa8, 0x2f, 0x17, 0x92, 0x03, 0x9c, 0x3f, 0xaa, 0xc3, 0xf5, 0xe1, 0x3c, 0xa5, + 0xe9, 0x7e, 0x01, 0x6b, 0x5d, 0x13, 0xa1, 0x93, 0x4a, 0x8c, 0x56, 0x0d, 0xb7, 0x62, 0xc7, 0xd8, + 0x8a, 0x91, 0x94, 0xb6, 0x2a, 0x47, 0xc5, 0x26, 0xad, 0x76, 0xab, 0xc6, 0xda, 0x47, 0xd0, 0x1e, + 0x3e, 0xa9, 0x42, 0xe5, 0x77, 0x6d, 0x95, 0x6f, 0x28, 0xd1, 0xaa, 0x88, 0x98, 0xba, 0xff, 0x0e, + 0xac, 0xef, 0xd2, 0x88, 0xa6, 0x41, 0x57, 0x1b, 0x87, 0xd4, 0x39, 0xd7, 0xa0, 0xb6, 0x49, 0xc9, + 0x2a, 0x07, 0x38, 0x6d, 0x68, 0x95, 0x27, 0x8a, 0xe5, 0x3a, 0x6b, 0xb0, 0xb2, 0x4b, 0x99, 0x86, + 0xeb, 0x5d, 0xfc, 0x45, 0x0d, 0x56, 0x71, 0x20, 0x3b, 0xcc, 0xce, 0xc4, 0x80, 0x54, 0xf5, 0x6f, + 0xc3, 0x92, 0x26, 0x9d, 0xa9, 0xd7, 0x48, 0x68, 0xf9, 0x6d, 0x43, 0xcb, 0xe5, 0x99, 0xf9, 0xcb, + 0x94, 0x99, 0x6f, 0x53, 0xfe, 0x4e, 0x4a, 0x70, 0x7b, 0x07, 0x56, 0x2b, 0x51, 0x2f, 0x62, 0xff, + 0x4e, 0x0b, 0xd6, 0x76, 0x29, 0x33, 0xcc, 0xd8, 0x30, 0xd0, 0x59, 0x03, 0xcc, 0xed, 0x32, 0x63, + 0x5e, 0xca, 0x72, 0xbb, 0x94, 0x8f, 0xe4, 0x65, 0x58, 0x08, 0x83, 0x8c, 0xd1, 0xa8, 0xe3, 0xf9, + 0x7e, 0x4a, 0x33, 0xe1, 0xf2, 0x1a, 0xee, 0xbc, 0x80, 0x6e, 0x0b, 0xa0, 0xf3, 0x8f, 0x35, 0xbe, + 0x31, 0x05, 0x56, 0x52, 0x59, 0x8f, 0xa1, 0x91, 0x7b, 0x05, 0xa1, 0xa4, 0x2d, 0x43, 0x49, 0x55, + 0x73, 0xb6, 0x0a, 0xae, 0x21, 0x27, 0xd0, 0xfe, 0x11, 0x2c, 0xbc, 0xe8, 0x17, 0xfa, 0x1d, 0x68, + 0x4b, 0xdb, 0x50, 0x1e, 0xf9, 0x23, 0xaf, 0x4f, 0x95, 0x5d, 0xb5, 0x61, 0x46, 0x39, 0x70, 0xc9, + 0x43, 0x3f, 0x3b, 0x57, 0xe1, 0x4a, 0xe5, 0x4c, 0x69, 0x58, 0x77, 0x60, 0x79, 0x97, 0x32, 0xed, + 0xe6, 0x15, 0xc5, 0xa1, 0x5e, 0xc0, 0xb9, 0x87, 0x96, 0x68, 0x4c, 0x90, 0x2a, 0xdc, 0x80, 0x46, + 0x7e, 0x88, 0x48, 0xdb, 0xd6, 0x00, 0xe7, 0x2e, 0x9a, 0xa9, 0x9a, 0xf5, 0xe4, 0x60, 0xcf, 0xa5, + 0x62, 0xda, 0x65, 0x98, 0x89, 0x59, 0xd2, 0xe9, 0xc6, 0xbe, 0x12, 0x7d, 0x3a, 0x66, 0xc9, 0x4e, + 0xec, 0x53, 0x69, 0x1a, 0xc6, 0x1c, 0x6d, 0x1a, 0x7f, 0x2d, 0xb6, 0xd2, 0x1e, 0x92, 0x72, 0xfc, + 0x00, 0x1a, 0x8a, 0xa0, 0xda, 0xca, 0x37, 0x8c, 0xad, 0xac, 0x9a, 0xb3, 0xf5, 0x44, 0x70, 0x94, + 0x3b, 0x39, 0x23, 0x05, 0xc8, 0xda, 0xef, 0xc2, 0xbc, 0x35, 0x74, 0x9e, 0x65, 0x37, 0xcc, 0x2d, + 0xbb, 0x07, 0x6b, 0x0f, 0x82, 0xcc, 0x3c, 0x71, 0xc7, 0xd9, 0xae, 0xcf, 0x60, 0x61, 0xcf, 0x0b, + 0xd2, 0x6c, 0x7f, 0x90, 0x24, 0x31, 0x9a, 0xf7, 0x2b, 0xb0, 0x98, 0x1f, 0xeb, 0x09, 0x1f, 0x93, + 0x93, 0x16, 0x34, 0x18, 0x67, 0x90, 0x97, 0x60, 0x5e, 0x1d, 0xe7, 0x02, 0x4d, 0x88, 0x34, 0x27, + 0x81, 0x88, 0xe4, 0x7c, 0x39, 0x69, 0xa9, 0xce, 0x0a, 0x2c, 0x08, 0x4c, 0x46, 0x9e, 0x0e, 0x2b, + 0xf0, 0xb7, 0x69, 0x08, 0x75, 0xfb, 0x38, 0x68, 0xc1, 0xf4, 0x09, 0x4d, 0x0f, 0xe3, 0x8c, 0x62, + 0xcc, 0x30, 0xe3, 0xaa, 0x47, 0x2e, 0xc8, 0x20, 0x0b, 0xa2, 0x5e, 0x27, 0xf3, 0x22, 0xff, 0x30, + 0x7e, 0x8e, 0x11, 0xc2, 0x8c, 0x3b, 0x87, 0xc0, 0x7d, 0x01, 0x23, 0x37, 0x60, 0xee, 0x98, 0xb1, + 0xa4, 0xc3, 0x43, 0x97, 0x78, 0xc0, 0x64, 0x40, 0x30, 0xcb, 0x61, 0x07, 0x02, 0xc4, 0x5f, 0x6c, + 0x44, 0x19, 0x64, 0x34, 0xf5, 0x7a, 0x34, 0x62, 0xad, 0x29, 0xf1, 0x62, 0x73, 0xe8, 0xc7, 0x0a, + 0x48, 0xae, 0x02, 0x20, 0x5a, 0x92, 0xc6, 0xcf, 0xcf, 0x5a, 0xd3, 0xc2, 0xf4, 0x38, 0x64, 0x8f, + 0x03, 0xb8, 0xfe, 0x0e, 0xbd, 0x8c, 0xaa, 0xd0, 0x23, 0xa0, 0x59, 0x6b, 0x46, 0xe8, 0x8f, 0x83, + 0x77, 0x34, 0x94, 0x74, 0x78, 0xdc, 0x21, 0xb5, 0xde, 0xf1, 0xb2, 0x8c, 0xb2, 0xac, 0xd5, 0x40, + 0x03, 0xba, 0x57, 0x61, 0x40, 0x85, 0xf8, 0x43, 0xce, 0xdb, 0xc6, 0x69, 0x3a, 0xfe, 0xb0, 0xa0, + 0x3c, 0xde, 0xf2, 0x06, 0xec, 0x98, 0x46, 0x8c, 0x9f, 0x1e, 0x9c, 0x49, 0x12, 0xb4, 0x00, 0x75, + 0xd3, 0xb4, 0x06, 0xb6, 0x93, 0xa0, 0xfd, 0x29, 0x0f, 0x2e, 0xca, 0x54, 0x2b, 0x4c, 0xf0, 0x75, + 0xdb, 0x95, 0xac, 0x29, 0x61, 0x6d, 0x3b, 0x32, 0x4d, 0xf3, 0x14, 0x9a, 0xbb, 0x94, 0x1d, 0x04, + 0xdd, 0x67, 0x34, 0x1d, 0xc3, 0x28, 0xc9, 0x2d, 0x98, 0xe4, 0x16, 0x25, 0x19, 0xac, 0xe8, 0x93, + 0x50, 0x46, 0x6c, 0x9c, 0x91, 0x8b, 0x18, 0x7c, 0x2f, 0x50, 0x73, 0x1d, 0x76, 0x96, 0x08, 0xbb, + 0x68, 0xb8, 0x0d, 0x84, 0x1c, 0x9c, 0x25, 0xd4, 0x79, 0x0a, 0x73, 0xe6, 0x24, 0xee, 0x34, 0x7c, + 0x1a, 0x06, 0xfd, 0x80, 0xd1, 0x54, 0x39, 0x0d, 0x0d, 0xe0, 0xf6, 0xc8, 0xb7, 0x48, 0xda, 0x31, + 0xfe, 0xe6, 0xef, 0xdb, 0xe7, 0x83, 0x98, 0x29, 0xda, 0xe2, 0xc1, 0xf9, 0xb3, 0x3a, 0x2c, 0xa8, + 0xe5, 0x48, 0x63, 0x56, 0x32, 0xd7, 0xce, 0x95, 0xf9, 0x06, 0xcc, 0x85, 0x5e, 0xc6, 0x3a, 0x83, + 0xc4, 0xf7, 0x54, 0x68, 0x33, 0xe1, 0xce, 0x72, 0xd8, 0xc7, 0x02, 0xc4, 0x2d, 0x5a, 0x45, 0xae, + 0xf8, 0x6e, 0x49, 0xee, 0x73, 0x5d, 0x73, 0x31, 0x04, 0x26, 0xf9, 0x1c, 0xb4, 0xf6, 0x9a, 0x8b, + 0xbf, 0x39, 0xec, 0x38, 0xe8, 0x1d, 0xa3, 0x75, 0xd7, 0x5c, 0xfc, 0xcd, 0x77, 0x30, 0x8c, 0x4f, + 0xd1, 0x96, 0x6b, 0x2e, 0xff, 0xc9, 0x21, 0x87, 0x81, 0x8f, 0xa6, 0x5b, 0x73, 0xf9, 0x4f, 0x0e, + 0xf1, 0xb2, 0x67, 0x68, 0xa8, 0x35, 0x97, 0xff, 0xe4, 0x51, 0xff, 0x49, 0x1c, 0x0e, 0xfa, 0xb4, + 0xd5, 0x40, 0xa0, 0x7c, 0x22, 0x57, 0xa0, 0x91, 0xa4, 0x41, 0x97, 0x76, 0x3c, 0x76, 0x8c, 0xc6, + 0x54, 0x73, 0x67, 0x10, 0xb0, 0xcd, 0x8e, 0x9d, 0x65, 0x58, 0xd2, 0x1b, 0xad, 0xbd, 0xe7, 0x27, + 0x30, 0x2d, 0x21, 0x23, 0x37, 0xfd, 0x4d, 0x98, 0x66, 0x02, 0xad, 0x55, 0xc7, 0xb7, 0x40, 0x1b, + 0x96, 0xad, 0x69, 0x57, 0xa1, 0x39, 0xdf, 0x07, 0x62, 0x72, 0x93, 0x1b, 0x71, 0x3b, 0xa7, 0x23, + 0xdc, 0xf1, 0xa2, 0x4d, 0x27, 0xcb, 0x09, 0x7c, 0x81, 0x87, 0xd1, 0x93, 0xd4, 0xe7, 0x8e, 0x24, + 0x7e, 0xf6, 0x8d, 0x9a, 0xe6, 0x0f, 0x61, 0x5e, 0x33, 0x7e, 0xc4, 0x68, 0x9f, 0x2b, 0xdc, 0xeb, + 0xc7, 0x83, 0x88, 0x21, 0xcf, 0x9a, 0x2b, 0x9f, 0xb8, 0x05, 0xa2, 0x7e, 0x91, 0x65, 0xcd, 0x15, + 0x0f, 0x64, 0x01, 0xea, 0x81, 0x2f, 0x93, 0xa7, 0x7a, 0xe0, 0x3b, 0xff, 0x57, 0x83, 0x25, 0x63, + 0x21, 0x17, 0x36, 0xca, 0x92, 0xc5, 0xd5, 0x2b, 0x2c, 0xee, 0x36, 0x4c, 0x1e, 0x06, 0x3e, 0xcf, + 0xd9, 0xb8, 0x5e, 0x57, 0x15, 0x39, 0x6b, 0x1d, 0x2e, 0xa2, 0x70, 0x54, 0x2f, 0x7b, 0x96, 0xb5, + 0x26, 0x47, 0xa2, 0x72, 0x94, 0xd2, 0xfb, 0x70, 0xa9, 0xfc, 0x3e, 0xd8, 0xba, 0x9c, 0x2a, 0xea, + 0x52, 0x44, 0xab, 0x9a, 0xb6, 0xb6, 0xbc, 0x2e, 0x40, 0x0e, 0x1c, 0xb9, 0xad, 0xbf, 0x0e, 0x10, + 0x6b, 0x4c, 0x69, 0x7f, 0x97, 0x4b, 0x42, 0x6b, 0x13, 0x34, 0x90, 0x9d, 0x0f, 0x31, 0xd4, 0x30, + 0x99, 0x4b, 0xe5, 0xdf, 0xb5, 0x68, 0x0a, 0x5b, 0x24, 0x25, 0x9a, 0x99, 0x45, 0xec, 0x6d, 0x24, + 0xb6, 0xdd, 0xed, 0xf2, 0xad, 0x37, 0x12, 0xf3, 0x91, 0x67, 0xf8, 0x53, 0x98, 0x96, 0x33, 0xa4, + 0x59, 0x08, 0x84, 0x7a, 0xe0, 0x93, 0x77, 0x01, 0x8c, 0x73, 0x48, 0xac, 0xeb, 0x8a, 0x92, 0x41, + 0x4e, 0x52, 0xd6, 0x80, 0xec, 0x0c, 0x74, 0xe7, 0x08, 0x96, 0x2b, 0x50, 0xb8, 0x28, 0x3a, 0xad, + 0x96, 0xa2, 0xa8, 0x67, 0xb2, 0x09, 0xb3, 0x2c, 0x66, 0x5e, 0xd8, 0xc9, 0x4f, 0x88, 0x9a, 0x0b, + 0x08, 0x7a, 0xca, 0x21, 0xe8, 0xa0, 0xe2, 0x50, 0x58, 0x2e, 0x77, 0x50, 0x71, 0xe8, 0x3b, 0x1e, + 0x06, 0x5e, 0xd6, 0xa2, 0xa5, 0x0a, 0x47, 0x6d, 0xd9, 0x6b, 0x30, 0xe3, 0x89, 0x29, 0x6a, 0x61, + 0x8b, 0x85, 0x85, 0xb9, 0x1a, 0xc1, 0x21, 0x78, 0x02, 0xed, 0xc4, 0xd1, 0x51, 0xd0, 0x53, 0xd6, + 0xf1, 0x0a, 0x3a, 0x2b, 0x05, 0xcb, 0x63, 0x12, 0xdf, 0x63, 0x1e, 0x72, 0x9b, 0x73, 0xf1, 0xb7, + 0xf3, 0x87, 0x35, 0x68, 0xee, 0xc5, 0x29, 0x3b, 0x8a, 0xc3, 0x20, 0x96, 0xe1, 0x3d, 0x0f, 0x47, + 0x54, 0xf8, 0x2f, 0xe3, 0x48, 0xf9, 0xc8, 0x3d, 0x64, 0x37, 0x0e, 0x22, 0x61, 0xab, 0x75, 0xa9, + 0xa0, 0x38, 0x88, 0xb8, 0xa9, 0x92, 0xeb, 0x30, 0xeb, 0xd3, 0xac, 0x9b, 0x06, 0x09, 0x4f, 0xe7, + 0xa4, 0x5b, 0x30, 0x41, 0x9c, 0xf0, 0xa1, 0x17, 0x7a, 0x51, 0x97, 0x4a, 0xcf, 0xae, 0x1e, 0x9d, + 0x55, 0x74, 0x57, 0x5a, 0x12, 0x23, 0xb3, 0xb6, 0xc1, 0x72, 0x29, 0xbf, 0x06, 0x8d, 0x44, 0x01, + 0xa5, 0xf9, 0xb5, 0xf4, 0x59, 0x5d, 0x58, 0x8e, 0x9b, 0xa3, 0x3a, 0x1b, 0x3c, 0xf6, 0xcf, 0xe9, + 0xed, 0x0f, 0xfa, 0x7d, 0x2f, 0x3d, 0x53, 0xdc, 0x22, 0x98, 0xdc, 0x89, 0x83, 0x88, 0x2b, 0x8a, + 0x2f, 0x4a, 0x05, 0x6f, 0xfc, 0xb7, 0x29, 0x7a, 0xdd, 0x12, 0xdd, 0xd4, 0xd6, 0x84, 0xad, 0xad, + 0x6b, 0x00, 0x09, 0x4d, 0xbb, 0x34, 0x62, 0x5e, 0x4f, 0xad, 0xd8, 0x80, 0x38, 0xc7, 0x40, 0x9e, + 0x1c, 0x1d, 0x85, 0x41, 0x44, 0x39, 0x5b, 0x29, 0xcc, 0x08, 0xed, 0x0f, 0x97, 0xc1, 0xe6, 0x34, + 0x51, 0xe2, 0xf4, 0x43, 0x58, 0x7a, 0x12, 0x55, 0x30, 0x52, 0xe4, 0x6a, 0xa3, 0xc8, 0xd5, 0x4b, + 0xe4, 0x3e, 0x80, 0x39, 0x43, 0xf0, 0x8c, 0xbc, 0x03, 0x0d, 0x29, 0xa3, 0x4e, 0x14, 0xda, 0xda, + 0x1b, 0x94, 0x56, 0xe8, 0xe6, 0xc8, 0xce, 0x9f, 0xd7, 0x60, 0x36, 0x97, 0x2c, 0x23, 0xf7, 0xe0, + 0x12, 0x57, 0xb7, 0xa2, 0x72, 0x4d, 0x53, 0xc9, 0x71, 0xb6, 0xf0, 0x5f, 0x11, 0x17, 0x0a, 0xe4, + 0xf6, 0x3e, 0x40, 0x0e, 0xac, 0x08, 0xeb, 0xee, 0xd8, 0x61, 0xdd, 0xe5, 0x32, 0x55, 0x25, 0x9a, + 0x11, 0xd9, 0xfd, 0xcb, 0x24, 0x4f, 0xf7, 0x2a, 0x8c, 0x45, 0xda, 0xe0, 0x1b, 0x30, 0x2b, 0xde, + 0x05, 0xee, 0x01, 0x94, 0xc0, 0x73, 0xf9, 0xd5, 0x46, 0x10, 0xb9, 0x80, 0xef, 0x06, 0x8e, 0x93, + 0xb7, 0x60, 0x1e, 0x85, 0xed, 0xc4, 0x42, 0x21, 0xf2, 0xc5, 0xb6, 0x27, 0xcc, 0x21, 0x8a, 0x54, + 0x19, 0x49, 0x60, 0xd5, 0x9a, 0xd2, 0xc9, 0x84, 0x08, 0xf2, 0x90, 0x7a, 0xcf, 0x08, 0xa5, 0x87, + 0x49, 0x29, 0x94, 0x25, 0x09, 0xca, 0x31, 0xa1, 0xba, 0xe5, 0x6e, 0x79, 0x84, 0xdc, 0x81, 0x39, + 0xc9, 0x11, 0x35, 0x23, 0x8f, 0x38, 0x5b, 0xc6, 0x59, 0x31, 0x11, 0x11, 0x48, 0x1f, 0x56, 0xcc, + 0x09, 0x5a, 0xc2, 0x4b, 0x38, 0xf1, 0xdd, 0xf1, 0x25, 0x8c, 0x4a, 0x02, 0x92, 0x6e, 0x69, 0xa0, + 0xfd, 0x5b, 0xd0, 0x1a, 0xb6, 0xa0, 0x8a, 0x6d, 0x7f, 0xd5, 0xde, 0xf6, 0x95, 0x0a, 0x93, 0xcc, + 0xcc, 0x0b, 0xc4, 0x4f, 0x61, 0x7d, 0x88, 0x30, 0x17, 0xb8, 0x75, 0x30, 0x2c, 0xd5, 0xb4, 0xa6, + 0x3f, 0xad, 0x41, 0x7b, 0xdb, 0xf7, 0x4b, 0xce, 0x29, 0xbf, 0x24, 0xf8, 0xa6, 0x5d, 0xee, 0x55, + 0xb8, 0x52, 0x29, 0x90, 0xbc, 0xcd, 0x78, 0x0e, 0x57, 0x5d, 0xda, 0x8f, 0x4f, 0xe8, 0x37, 0x2d, + 0xb2, 0x73, 0x1d, 0xae, 0x0d, 0xe3, 0x2c, 0x65, 0xc3, 0xeb, 0x3d, 0xfb, 0x7a, 0x5c, 0x07, 0x46, + 0xff, 0x59, 0x83, 0x79, 0xfb, 0xe2, 0xfc, 0x45, 0xe5, 0xe2, 0xaf, 0x03, 0x49, 0x69, 0xc6, 0x3a, + 0x49, 0x1c, 0x86, 0x3c, 0x25, 0xf7, 0x69, 0xe8, 0x9d, 0xc9, 0x2b, 0xfb, 0x26, 0x1f, 0xd9, 0x13, + 0x03, 0x0f, 0x38, 0x9c, 0xac, 0xc3, 0xb4, 0x97, 0x04, 0x1d, 0x6e, 0x35, 0x22, 0x1f, 0x9f, 0xf2, + 0x92, 0xe0, 0x43, 0x7a, 0x46, 0x1c, 0x98, 0x97, 0x03, 0x9d, 0x90, 0x9e, 0xd0, 0x10, 0x63, 0xbe, + 0x09, 0x77, 0x56, 0x0c, 0x3f, 0xe6, 0x20, 0x72, 0x1b, 0x9a, 0x49, 0x1a, 0x70, 0xf3, 0xcb, 0x6b, + 0x03, 0xd3, 0x28, 0xcd, 0xa2, 0x84, 0xab, 0xd5, 0x39, 0x3f, 0x86, 0xcb, 0x15, 0xba, 0x90, 0x3e, + 0xea, 0x7b, 0xb0, 0x68, 0x57, 0x18, 0x94, 0x9f, 0xd2, 0x51, 0xab, 0x35, 0xd1, 0x5d, 0x38, 0xb2, + 0xe8, 0xc8, 0xe8, 0x13, 0x71, 0x5c, 0x8f, 0xe9, 0x3b, 0x2d, 0xe7, 0x73, 0x58, 0xc9, 0x81, 0x3b, + 0x71, 0x74, 0x42, 0xd3, 0x8c, 0x5b, 0x1b, 0x81, 0xc9, 0xa3, 0x34, 0x56, 0x17, 0xb2, 0xf8, 0x9b, + 0xc7, 0x6d, 0x2c, 0x96, 0x66, 0x50, 0x67, 0x31, 0xc7, 0x49, 0x3d, 0xa6, 0x4e, 0x29, 0xfc, 0xcd, + 0xe3, 0xe4, 0x00, 0x89, 0xd0, 0x0e, 0x8e, 0x09, 0x53, 0x9d, 0x95, 0x30, 0xce, 0xc5, 0x79, 0x8a, + 0xe1, 0xa3, 0x29, 0x8a, 0x5c, 0xe3, 0x6f, 0xc0, 0xac, 0x58, 0x23, 0x9f, 0xa9, 0xd6, 0xb7, 0x61, + 0xad, 0xaf, 0x20, 0xa6, 0x0b, 0x47, 0x1a, 0xea, 0xfc, 0x77, 0x1d, 0xe6, 0x30, 0x62, 0x7d, 0x40, + 0x99, 0x17, 0x84, 0xa3, 0x63, 0x69, 0x11, 0x83, 0xd6, 0x75, 0x0c, 0xfa, 0x12, 0xcc, 0x9b, 0x17, + 0x22, 0x67, 0x2a, 0x99, 0x35, 0xae, 0x43, 0xce, 0xc8, 0xcb, 0xb0, 0x80, 0xa9, 0x75, 0x8e, 0x25, + 0x6c, 0x66, 0x1e, 0xa1, 0x1a, 0xcd, 0x4e, 0x04, 0x2e, 0x15, 0x12, 0x01, 0x3e, 0x8c, 0xc1, 0x74, + 0x27, 0x0b, 0x7c, 0x9d, 0x27, 0x20, 0x64, 0x3f, 0xf0, 0x8d, 0x61, 0x9c, 0x3d, 0x6d, 0x0c, 0xe3, + 0x6c, 0x9e, 0x03, 0xa5, 0x54, 0x14, 0x0a, 0xb0, 0xde, 0x35, 0x83, 0x46, 0x37, 0xa7, 0x80, 0x07, + 0x41, 0x1f, 0xab, 0x61, 0xf2, 0x72, 0xbb, 0x21, 0x2c, 0x56, 0x3c, 0xe5, 0x69, 0x1a, 0x98, 0x69, + 0x5a, 0x9e, 0xd4, 0xcd, 0x5a, 0x49, 0xdd, 0x26, 0xcc, 0xc6, 0x09, 0x8d, 0x3a, 0x32, 0xc5, 0x9e, + 0x13, 0xd1, 0x03, 0x07, 0x3d, 0x45, 0x88, 0xbc, 0x32, 0x41, 0x9d, 0x67, 0xe3, 0xe4, 0xa5, 0xb6, + 0x62, 0xea, 0x45, 0xc5, 0xa8, 0x44, 0x70, 0xe2, 0xbc, 0x44, 0xd0, 0xd9, 0xc6, 0xa8, 0x58, 0x31, + 0x96, 0xe6, 0xf3, 0x3a, 0x4c, 0xa1, 0x9a, 0x94, 0xe5, 0xac, 0x58, 0x69, 0x8c, 0x34, 0x0a, 0x57, + 0xe2, 0x38, 0x1f, 0x60, 0x0d, 0x11, 0x87, 0xc6, 0x11, 0xfd, 0x32, 0xcc, 0x88, 0x5d, 0xd1, 0x56, + 0x33, 0x8d, 0xcf, 0x8f, 0x7c, 0xe7, 0xdf, 0x6a, 0x40, 0xf6, 0x07, 0x87, 0xfd, 0x60, 0x7c, 0x6a, + 0xe3, 0x27, 0xe8, 0x04, 0x26, 0xd1, 0x4c, 0x84, 0x39, 0xe2, 0xef, 0x82, 0x85, 0x4c, 0x16, 0x2d, + 0x24, 0xdf, 0xce, 0x4b, 0xd5, 0x39, 0xfa, 0x94, 0xb9, 0xf9, 0xdc, 0xc5, 0x87, 0x01, 0x8d, 0x58, + 0x47, 0x5e, 0xb6, 0x70, 0x17, 0x8f, 0x80, 0x47, 0xbe, 0xb3, 0x0f, 0xcb, 0xd6, 0xca, 0xa4, 0xa6, + 0x6f, 0xc0, 0x9c, 0x10, 0x20, 0x09, 0xbd, 0xae, 0xbe, 0x0d, 0x9f, 0x45, 0xd8, 0x1e, 0x82, 0x46, + 0xe9, 0xeb, 0x8f, 0x6b, 0xb0, 0xb2, 0x1f, 0xf4, 0x07, 0xa1, 0xc7, 0xe8, 0x2f, 0x41, 0x63, 0xf9, + 0xf2, 0x27, 0xac, 0xe5, 0x2b, 0x4d, 0x4e, 0xe6, 0x9a, 0x74, 0xfe, 0xa7, 0x06, 0xab, 0x05, 0x51, + 0x74, 0x4c, 0x68, 0x1b, 0xd3, 0x90, 0xcb, 0x01, 0x89, 0x64, 0x30, 0xad, 0x5b, 0x4c, 0x5f, 0x82, + 0xf9, 0x7e, 0x10, 0x05, 0xfd, 0x41, 0xbf, 0x23, 0x74, 0x2f, 0x64, 0x9a, 0x93, 0xc0, 0x3d, 0xdc, + 0x02, 0x8e, 0xe4, 0x3d, 0x37, 0x90, 0x26, 0x25, 0x92, 0x00, 0x0a, 0xa4, 0x37, 0x61, 0x25, 0x8f, + 0xdb, 0x3b, 0x3d, 0x2f, 0x88, 0x3a, 0x61, 0x9c, 0x65, 0x72, 0x8f, 0x49, 0x3e, 0xb6, 0xeb, 0x05, + 0xd1, 0xe3, 0x38, 0xcb, 0x0c, 0x27, 0x30, 0x65, 0x3a, 0x01, 0x1e, 0xc0, 0x34, 0x3f, 0x39, 0xf6, + 0x42, 0x7a, 0x3f, 0xee, 0x1f, 0xbe, 0x58, 0xdd, 0xdf, 0x80, 0x39, 0x71, 0xef, 0xc6, 0xbc, 0xb4, + 0x47, 0xd5, 0x0e, 0xcc, 0x22, 0xec, 0x00, 0x41, 0x95, 0xdb, 0xf0, 0x5f, 0x35, 0x20, 0x3b, 0x3c, + 0x94, 0x09, 0xc7, 0xb6, 0x07, 0xee, 0x4a, 0x44, 0xde, 0x9c, 0x5b, 0x58, 0x43, 0x42, 0x1e, 0xd9, + 0xe6, 0x37, 0x61, 0x99, 0x9f, 0x5e, 0xcd, 0xe4, 0x05, 0x2f, 0xc7, 0x4a, 0x7e, 0xfc, 0x65, 0x58, + 0x38, 0xf5, 0xc2, 0x90, 0x32, 0x5d, 0x62, 0x93, 0x37, 0xf1, 0x02, 0xaa, 0x72, 0x70, 0xb5, 0xe0, + 0x69, 0x63, 0xc1, 0xab, 0xb0, 0x6c, 0xad, 0x57, 0x46, 0x43, 0xf7, 0x60, 0x4d, 0x80, 0xb7, 0xc3, + 0x70, 0x6c, 0xaf, 0xea, 0xfc, 0x65, 0x1d, 0xd6, 0x4b, 0xd3, 0x74, 0xd8, 0x60, 0x9b, 0xf1, 0x4d, + 0xbd, 0xdc, 0xea, 0x09, 0x5b, 0xf2, 0x51, 0xce, 0x6a, 0xff, 0x53, 0x0d, 0xa6, 0x04, 0x68, 0xe4, + 0x6e, 0x7c, 0xaa, 0x1c, 0x82, 0x34, 0x38, 0x91, 0x11, 0x7d, 0x67, 0x3c, 0x66, 0xe2, 0x3f, 0xb3, + 0xac, 0x2a, 0x3c, 0x89, 0xac, 0xa8, 0x7e, 0x0f, 0x9a, 0x45, 0x84, 0x0b, 0x95, 0x9c, 0xc4, 0xad, + 0xca, 0xc3, 0x13, 0x6a, 0x94, 0x51, 0x7f, 0x51, 0x83, 0xc5, 0x9d, 0x38, 0xf2, 0x03, 0x7e, 0x62, + 0xee, 0x79, 0xa9, 0xd7, 0xcf, 0x64, 0x25, 0x5f, 0x80, 0xd4, 0xb5, 0xbb, 0x06, 0x0c, 0xb9, 0xe0, + 0xbc, 0x0a, 0xd0, 0x3d, 0xa6, 0xdd, 0x67, 0x1d, 0x79, 0xe3, 0x28, 0xca, 0xff, 0x1c, 0x72, 0x3f, + 0xf0, 0x33, 0xf2, 0x06, 0x2c, 0xe7, 0xc3, 0x1d, 0x2f, 0xf2, 0x3b, 0xf2, 0xba, 0x11, 0xab, 0x1b, + 0x1a, 0x6f, 0x3b, 0xf2, 0xb7, 0xb3, 0x67, 0x19, 0x8f, 0x15, 0xf5, 0x2d, 0x5b, 0xc7, 0x72, 0xe1, + 0x8b, 0x1a, 0xbe, 0x8d, 0x60, 0xe7, 0x7f, 0x6b, 0x78, 0x02, 0xaa, 0x55, 0xc9, 0xdd, 0xce, 0x2f, + 0xd6, 0xf0, 0xbe, 0xd5, 0xda, 0xb2, 0x7a, 0x61, 0xcb, 0x08, 0x4c, 0x06, 0x8c, 0xf6, 0xd5, 0xc1, + 0xc2, 0x7f, 0x93, 0xfb, 0xd0, 0xd4, 0x2b, 0xee, 0x24, 0xa8, 0x16, 0xf9, 0x9a, 0xac, 0xe7, 0x89, + 0xa3, 0xa5, 0x35, 0x77, 0xb1, 0x5b, 0x50, 0xa3, 0x7a, 0xbd, 0x2e, 0x8d, 0xe5, 0xa8, 0xbb, 0xa8, + 0x6d, 0xe9, 0x9f, 0xc4, 0x93, 0x90, 0x9a, 0x76, 0x07, 0x8c, 0xfa, 0x32, 0x54, 0xd6, 0xcf, 0xce, + 0x7f, 0xd4, 0x60, 0x71, 0xdb, 0xf7, 0x71, 0xdd, 0xe3, 0xb8, 0x09, 0xb5, 0xca, 0xfa, 0x39, 0xab, + 0x9c, 0xf8, 0x8a, 0xab, 0xfc, 0xda, 0x4e, 0x64, 0x88, 0x12, 0x1c, 0x07, 0x9a, 0xf9, 0x3a, 0xab, + 0xb7, 0xd7, 0xf9, 0x16, 0x10, 0x91, 0x5e, 0x59, 0xea, 0x28, 0x62, 0xad, 0xc2, 0xb2, 0x85, 0x25, + 0x7d, 0xcd, 0xfb, 0x70, 0x6b, 0x97, 0xb2, 0x9d, 0xf4, 0x2c, 0x61, 0xb1, 0x0a, 0x67, 0x1f, 0xd0, + 0x24, 0xce, 0x02, 0xe5, 0xb9, 0xe8, 0x58, 0xde, 0xe7, 0x9f, 0x6b, 0x70, 0x7b, 0x0c, 0x42, 0x72, + 0x09, 0x9f, 0x95, 0xef, 0x97, 0x7e, 0xd3, 0x6c, 0x6f, 0x19, 0x8b, 0xca, 0x96, 0x86, 0xc8, 0x2e, + 0x03, 0x4d, 0xb2, 0xfd, 0x1e, 0x2c, 0xd8, 0x83, 0x17, 0x72, 0x15, 0x21, 0xdc, 0x3c, 0x47, 0x88, + 0x71, 0x6c, 0xee, 0x26, 0x2c, 0x74, 0x2d, 0x12, 0x92, 0x51, 0x01, 0xea, 0xec, 0xc0, 0x2b, 0xe7, + 0x72, 0x93, 0x6a, 0x1b, 0x9a, 0xa1, 0x3b, 0x7f, 0x37, 0x09, 0xeb, 0x9f, 0x04, 0xec, 0xd8, 0x4f, + 0xbd, 0x53, 0x65, 0x7d, 0xe3, 0x08, 0x59, 0x48, 0xde, 0xeb, 0xe5, 0xfb, 0x86, 0x57, 0x61, 0x29, + 0x8e, 0x28, 0xe6, 0x18, 0x9d, 0xc4, 0xcb, 0xb2, 0xd3, 0x38, 0x55, 0x67, 0xe9, 0x62, 0x1c, 0x51, + 0x9e, 0x67, 0xec, 0x49, 0x70, 0xe1, 0x34, 0x9e, 0x2c, 0x9e, 0xc6, 0x4d, 0x98, 0x48, 0x82, 0x48, + 0xd6, 0x4c, 0xf8, 0x4f, 0x7e, 0x76, 0xb2, 0xd4, 0xf3, 0x0d, 0xca, 0xf2, 0xec, 0x44, 0xa8, 0xa6, + 0x6b, 0xde, 0xe2, 0x4f, 0x17, 0x6e, 0xf1, 0x0d, 0x9d, 0xcc, 0xd8, 0xb7, 0x16, 0x9b, 0x30, 0x2b, + 0x7f, 0x76, 0x98, 0xd7, 0x93, 0x29, 0x10, 0x48, 0xd0, 0x81, 0xd7, 0x33, 0xa2, 0x35, 0xb0, 0xa2, + 0xb5, 0xab, 0x00, 0x47, 0x94, 0x76, 0xac, 0x64, 0xa8, 0x71, 0x44, 0xa9, 0x70, 0xba, 0x3c, 0x54, + 0x3e, 0xf4, 0xa2, 0x67, 0x1d, 0xbc, 0x83, 0x98, 0x13, 0xe2, 0x70, 0xc0, 0x47, 0x5e, 0x1f, 0x63, + 0x62, 0x1c, 0x54, 0x32, 0xcd, 0x0b, 0x8d, 0x72, 0xd8, 0x76, 0x7e, 0x9b, 0x82, 0x28, 0xdd, 0x80, + 0x9d, 0xb5, 0x16, 0xf2, 0xf9, 0x3b, 0x01, 0x3b, 0xd3, 0xf3, 0x51, 0x67, 0xe9, 0x59, 0x6b, 0x31, + 0x9f, 0xbf, 0x23, 0x40, 0x5c, 0xbc, 0xec, 0x34, 0x38, 0xa2, 0xa2, 0x31, 0xa4, 0x29, 0x5b, 0xa5, + 0x38, 0x64, 0x27, 0xf6, 0x31, 0x8c, 0x3c, 0x0d, 0x52, 0x23, 0x39, 0x5d, 0x12, 0x29, 0x2c, 0x07, + 0x2a, 0xd3, 0x70, 0x5e, 0x85, 0xa6, 0x32, 0x17, 0xb3, 0x77, 0x32, 0xa5, 0xd9, 0x20, 0x64, 0xaa, + 0x77, 0x52, 0x3c, 0x39, 0x6f, 0x61, 0x57, 0xc4, 0xe3, 0xb8, 0xd7, 0xcb, 0xd3, 0x27, 0x69, 0x5a, + 0x6b, 0x30, 0x15, 0x22, 0x5c, 0x4d, 0x11, 0x4f, 0x4e, 0x84, 0xf7, 0x39, 0x85, 0x29, 0x79, 0xd5, + 0x22, 0x88, 0x8e, 0x62, 0x99, 0x2d, 0xe0, 0x6f, 0xfe, 0x2e, 0xfa, 0xf4, 0x70, 0xd0, 0x53, 0x3d, + 0x50, 0xf8, 0xc0, 0x31, 0x4f, 0xbd, 0x34, 0x92, 0x07, 0x2a, 0xfe, 0xe6, 0x98, 0x34, 0x4d, 0xe3, + 0x54, 0x9e, 0x9e, 0xe2, 0xc1, 0xd9, 0x85, 0xf5, 0xfd, 0x8b, 0x89, 0xc8, 0x09, 0x89, 0xdb, 0x1a, + 0xf9, 0xfa, 0xe3, 0x83, 0xf3, 0xa1, 0xd5, 0x01, 0x82, 0x5d, 0x02, 0xe3, 0xbc, 0x46, 0x2b, 0x70, + 0x09, 0x7d, 0xb9, 0x22, 0x86, 0x0f, 0x3c, 0x23, 0x6c, 0x95, 0xa9, 0xe9, 0x1e, 0xb4, 0x72, 0x47, + 0x85, 0xf0, 0x84, 0xdf, 0xae, 0xe8, 0xa8, 0xb0, 0xe6, 0x8e, 0xd7, 0x52, 0xf1, 0x4b, 0xed, 0x92, + 0xf8, 0x02, 0x96, 0x4d, 0xd1, 0xbe, 0xd1, 0xac, 0xff, 0xa7, 0x35, 0xbc, 0x21, 0xd3, 0x19, 0xd8, + 0x3e, 0x4b, 0xa9, 0xd7, 0xff, 0x46, 0x0b, 0xe2, 0xdf, 0x87, 0x1b, 0x66, 0xbf, 0xd4, 0x85, 0x25, + 0x71, 0x7e, 0x0f, 0xcb, 0x88, 0xa2, 0xc8, 0xff, 0x2b, 0x90, 0xff, 0x3d, 0xb8, 0x66, 0xc8, 0x7f, + 0x41, 0x31, 0x9c, 0xbf, 0xa8, 0xe1, 0x2d, 0xe2, 0xf6, 0xc0, 0x0f, 0x98, 0x15, 0x73, 0x70, 0xcf, + 0xc4, 0xbc, 0x94, 0x75, 0x7c, 0x8f, 0x51, 0xdd, 0xc4, 0xc9, 0x21, 0x0f, 0x3c, 0x86, 0x97, 0x27, + 0x34, 0xf2, 0xc5, 0xa0, 0xbc, 0x0c, 0xa0, 0x91, 0xaf, 0x86, 0x44, 0xe6, 0x70, 0x78, 0x66, 0x25, + 0x6a, 0xf7, 0xf1, 0x9c, 0xc6, 0xa6, 0x17, 0x7c, 0xe3, 0x2f, 0xb9, 0xe2, 0x81, 0xbf, 0xd6, 0xf1, + 0xd1, 0x11, 0x7f, 0xe5, 0x2e, 0x21, 0x58, 0x3e, 0x39, 0x3b, 0xa2, 0x28, 0x6d, 0x88, 0x26, 0xdf, + 0xb7, 0x57, 0x61, 0x8a, 0x62, 0x98, 0x5c, 0xac, 0x6e, 0x1b, 0xb8, 0x12, 0xc3, 0xf9, 0x2b, 0x61, + 0x61, 0x1f, 0x04, 0x19, 0x8b, 0xd3, 0xa0, 0xbb, 0xe3, 0x45, 0x7e, 0x38, 0x56, 0x18, 0x74, 0x81, + 0x1d, 0xda, 0x80, 0x46, 0x8a, 0xfd, 0x7f, 0xc1, 0x17, 0x54, 0xf6, 0x46, 0xe4, 0x00, 0x7e, 0x2e, + 0xf7, 0x52, 0x2f, 0x1a, 0x84, 0x5e, 0xca, 0x4f, 0x89, 0x49, 0x71, 0xa3, 0x6c, 0x80, 0x9c, 0x07, + 0x58, 0xf9, 0x2c, 0x89, 0x28, 0x57, 0x7b, 0x13, 0xa6, 0xba, 0x08, 0x92, 0xab, 0x5d, 0x30, 0x72, + 0x30, 0x3f, 0xa4, 0xae, 0x1c, 0x75, 0xfe, 0xa0, 0x06, 0x53, 0x02, 0xc4, 0xbd, 0xad, 0x6e, 0x9c, + 0x9f, 0x70, 0xf1, 0xb7, 0x6a, 0xc7, 0xa9, 0xe7, 0xed, 0x38, 0xaa, 0x69, 0x67, 0xc2, 0x68, 0xda, + 0x21, 0x30, 0x19, 0x27, 0x34, 0x52, 0xcd, 0x3d, 0xfc, 0x37, 0xdf, 0xb5, 0x6e, 0x18, 0x67, 0x54, + 0x66, 0x2e, 0xe2, 0xc1, 0x68, 0xd4, 0x99, 0x32, 0x1b, 0x75, 0x9c, 0xe7, 0x00, 0xf9, 0x36, 0xa0, + 0x24, 0xdc, 0x6c, 0xe5, 0xa5, 0x33, 0xff, 0x4d, 0xae, 0x01, 0x04, 0x3e, 0x8d, 0x58, 0x70, 0x14, + 0x50, 0xd5, 0xf0, 0x61, 0x40, 0x78, 0x18, 0xd0, 0xa7, 0x59, 0xa6, 0xaa, 0xa5, 0x0d, 0x57, 0x3d, + 0x72, 0x45, 0xf3, 0xb5, 0x64, 0xcc, 0xeb, 0x27, 0x2a, 0x26, 0xd1, 0x00, 0xe7, 0x10, 0x1a, 0xbb, + 0x3b, 0x07, 0xfb, 0x18, 0xee, 0x70, 0xc6, 0x1f, 0x7f, 0xfc, 0xe8, 0x81, 0x62, 0xcc, 0x7f, 0xeb, + 0x62, 0x43, 0xdd, 0x28, 0x36, 0x10, 0xbe, 0xcb, 0xec, 0x58, 0x25, 0x4d, 0xfc, 0x37, 0xb7, 0xe0, + 0x88, 0x3e, 0x67, 0x9d, 0x74, 0x10, 0x49, 0x2e, 0xd3, 0xfc, 0xd9, 0x1d, 0x44, 0xce, 0x03, 0x58, + 0xd7, 0x3c, 0x1e, 0x8a, 0x14, 0x46, 0xd9, 0xd2, 0x6d, 0x98, 0x12, 0xa1, 0x96, 0x6c, 0x7b, 0x59, + 0xd2, 0xbe, 0x5f, 0x4d, 0x70, 0x25, 0x82, 0xb3, 0x0d, 0x2b, 0x1a, 0xb8, 0xcf, 0xe2, 0xe4, 0x2b, + 0x90, 0xb8, 0x6c, 0x08, 0xc2, 0x49, 0x6c, 0x87, 0xa1, 0x4a, 0x85, 0x5b, 0xb0, 0x66, 0x0c, 0xf1, + 0x14, 0x5b, 0x8d, 0x98, 0x93, 0x1e, 0x07, 0x19, 0x33, 0x26, 0xfd, 0x4d, 0xcd, 0x98, 0xf5, 0x71, + 0x12, 0xc6, 0x9e, 0xaf, 0xa4, 0xda, 0x84, 0x59, 0xc1, 0xb4, 0x63, 0x94, 0x6a, 0x40, 0x80, 0x30, + 0x50, 0xca, 0x11, 0xb0, 0x87, 0xa1, 0x6e, 0x22, 0x3c, 0xf0, 0x98, 0xa7, 0xbb, 0x1b, 0x26, 0xf2, + 0xee, 0x06, 0xfe, 0xea, 0x79, 0x69, 0xf7, 0x38, 0x38, 0xa1, 0xbe, 0x0c, 0x00, 0xf4, 0x33, 0xdf, + 0xe7, 0xf8, 0x84, 0xa6, 0xa7, 0x69, 0xc0, 0x84, 0xd5, 0xcd, 0xb8, 0x39, 0xc0, 0xd9, 0x85, 0x76, + 0xae, 0x0f, 0xea, 0xf9, 0xea, 0xd7, 0x85, 0x75, 0x78, 0x1f, 0x56, 0x35, 0xf0, 0x47, 0x03, 0xaa, + 0x9b, 0x0d, 0x2e, 0x42, 0xe3, 0x07, 0xd0, 0xd2, 0xc0, 0xed, 0x01, 0x8b, 0x1f, 0x1b, 0x8a, 0x5b, + 0xb3, 0xc8, 0x34, 0xd4, 0x1c, 0xe3, 0x1a, 0x4f, 0xc4, 0x48, 0xea, 0x1a, 0xef, 0x33, 0x6b, 0x4f, + 0xc5, 0xc6, 0xe5, 0x01, 0x9d, 0xee, 0x6d, 0x37, 0xaf, 0xff, 0x5f, 0x83, 0x69, 0x41, 0x54, 0xdd, + 0xd0, 0x54, 0x88, 0xaa, 0x30, 0x9c, 0xd8, 0xd8, 0x62, 0xb9, 0xde, 0x73, 0xc8, 0xe7, 0x8a, 0xa8, + 0x9f, 0xa3, 0x08, 0x6b, 0x8f, 0x1b, 0xb2, 0x83, 0xe5, 0x7d, 0x43, 0x39, 0xb2, 0x3b, 0xfb, 0x5c, + 0x96, 0x8a, 0x4e, 0x3d, 0xa7, 0x73, 0xf7, 0x67, 0xdf, 0x86, 0x85, 0xdd, 0x58, 0xe4, 0x55, 0x07, + 0x3c, 0x9d, 0x48, 0xc9, 0x13, 0x98, 0x96, 0xdf, 0xb1, 0x90, 0xb5, 0xd2, 0x87, 0x2d, 0xa8, 0xfe, + 0xf6, 0xfa, 0x90, 0x0f, 0x5e, 0x9c, 0xe5, 0x2f, 0xff, 0xf5, 0xdf, 0x7f, 0x5e, 0x9f, 0x27, 0xb3, + 0x77, 0x4e, 0xde, 0xba, 0xd3, 0xa3, 0x0c, 0xe3, 0xd6, 0x1e, 0xcc, 0x5b, 0x9f, 0x1e, 0x90, 0x0d, + 0xeb, 0xf3, 0x81, 0xc2, 0x17, 0x09, 0xed, 0xab, 0x23, 0x3f, 0x2e, 0x70, 0x2e, 0x23, 0x8b, 0x65, + 0xb2, 0x24, 0x59, 0xe4, 0x5f, 0x15, 0x90, 0xcf, 0x61, 0xf1, 0x21, 0xd6, 0x33, 0x35, 0x51, 0xb2, + 0x99, 0x13, 0xab, 0xfc, 0xa2, 0xa2, 0x7d, 0x7d, 0x38, 0x82, 0x64, 0x78, 0x05, 0x19, 0xae, 0x92, + 0x65, 0xce, 0x50, 0xd4, 0x4b, 0x35, 0x4f, 0x92, 0x41, 0x53, 0xf6, 0x68, 0xbf, 0x50, 0x9e, 0x1b, + 0xc8, 0x73, 0x8d, 0xac, 0x70, 0x9e, 0xbe, 0x60, 0x90, 0x33, 0x8d, 0xb1, 0x1c, 0x63, 0x7e, 0x53, + 0x40, 0xae, 0x0d, 0xfd, 0xd8, 0x40, 0xb0, 0xdc, 0x3c, 0xe7, 0x63, 0x04, 0x7b, 0x95, 0x3d, 0xca, + 0x71, 0xf5, 0xf7, 0x08, 0xe4, 0xe7, 0x22, 0x46, 0xaf, 0xfc, 0xfa, 0x85, 0xbc, 0x72, 0xfe, 0x27, + 0x37, 0x42, 0x86, 0x5b, 0xe3, 0x7e, 0x9b, 0xe3, 0x7c, 0x0b, 0x85, 0xb9, 0x46, 0x36, 0xa4, 0x30, + 0xd6, 0xf7, 0x38, 0xea, 0x8b, 0x1f, 0xd2, 0x85, 0x39, 0xf3, 0x43, 0x02, 0x72, 0xa5, 0x22, 0x25, + 0xd0, 0xcc, 0x37, 0xaa, 0x07, 0x25, 0xc3, 0x16, 0x32, 0x24, 0xa4, 0x29, 0x19, 0xea, 0xef, 0x0e, + 0xc8, 0x17, 0xb0, 0x58, 0x68, 0xc2, 0x27, 0x4e, 0x61, 0xfb, 0x2a, 0x3e, 0xa8, 0x68, 0xbf, 0x34, + 0x12, 0x47, 0x72, 0xbd, 0x86, 0x5c, 0x5b, 0xce, 0xb2, 0xb1, 0xcb, 0x8a, 0xf3, 0x77, 0x6b, 0xaf, + 0x92, 0x0c, 0xf7, 0xd9, 0xec, 0x17, 0x1f, 0x8b, 0xf7, 0xe6, 0x39, 0xcd, 0xe6, 0xa5, 0xbd, 0x56, + 0x3c, 0xf1, 0x6d, 0xcd, 0xb0, 0x07, 0xd7, 0xf8, 0xca, 0x01, 0xf3, 0xe5, 0x71, 0xf8, 0x5e, 0xad, + 0xfe, 0x4a, 0x42, 0x7e, 0xa8, 0xe1, 0xb4, 0x91, 0xeb, 0x0a, 0x21, 0x05, 0xae, 0x31, 0x4b, 0x48, + 0x66, 0x7d, 0x44, 0x22, 0x99, 0xda, 0x56, 0x5d, 0xf1, 0x19, 0x47, 0xe5, 0x4a, 0xcd, 0xef, 0x32, + 0x86, 0xae, 0x34, 0x66, 0x49, 0x46, 0x9e, 0xc3, 0x82, 0x70, 0x17, 0x2f, 0x7e, 0x67, 0xaf, 0x22, + 0xdf, 0x75, 0x87, 0xe4, 0x3e, 0xc3, 0xdc, 0xd8, 0x4f, 0xa0, 0xa1, 0x13, 0x1b, 0xd2, 0x32, 0x16, + 0x61, 0x75, 0xd4, 0xb7, 0x87, 0xf4, 0x4b, 0x2b, 0x6b, 0x75, 0xe6, 0xe5, 0xaa, 0x44, 0xf7, 0x33, + 0x27, 0xfc, 0x63, 0x80, 0xbc, 0x81, 0x9a, 0x5c, 0x2e, 0x51, 0xd6, 0x9a, 0x6b, 0x57, 0x0d, 0xa9, + 0x4f, 0xc5, 0x90, 0x7c, 0x93, 0x2c, 0x58, 0xe4, 0xd5, 0xfb, 0xa6, 0xf3, 0x38, 0xeb, 0x7d, 0x2b, + 0xb6, 0x5c, 0xb7, 0x87, 0xf7, 0xda, 0xaa, 0x4d, 0x71, 0xd4, 0xcb, 0xa6, 0xef, 0xeb, 0xf9, 0x0a, + 0xc4, 0x61, 0x61, 0x34, 0xf9, 0x6e, 0x54, 0x71, 0xa9, 0x3c, 0x2c, 0xca, 0x1d, 0xbb, 0xa5, 0xc3, + 0x22, 0x6f, 0xcc, 0x25, 0xcf, 0xf0, 0x53, 0x59, 0xa3, 0x47, 0x95, 0x98, 0xb4, 0xca, 0x0d, 0xbb, + 0xed, 0x6b, 0xc3, 0x86, 0xb3, 0x6a, 0xfb, 0x96, 0x57, 0x7a, 0xf8, 0x52, 0x9d, 0x89, 0x5c, 0x30, + 0x9f, 0x25, 0xf2, 0xc8, 0xaf, 0xcb, 0xf2, 0x3a, 0xb2, 0x6c, 0x93, 0x56, 0x99, 0x65, 0x86, 0x0c, + 0xde, 0xac, 0x49, 0x5b, 0x13, 0x4d, 0xb1, 0x96, 0xad, 0x59, 0xbd, 0xb3, 0xed, 0xcb, 0x15, 0x23, + 0x92, 0xcb, 0x2a, 0x72, 0x59, 0x24, 0xf3, 0xda, 0x1b, 0x23, 0x2d, 0x61, 0x0e, 0xba, 0x5b, 0xc9, + 0x32, 0x87, 0x62, 0x4b, 0xab, 0xe5, 0x7e, 0x4b, 0x8d, 0xad, 0x25, 0xf7, 0xab, 0x5b, 0x57, 0xc9, + 0xef, 0xdb, 0x1d, 0xb2, 0xaa, 0x63, 0xcf, 0x19, 0xd9, 0x62, 0x57, 0x7a, 0x51, 0x87, 0xb6, 0xe1, + 0x39, 0x9b, 0xc8, 0xf9, 0x32, 0x59, 0x2f, 0x72, 0x96, 0x2d, 0x7d, 0xe4, 0xcb, 0x1a, 0x2c, 0x57, + 0x34, 0x8c, 0xe5, 0x12, 0x0c, 0x6f, 0x6f, 0xcb, 0x25, 0x18, 0xd5, 0x71, 0xe6, 0xa0, 0x04, 0x1b, + 0x0e, 0x4a, 0xe0, 0xf9, 0xbe, 0x96, 0x40, 0x5e, 0x8e, 0xf2, 0x97, 0xe2, 0x67, 0x35, 0x58, 0xab, + 0x6e, 0x0e, 0x23, 0x2f, 0xeb, 0xef, 0xfe, 0x46, 0xb5, 0xad, 0xb5, 0x6f, 0x9e, 0x87, 0x26, 0xa5, + 0x79, 0x19, 0xa5, 0xd9, 0x74, 0xda, 0x5c, 0x9a, 0x14, 0x71, 0xab, 0x04, 0x3a, 0xc5, 0x8a, 0x9a, + 0xdd, 0x7e, 0x45, 0x8c, 0xb0, 0xa6, 0xba, 0x4b, 0xad, 0x7d, 0x63, 0x04, 0x86, 0xed, 0x39, 0xc9, + 0xaa, 0xdc, 0x10, 0xec, 0x59, 0xd2, 0x7d, 0x5c, 0xd2, 0x3d, 0xe4, 0xed, 0x4d, 0x96, 0x7b, 0x28, + 0x75, 0x6c, 0x59, 0xee, 0xa1, 0xdc, 0x44, 0x55, 0x72, 0x0f, 0xc8, 0x0c, 0x1b, 0xaa, 0xc8, 0xa7, + 0xf8, 0xda, 0xc8, 0x72, 0x6e, 0xab, 0xe8, 0x65, 0xb2, 0xaa, 0xd7, 0xc6, 0x2e, 0xd8, 0x96, 0xbc, + 0xb4, 0xa8, 0x12, 0x73, 0xed, 0xb9, 0x30, 0xa3, 0xd0, 0xc9, 0x7a, 0x91, 0x80, 0xa2, 0x5c, 0xd9, + 0x91, 0xe3, 0xac, 0x23, 0xd1, 0x25, 0x67, 0xce, 0x24, 0xca, 0x69, 0x1e, 0xc2, 0xac, 0xd1, 0x7d, + 0x42, 0xb4, 0x7f, 0x2f, 0x37, 0xdb, 0xb4, 0xaf, 0x54, 0x8e, 0xd9, 0x5e, 0xcc, 0x59, 0xe4, 0x0c, + 0x32, 0x44, 0xd0, 0x3c, 0x7e, 0x07, 0xe6, 0xad, 0x06, 0x90, 0x5c, 0xf9, 0x55, 0x2d, 0x2a, 0xb9, + 0xf2, 0x2b, 0xbb, 0x46, 0x54, 0x8c, 0xeb, 0xa0, 0xf2, 0x33, 0x89, 0xa2, 0x79, 0x7d, 0x06, 0x0d, + 0xdd, 0x77, 0x91, 0xeb, 0xbf, 0xd8, 0x8a, 0x71, 0x1e, 0x0f, 0x6b, 0x0f, 0x4e, 0xf9, 0xe4, 0xc3, + 0xb8, 0x7f, 0x28, 0xf5, 0x65, 0x74, 0x15, 0xe4, 0xfa, 0x2a, 0xb7, 0x56, 0xe4, 0xfa, 0xaa, 0x6a, + 0x43, 0xb0, 0xf4, 0xd5, 0x45, 0x04, 0xbd, 0x86, 0x14, 0x16, 0x0b, 0xd5, 0xfc, 0x3c, 0xa2, 0xa9, + 0xee, 0x5d, 0xc8, 0x23, 0x9a, 0x21, 0x6d, 0x00, 0x76, 0xcc, 0x28, 0xf8, 0x79, 0x61, 0x98, 0xdb, + 0x96, 0x70, 0xf7, 0xa2, 0xd6, 0x6d, 0xd9, 0xad, 0x55, 0xd4, 0xb7, 0xec, 0xd6, 0x2e, 0x8c, 0x97, + 0xdc, 0xbd, 0xb8, 0xee, 0x23, 0x4f, 0x61, 0x46, 0x15, 0x59, 0x73, 0xa3, 0x2d, 0x94, 0x97, 0xdb, + 0xad, 0xf2, 0x80, 0xa4, 0x6a, 0x19, 0xae, 0xe7, 0xfb, 0x48, 0x55, 0x6e, 0x84, 0x51, 0x72, 0xcd, + 0x37, 0xa2, 0x5c, 0xad, 0xcd, 0x37, 0xa2, 0xaa, 0x46, 0x6b, 0x6d, 0x84, 0xf0, 0x5c, 0x9a, 0xc7, + 0xdf, 0xd7, 0xf0, 0x2a, 0x7a, 0x74, 0xc5, 0x94, 0xbc, 0x79, 0x81, 0xe2, 0xaa, 0x10, 0xe8, 0xad, + 0x0b, 0x97, 0x63, 0x9d, 0x5b, 0x28, 0xa6, 0xe3, 0x5c, 0x55, 0x87, 0x29, 0x4e, 0xf3, 0x05, 0xba, + 0xae, 0xcd, 0x72, 0xa1, 0xff, 0xb6, 0x26, 0xfe, 0x06, 0xc3, 0x08, 0xba, 0x64, 0x6b, 0x4c, 0x01, + 0x94, 0xc0, 0x77, 0xc6, 0xc6, 0x97, 0xe2, 0xde, 0x44, 0x71, 0xaf, 0x3b, 0x57, 0x46, 0x88, 0xcb, + 0x85, 0xfd, 0x5d, 0xb8, 0xa2, 0x2b, 0xab, 0x16, 0xdd, 0xf7, 0x07, 0x91, 0x9f, 0xe5, 0x29, 0xf1, + 0x90, 0xf2, 0x6b, 0x6e, 0x38, 0xc5, 0x82, 0x9b, 0x7d, 0x3e, 0x9e, 0xca, 0x51, 0x21, 0xc6, 0x11, + 0xa7, 0xcd, 0xb9, 0x27, 0xb0, 0xa4, 0xe6, 0xbd, 0x1f, 0x78, 0xec, 0x6b, 0xf3, 0x94, 0x71, 0x95, + 0xb3, 0x6a, 0xf2, 0x3c, 0x0a, 0x3c, 0xa6, 0x39, 0x66, 0xd8, 0x28, 0x63, 0xd5, 0xd2, 0xcc, 0xbc, + 0xbf, 0xb2, 0xca, 0x66, 0xe6, 0xfd, 0xd5, 0x65, 0x3f, 0x3b, 0xef, 0xef, 0x51, 0x26, 0xca, 0x70, + 0xbe, 0x64, 0x70, 0x02, 0xcd, 0xfd, 0xa1, 0x4c, 0xf7, 0xbf, 0x32, 0x53, 0x19, 0x03, 0x39, 0xc8, + 0x34, 0x2b, 0x30, 0xe5, 0x8b, 0x3d, 0x11, 0x5d, 0x41, 0x66, 0x95, 0x8d, 0x6c, 0x0e, 0xaf, 0xbf, + 0x95, 0xf9, 0x56, 0x16, 0xe8, 0x6c, 0xbe, 0x46, 0x72, 0x86, 0xdf, 0x9e, 0x73, 0xbe, 0x67, 0x40, + 0xec, 0x04, 0x0d, 0xbf, 0x59, 0xd4, 0x5e, 0xa0, 0xa2, 0xb6, 0x36, 0x5e, 0x76, 0x76, 0x03, 0x19, + 0x5f, 0x71, 0xd6, 0xca, 0xd9, 0x19, 0xe7, 0xcd, 0x59, 0xff, 0x04, 0x96, 0x0b, 0x69, 0xff, 0x0b, + 0xe2, 0x6d, 0x99, 0x73, 0x21, 0xe7, 0x57, 0xcc, 0x19, 0xa6, 0xe0, 0x85, 0x82, 0x19, 0xb9, 0x51, + 0x95, 0xea, 0x58, 0xf5, 0xa8, 0x51, 0x49, 0x97, 0x3c, 0x37, 0xc8, 0x5a, 0x29, 0x13, 0x52, 0x89, + 0xc2, 0x9f, 0xd4, 0xb0, 0x58, 0x32, 0xa4, 0x5e, 0x47, 0x6e, 0x57, 0xe5, 0xda, 0x17, 0x16, 0x43, + 0xfa, 0x13, 0x72, 0xad, 0x98, 0x90, 0x97, 0xc4, 0x39, 0xc6, 0xcb, 0x0f, 0xb3, 0xea, 0x66, 0x5d, + 0x07, 0x54, 0x94, 0xe3, 0x86, 0xe6, 0xcb, 0xc5, 0x5b, 0x00, 0x99, 0xd0, 0x2a, 0x4e, 0x3f, 0xb5, + 0xff, 0x18, 0x84, 0xc5, 0xf2, 0x66, 0xc5, 0xaa, 0x2f, 0xc2, 0xfa, 0x25, 0x64, 0x7d, 0x95, 0x5c, + 0x29, 0xac, 0xb7, 0x20, 0x82, 0x08, 0x6b, 0x8d, 0xea, 0x8e, 0x19, 0xd6, 0x96, 0x4a, 0x88, 0x56, + 0x58, 0x5b, 0xae, 0xe2, 0x95, 0xc2, 0x5a, 0x8f, 0xa3, 0xe0, 0x61, 0x48, 0x18, 0x34, 0x8b, 0x55, + 0x16, 0xe3, 0x55, 0xae, 0xae, 0xbf, 0x18, 0xaf, 0xf2, 0x90, 0x2b, 0xe7, 0x42, 0xd4, 0xde, 0x65, + 0xe2, 0xe6, 0xfa, 0x8e, 0x6c, 0x45, 0x23, 0x0c, 0x16, 0x0b, 0x15, 0x10, 0x63, 0x2f, 0x2b, 0x4b, + 0x23, 0x63, 0xf0, 0xb4, 0xdd, 0x87, 0xe6, 0x39, 0x40, 0x32, 0xfc, 0x35, 0x7a, 0x0e, 0xcb, 0x15, + 0xd5, 0x0c, 0x23, 0x77, 0x1c, 0x5a, 0xea, 0x68, 0x97, 0xa5, 0xb3, 0x6e, 0xf5, 0xed, 0xfb, 0x9d, + 0x9c, 0x77, 0x4a, 0x05, 0xe7, 0xc4, 0x58, 0xaf, 0xfc, 0xd3, 0x51, 0x65, 0x8a, 0x56, 0x01, 0xa9, + 0xbd, 0x39, 0x74, 0xbc, 0xf2, 0x68, 0xd0, 0x2c, 0xe5, 0xdd, 0x7e, 0x08, 0x0b, 0xb6, 0xa8, 0xc6, + 0xd5, 0x42, 0x55, 0x21, 0xe6, 0xdc, 0x15, 0xda, 0xef, 0x8c, 0x66, 0xf7, 0x39, 0xd2, 0x8e, 0x60, + 0xde, 0x2a, 0x91, 0x19, 0xe6, 0x5a, 0x51, 0x7c, 0x1b, 0xdf, 0x7e, 0x8a, 0xfa, 0xcc, 0x58, 0x9c, + 0x08, 0x87, 0xd8, 0x2c, 0x96, 0xe4, 0xc8, 0x66, 0x25, 0xcb, 0xbc, 0xee, 0xf6, 0xf5, 0xb9, 0x66, + 0x06, 0x57, 0x59, 0xd3, 0xab, 0xe0, 0x6a, 0x57, 0xfb, 0xce, 0xdf, 0xc7, 0x73, 0x98, 0xa2, 0x33, + 0x2a, 0x96, 0xbd, 0x0e, 0xe2, 0x5e, 0x2f, 0xa4, 0xa4, 0xbc, 0xa2, 0x42, 0x5d, 0x6c, 0x8c, 0x35, + 0x5b, 0x67, 0x5f, 0xce, 0xde, 0x1b, 0xb0, 0x58, 0xbd, 0x37, 0x3f, 0xc1, 0xe3, 0xa7, 0x50, 0x34, + 0xb7, 0x8e, 0x9f, 0xea, 0x9a, 0x7f, 0xdb, 0x19, 0x85, 0x32, 0xe4, 0x1c, 0x3a, 0x96, 0x78, 0xa2, + 0xd4, 0x9e, 0x1d, 0x4e, 0xe1, 0x1f, 0xb2, 0x7b, 0xfb, 0xff, 0x03, 0x00, 0x00, 0xff, 0xff, 0x43, + 0x1f, 0xbd, 0xbb, 0xfb, 0x4e, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -6347,6 +6348,7 @@ type GoCryptoTraderClient interface { GetOrderbook(ctx context.Context, in *GetOrderbookRequest, opts ...grpc.CallOption) (*OrderbookResponse, error) GetOrderbooks(ctx context.Context, in *GetOrderbooksRequest, opts ...grpc.CallOption) (*GetOrderbooksResponse, error) GetAccountInfo(ctx context.Context, in *GetAccountInfoRequest, opts ...grpc.CallOption) (*GetAccountInfoResponse, error) + GetAccountInfoStream(ctx context.Context, in *GetAccountInfoRequest, opts ...grpc.CallOption) (GoCryptoTrader_GetAccountInfoStreamClient, error) 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) @@ -6551,6 +6553,38 @@ func (c *goCryptoTraderClient) GetAccountInfo(ctx context.Context, in *GetAccoun return out, nil } +func (c *goCryptoTraderClient) GetAccountInfoStream(ctx context.Context, in *GetAccountInfoRequest, opts ...grpc.CallOption) (GoCryptoTrader_GetAccountInfoStreamClient, error) { + stream, err := c.cc.NewStream(ctx, &_GoCryptoTrader_serviceDesc.Streams[0], "/gctrpc.GoCryptoTrader/GetAccountInfoStream", opts...) + if err != nil { + return nil, err + } + x := &goCryptoTraderGetAccountInfoStreamClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type GoCryptoTrader_GetAccountInfoStreamClient interface { + Recv() (*GetAccountInfoResponse, error) + grpc.ClientStream +} + +type goCryptoTraderGetAccountInfoStreamClient struct { + grpc.ClientStream +} + +func (x *goCryptoTraderGetAccountInfoStreamClient) Recv() (*GetAccountInfoResponse, error) { + m := new(GetAccountInfoResponse) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + func (c *goCryptoTraderClient) GetConfig(ctx context.Context, in *GetConfigRequest, opts ...grpc.CallOption) (*GetConfigResponse, error) { out := new(GetConfigResponse) err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTrader/GetConfig", in, out, opts...) @@ -6786,7 +6820,7 @@ func (c *goCryptoTraderClient) DisableExchangePair(ctx context.Context, in *Exch } func (c *goCryptoTraderClient) GetOrderbookStream(ctx context.Context, in *GetOrderbookStreamRequest, opts ...grpc.CallOption) (GoCryptoTrader_GetOrderbookStreamClient, error) { - stream, err := c.cc.NewStream(ctx, &_GoCryptoTrader_serviceDesc.Streams[0], "/gctrpc.GoCryptoTrader/GetOrderbookStream", opts...) + stream, err := c.cc.NewStream(ctx, &_GoCryptoTrader_serviceDesc.Streams[1], "/gctrpc.GoCryptoTrader/GetOrderbookStream", opts...) if err != nil { return nil, err } @@ -6818,7 +6852,7 @@ func (x *goCryptoTraderGetOrderbookStreamClient) Recv() (*OrderbookResponse, err } func (c *goCryptoTraderClient) GetExchangeOrderbookStream(ctx context.Context, in *GetExchangeOrderbookStreamRequest, opts ...grpc.CallOption) (GoCryptoTrader_GetExchangeOrderbookStreamClient, error) { - stream, err := c.cc.NewStream(ctx, &_GoCryptoTrader_serviceDesc.Streams[1], "/gctrpc.GoCryptoTrader/GetExchangeOrderbookStream", opts...) + stream, err := c.cc.NewStream(ctx, &_GoCryptoTrader_serviceDesc.Streams[2], "/gctrpc.GoCryptoTrader/GetExchangeOrderbookStream", opts...) if err != nil { return nil, err } @@ -6850,7 +6884,7 @@ func (x *goCryptoTraderGetExchangeOrderbookStreamClient) Recv() (*OrderbookRespo } func (c *goCryptoTraderClient) GetTickerStream(ctx context.Context, in *GetTickerStreamRequest, opts ...grpc.CallOption) (GoCryptoTrader_GetTickerStreamClient, error) { - stream, err := c.cc.NewStream(ctx, &_GoCryptoTrader_serviceDesc.Streams[2], "/gctrpc.GoCryptoTrader/GetTickerStream", opts...) + stream, err := c.cc.NewStream(ctx, &_GoCryptoTrader_serviceDesc.Streams[3], "/gctrpc.GoCryptoTrader/GetTickerStream", opts...) if err != nil { return nil, err } @@ -6882,7 +6916,7 @@ func (x *goCryptoTraderGetTickerStreamClient) Recv() (*TickerResponse, error) { } func (c *goCryptoTraderClient) GetExchangeTickerStream(ctx context.Context, in *GetExchangeTickerStreamRequest, opts ...grpc.CallOption) (GoCryptoTrader_GetExchangeTickerStreamClient, error) { - stream, err := c.cc.NewStream(ctx, &_GoCryptoTrader_serviceDesc.Streams[3], "/gctrpc.GoCryptoTrader/GetExchangeTickerStream", opts...) + stream, err := c.cc.NewStream(ctx, &_GoCryptoTrader_serviceDesc.Streams[4], "/gctrpc.GoCryptoTrader/GetExchangeTickerStream", opts...) if err != nil { return nil, err } @@ -7031,6 +7065,7 @@ type GoCryptoTraderServer interface { GetOrderbook(context.Context, *GetOrderbookRequest) (*OrderbookResponse, error) GetOrderbooks(context.Context, *GetOrderbooksRequest) (*GetOrderbooksResponse, error) GetAccountInfo(context.Context, *GetAccountInfoRequest) (*GetAccountInfoResponse, error) + GetAccountInfoStream(*GetAccountInfoRequest, GoCryptoTrader_GetAccountInfoStreamServer) error GetConfig(context.Context, *GetConfigRequest) (*GetConfigResponse, error) GetPortfolio(context.Context, *GetPortfolioRequest) (*GetPortfolioResponse, error) GetPortfolioSummary(context.Context, *GetPortfolioSummaryRequest) (*GetPortfolioSummaryResponse, error) @@ -7129,6 +7164,9 @@ func (*UnimplementedGoCryptoTraderServer) GetOrderbooks(ctx context.Context, req func (*UnimplementedGoCryptoTraderServer) GetAccountInfo(ctx context.Context, req *GetAccountInfoRequest) (*GetAccountInfoResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetAccountInfo not implemented") } +func (*UnimplementedGoCryptoTraderServer) GetAccountInfoStream(req *GetAccountInfoRequest, srv GoCryptoTrader_GetAccountInfoStreamServer) error { + return status.Errorf(codes.Unimplemented, "method GetAccountInfoStream not implemented") +} func (*UnimplementedGoCryptoTraderServer) GetConfig(ctx context.Context, req *GetConfigRequest) (*GetConfigResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetConfig not implemented") } @@ -7563,6 +7601,27 @@ func _GoCryptoTrader_GetAccountInfo_Handler(srv interface{}, ctx context.Context return interceptor(ctx, in, info, handler) } +func _GoCryptoTrader_GetAccountInfoStream_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(GetAccountInfoRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(GoCryptoTraderServer).GetAccountInfoStream(m, &goCryptoTraderGetAccountInfoStreamServer{stream}) +} + +type GoCryptoTrader_GetAccountInfoStreamServer interface { + Send(*GetAccountInfoResponse) error + grpc.ServerStream +} + +type goCryptoTraderGetAccountInfoStreamServer struct { + grpc.ServerStream +} + +func (x *goCryptoTraderGetAccountInfoStreamServer) Send(m *GetAccountInfoResponse) error { + return x.ServerStream.SendMsg(m) +} + func _GoCryptoTrader_GetConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetConfigRequest) if err := dec(in); err != nil { @@ -8535,6 +8594,11 @@ var _GoCryptoTrader_serviceDesc = grpc.ServiceDesc{ }, }, Streams: []grpc.StreamDesc{ + { + StreamName: "GetAccountInfoStream", + Handler: _GoCryptoTrader_GetAccountInfoStream_Handler, + ServerStreams: true, + }, { StreamName: "GetOrderbookStream", Handler: _GoCryptoTrader_GetOrderbookStream_Handler, diff --git a/gctrpc/rpc.pb.gw.go b/gctrpc/rpc.pb.gw.go index ba85c1f711e..28ff36268a7 100644 --- a/gctrpc/rpc.pb.gw.go +++ b/gctrpc/rpc.pb.gw.go @@ -491,6 +491,34 @@ func local_request_GoCryptoTrader_GetAccountInfo_0(ctx context.Context, marshale } +var ( + filter_GoCryptoTrader_GetAccountInfoStream_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_GoCryptoTrader_GetAccountInfoStream_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (GoCryptoTrader_GetAccountInfoStreamClient, runtime.ServerMetadata, error) { + 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_GetAccountInfoStream_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + stream, err := client.GetAccountInfoStream(ctx, &protoReq) + if err != nil { + return nil, metadata, err + } + header, err := stream.Header() + if err != nil { + return nil, metadata, err + } + metadata.HeaderMD = header + return stream, metadata, nil + +} + func request_GoCryptoTrader_GetConfig_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetConfigRequest var metadata runtime.ServerMetadata @@ -2089,6 +2117,13 @@ func RegisterGoCryptoTraderHandlerServer(ctx context.Context, mux *runtime.Serve }) + mux.Handle("GET", pattern_GoCryptoTrader_GetAccountInfoStream_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + err := status.Error(codes.Unimplemented, "streaming calls are not yet supported in the in-process transport") + _, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + }) + mux.Handle("GET", pattern_GoCryptoTrader_GetConfig_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -3238,6 +3273,26 @@ func RegisterGoCryptoTraderHandlerClient(ctx context.Context, mux *runtime.Serve }) + mux.Handle("GET", pattern_GoCryptoTrader_GetAccountInfoStream_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_GetAccountInfoStream_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_GetAccountInfoStream_0(ctx, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...) + + }) + mux.Handle("GET", pattern_GoCryptoTrader_GetConfig_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -4096,6 +4151,8 @@ var ( pattern_GoCryptoTrader_GetAccountInfo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getaccountinfo"}, "", runtime.AssumeColonVerbOpt(true))) + pattern_GoCryptoTrader_GetAccountInfoStream_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getaccountinfostream"}, "", runtime.AssumeColonVerbOpt(true))) + pattern_GoCryptoTrader_GetConfig_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getconfig"}, "", runtime.AssumeColonVerbOpt(true))) pattern_GoCryptoTrader_GetPortfolio_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getportfolio"}, "", runtime.AssumeColonVerbOpt(true))) @@ -4214,6 +4271,8 @@ var ( forward_GoCryptoTrader_GetAccountInfo_0 = runtime.ForwardResponseMessage + forward_GoCryptoTrader_GetAccountInfoStream_0 = runtime.ForwardResponseStream + forward_GoCryptoTrader_GetConfig_0 = runtime.ForwardResponseMessage forward_GoCryptoTrader_GetPortfolio_0 = runtime.ForwardResponseMessage diff --git a/gctrpc/rpc.proto b/gctrpc/rpc.proto index 9a8ed00342c..a07a268c44d 100644 --- a/gctrpc/rpc.proto +++ b/gctrpc/rpc.proto @@ -542,7 +542,7 @@ message Candle { } message AuditEvent { - string type = 1 ; + string type = 1; string identifier = 2; string message = 3; string timestamp = 4; @@ -711,6 +711,12 @@ service GoCryptoTrader { }; } + rpc GetAccountInfoStream (GetAccountInfoRequest) returns (stream GetAccountInfoResponse) { + option (google.api.http) = { + get: "/v1/getaccountinfostream" + }; + } + rpc GetConfig (GetConfigRequest) returns (GetConfigResponse) { option (google.api.http) = { get: "/v1/getconfig" diff --git a/gctrpc/rpc.swagger.json b/gctrpc/rpc.swagger.json index 247a6299e6e..24333924415 100644 --- a/gctrpc/rpc.swagger.json +++ b/gctrpc/rpc.swagger.json @@ -495,6 +495,39 @@ ] } }, + "/v1/getaccountinfostream": { + "get": { + "operationId": "GetAccountInfoStream", + "responses": { + "200": { + "description": "A successful response.(streaming responses)", + "schema": { + "type": "object", + "properties": { + "result": { + "$ref": "#/definitions/gctrpcGetAccountInfoResponse" + }, + "error": { + "$ref": "#/definitions/runtimeStreamError" + } + }, + "title": "Stream result of gctrpcGetAccountInfoResponse" + } + } + }, + "parameters": [ + { + "name": "exchange", + "in": "query", + "required": false, + "type": "string" + } + ], + "tags": [ + "GoCryptoTrader" + ] + } + }, "/v1/getauditevent": { "get": { "operationId": "GetAuditEvent", @@ -676,7 +709,16 @@ "200": { "description": "A successful response.(streaming responses)", "schema": { - "$ref": "#/x-stream-definitions/gctrpcOrderbookResponse" + "type": "object", + "properties": { + "result": { + "$ref": "#/definitions/gctrpcOrderbookResponse" + }, + "error": { + "$ref": "#/definitions/runtimeStreamError" + } + }, + "title": "Stream result of gctrpcOrderbookResponse" } } }, @@ -791,7 +833,16 @@ "200": { "description": "A successful response.(streaming responses)", "schema": { - "$ref": "#/x-stream-definitions/gctrpcTickerResponse" + "type": "object", + "properties": { + "result": { + "$ref": "#/definitions/gctrpcTickerResponse" + }, + "error": { + "$ref": "#/definitions/runtimeStreamError" + } + }, + "title": "Stream result of gctrpcTickerResponse" } } }, @@ -1011,7 +1062,16 @@ "200": { "description": "A successful response.(streaming responses)", "schema": { - "$ref": "#/x-stream-definitions/gctrpcOrderbookResponse" + "type": "object", + "properties": { + "result": { + "$ref": "#/definitions/gctrpcOrderbookResponse" + }, + "error": { + "$ref": "#/definitions/runtimeStreamError" + } + }, + "title": "Stream result of gctrpcOrderbookResponse" } } }, @@ -1191,7 +1251,16 @@ "200": { "description": "A successful response.(streaming responses)", "schema": { - "$ref": "#/x-stream-definitions/gctrpcTickerResponse" + "type": "object", + "properties": { + "result": { + "$ref": "#/definitions/gctrpcTickerResponse" + }, + "error": { + "$ref": "#/definitions/runtimeStreamError" + } + }, + "title": "Stream result of gctrpcTickerResponse" } } }, @@ -2844,31 +2913,5 @@ } } } - }, - "x-stream-definitions": { - "gctrpcOrderbookResponse": { - "type": "object", - "properties": { - "result": { - "$ref": "#/definitions/gctrpcOrderbookResponse" - }, - "error": { - "$ref": "#/definitions/runtimeStreamError" - } - }, - "title": "Stream result of gctrpcOrderbookResponse" - }, - "gctrpcTickerResponse": { - "type": "object", - "properties": { - "result": { - "$ref": "#/definitions/gctrpcTickerResponse" - }, - "error": { - "$ref": "#/definitions/runtimeStreamError" - } - }, - "title": "Stream result of gctrpcTickerResponse" - } } } diff --git a/gctscript/modules/wrapper_types.go b/gctscript/modules/wrapper_types.go index 21a94d23497..5216c5a06b6 100644 --- a/gctscript/modules/wrapper_types.go +++ b/gctscript/modules/wrapper_types.go @@ -2,6 +2,7 @@ package modules import ( "github.com/thrasher-corp/gocryptotrader/currency" + "github.com/thrasher-corp/gocryptotrader/exchanges/account" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" @@ -27,7 +28,7 @@ type Exchange interface { QueryOrder(exch, orderid string) (*order.Detail, error) SubmitOrder(exch string, submit *order.Submit) (*order.SubmitResponse, error) CancelOrder(exch, orderid string) (bool, error) - AccountInformation(exch string) (*AccountInfo, error) + AccountInformation(exch string) (account.Holdings, error) DepositAddress(exch string, currencyCode currency.Code) (string, error) WithdrawalFiatFunds(exch, bankaccountid string, request *withdraw.FiatRequest) (out string, err error) WithdrawalCryptoFunds(exch string, request *withdraw.CryptoRequest) (out string, err error) @@ -37,23 +38,3 @@ type Exchange interface { func SetModuleWrapper(wrapper GCT) { Wrapper = wrapper } - -// AccountInfo is a Generic type to hold each exchange's holdings in -// all enabled currencies -type AccountInfo struct { - Exchange string - Accounts []Account -} - -// Account defines a singular account type with associated currencies -type Account struct { - ID string - Currencies []AccountCurrencyInfo -} - -// AccountCurrencyInfo is a sub type to store currency name and value -type AccountCurrencyInfo struct { - CurrencyName currency.Code - TotalValue float64 - Hold float64 -} diff --git a/gctscript/wrappers/gct/exchange/exchange.go b/gctscript/wrappers/gct/exchange/exchange.go index 1ee74ff4775..e8715123698 100644 --- a/gctscript/wrappers/gct/exchange/exchange.go +++ b/gctscript/wrappers/gct/exchange/exchange.go @@ -1,7 +1,6 @@ package exchange import ( - "encoding/json" "errors" "fmt" "strconv" @@ -9,12 +8,12 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/engine" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/account" "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/ticker" "github.com/thrasher-corp/gocryptotrader/exchanges/withdraw" - "github.com/thrasher-corp/gocryptotrader/gctscript/modules" ) // Exchange implements all required methods for Wrapper @@ -120,29 +119,18 @@ func (e Exchange) CancelOrder(exch, orderID string) (bool, error) { } // AccountInformation returns account information (balance etc) for requested exchange -func (e Exchange) AccountInformation(exch string) (*modules.AccountInfo, error) { +func (e Exchange) AccountInformation(exch string) (account.Holdings, error) { ex, err := e.GetExchange(exch) if err != nil { - return nil, err - } - - r, err := ex.GetAccountInfo() - if err != nil { - return nil, err + return account.Holdings{}, err } - temp, err := json.Marshal(r) + accountInfo, err := ex.FetchAccountInfo() if err != nil { - return nil, err - } - - accountInfo := modules.AccountInfo{} - err = json.Unmarshal(temp, &accountInfo) - if err != nil { - return nil, err + return account.Holdings{}, err } - return &accountInfo, nil + return accountInfo, nil } // DepositAddress gets the address required to deposit funds for currency type diff --git a/gctscript/wrappers/validator/validator.go b/gctscript/wrappers/validator/validator.go index 4723710fce3..25ccf0b58dd 100644 --- a/gctscript/wrappers/validator/validator.go +++ b/gctscript/wrappers/validator/validator.go @@ -4,12 +4,12 @@ import ( "time" "github.com/thrasher-corp/gocryptotrader/currency" + "github.com/thrasher-corp/gocryptotrader/exchanges/account" "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/ticker" "github.com/thrasher-corp/gocryptotrader/exchanges/withdraw" - "github.com/thrasher-corp/gocryptotrader/gctscript/modules" ) // Exchanges validator for test execution/scripts @@ -152,17 +152,17 @@ func (w Wrapper) CancelOrder(exch, orderid string) (bool, error) { } // AccountInformation validator for test execution/scripts -func (w Wrapper) AccountInformation(exch string) (*modules.AccountInfo, error) { +func (w Wrapper) AccountInformation(exch string) (account.Holdings, error) { if exch == exchError.String() { - return &modules.AccountInfo{}, errTestFailed + return account.Holdings{}, errTestFailed } - return &modules.AccountInfo{ + return account.Holdings{ Exchange: exch, - Accounts: []modules.Account{ + Accounts: []account.SubAccount{ { ID: exch, - Currencies: []modules.AccountCurrencyInfo{ + Currencies: []account.Balance{ { CurrencyName: currency.Code{ Item: ¤cy.Item{