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
This commit is contained in:
Ryan O'Hara-Reid
2020-01-31 12:09:24 +11:00
committed by GitHub
parent db23af2ed6
commit a32d16e1f5
75 changed files with 2010 additions and 857 deletions

View File

@@ -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")
}

View File

@@ -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}),
})

View File

@@ -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: "<exchange>",
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",

View File

@@ -102,6 +102,7 @@ func main() {
getOrderbookCommand,
getOrderbooksCommand,
getAccountInfoCommand,
getAccountInfoStreamCommand,
getConfigCommand,
getPortfolioCommand,
getPortfolioSummaryCommand,

View File

@@ -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()

View File

@@ -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")
}

View File

@@ -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)

View File

@@ -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)

View File

@@ -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"`
}

View File

@@ -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

View File

@@ -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)
}

View File

@@ -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()
}

View File

@@ -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
}

View File

@@ -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")
}

View File

@@ -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)
}

View File

@@ -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)

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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")
}

View File

@@ -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)
}

View File

@@ -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")
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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})
acc.Currencies = append(acc.Currencies,
account.Balance{
CurrencyName: c,
TotalValue: hold + available,
Hold: hold,
})
}
info.Accounts = append(info.Accounts, account)
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)
}

View File

@@ -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")
}

View File

@@ -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)
}

View File

@@ -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() {
if !e.LoadedByConfig {
return 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
}
// 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
}
// 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() {
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
}

View File

@@ -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

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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),

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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

View File

@@ -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")
}

View File

@@ -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)
}

View File

@@ -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")
}

View File

@@ -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)
}

View File

@@ -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 {

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)

View File

@@ -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)
}

View File

@@ -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 {

View File

@@ -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]) {

View File

@@ -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 {

View File

@@ -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

View File

@@ -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,

View File

@@ -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")
}

View File

@@ -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)
}

View File

@@ -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")
}

View File

@@ -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)
}

View File

@@ -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,

View File

@@ -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

View File

@@ -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"

View File

@@ -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"
}
}
}

View File

@@ -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
}

View File

@@ -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
return account.Holdings{}, err
}
r, err := ex.GetAccountInfo()
accountInfo, err := ex.FetchAccountInfo()
if err != nil {
return nil, err
return account.Holdings{}, err
}
temp, err := json.Marshal(r)
if err != nil {
return nil, err
}
accountInfo := modules.AccountInfo{}
err = json.Unmarshal(temp, &accountInfo)
if err != nil {
return nil, err
}
return &accountInfo, nil
return accountInfo, nil
}
// DepositAddress gets the address required to deposit funds for currency type

View File

@@ -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: &currency.Item{