ZB: Remove exchange implementation (#1450)

This commit is contained in:
Adrian Gallagher
2024-01-25 15:45:55 +11:00
committed by GitHub
parent 130642bab6
commit 804cee4287
24 changed files with 2 additions and 8510 deletions

View File

@@ -44,7 +44,6 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader
| Okx | Yes | Yes | NA |
| Poloniex | Yes | Yes | NA |
| Yobit | Yes | NA | NA |
| ZB.COM | Yes | Yes | NA |
We are aiming to support the top 30 exchanges sorted by average liquidity as [ranked by CoinMarketCap](https://coinmarketcap.com/rankings/exchanges/).
However, we welcome pull requests for any exchange which does not match this criterion. If you need help with this, please join us on [Slack](https://join.slack.com/t/gocryptotrader/shared_invite/enQtNTQ5NDAxMjA2Mjc5LTc5ZDE1ZTNiOGM3ZGMyMmY1NTAxYWZhODE0MWM5N2JlZDk1NDU0YTViYzk4NTk3OTRiMDQzNGQ1YTc4YmRlMTk).

View File

@@ -67,7 +67,6 @@ _b in this context is an `IBotExchange` implemented struct_
| Okx | Yes | Yes | Yes |
| Poloniex | Yes | Yes | Yes |
| Yobit | Yes | NA | No |
| ZB.COM | Yes | Yes | No |
### Please click GoDocs chevron above to view current GoDoc information for this package

View File

@@ -1,105 +0,0 @@
{{define "exchanges zb" -}}
{{template "header" .}}
## ZB Exchange
### Current Features
+ REST functions
### How to enable
+ [Enable via configuration](https://github.com/thrasher-corp/gocryptotrader/tree/master/config#enable-exchange-via-config-example)
+ Individual package example below:
```go
// Exchanges will be abstracted out in further updates and examples will be
// supplied then
```
### How to do REST public/private calls
+ If enabled via "configuration".json file the exchange will be added to the
IBotExchange array in the ```go var bot Bot``` and you will only be able to use
the wrapper interface functions for accessing exchange data. View routines.go
for an example of integration usage with GoCryptoTrader. Rudimentary example
below:
main.go
```go
var z exchange.IBotExchange
for i := range bot.Exchanges {
if bot.Exchanges[i].GetName() == "ZB" {
z = bot.Exchanges[i]
}
}
// Public calls - wrapper functions
// Fetches current ticker information
tick, err := z.FetchTicker()
if err != nil {
// Handle error
}
// Fetches current orderbook information
ob, err := z.FetchOrderbook()
if err != nil {
// Handle error
}
// Private calls - wrapper functions - make sure your APIKEY and APISECRET are
// set and AuthenticatedAPISupport is set to true
// Fetches current account information
accountInfo, err := z.GetAccountInfo()
if err != nil {
// Handle error
}
```
+ If enabled via individually importing package, rudimentary example below:
```go
// Public calls
// Fetches current ticker information
ticker, err := z.GetTicker()
if err != nil {
// Handle error
}
// Fetches current orderbook information
ob, err := z.GetOrderBook()
if err != nil {
// Handle error
}
// Private calls - make sure your APIKEY and APISECRET are set and
// AuthenticatedAPISupport is set to true
// GetUserInfo returns account info
accountInfo, err := z.GetUserInfo(...)
if err != nil {
// Handle error
}
// Submits an order and the exchange and returns its tradeID
tradeID, err := z.Trade(...)
if err != nil {
// Handle error
}
```
### How to do LongPolling public/private calls
```go
// Exchanges will be abstracted out in further updates and examples will be
// supplied then
```
### Please click GoDocs chevron above to view current GoDoc information for this package
{{template "contributions"}}
{{template "donations" .}}
{{end}}

View File

@@ -45,7 +45,6 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader
| Okx | Yes | Yes | NA |
| Poloniex | Yes | Yes | NA |
| Yobit | Yes | NA | NA |
| ZB.COM | Yes | Yes | NA |
We are aiming to support the top 30 exchanges sorted by average liquidity as [ranked by CoinMarketCap](https://coinmarketcap.com/rankings/exchanges/).
However, we welcome pull requests for any exchange which does not match this criterion. If you need help with this, please join us on [Slack](https://join.slack.com/t/gocryptotrader/shared_invite/enQtNTQ5NDAxMjA2Mjc5LTc5ZDE1ZTNiOGM3ZGMyMmY1NTAxYWZhODE0MWM5N2JlZDk1NDU0YTViYzk4NTk3OTRiMDQzNGQ1YTc4YmRlMTk).

View File

@@ -2388,84 +2388,6 @@
"supportedCurrencies": ""
}
]
},
{
"name": "ZB",
"enabled": true,
"verbose": false,
"httpTimeout": 15000000000,
"websocketResponseCheckTimeout": 30000000,
"websocketResponseMaxLimit": 7000000000,
"websocketTrafficTimeout": 30000000000,
"websocketOrderbookBufferLimit": 5,
"baseCurrencies": "USD",
"currencyPairs": {
"requestFormat": {
"uppercase": false,
"delimiter": "_"
},
"configFormat": {
"uppercase": true,
"delimiter": "_"
},
"useGlobalFormat": true,
"assetTypes": [
"spot"
],
"pairs": {
"spot": {
"enabled": "BTC_USDT,ETH_USDT",
"available": "NXWC_QC,LBTC_USDT,CDC_QC,EPC_QC,BSV_USDT,AE_QC,EDO_USDT,HLC_QC,KPG_QC,NEO_QC,PAX_QC,HC_BTC,BTN_QC,UFO_USDT,TRUE_BTC,DNA_QC,GUSD_QC,BTP_QC,VBT_QC,OMG_QC,HSR_QC,ACC_USDT,KAN_BTC,LBTC_QC,BITE_BTC,GUCS_USDT,BCHABC_QC,QTUM_USDT,BTS_USDT,ETH_QC,XLM_USDT,QTUM_QC,SNT_USDT,UBTC_QC,ZB_QC,ZB_BTC,XRP_USDT,USDC_USDT,LUCKY_USDT,BTC_USDT,NEO_BTC,PAX_USDT,QUN_QC,LVN_USDT,EOS_BTC,ADA_QC,BAT_QC,ETH_USDT,DASH_BTC,VSYS_BTC,MTL_USDT,TSR_USDT,BCHABC_USDT,EOS_USDT,XMR_QC,XTZ_USDT,BCH_QC,DOGE_USDT,BTS_BTC,GUSD_USDT,LTC_QC,XWC_USDT,NXWC_USDT,TRUE_QC,ZB_USDT,BCHSV_USDT,ADA_BTC,BDS_QC,ETC_USDT,DASH_USDT,KAN_QC,HNS_USDT,CDC_USDT,DDM_QC,LTC_BTC,HC_USDT,TRUE_USDT,GRIN_QC,HOTC_QC,ETZ_USDT,SLT_QC,XWC_QC,BITCNY_QC,BTC_QC,CRO_USDT,CRO_QC,BSV_QC,ADA_USDT,DASH_QC,TRX_BTC,TOPC_USDT,USDT_QC,BCH_USDT,TRX_QC,LTG_USDT,PDX_QC,GST_USDT,GRAM_QC,BTH_QC,AAA_QC,LVN_QC,DOGE_QC,HSR_USDT,DNA_USDT,ETZ_QC,CHAT_USDT,B91_QC,DDM_USDT,DOGE_BTC,BTS_QC,SAFE_QC,TUSD_USDT,UFO_QC,TOPC_QC,XRP_BTC,HC_QC,MCO_QC,BTM_QC,HPY_QC,FN_QC,OMG_USDT,ICX_QC,LEO_USDT,BRC_QC,BCHSV_QC,LTC_USDT,NEO_USDT,ZRX_QC,VSYS_QC,XRP_QC,EOS_QC,XLM_QC,HX_QC,TV_QC,TRX_USDT,QTUM_BTC,MANA_QC,ETH_BTC,XEM_QC,GNT_USDT,USDC_QC,SAFE_USDT,GRAM_USDT,LUCKY_QC,OMG_BTC,HSR_BTC,KNC_QC,ETC_BTC,BTH_USDT,EPC_BTC,XLM_BTC,XMR_USDT,ETC_QC"
}
}
},
"api": {
"authenticatedSupport": false,
"authenticatedWebsocketApiSupport": false,
"endpoints": {
"url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API",
"urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API",
"websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API"
},
"credentials": {
"key": "Key",
"secret": "Secret"
},
"credentialsValidator": {
"requiresKey": true,
"requiresSecret": true
}
},
"features": {
"supports": {
"restAPI": true,
"restCapabilities": {
"tickerBatching": true,
"autoPairUpdates": true
},
"websocketAPI": true,
"websocketCapabilities": {}
},
"enabled": {
"autoPairUpdates": true,
"websocketAPI": false
}
},
"bankAccounts": [
{
"enabled": false,
"bankName": "",
"bankAddress": "",
"bankPostalCode": "",
"bankPostalCity": "",
"bankCountry": "",
"accountName": "",
"accountNumber": "",
"swiftCode": "",
"iban": "",
"supportedCurrencies": ""
}
]
}
],
"bankAccounts": [

View File

@@ -221,7 +221,6 @@ Yes means supported, No means not yet implemented and NA means protocol unsuppor
| Okx | Yes | Yes | NA |
| Poloniex | Yes | Yes | NA |
| Yobit | Yes | NA | NA |
| ZB.COM | Yes | Yes | NA |
```
#### Add exchange to the list of [supported exchanges](../exchanges/support.go):
@@ -252,7 +251,6 @@ var Exchanges = []string{
"okx",
"poloniex",
"yobit",
"zb",
```
#### Increment the default number of supported exchanges in [config/config_test.go](../config/config_test.go):

View File

@@ -68,4 +68,3 @@ $ ./gctcli withdrawcryptofunds --exchange=binance --currency=USDT --address=TJU9
| Okx | Yes | Yes | |
| Poloniex | Yes | Yes | |
| Yobit | No | No | |
| ZB.COM | Yes | No | Addresses must be created via their website first |

View File

@@ -89,5 +89,4 @@ A helper tool [cmd/dbseed](../cmd/dbseed/README.md) has been created for assisti
| Okcoin | Y |
| Okx | Y |
| Poloniex | Y |
| Yobit | |
| ZB | Y |
| Yobit | |

View File

@@ -56,7 +56,6 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/stats"
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
"github.com/thrasher-corp/gocryptotrader/exchanges/yobit"
"github.com/thrasher-corp/gocryptotrader/exchanges/zb"
"github.com/thrasher-corp/gocryptotrader/gctscript/vm"
"github.com/thrasher-corp/gocryptotrader/log"
)
@@ -1045,8 +1044,6 @@ func NewSupportedExchangeByName(name string) (exchange.IBotExchange, error) {
return new(poloniex.Poloniex), nil
case "yobit":
return new(yobit.Yobit), nil
case "zb":
return new(zb.ZB), nil
default:
return nil, fmt.Errorf("'%s', %w", name, ErrExchangeNotFound)
}

View File

@@ -39,5 +39,4 @@ var Exchanges = []string{
"okx",
"poloniex",
"yobit",
"zb",
}

View File

@@ -85,7 +85,6 @@ _b in this context is an `IBotExchange` implemented struct_
| Okx | Yes | Yes | Yes |
| Poloniex | Yes | Yes | Yes |
| Yobit | Yes | NA | No |
| ZB.COM | Yes | Yes | No |
### Please click GoDocs chevron above to view current GoDoc information for this package

View File

@@ -1,139 +0,0 @@
# GoCryptoTrader package Zb
<img src="/common/gctlogo.png?raw=true" width="350px" height="350px" hspace="70">
[![Build Status](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml/badge.svg?branch=master)](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml)
[![Software License](https://img.shields.io/badge/License-MIT-orange.svg?style=flat-square)](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE)
[![GoDoc](https://godoc.org/github.com/thrasher-corp/gocryptotrader?status.svg)](https://godoc.org/github.com/thrasher-corp/gocryptotrader/exchanges/zb)
[![Coverage Status](http://codecov.io/github/thrasher-corp/gocryptotrader/coverage.svg?branch=master)](http://codecov.io/github/thrasher-corp/gocryptotrader?branch=master)
[![Go Report Card](https://goreportcard.com/badge/github.com/thrasher-corp/gocryptotrader)](https://goreportcard.com/report/github.com/thrasher-corp/gocryptotrader)
This zb package is part of the GoCryptoTrader codebase.
## This is still in active development
You can track ideas, planned features and what's in progress on this Trello board: [https://trello.com/b/ZAhMhpOy/gocryptotrader](https://trello.com/b/ZAhMhpOy/gocryptotrader).
Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader Slack](https://join.slack.com/t/gocryptotrader/shared_invite/enQtNTQ5NDAxMjA2Mjc5LTc5ZDE1ZTNiOGM3ZGMyMmY1NTAxYWZhODE0MWM5N2JlZDk1NDU0YTViYzk4NTk3OTRiMDQzNGQ1YTc4YmRlMTk)
## ZB Exchange
### Current Features
+ REST functions
### How to enable
+ [Enable via configuration](https://github.com/thrasher-corp/gocryptotrader/tree/master/config#enable-exchange-via-config-example)
+ Individual package example below:
```go
// Exchanges will be abstracted out in further updates and examples will be
// supplied then
```
### How to do REST public/private calls
+ If enabled via "configuration".json file the exchange will be added to the
IBotExchange array in the ```go var bot Bot``` and you will only be able to use
the wrapper interface functions for accessing exchange data. View routines.go
for an example of integration usage with GoCryptoTrader. Rudimentary example
below:
main.go
```go
var z exchange.IBotExchange
for i := range bot.Exchanges {
if bot.Exchanges[i].GetName() == "ZB" {
z = bot.Exchanges[i]
}
}
// Public calls - wrapper functions
// Fetches current ticker information
tick, err := z.FetchTicker()
if err != nil {
// Handle error
}
// Fetches current orderbook information
ob, err := z.FetchOrderbook()
if err != nil {
// Handle error
}
// Private calls - wrapper functions - make sure your APIKEY and APISECRET are
// set and AuthenticatedAPISupport is set to true
// Fetches current account information
accountInfo, err := z.GetAccountInfo()
if err != nil {
// Handle error
}
```
+ If enabled via individually importing package, rudimentary example below:
```go
// Public calls
// Fetches current ticker information
ticker, err := z.GetTicker()
if err != nil {
// Handle error
}
// Fetches current orderbook information
ob, err := z.GetOrderBook()
if err != nil {
// Handle error
}
// Private calls - make sure your APIKEY and APISECRET are set and
// AuthenticatedAPISupport is set to true
// GetUserInfo returns account info
accountInfo, err := z.GetUserInfo(...)
if err != nil {
// Handle error
}
// Submits an order and the exchange and returns its tradeID
tradeID, err := z.Trade(...)
if err != nil {
// Handle error
}
```
### How to do LongPolling public/private calls
```go
// Exchanges will be abstracted out in further updates and examples will be
// supplied then
```
### Please click GoDocs chevron above to view current GoDoc information for this package
## Contribution
Please feel free to submit any pull requests or suggest any desired features to be added.
When submitting a PR, please abide by our coding guidelines:
+ Code must adhere to the official Go [formatting](https://golang.org/doc/effective_go.html#formatting) guidelines (i.e. uses [gofmt](https://golang.org/cmd/gofmt/)).
+ Code must be documented adhering to the official Go [commentary](https://golang.org/doc/effective_go.html#commentary) guidelines.
+ Code must adhere to our [coding style](https://github.com/thrasher-corp/gocryptotrader/blob/master/doc/coding_style.md).
+ Pull requests need to be based on and opened against the `master` branch.
## Donations
<img src="https://github.com/thrasher-corp/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
***bc1qk0jareu4jytc0cfrhr5wgshsq8282awpavfahc***

View File

@@ -1,49 +0,0 @@
package zb
import (
"context"
"time"
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
"golang.org/x/time/rate"
)
const (
zbRateInterval = time.Second
zbAuthLimit = 60
zbUnauthLimit = 60
zbKlineDataInterval = time.Second * 2
zbKlineDataLimit = 1
// Used to match endpoints to rate limits
klineFunc request.EndpointLimit = iota
)
// RateLimit implements the request.Limiter interface
type RateLimit struct {
Auth *rate.Limiter
UnAuth *rate.Limiter
KlineData *rate.Limiter
}
// Limit limits the outbound requests
func (r *RateLimit) Limit(ctx context.Context, f request.EndpointLimit) error {
switch f {
case request.Auth:
return r.Auth.Wait(ctx)
case klineFunc:
return r.KlineData.Wait(ctx)
default:
return r.UnAuth.Wait(ctx)
}
}
// SetRateLimit returns the rate limit for the exchange
func SetRateLimit() *RateLimit {
return &RateLimit{
Auth: request.NewRateLimit(zbRateInterval, zbAuthLimit),
UnAuth: request.NewRateLimit(zbRateInterval, zbUnauthLimit),
KlineData: request.NewRateLimit(zbKlineDataInterval, zbKlineDataLimit),
}
}

View File

@@ -1,592 +0,0 @@
package zb
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/common/convert"
"github.com/thrasher-corp/gocryptotrader/common/crypto"
"github.com/thrasher-corp/gocryptotrader/currency"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
)
const (
zbTradeURL = "https://api.zb.com"
zbMarketURL = "https://trade.zb.com/api"
zbAPIVersion = "v1"
zbData = "data"
zbAccountInfo = "getAccountInfo"
zbMarkets = "markets"
zbKline = "kline"
zbOrder = "order"
zbCancelOrder = "cancelOrder"
zbTicker = "ticker"
zbTrades = "trades"
zbTickers = "allTicker"
zbDepth = "depth"
zbUnfinishedOrdersIgnoreTradeType = "getUnfinishedOrdersIgnoreTradeType"
zbGetOrdersGet = "getOrders"
zbGetOrder = "getOrder"
zbWithdraw = "withdraw"
zbDepositAddress = "getUserAddress"
zbMultiChainDepositAddress = "getPayinAddress"
zbWithdrawalRecords = "getWithdrawRecord"
zbDepositRecords = "getChargeRecord"
)
// ZB is the overarching type across this package
// 47.91.169.147 api.zb.com
// 47.52.55.212 trade.zb.com
type ZB struct {
exchange.Base
}
// SpotNewOrder submits an order to ZB
func (z *ZB) SpotNewOrder(ctx context.Context, arg SpotNewOrderRequestParams) (int64, error) {
creds, err := z.GetCredentials(ctx)
if err != nil {
return 0, err
}
var result SpotNewOrderResponse
vals := url.Values{}
vals.Set("accesskey", creds.Key)
vals.Set("method", "order")
vals.Set("amount", strconv.FormatFloat(arg.Amount, 'f', -1, 64))
vals.Set("currency", arg.Symbol)
vals.Set("price", strconv.FormatFloat(arg.Price, 'f', -1, 64))
vals.Set("tradeType", string(arg.Type))
err = z.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpotSupplementary, http.MethodGet, vals, &result, request.Auth)
if err != nil {
return 0, err
}
if result.Code != 1000 {
return 0, fmt.Errorf("%w unsuccessful new order, message: %s code: %d", request.ErrAuthRequestFailed, result.Message, result.Code)
}
newOrderID, err := strconv.ParseInt(result.ID, 10, 64)
if err != nil {
return 0, err
}
return newOrderID, nil
}
// GetDepositRecords returns the deposit records
func (z *ZB) GetDepositRecords(ctx context.Context, arg *WalletRecordsRequest) (*DepositRecordsResponse, error) {
if arg == nil {
return nil, fmt.Errorf("%w WalletRecordsRequest", common.ErrNilPointer)
}
var resp DepositRecordsResponse
vals := url.Values{}
vals.Set("method", "getChargeRecord")
vals.Set("currency", arg.Currency.String())
if arg.PageSize > 0 {
vals.Set("pageIndex", strconv.FormatInt(arg.PageIndex, 10))
}
if arg.PageIndex > 0 {
vals.Set("pageSize", strconv.FormatInt(arg.PageSize, 10))
}
return &resp, z.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpotSupplementary, http.MethodGet, vals, &resp, request.Auth)
}
// GetWithdrawalRecords returns the withdrawal records
func (z *ZB) GetWithdrawalRecords(ctx context.Context, arg *WalletRecordsRequest) (*WithdrawalRecordsResponse, error) {
if arg == nil {
return nil, fmt.Errorf("%w WalletRecordsRequest", common.ErrNilPointer)
}
var resp WithdrawalRecordsResponse
vals := url.Values{}
vals.Set("method", "getWithdrawRecord")
vals.Set("currency", arg.Currency.String())
vals.Set("pageIndex", strconv.FormatInt(arg.PageIndex, 10))
vals.Set("pageSize", strconv.FormatInt(arg.PageSize, 10))
return &resp, z.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpotSupplementary, http.MethodGet, vals, &resp, request.Auth)
}
// CancelExistingOrder cancels an order
func (z *ZB) CancelExistingOrder(ctx context.Context, orderID int64, symbol string) error {
creds, err := z.GetCredentials(ctx)
if err != nil {
return err
}
type response struct {
Code int `json:"code"` // Result code
Message string `json:"message"` // Result Message
}
vals := url.Values{}
vals.Set("accesskey", creds.Key)
vals.Set("method", "cancelOrder")
vals.Set("id", strconv.FormatInt(orderID, 10))
vals.Set("currency", symbol)
var result response
err = z.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpotSupplementary, http.MethodGet, vals, &result, request.Auth)
if err != nil {
return err
}
if result.Code != 1000 {
return fmt.Errorf("%w %v", request.ErrAuthRequestFailed, result.Message)
}
return nil
}
// GetAccountInformation returns account information including coin information
// and pricing
func (z *ZB) GetAccountInformation(ctx context.Context) (AccountsResponse, error) {
creds, err := z.GetCredentials(ctx)
if err != nil {
return AccountsResponse{}, err
}
var result AccountsResponse
vals := url.Values{}
vals.Set("accesskey", creds.Key)
vals.Set("method", "getAccountInfo")
return result, z.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpotSupplementary, http.MethodGet, vals, &result, request.Auth)
}
// GetUnfinishedOrdersIgnoreTradeType returns unfinished orders
func (z *ZB) GetUnfinishedOrdersIgnoreTradeType(ctx context.Context, currency string, pageindex, pagesize int64) ([]Order, error) {
creds, err := z.GetCredentials(ctx)
if err != nil {
return nil, err
}
var result []Order
vals := url.Values{}
vals.Set("accesskey", creds.Key)
vals.Set("method", zbUnfinishedOrdersIgnoreTradeType)
vals.Set("currency", currency)
vals.Set("pageIndex", strconv.FormatInt(pageindex, 10))
vals.Set("pageSize", strconv.FormatInt(pagesize, 10))
return result, z.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpotSupplementary, http.MethodGet, vals, &result, request.Auth)
}
// GetOrders returns finished orders
func (z *ZB) GetOrders(ctx context.Context, currency string, pageindex, side int64) ([]Order, error) {
creds, err := z.GetCredentials(ctx)
if err != nil {
return nil, err
}
var response []Order
vals := url.Values{}
vals.Set("accesskey", creds.Key)
vals.Set("method", zbGetOrdersGet)
vals.Set("currency", currency)
vals.Set("pageIndex", strconv.FormatInt(pageindex, 10))
vals.Set("tradeType", strconv.FormatInt(side, 10))
return response, z.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpotSupplementary, http.MethodGet, vals, &response, request.Auth)
}
// GetSingleOrder Get single buy order or sell order
func (z *ZB) GetSingleOrder(ctx context.Context, orderID, customerOrderID string, currency currency.Pair) (*Order, error) {
creds, err := z.GetCredentials(ctx)
if err != nil {
return nil, err
}
var response Order
pFmt, err := z.GetPairFormat(asset.Spot, true)
if err != nil {
return nil, err
}
vals := url.Values{}
vals.Set("accesskey", creds.Key)
vals.Set("method", zbGetOrder)
vals.Set("currency", pFmt.Format(currency))
if orderID != "" {
vals.Set("id", orderID)
}
if customerOrderID != "" {
vals.Set("customerOrderId", customerOrderID)
}
return &response, z.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpotSupplementary, http.MethodGet, vals, &response, request.Auth)
}
// GetMarkets returns market information including pricing, symbols and
// each symbols decimal precision
func (z *ZB) GetMarkets(ctx context.Context) (map[string]MarketResponseItem, error) {
endpoint := fmt.Sprintf("/%s/%s/%s", zbData, zbAPIVersion, zbMarkets)
var res map[string]MarketResponseItem
err := z.SendHTTPRequest(ctx, exchange.RestSpot, endpoint, &res, request.UnAuth)
if err != nil {
return nil, err
}
return res, nil
}
// GetLatestSpotPrice returns latest spot price of symbol
//
// symbol: string of currency pair
// 获取最新价格
func (z *ZB) GetLatestSpotPrice(ctx context.Context, symbol string) (float64, error) {
res, err := z.GetTicker(ctx, symbol)
if err != nil {
return 0, err
}
return res.Ticker.Last, nil
}
// GetTicker returns a ticker for a given symbol
func (z *ZB) GetTicker(ctx context.Context, symbol string) (TickerResponse, error) {
urlPath := fmt.Sprintf("/%s/%s/%s?market=%s", zbData, zbAPIVersion, zbTicker, symbol)
var res TickerResponse
err := z.SendHTTPRequest(ctx, exchange.RestSpot, urlPath, &res, request.UnAuth)
return res, err
}
// GetTrades returns trades for a given symbol
func (z *ZB) GetTrades(ctx context.Context, symbol string) (TradeHistory, error) {
urlPath := fmt.Sprintf("/%s/%s/%s?market=%s", zbData, zbAPIVersion, zbTrades, symbol)
var res TradeHistory
err := z.SendHTTPRequest(ctx, exchange.RestSpot, urlPath, &res, request.UnAuth)
return res, err
}
// GetTickers returns ticker data for all supported symbols
func (z *ZB) GetTickers(ctx context.Context) (map[string]TickerChildResponse, error) {
urlPath := fmt.Sprintf("/%s/%s/%s", zbData, zbAPIVersion, zbTickers)
resp := make(map[string]TickerChildResponse)
err := z.SendHTTPRequest(ctx, exchange.RestSpot, urlPath, &resp, request.UnAuth)
return resp, err
}
// GetOrderbook returns the orderbook for a given symbol
func (z *ZB) GetOrderbook(ctx context.Context, symbol string) (*OrderbookResponse, error) {
urlPath := fmt.Sprintf("/%s/%s/%s?market=%s", zbData, zbAPIVersion, zbDepth, symbol)
var res OrderbookResponse
err := z.SendHTTPRequest(ctx, exchange.RestSpot, urlPath, &res, request.UnAuth)
if err != nil {
return nil, err
}
if len(res.Asks) == 0 {
return nil, errors.New("ZB GetOrderbook asks is empty")
}
if len(res.Bids) == 0 {
return nil, errors.New("ZB GetOrderbook bids is empty")
}
// reverse asks data
eLen := len(res.Asks)
var target int
for i := eLen/2 - 1; i >= 0; i-- {
target = eLen - 1 - i
(res.Asks)[i], (res.Asks)[target] = (res.Asks)[target], (res.Asks)[i]
}
return &res, nil
}
// GetSpotKline returns Kline data
func (z *ZB) GetSpotKline(ctx context.Context, arg KlinesRequestParams) (KLineResponse, error) {
vals := url.Values{}
vals.Set("type", arg.Type)
vals.Set("market", arg.Symbol)
if arg.Since > 0 {
vals.Set("since", strconv.FormatInt(arg.Since, 10))
}
if arg.Size != 0 {
vals.Set("size", fmt.Sprintf("%d", arg.Size))
}
urlPath := fmt.Sprintf("/%s/%s/%s?%s", zbData, zbAPIVersion, zbKline, vals.Encode())
var res KLineResponse
resp := struct {
Data [][]float64 `json:"data"`
MoneyType string `json:"moneyType"`
Symbol string `json:"symbol"`
}{}
err := z.SendHTTPRequest(ctx, exchange.RestSpot, urlPath, &resp, klineFunc)
if err != nil {
return res, err
}
if resp.Data == nil || resp.Symbol == "" || resp.MoneyType == "" {
return res, errors.New("GetSpotKline received empty data")
}
res.MoneyType = resp.MoneyType
res.Symbol = resp.Symbol
for x := range resp.Data {
if len(resp.Data[x]) < 6 {
return res, errors.New("unexpected kline data length")
}
timestamp, err := convert.TimeFromUnixTimestampFloat(resp.Data[x][0])
if err != nil {
return res, err
}
res.Data = append(res.Data, &KLineResponseData{
KlineTime: timestamp,
Open: resp.Data[x][1],
High: resp.Data[x][2],
Low: resp.Data[x][3],
Close: resp.Data[x][4],
Volume: resp.Data[x][5],
})
}
return res, nil
}
// GetCryptoAddress fetches and returns the deposit address
// NOTE - PLEASE BE AWARE THAT YOU NEED TO GENERATE A DEPOSIT ADDRESS VIA
// LOGGING IN AND NOT BY USING THIS ENDPOINT OTHERWISE THIS WILL GIVE YOU A
// GENERAL ERROR RESPONSE.
func (z *ZB) GetCryptoAddress(ctx context.Context, currency currency.Code) (*UserAddress, error) {
var resp UserAddress
vals := url.Values{}
vals.Set("method", zbDepositAddress)
vals.Set("currency", currency.Lower().String())
if err := z.SendAuthenticatedHTTPRequest(ctx,
exchange.RestSpotSupplementary,
http.MethodGet,
vals,
&resp,
request.Auth); err != nil {
return nil, err
}
if !resp.Message.IsSuccessful {
return nil, errors.New(resp.Message.Description)
}
if strings.Contains(resp.Message.Data.Address, "_") {
splitter := strings.Split(resp.Message.Data.Address, "_")
resp.Message.Data.Address, resp.Message.Data.Tag = splitter[0], splitter[1]
}
return &resp, nil
}
// GetMultiChainDepositAddress returns deposit addresses for a given currency
func (z *ZB) GetMultiChainDepositAddress(ctx context.Context, currency currency.Code) ([]MultiChainDepositAddress, error) {
var resp MultiChainDepositAddressResponse
vals := url.Values{}
vals.Set("method", zbMultiChainDepositAddress)
vals.Set("currency", currency.Lower().String())
if err := z.SendAuthenticatedHTTPRequest(ctx,
exchange.RestSpotSupplementary,
http.MethodGet,
vals,
&resp,
request.Auth); err != nil {
return nil, err
}
if !resp.Message.IsSuccessful {
return nil, errors.New(resp.Message.Description)
}
return resp.Message.Data, nil
}
// SendHTTPRequest sends an unauthenticated HTTP request
func (z *ZB) SendHTTPRequest(ctx context.Context, ep exchange.URL, path string, result interface{}, f request.EndpointLimit) error {
endpoint, err := z.API.Endpoints.GetURL(ep)
if err != nil {
return err
}
item := &request.Item{
Method: http.MethodGet,
Path: endpoint + path,
Result: result,
Verbose: z.Verbose,
HTTPDebugging: z.HTTPDebugging,
HTTPRecording: z.HTTPRecording,
}
return z.SendPayload(ctx, f, func() (*request.Item, error) {
return item, nil
}, request.UnauthenticatedRequest)
}
// SendAuthenticatedHTTPRequest sends authenticated requests to the zb API
func (z *ZB) SendAuthenticatedHTTPRequest(ctx context.Context, ep exchange.URL, httpMethod string, params url.Values, result interface{}, f request.EndpointLimit) error {
creds, err := z.GetCredentials(ctx)
if err != nil {
return err
}
endpoint, err := z.API.Endpoints.GetURL(ep)
if err != nil {
return err
}
params.Set("accesskey", creds.Key)
hex, err := crypto.Sha1ToHex(creds.Secret)
if err != nil {
return err
}
hmac, err := crypto.GetHMAC(crypto.HashMD5,
[]byte(params.Encode()),
[]byte(hex))
if err != nil {
return err
}
var intermediary json.RawMessage
newRequest := func() (*request.Item, error) {
params.Set("reqTime", strconv.FormatInt(time.Now().UnixMilli(), 10))
params.Set("sign", fmt.Sprintf("%x", hmac))
urlPath := fmt.Sprintf("%s/%s?%s",
endpoint,
params.Get("method"),
params.Encode())
return &request.Item{
Method: httpMethod,
Path: urlPath,
Result: &intermediary,
Verbose: z.Verbose,
HTTPDebugging: z.HTTPDebugging,
HTTPRecording: z.HTTPRecording,
}, nil
}
err = z.SendPayload(ctx, f, newRequest, request.AuthenticatedRequest)
if err != nil {
return err
}
errCap := struct {
Code int64 `json:"code"`
Message string `json:"message"`
}{}
err = json.Unmarshal(intermediary, &errCap)
if err == nil {
if errCap.Code > 1000 {
return fmt.Errorf("%w error code: %d error code message: %s error message: %s",
request.ErrAuthRequestFailed,
errCap.Code,
errorCode[errCap.Code],
errCap.Message)
}
}
return json.Unmarshal(intermediary, result)
}
// GetFee returns an estimate of fee based on type of transaction
func (z *ZB) GetFee(feeBuilder *exchange.FeeBuilder) (float64, error) {
var fee float64
switch feeBuilder.FeeType {
case exchange.CryptocurrencyTradeFee:
fee = calculateTradingFee(feeBuilder.PurchasePrice, feeBuilder.Amount)
case exchange.CryptocurrencyWithdrawalFee:
fee = getWithdrawalFee(feeBuilder.Pair.Base)
case exchange.OfflineTradeFee:
fee = getOfflineTradeFee(feeBuilder.PurchasePrice, feeBuilder.Amount)
}
if fee < 0 {
fee = 0
}
return fee, nil
}
// getOfflineTradeFee calculates the worst case-scenario trading fee
func getOfflineTradeFee(price, amount float64) float64 {
return 0.002 * price * amount
}
func calculateTradingFee(purchasePrice, amount float64) (fee float64) {
fee = 0.002
return fee * amount * purchasePrice
}
func getWithdrawalFee(c currency.Code) float64 {
return WithdrawalFees[c]
}
var errorCode = map[int64]string{
1000: "Successful call",
1001: "General error message",
1002: "internal error",
1003: "Verification failed",
1004: "Financial security password lock",
1005: "The fund security password is incorrect. Please confirm and re-enter.",
1006: "Real-name certification is awaiting review or review",
1009: "This interface is being maintained",
1010: "Not open yet",
1012: "Insufficient permissions",
1013: "Can not trade, if you have any questions, please contact online customer service",
1014: "Cannot be sold during the pre-sale period",
2002: "Insufficient balance in Bitcoin account",
2003: "Insufficient balance of Litecoin account",
2005: "Insufficient balance in Ethereum account",
2006: "Insufficient balance in ETC currency account",
2007: "Insufficient balance of BTS currency account",
2009: "Insufficient account balance",
3001: "Pending order not found",
3002: "Invalid amount",
3003: "Invalid quantity",
3004: "User does not exist",
3005: "Invalid parameter",
3006: "Invalid IP or inconsistent with the bound IP",
3007: "Request time has expired",
3008: "Transaction history not found",
4001: "API interface is locked",
4002: "Request too frequently",
}
// Withdraw transfers funds
func (z *ZB) Withdraw(ctx context.Context, currency, address, safepassword string, amount, fees float64, itransfer bool) (string, error) {
creds, err := z.GetCredentials(ctx)
if err != nil {
return "", err
}
type response struct {
Code int `json:"code"` // Result code
Message string `json:"message"` // Result Message
ID string `json:"id"` // Withdrawal ID
}
vals := url.Values{}
vals.Set("accesskey", creds.Key)
vals.Set("amount", strconv.FormatFloat(amount, 'f', -1, 64))
vals.Set("currency", currency)
vals.Set("fees", strconv.FormatFloat(fees, 'f', -1, 64))
vals.Set("itransfer", strconv.FormatBool(itransfer))
vals.Set("method", "withdraw")
vals.Set("receiveAddr", address)
vals.Set("safePwd", safepassword)
var resp response
err = z.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpotSupplementary, http.MethodGet, vals, &resp, request.Auth)
if err != nil {
return "", err
}
if resp.Code != 1000 {
return "", fmt.Errorf("%w %v", request.ErrAuthRequestFailed, resp.Message)
}
return resp.ID, nil
}

View File

@@ -1,41 +0,0 @@
//go:build mock_test_off
// This will build if build tag mock_test_off is parsed and will do live testing
// using all tests in (exchange)_test.go
package zb
import (
"log"
"os"
"testing"
"github.com/thrasher-corp/gocryptotrader/config"
"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
)
var mockTests = false
func TestMain(m *testing.M) {
cfg := config.GetConfig()
err := cfg.LoadConfig("../../testdata/configtest.json", true)
if err != nil {
log.Fatal("ZB load config error", err)
}
zbConfig, err := cfg.GetExchangeConfig("ZB")
if err != nil {
log.Fatal("ZB Setup() init error", err)
}
zbConfig.API.AuthenticatedSupport = true
zbConfig.API.Credentials.Key = apiKey
zbConfig.API.Credentials.Secret = apiSecret
z.SetDefaults()
z.Websocket = sharedtestvalues.NewTestWebsocket()
err = z.Setup(zbConfig)
if err != nil {
log.Fatal("ZB setup error", err)
}
log.Printf(sharedtestvalues.LiveTesting, z.Name)
z.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride()
z.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride()
os.Exit(m.Run())
}

View File

@@ -1,64 +0,0 @@
//go:build !mock_test_off
// This will build if build tag mock_test_off is not parsed and will try to mock
// all tests in _test.go
package zb
import (
"log"
"os"
"testing"
"github.com/thrasher-corp/gocryptotrader/config"
"github.com/thrasher-corp/gocryptotrader/exchanges/mock"
"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
)
const mockfile = "../../testdata/http_mock/zb/zb.json"
var mockTests = true
func TestMain(m *testing.M) {
cfg := config.GetConfig()
err := cfg.LoadConfig("../../testdata/configtest.json", true)
if err != nil {
log.Fatal("ZB load config error", err)
}
var zbConfig *config.Exchange
zbConfig, err = cfg.GetExchangeConfig("ZB")
if err != nil {
log.Fatal("ZB Setup() init error", err)
}
zbConfig.API.AuthenticatedSupport = true
zbConfig.API.AuthenticatedWebsocketSupport = true
zbConfig.API.Credentials.Key = apiKey
zbConfig.API.Credentials.Secret = apiSecret
z.SkipAuthCheck = true
z.SetDefaults()
z.Websocket = sharedtestvalues.NewTestWebsocket()
err = z.Setup(zbConfig)
if err != nil {
log.Fatal("ZB setup error", err)
}
serverDetails, newClient, err := mock.NewVCRServer(mockfile)
if err != nil {
log.Fatalf("Mock server error %s", err)
}
err = z.SetHTTPClient(newClient)
if err != nil {
log.Fatalf("Mock server error %s", err)
}
endpoints := z.API.Endpoints.GetURLMap()
for k := range endpoints {
err = z.API.Endpoints.SetRunning(k, serverDetails)
if err != nil {
log.Fatal(err)
}
}
log.Printf(sharedtestvalues.MockTesting,
z.Name)
os.Exit(m.Run())
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,307 +0,0 @@
package zb
import (
"time"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
)
// OrderbookResponse holds the orderbook data for a symbol
type OrderbookResponse struct {
Timestamp int64 `json:"timestamp"`
Asks [][2]float64 `json:"asks"`
Bids [][2]float64 `json:"bids"`
}
// AccountsResponseCoin holds the accounts coin details
type AccountsResponseCoin struct {
Freeze string `json:"freez"` // 冻结资产
EnName string `json:"enName"` // 币种英文名
UnitDecimal int `json:"unitDecimal"` // 保留小数位
UnName string `json:"cnName"` // 币种中文名
UnitTag string `json:"unitTag"` // 币种符号
Available string `json:"available"` // 可用资产
Key string `json:"key"` // 币种
}
// AccountsBaseResponse holds basic account details
type AccountsBaseResponse struct {
UserName string `json:"username"` // 用户名
TradePasswordEnabled bool `json:"trade_password_enabled"` // 是否开通交易密码
AuthGoogleEnabled bool `json:"auth_google_enabled"` // 是否开通谷歌验证
AuthMobileEnabled bool `json:"auth_mobile_enabled"` // 是否开通手机验证
}
// Order is the order details for retrieving all orders
type Order struct {
Currency string `json:"currency"`
ID string `json:"id"`
Price float64 `json:"price"`
Status int64 `json:"status"`
TotalAmount float64 `json:"total_amount"`
TradeAmount float64 `json:"trade_amount"`
TradeDate int64 `json:"trade_date"`
TradeMoney float64 `json:"trade_money"`
Type int64 `json:"type"`
Fees float64 `json:"fees,omitempty"`
TradePrice float64 `json:"trade_price,omitempty"`
No int64 `json:"no,string,omitempty"`
}
// AccountsResponse 用户基本信息
type AccountsResponse struct {
Result struct {
Coins []AccountsResponseCoin `json:"coins"`
Base AccountsBaseResponse `json:"base"`
} `json:"result"` // 用户名
AssetPerm bool `json:"assetPerm"` // 是否开通交易密码
LeverPerm bool `json:"leverPerm"` // 是否开通谷歌验证
EntrustPerm bool `json:"entrustPerm"` // 是否开通手机验证
MoneyPerm bool `json:"moneyPerm"` // 资产列表
}
// MarketResponseItem stores market data
type MarketResponseItem struct {
AmountScale float64 `json:"amountScale"`
PriceScale float64 `json:"priceScale"`
}
// TickerResponse holds the ticker response data
type TickerResponse struct {
Date string `json:"date"`
Ticker TickerChildResponse `json:"ticker"`
}
// TickerChildResponse holds the ticker child response data
type TickerChildResponse struct {
Volume float64 `json:"vol,string"` // 成交量(最近的24小时)
Last float64 `json:"last,string"` // 最新成交价
Sell float64 `json:"sell,string"` // 卖一价
Buy float64 `json:"buy,string"` // 买一价
High float64 `json:"high,string"` // 最高价
Low float64 `json:"low,string"` // 最低价
}
// SpotNewOrderRequestParamsType ZB 交易类型
type SpotNewOrderRequestParamsType string
var (
// SpotNewOrderRequestParamsTypeBuy 买
SpotNewOrderRequestParamsTypeBuy = SpotNewOrderRequestParamsType("1")
// SpotNewOrderRequestParamsTypeSell 卖
SpotNewOrderRequestParamsTypeSell = SpotNewOrderRequestParamsType("0")
)
// SpotNewOrderRequestParams is the params used for placing an order
type SpotNewOrderRequestParams struct {
Amount float64 `json:"amount"` // 交易数量
Price float64 `json:"price"` // 下单价格,
Symbol string `json:"currency"` // 交易对, btcusdt, bccbtc......
Type SpotNewOrderRequestParamsType `json:"tradeType"` // 订单类型, buy-market: 市价买, sell-market: 市价卖, buy-limit: 限价买, sell-limit: 限价卖
}
// SpotNewOrderResponse stores the new order response data
type SpotNewOrderResponse struct {
Code int `json:"code"` // 返回代码
Message string `json:"message"` // 提示信息
ID string `json:"id"` // 委托挂单号
}
// KlinesRequestParams represents Klines request data.
type KlinesRequestParams struct {
Symbol string // 交易对, zb_qc,zb_usdt,zb_btc...
Type string // K线类型, 1min, 3min, 15min, 30min, 1hour......
Since int64 // 从这个时间戳之后的
Size int64 // 返回数据的条数限制(默认为1000如果返回数据多于1000条那么只返回1000条)
}
// KLineResponseData Kline Data
type KLineResponseData struct {
KlineTime time.Time `json:"klineTime"`
Open float64 `json:"open"` // 开盘价
Close float64 `json:"close"` // 收盘价, 当K线为最晚的一根时, 时最新成交价
Low float64 `json:"low"` // 最低价
High float64 `json:"high"` // 最高价
Volume float64 `json:"vol"` // 成交量
}
// KLineResponse K线返回类型
type KLineResponse struct {
// Data string `json:"data"` // 买入货币
MoneyType string `json:"moneyType"` // 卖出货币
Symbol string `json:"symbol"` // 内容说明
Data []*KLineResponseData `json:"data"` // KLine数据
}
// UserAddress defines Users Address for depositing funds
type UserAddress struct {
Code int64 `json:"code"`
Message struct {
Description string `json:"des"`
IsSuccessful bool `json:"isSuc"`
Data struct {
Address string `json:"key"`
Tag string // custom field we populate
} `json:"datas"`
} `json:"message"`
}
// MultiChainDepositAddress stores an individual multichain deposit item
type MultiChainDepositAddress struct {
Blockchain string `json:"blockChain"`
IsUseMemo bool `json:"isUseMemo"`
Account string `json:"account"`
Address string `json:"address"`
Memo string `json:"memo"`
CanDeposit bool `json:"canDeposit"`
CanWithdraw bool `json:"canWithdraw"`
}
// MultiChainDepositAddressResponse stores the multichain deposit address response
type MultiChainDepositAddressResponse struct {
Code int64 `json:"code"`
Message struct {
Description string `json:"des"`
IsSuccessful bool `json:"isSuc"`
Data []MultiChainDepositAddress `json:"datas"`
} `json:"message"`
}
// WithdrawalFees the large list of predefined withdrawal fees
// Prone to change, using highest value
var WithdrawalFees = map[currency.Code]float64{
currency.ZB: 5,
currency.BTC: 0.001,
currency.BCH: 0.0006,
currency.LTC: 0.005,
currency.ETH: 0.01,
currency.ETC: 0.01,
currency.BTS: 3,
currency.EOS: 0.1,
currency.QTUM: 0.01,
currency.HC: 0.001,
currency.XRP: 0.1,
currency.QC: 5,
currency.DASH: 0.002,
currency.BCD: 0,
currency.UBTC: 0.001,
currency.SBTC: 0,
currency.INK: 60,
currency.BTH: 0.01,
currency.LBTC: 0.01,
currency.CHAT: 20,
currency.BITCNY: 20,
currency.HLC: 100,
currency.BTP: 0.001,
currency.TOPC: 200,
currency.ENT: 50,
currency.BAT: 40,
currency.FIRST: 30,
currency.SAFE: 0.001,
currency.QUN: 200,
currency.BTN: 0.005,
currency.TRUE: 5,
currency.CDC: 1,
currency.DDM: 1,
currency.HOTC: 150,
currency.USDT: 5,
currency.XUC: 1,
currency.EPC: 40,
currency.BDS: 3,
currency.GRAM: 5,
currency.DOGE: 20,
currency.NEO: 0,
currency.OMG: 0.5,
currency.BTM: 4,
currency.SNT: 60,
currency.AE: 3,
currency.ICX: 3,
currency.ZRX: 10,
currency.EDO: 4,
currency.FUN: 250,
currency.MANA: 70,
currency.RCN: 70,
currency.MCO: 0.6,
currency.MITH: 10,
currency.KNC: 5,
currency.XLM: 0.1,
currency.GNT: 20,
currency.MTL: 3,
currency.SUB: 20,
currency.XEM: 4,
currency.EOSDAC: 0,
currency.KAN: 350,
currency.AAA: 1,
currency.XWC: 1,
currency.PDX: 1,
currency.SLT: 100,
currency.ADA: 1,
currency.HPY: 100,
currency.PAX: 5,
currency.XTZ: 0.1,
}
// orderSideMap holds order type info based on Alphapoint data
var orderSideMap = map[int64]order.Side{
0: order.Buy,
1: order.Sell,
}
// TradeHistory defines a slice of historic trades
type TradeHistory []struct {
Amount float64 `json:"amount,string"`
Date int64 `json:"date"`
Price float64 `json:"price,string"`
Tid int64 `json:"tid"`
TradeType string `json:"trade_type"`
Type string `json:"type"`
}
// WalletRecordsRequest hold request params
type WalletRecordsRequest struct {
Currency currency.Code `json:"method"`
PageIndex int64 `json:"pageIndex"`
PageSize int64 `json:"pageSize"`
}
// DepositRecordsResponse holds response data
type DepositRecordsResponse struct {
List []DepositRecord `json:"list"`
PageIndex int64 `json:"pageIndex"`
PageSize int64 `json:"pageSize"`
Total int64 `json:"total"`
}
// DepositRecord holds details of a deposit
type DepositRecord struct {
Address string `json:"address"`
Amount float64 `json:"amount,string"`
ConfirmTimes int64 `json:"confirmTimes"`
Currency string `json:"currency"`
Description string `json:"description"`
Hash string `json:"hash"`
ID int64 `json:"id"`
InternalTransfer int64 `json:"itransfer"`
Status int64 `json:"status"`
SubmitTime int64 `json:"submit_time,string"`
}
// WithdrawalRecordsResponse holds response data
type WithdrawalRecordsResponse struct {
List []WithdrawalRecord `json:"list"`
PageIndex int64 `json:"pageIndex"`
PageSize int64 `json:"pageSize"`
Total int64 `json:"total"`
}
// WithdrawalRecord holds details of a withdrawal
type WithdrawalRecord struct {
Amount float64 `json:"amount"`
Fees float64 `json:"fees"`
ID int64 `json:"id"`
ManageTime int64 `json:"manageTime"`
Status int64 `json:"status"`
SubmitTime int64 `json:"submitTime"`
ToAddress string `json:"toAddress"`
}

View File

@@ -1,778 +0,0 @@
package zb
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"regexp"
"strconv"
"strings"
"time"
"github.com/gorilla/websocket"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/common/crypto"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
"github.com/thrasher-corp/gocryptotrader/exchanges/subscription"
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
"github.com/thrasher-corp/gocryptotrader/exchanges/trade"
)
const (
zbWebsocketAPI = "wss://api.zb.com/websocket"
zWebsocketAddChannel = "addChannel"
zbWebsocketRateLimit = 20
)
// WsConnect initiates a websocket connection
func (z *ZB) WsConnect() error {
if !z.Websocket.IsEnabled() || !z.IsEnabled() {
return errors.New(stream.WebsocketNotEnabled)
}
var dialer websocket.Dialer
err := z.Websocket.Conn.Dial(&dialer, http.Header{})
if err != nil {
return err
}
z.Websocket.Wg.Add(1)
go z.wsReadData()
return nil
}
// wsReadData handles all the websocket data coming from the websocket
// connection
func (z *ZB) wsReadData() {
defer z.Websocket.Wg.Done()
for {
resp := z.Websocket.Conn.ReadMessage()
if resp.Raw == nil {
return
}
err := z.wsHandleData(resp.Raw)
if err != nil {
z.Websocket.DataHandler <- err
}
}
}
func (z *ZB) wsHandleData(respRaw []byte) error {
fixedJSON := z.wsFixInvalidJSON(respRaw)
var result Generic
err := json.Unmarshal(fixedJSON, &result)
if err != nil {
return err
}
if result.No > 0 {
if z.Websocket.Match.IncomingWithData(result.No, fixedJSON) {
return nil
}
}
if result.Code > 0 && result.Code != 1000 {
return fmt.Errorf("%v request failed, message: %v, error code: %v",
z.Name,
result.Message,
wsErrCodes[result.Code])
}
switch {
case strings.Contains(result.Channel, "markets"):
var markets Markets
err := json.Unmarshal(result.Data, &markets)
if err != nil {
return err
}
case strings.Contains(result.Channel, "ticker"):
cPair := strings.Split(result.Channel, currency.UnderscoreDelimiter)
var wsTicker WsTicker
err := json.Unmarshal(fixedJSON, &wsTicker)
if err != nil {
return err
}
p, err := currency.NewPairFromString(cPair[0])
if err != nil {
return err
}
z.Websocket.DataHandler <- &ticker.Price{
ExchangeName: z.Name,
Close: wsTicker.Data.Last,
Volume: wsTicker.Data.Volume24Hr,
High: wsTicker.Data.High,
Low: wsTicker.Data.Low,
Last: wsTicker.Data.Last,
Bid: wsTicker.Data.Buy,
Ask: wsTicker.Data.Sell,
LastUpdated: time.UnixMilli(wsTicker.Date),
AssetType: asset.Spot,
Pair: p,
}
case strings.Contains(result.Channel, "depth"):
var depth WsDepth
err := json.Unmarshal(fixedJSON, &depth)
if err != nil {
return err
}
channelInfo := strings.Split(result.Channel, currency.UnderscoreDelimiter)
cPair, err := currency.NewPairFromString(channelInfo[0])
if err != nil {
return err
}
book := orderbook.Base{
Bids: make(orderbook.Items, len(depth.Bids)),
Asks: make(orderbook.Items, len(depth.Asks)),
Asset: asset.Spot,
Pair: cPair,
Exchange: z.Name,
VerifyOrderbook: z.CanVerifyOrderbook,
LastUpdated: time.Now(), // This is temp to pass test as the API is broken.
}
for i := range depth.Asks {
amt, ok := depth.Asks[i][1].(float64)
if !ok {
return common.GetTypeAssertError("float64", depth.Asks[i][1], "ask amount")
}
price, ok := depth.Asks[i][0].(float64)
if !ok {
return common.GetTypeAssertError("float64", depth.Asks[i][0], "ask price")
}
book.Asks[i] = orderbook.Item{
Amount: amt,
Price: price,
}
}
for i := range depth.Bids {
amt, ok := depth.Bids[i][1].(float64)
if !ok {
return common.GetTypeAssertError("float64", depth.Bids[i][1], "bid amount")
}
price, ok := depth.Bids[i][0].(float64)
if !ok {
return common.GetTypeAssertError("float64", depth.Bids[i][0], "bid price")
}
book.Bids[i] = orderbook.Item{
Amount: amt,
Price: price,
}
}
book.Asks.Reverse() // Reverse asks for correct alignment
err = z.Websocket.Orderbook.LoadSnapshot(&book)
if err != nil {
return err
}
case strings.Contains(result.Channel, "_order"):
cPair := strings.Split(result.Channel, currency.UnderscoreDelimiter)
var o WsSubmitOrderResponse
err := json.Unmarshal(fixedJSON, &o)
if err != nil {
return err
}
if !o.Success {
return fmt.Errorf("%s - Order %v failed to be placed. %s",
z.Name,
o.Data.EntrustID,
respRaw)
}
p, err := currency.NewPairFromString(cPair[0])
if err != nil {
return err
}
var a asset.Item
a, err = z.GetPairAssetType(p)
if err != nil {
return err
}
z.Websocket.DataHandler <- &order.Detail{
Exchange: z.Name,
OrderID: strconv.FormatInt(o.Data.EntrustID, 10),
Pair: p,
AssetType: a,
}
case strings.Contains(result.Channel, "_cancelorder"):
cPair := strings.Split(result.Channel, currency.UnderscoreDelimiter)
var o WsSubmitOrderResponse
err := json.Unmarshal(fixedJSON, &o)
if err != nil {
return err
}
if !o.Success {
return fmt.Errorf("%s - Order %v failed to be cancelled. %s",
z.Name,
o.Data.EntrustID,
respRaw)
}
p, err := currency.NewPairFromString(cPair[0])
if err != nil {
return err
}
z.Websocket.DataHandler <- &order.Detail{
Exchange: z.Name,
OrderID: strconv.FormatInt(o.Data.EntrustID, 10),
Pair: p,
Status: order.Cancelled,
}
case strings.Contains(result.Channel, "trades"):
if !z.IsSaveTradeDataEnabled() {
return nil
}
var tradeData WsTrades
err := json.Unmarshal(fixedJSON, &tradeData)
if err != nil {
return err
}
var trades []trade.Data
for i := range tradeData.Data {
channelInfo := strings.Split(result.Channel, currency.UnderscoreDelimiter)
cPair, err := currency.NewPairFromString(channelInfo[0])
if err != nil {
return err
}
var tSide order.Side
tSide, err = order.StringToOrderSide(tradeData.Data[i].Type)
if err != nil {
return &order.ClassificationError{
Exchange: z.Name,
Err: err,
}
}
trades = append(trades, trade.Data{
Timestamp: time.Unix(tradeData.Data[i].Date, 0),
CurrencyPair: cPair,
AssetType: asset.Spot,
Exchange: z.Name,
Price: tradeData.Data[i].Price,
Amount: tradeData.Data[i].Amount,
Side: tSide,
TID: strconv.FormatInt(tradeData.Data[i].TID, 10),
})
}
return trade.AddTradesToBuffer(z.Name, trades...)
default:
z.Websocket.DataHandler <- stream.UnhandledMessageWarning{
Message: z.Name +
stream.UnhandledMessage +
string(respRaw)}
}
return nil
}
// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions()
func (z *ZB) GenerateDefaultSubscriptions() ([]subscription.Subscription, error) {
var subscriptions []subscription.Subscription
// market configuration is its own channel
subscriptions = append(subscriptions, subscription.Subscription{
Channel: "markets",
})
channels := []string{"%s_ticker", "%s_depth", "%s_trades"}
enabledCurrencies, err := z.GetEnabledPairs(asset.Spot)
if err != nil {
return nil, err
}
for i := range channels {
for j := range enabledCurrencies {
enabledCurrencies[j].Delimiter = ""
subscriptions = append(subscriptions, subscription.Subscription{
Channel: fmt.Sprintf(channels[i], enabledCurrencies[j].Lower().String()),
Pair: enabledCurrencies[j].Lower(),
Asset: asset.Spot,
})
}
}
return subscriptions, nil
}
// Subscribe sends a websocket message to receive data from the channel
func (z *ZB) Subscribe(channelsToSubscribe []subscription.Subscription) error {
var errs error
for i := range channelsToSubscribe {
subscriptionRequest := Subscription{
Event: zWebsocketAddChannel,
Channel: channelsToSubscribe[i].Channel,
}
err := z.Websocket.Conn.SendJSONMessage(subscriptionRequest)
if err != nil {
errs = common.AppendError(errs, err)
continue
}
z.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe[i])
}
if errs != nil {
return errs
}
return nil
}
func (z *ZB) wsGenerateSignature(secret string, request interface{}) (string, error) {
jsonResponse, err := json.Marshal(request)
if err != nil {
return "", err
}
hex, err := crypto.Sha1ToHex(secret)
if err != nil {
return "", err
}
hmac, err := crypto.GetHMAC(crypto.HashMD5,
jsonResponse,
[]byte(hex))
if err != nil {
return "", err
}
return fmt.Sprintf("%x", hmac), nil
}
func (z *ZB) wsFixInvalidJSON(json []byte) []byte {
invalidZbJSONRegex := `(\"\[|\"\{)(.*)(\]\"|\}\")`
regexChecker := regexp.MustCompile(invalidZbJSONRegex)
matchingResults := regexChecker.Find(json)
if matchingResults == nil {
return json
}
// Remove first quote character
capturedInvalidZBJSON := strings.Replace(string(matchingResults), "\"", "", 1)
// Remove last quote character
fixedJSON := capturedInvalidZBJSON[:len(capturedInvalidZBJSON)-1]
return []byte(strings.Replace(string(json), string(matchingResults), fixedJSON, 1))
}
func (z *ZB) wsAddSubUser(ctx context.Context, username, password string) (*WsGetSubUserListResponse, error) {
if !z.IsWebsocketAuthenticationSupported() {
return nil, fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", z.Name)
}
creds, err := z.GetCredentials(ctx)
if err != nil {
return nil, err
}
request := WsAddSubUserRequest{
Memo: "memo",
Password: password,
SubUserName: username,
}
request.Channel = "addSubUser"
request.Event = zWebsocketAddChannel
request.Accesskey = creds.Key
request.No = z.Websocket.Conn.GenerateMessageID(true)
request.Sign, err = z.wsGenerateSignature(creds.Secret, request)
if err != nil {
return nil, err
}
resp, err := z.Websocket.Conn.SendMessageReturnResponse(request.No, request)
if err != nil {
return nil, err
}
var genericResponse Generic
err = json.Unmarshal(resp, &genericResponse)
if err != nil {
return nil, err
}
if genericResponse.Code > 0 && genericResponse.Code != 1000 {
return nil,
fmt.Errorf("%v request failed, message: %v, error code: %v",
z.Name,
genericResponse.Message,
wsErrCodes[genericResponse.Code])
}
var response WsGetSubUserListResponse
err = json.Unmarshal(resp, &response)
if err != nil {
return nil, err
}
return &response, nil
}
func (z *ZB) wsGetSubUserList(ctx context.Context) (*WsGetSubUserListResponse, error) {
if !z.IsWebsocketAuthenticationSupported() {
return nil,
fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", z.Name)
}
creds, err := z.GetCredentials(ctx)
if err != nil {
return nil, err
}
request := WsAuthenticatedRequest{}
request.Channel = "getSubUserList"
request.Event = zWebsocketAddChannel
request.Accesskey = creds.Key
request.No = z.Websocket.Conn.GenerateMessageID(true)
request.Sign, err = z.wsGenerateSignature(creds.Secret, request)
if err != nil {
return nil, err
}
resp, err := z.Websocket.Conn.SendMessageReturnResponse(request.No, request)
if err != nil {
return nil, err
}
var response WsGetSubUserListResponse
err = json.Unmarshal(resp, &response)
if err != nil {
return nil, err
}
if response.Code > 0 && response.Code != 1000 {
return &response,
fmt.Errorf("%v request failed, message: %v, error code: %v",
z.Name,
response.Message,
wsErrCodes[response.Code])
}
return &response, nil
}
func (z *ZB) wsDoTransferFunds(ctx context.Context, pair currency.Code, amount float64, fromUserName, toUserName string) (*WsRequestResponse, error) {
if !z.IsWebsocketAuthenticationSupported() {
return nil,
fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", z.Name)
}
creds, err := z.GetCredentials(ctx)
if err != nil {
return nil, err
}
request := WsDoTransferFundsRequest{
Amount: amount,
Currency: pair,
FromUserName: fromUserName,
ToUserName: toUserName,
No: z.Websocket.Conn.GenerateMessageID(true),
}
request.Channel = "doTransferFunds"
request.Event = zWebsocketAddChannel
request.Accesskey = creds.Key
request.Sign, err = z.wsGenerateSignature(creds.Secret, request)
if err != nil {
return nil, err
}
resp, err := z.Websocket.Conn.SendMessageReturnResponse(request.No, request)
if err != nil {
return nil, err
}
var response WsRequestResponse
err = json.Unmarshal(resp, &response)
if err != nil {
return nil, err
}
if response.Code > 0 && response.Code != 1000 {
return &response,
fmt.Errorf("%v request failed, message: %v, error code: %v",
z.Name,
response.Message,
wsErrCodes[response.Code])
}
return &response, nil
}
func (z *ZB) wsCreateSubUserKey(ctx context.Context, assetPerm, entrustPerm, leverPerm, moneyPerm bool, keyName, toUserID string) (*WsRequestResponse, error) {
if !z.IsWebsocketAuthenticationSupported() {
return nil,
fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", z.Name)
}
creds, err := z.GetCredentials(ctx)
if err != nil {
return nil, err
}
request := WsCreateSubUserKeyRequest{
AssetPerm: assetPerm,
EntrustPerm: entrustPerm,
KeyName: keyName,
LeverPerm: leverPerm,
MoneyPerm: moneyPerm,
No: z.Websocket.Conn.GenerateMessageID(true),
ToUserID: toUserID,
}
request.Channel = "createSubUserKey"
request.Event = zWebsocketAddChannel
request.Accesskey = creds.Key
request.Sign, err = z.wsGenerateSignature(creds.Secret, request)
if err != nil {
return nil, err
}
resp, err := z.Websocket.Conn.SendMessageReturnResponse(request.No, request)
if err != nil {
return nil, err
}
var response WsRequestResponse
err = json.Unmarshal(resp, &response)
if err != nil {
return nil, err
}
if response.Code > 0 && response.Code != 1000 {
return &response,
fmt.Errorf("%v request failed, message: %v, error code: %v",
z.Name,
response.Message,
wsErrCodes[response.Code])
}
return &response, nil
}
func (z *ZB) wsSubmitOrder(ctx context.Context, pair currency.Pair, amount, price float64, tradeType int64) (*WsSubmitOrderResponse, error) {
if !z.IsWebsocketAuthenticationSupported() {
return nil,
fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", z.Name)
}
creds, err := z.GetCredentials(ctx)
if err != nil {
return nil, err
}
request := WsSubmitOrderRequest{
Amount: amount,
Price: price,
TradeType: tradeType,
No: z.Websocket.Conn.GenerateMessageID(true),
}
request.Channel = pair.String() + "_order"
request.Event = zWebsocketAddChannel
request.Accesskey = creds.Key
request.Sign, err = z.wsGenerateSignature(creds.Secret, request)
if err != nil {
return nil, err
}
resp, err := z.Websocket.Conn.SendMessageReturnResponse(request.No, request)
if err != nil {
return nil, err
}
var response WsSubmitOrderResponse
err = json.Unmarshal(resp, &response)
if err != nil {
return nil, err
}
if response.Code > 0 && response.Code != 1000 {
return &response,
fmt.Errorf("%v request failed, message: %v, error code: %v",
z.Name,
response.Message,
wsErrCodes[response.Code])
}
return &response, nil
}
func (z *ZB) wsCancelOrder(ctx context.Context, pair currency.Pair, orderID int64) (*WsCancelOrderResponse, error) {
if !z.IsWebsocketAuthenticationSupported() {
return nil,
fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", z.Name)
}
creds, err := z.GetCredentials(ctx)
if err != nil {
return nil, err
}
request := WsCancelOrderRequest{
ID: orderID,
No: z.Websocket.Conn.GenerateMessageID(true),
}
request.Channel = pair.String() + "_cancelorder"
request.Event = zWebsocketAddChannel
request.Accesskey = creds.Key
request.Sign, err = z.wsGenerateSignature(creds.Secret, request)
if err != nil {
return nil, err
}
resp, err := z.Websocket.Conn.SendMessageReturnResponse(request.No, request)
if err != nil {
return nil, err
}
var response WsCancelOrderResponse
err = json.Unmarshal(resp, &response)
if err != nil {
return nil, err
}
if response.Code > 0 && response.Code != 1000 {
return &response,
fmt.Errorf("%v request failed, message: %v, error code: %v",
z.Name,
response.Message,
wsErrCodes[response.Code])
}
return &response, nil
}
func (z *ZB) wsGetOrder(ctx context.Context, pair currency.Pair, orderID int64) (*WsGetOrderResponse, error) {
if !z.IsWebsocketAuthenticationSupported() {
return nil,
fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", z.Name)
}
creds, err := z.GetCredentials(ctx)
if err != nil {
return nil, err
}
request := WsGetOrderRequest{
ID: orderID,
No: z.Websocket.Conn.GenerateMessageID(true),
}
request.Channel = pair.String() + "_getorder"
request.Event = zWebsocketAddChannel
request.Accesskey = creds.Key
request.Sign, err = z.wsGenerateSignature(creds.Secret, request)
if err != nil {
return nil, err
}
resp, err := z.Websocket.Conn.SendMessageReturnResponse(request.No, request)
if err != nil {
return nil, err
}
var response WsGetOrderResponse
err = json.Unmarshal(resp, &response)
if err != nil {
return nil, err
}
if response.Code > 0 && response.Code != 1000 {
return &response,
fmt.Errorf("%v request failed, message: %v, error code: %v",
z.Name,
response.Message,
wsErrCodes[response.Code])
}
return &response, nil
}
func (z *ZB) wsGetOrders(ctx context.Context, pair currency.Pair, pageIndex, tradeType int64) (*WsGetOrdersResponse, error) {
if !z.IsWebsocketAuthenticationSupported() {
return nil,
fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", z.Name)
}
creds, err := z.GetCredentials(ctx)
if err != nil {
return nil, err
}
request := WsGetOrdersRequest{
PageIndex: pageIndex,
TradeType: tradeType,
No: z.Websocket.Conn.GenerateMessageID(true),
}
request.Channel = pair.String() + "_getorders"
request.Event = zWebsocketAddChannel
request.Accesskey = creds.Key
request.Sign, err = z.wsGenerateSignature(creds.Secret, request)
if err != nil {
return nil, err
}
resp, err := z.Websocket.Conn.SendMessageReturnResponse(request.No, request)
if err != nil {
return nil, err
}
var response WsGetOrdersResponse
err = json.Unmarshal(resp, &response)
if err != nil {
return nil, err
}
if response.Code > 0 && response.Code != 1000 {
return &response,
fmt.Errorf("%v request failed, message: %v, error code: %v",
z.Name,
response.Message,
wsErrCodes[response.Code])
}
return &response, nil
}
func (z *ZB) wsGetOrdersIgnoreTradeType(ctx context.Context, pair currency.Pair, pageIndex, pageSize int64) (*WsGetOrdersIgnoreTradeTypeResponse, error) {
if !z.IsWebsocketAuthenticationSupported() {
return nil,
fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", z.Name)
}
creds, err := z.GetCredentials(ctx)
if err != nil {
return nil, err
}
request := WsGetOrdersIgnoreTradeTypeRequest{
PageIndex: pageIndex,
PageSize: pageSize,
No: z.Websocket.Conn.GenerateMessageID(true),
}
request.Channel = pair.String() + "_getordersignoretradetype"
request.Event = zWebsocketAddChannel
request.Accesskey = creds.Key
request.Sign, err = z.wsGenerateSignature(creds.Secret, request)
if err != nil {
return nil, err
}
resp, err := z.Websocket.Conn.SendMessageReturnResponse(request.No, request)
if err != nil {
return nil, err
}
var response WsGetOrdersIgnoreTradeTypeResponse
err = json.Unmarshal(resp, &response)
if err != nil {
return nil, err
}
if response.Code > 0 && response.Code != 1000 {
return &response,
fmt.Errorf("%v request failed, message: %v, error code: %v",
z.Name,
response.Message,
wsErrCodes[response.Code])
}
return &response, nil
}
func (z *ZB) wsGetAccountInfoRequest(ctx context.Context) (*WsGetAccountInfoResponse, error) {
if !z.IsWebsocketAuthenticationSupported() {
return nil,
fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", z.Name)
}
creds, err := z.GetCredentials(ctx)
if err != nil {
return nil, err
}
request := WsAuthenticatedRequest{
Channel: "getaccountinfo",
Event: zWebsocketAddChannel,
Accesskey: creds.Key,
No: z.Websocket.Conn.GenerateMessageID(true),
}
request.Sign, err = z.wsGenerateSignature(creds.Secret, request)
if err != nil {
return nil, err
}
resp, err := z.Websocket.Conn.SendMessageReturnResponse(request.No, request)
if err != nil {
return nil, err
}
var response WsGetAccountInfoResponse
err = json.Unmarshal(resp, &response)
if err != nil {
return nil, err
}
if response.Code > 0 && response.Code != 1000 {
return &response,
fmt.Errorf("%v request failed, message: %v, error code: %v",
z.Name,
response.Message,
wsErrCodes[response.Code])
}
return &response, nil
}

View File

@@ -1,289 +0,0 @@
package zb
import (
"encoding/json"
"github.com/thrasher-corp/gocryptotrader/currency"
)
// Subscription defines an initial subscription type to be sent
type Subscription struct {
Event string `json:"event"`
Channel string `json:"channel"`
No int64 `json:"no,string,omitempty"`
}
// Generic defines a generic fields associated with many return types
type Generic struct {
Code int64 `json:"code"`
Channel string `json:"channel"`
Message interface{} `json:"message"`
No int64 `json:"no,string,omitempty"`
Data json.RawMessage `json:"data"`
}
// Markets defines market data
type Markets map[string]struct {
AmountScale int64 `json:"amountScale"`
PriceScale int64 `json:"priceScale"`
}
// WsTicker defines websocket ticker data
type WsTicker struct {
Date int64 `json:"date,string"`
Data struct {
Volume24Hr float64 `json:"vol,string"`
High float64 `json:"high,string"`
Low float64 `json:"low,string"`
Last float64 `json:"last,string"`
Buy float64 `json:"buy,string"`
Sell float64 `json:"sell,string"`
} `json:"ticker"`
}
// WsDepth defines websocket orderbook data
type WsDepth struct {
Timestamp int64 `json:"timestamp"`
Asks [][]interface{} `json:"asks"`
Bids [][]interface{} `json:"bids"`
}
// WsTrades defines websocket trade data
type WsTrades struct {
Data []struct {
Amount float64 `json:"amount,string"`
Price float64 `json:"price,string"`
TID int64 `json:"tid"`
Date int64 `json:"date"`
Type string `json:"type"`
TradeType string `json:"trade_type"`
} `json:"data"`
}
// WsAuthenticatedRequest base request type
type WsAuthenticatedRequest struct {
Accesskey string `json:"accesskey"`
Channel string `json:"channel"`
Event string `json:"event"`
No int64 `json:"no,string,omitempty"`
Sign string `json:"sign,omitempty"`
}
// WsAddSubUserRequest data to add sub users
type WsAddSubUserRequest struct {
Accesskey string `json:"accesskey"`
Channel string `json:"channel"`
Event string `json:"event"`
Memo string `json:"memo"`
Password string `json:"password"`
SubUserName string `json:"subUserName"`
No int64 `json:"no,string,omitempty"`
Sign string `json:"sign,omitempty"`
}
// WsCreateSubUserKeyRequest data to add sub user keys
type WsCreateSubUserKeyRequest struct {
Accesskey string `json:"accesskey"`
AssetPerm bool `json:"assetPerm,string"`
Channel string `json:"channel"`
EntrustPerm bool `json:"entrustPerm,string"`
Event string `json:"event"`
KeyName string `json:"keyName"`
LeverPerm bool `json:"leverPerm,string"`
MoneyPerm bool `json:"moneyPerm,string"`
No int64 `json:"no,string,omitempty"`
Sign string `json:"sign,omitempty"`
ToUserID string `json:"toUserId"`
}
// WsDoTransferFundsRequest data to transfer funds
type WsDoTransferFundsRequest struct {
Accesskey string `json:"accesskey"`
Amount float64 `json:"amount,string"`
Channel string `json:"channel"`
Currency currency.Code `json:"currency"`
Event string `json:"event"`
FromUserName string `json:"fromUserName"`
No int64 `json:"no,string"`
Sign string `json:"sign,omitempty"`
ToUserName string `json:"toUserName"`
}
// WsGetSubUserListResponse data response from GetSubUserList
type WsGetSubUserListResponse struct {
Success bool `json:"success"`
Code int64 `json:"code"`
Channel string `json:"channel"`
Message []WsGetSubUserListResponseData `json:"message"`
No int64 `json:"no,string"`
}
// WsGetSubUserListResponseData user data
type WsGetSubUserListResponseData struct {
IsOpenAPI bool `json:"isOpenApi,omitempty"`
Memo string `json:"memo,omitempty"`
UserName string `json:"userName,omitempty"`
UserID int64 `json:"userId,omitempty"`
IsFreez bool `json:"isFreez,omitempty"`
}
// WsRequestResponse generic response data
type WsRequestResponse struct {
Success bool `json:"success"`
Code int64 `json:"code"`
Channel string `json:"channel"`
Message interface{} `json:"message"`
No int64 `json:"no,string"`
}
// WsSubmitOrderRequest creates an order via ws
type WsSubmitOrderRequest struct {
Accesskey string `json:"accesskey"`
Amount float64 `json:"amount,string"`
Channel string `json:"channel"`
Event string `json:"event"`
No int64 `json:"no,string,omitempty"`
Price float64 `json:"price,string"`
Sign string `json:"sign,omitempty"`
TradeType int64 `json:"tradeType,string"`
}
// WsSubmitOrderResponse data about submitted order
type WsSubmitOrderResponse struct {
Message string `json:"message"`
No int64 `json:"no,string"`
Data struct {
EntrustID int64 `json:"intrustID"`
} `json:"data"`
Code int64 `json:"code"`
Channel string `json:"channel"`
Success bool `json:"success"`
}
// WsCancelOrderRequest order cancel request
type WsCancelOrderRequest struct {
Accesskey string `json:"accesskey"`
Channel string `json:"channel"`
Event string `json:"event"`
ID int64 `json:"id"`
Sign string `json:"sign,omitempty"`
No int64 `json:"no,string"`
}
// WsCancelOrderResponse order cancel response
type WsCancelOrderResponse struct {
Message string `json:"message"`
No int64 `json:"no,string"`
Code int64 `json:"code"`
Channel string `json:"channel"`
Success bool `json:"success"`
}
// WsGetOrderRequest Get specific order details
type WsGetOrderRequest struct {
Accesskey string `json:"accesskey"`
Channel string `json:"channel"`
Event string `json:"event"`
ID int64 `json:"id"`
Sign string `json:"sign,omitempty"`
No int64 `json:"no,string"`
}
// WsGetOrderResponse contains order data
type WsGetOrderResponse struct {
Message string `json:"message"`
No int64 `json:"no,string"`
Code int64 `json:"code"`
Channel string `json:"channel"`
Success bool `json:"success"`
Data []Order `json:"data"`
}
// WsGetOrdersRequest get more orders, with no orderID filtering
type WsGetOrdersRequest struct {
Accesskey string `json:"accesskey"`
Channel string `json:"channel"`
Event string `json:"event"`
No int64 `json:"no,string"`
PageIndex int64 `json:"pageIndex"`
TradeType int64 `json:"tradeType"`
Sign string `json:"sign,omitempty"`
}
// WsGetOrdersResponse contains orders data
type WsGetOrdersResponse struct {
Message string `json:"message"`
No int64 `json:"no,string"`
Code int64 `json:"code"`
Channel string `json:"channel"`
Success bool `json:"success"`
Data []Order `json:"data"`
}
// WsGetOrdersIgnoreTradeTypeRequest ws request
type WsGetOrdersIgnoreTradeTypeRequest struct {
Accesskey string `json:"accesskey"`
Channel string `json:"channel"`
Event string `json:"event"`
No int64 `json:"no,string"`
PageIndex int64 `json:"pageIndex"`
PageSize int64 `json:"pageSize"`
Sign string `json:"sign,omitempty"`
}
// WsGetOrdersIgnoreTradeTypeResponse contains orders data
type WsGetOrdersIgnoreTradeTypeResponse struct {
Message string `json:"message"`
No int64 `json:"no,string"`
Code int64 `json:"code"`
Channel string `json:"channel"`
Success bool `json:"success"`
Data []Order `json:"data"`
}
// WsGetAccountInfoResponse contains account data
type WsGetAccountInfoResponse struct {
Message string `json:"message"`
No int64 `json:"no,string"`
Data struct {
Coins []AccountsResponseCoin `json:"coins"`
Base AccountsBaseResponse `json:"base"`
} `json:"data"`
Code int64 `json:"code"`
Channel string `json:"channel"`
Success bool `json:"success"`
}
var wsErrCodes = map[int64]string{
1000: "Successful call",
1001: "General error message",
1002: "internal error",
1003: "Verification failed",
1004: "Financial security password lock",
1005: "The fund security password is incorrect. Please confirm and re-enter.",
1006: "Real-name certification is awaiting review or review",
1007: "Channel is empty",
1008: "Event is empty",
1009: "This interface is being maintained",
1011: "Not open yet",
1012: "Insufficient permissions",
1013: "Can not trade, if you have any questions, please contact online customer service",
1014: "Cannot be sold during the pre-sale period",
2002: "Insufficient balance in Bitcoin account",
2003: "Insufficient balance of Litecoin account",
2005: "Insufficient balance in Ethereum account",
2006: "Insufficient balance in ETC currency account",
2007: "Insufficient balance of BTS currency account",
2008: "Insufficient balance in EOS currency account",
2009: "Insufficient account balance",
3001: "Pending order not found",
3002: "Invalid amount",
3003: "Invalid quantity",
3004: "User does not exist",
3005: "Invalid parameter",
3006: "Invalid IP or inconsistent with the bound IP",
3007: "Request time has expired",
3008: "Transaction history not found",
4001: "API interface is locked",
4002: "Request too frequently",
}

File diff suppressed because it is too large Load Diff

View File

@@ -2307,84 +2307,6 @@
}
]
},
{
"name": "ZB",
"enabled": true,
"verbose": false,
"httpTimeout": 15000000000,
"websocketResponseCheckTimeout": 30000000,
"websocketResponseMaxLimit": 7000000000,
"websocketTrafficTimeout": 30000000000,
"websocketOrderbookBufferLimit": 5,
"baseCurrencies": "USD",
"currencyPairs": {
"requestFormat": {
"uppercase": false,
"delimiter": "_"
},
"configFormat": {
"uppercase": true,
"delimiter": "_"
},
"useGlobalFormat": true,
"assetTypes": [
"spot"
],
"pairs": {
"spot": {
"enabled": "BTC_USDT,ETH_USDT",
"available": "TRUE_BTC,LTC_USDT,ACC_USDT,MANA_BTC,GRIN_USDT,HC_USDT,ADA_BTC,SLT_BTC,TRX_QC,CRO_USDT,BCHABC_USDT,MANA_QC,OMG_USDT,BTS_QC,TRUE_USDT,BCW_QC,TOPC_QC,ZB_BTC,SAFE_QC,BTH_USDT,MCO_USDT,UBTC_QC,XEM_USDT,BTC_QC,B91_QC,BCW_USDT,MCO_QC,BTP_QC,CHAT_USDT,VSYS_BTC,HSR_USDT,DDM_USDT,MANA_USDT,XLM_BTC,ETC_QC,KAN_QC,ZRX_USDT,TUSD_USDT,SBTC_USDT,NXWC_USDT,BRC_BTC,PDX_BTC,XUC_QC,ETH_QC,EOSDAC_USDT,BTN_USDT,GNT_QC,BRC_USDT,LTC_BTC,SUB_QC,INK_USDT,EOSDAC_QC,TRUE_QC,XLM_USDT,BTS_BTC,HLC_QC,YTNB_USDT,AE_BTC,PDX_QC,DOGE_USDT,XWC_USDT,ADA_QC,BSV_USDT,XEM_BTC,DDM_QC,LBTC_USDT,SAFE_USDT,OMG_QC,EDO_USDT,XMR_QC,MITH_QC,TV_QC,TOPC_USDT,CRO_QC,LVN_USDT,HOTC_QC,TRX_USDT,XLM_QC,HLC_USDT,QUN_USDT,BTM_BTC,TV_USDT,NEO_BTC,QTUM_USDT,ZRX_QC,SNT_USDT,XWC_QC,HC_QC,ENTC_USDT,XTZ_USDT,BITCNY_QC,EPC_QC,KAN_BTC,AAA_QC,PAX_USDT,BCHABC_QC,QTUM_BTC,HPY_QC,GNT_USDT,BCD_QC,SLT_QC,BAT_BTC,HC_BTC,BCHSV_QC,HSR_BTC,AE_QC,HX_QC,INK_QC,LBTC_QC,BDS_QC,XRP_BTC,VSYS_ZB,ETC_USDT,OMG_BTC,1ST_USDT,SLT_USDT,BAR_USDT,NEO_USDT,BCD_USDT,MTL_USDT,XEM_QC,BSV_QC,BTP_USDT,TRX_BTC,XRP_QC,ETZ_QC,LVN_QC,BTM_QC,DASH_BTC,BTN_QC,LBTC_BTC,EPC_BTC,FN_QC,PAX_QC,NWT_USDT,XMR_USDT,ICX_BTC,1ST_QC,BCX_USDT,EOS_USDT,KNC_QC,EOS_BTC,BTM_USDT,USDT_QC,BAT_USDT,NEO_QC,LTC_QC,ETZ_USDT,CDC_QC,ICX_QC,RCN_USDT,BTC_USDT,BRC_QC,TSR_USDT,ETH_BTC,GRIN_QC,SNT_QC,ICX_USDT,ETC_BTC,TV_BTC,ADA_USDT,GNT_BTC,CDC_USDT,B91_USDT,DASH_QC,PDX_USDT,GRAM_USDT,GRAM_QC,HSR_QC,HOTC_USDT,XRP_USDT,VSYS_QC,LEO_USDT,HX_USDT,QTUM_QC,ZB_QC,ETH_USDT,BCHSV_USDT,QUN_QC,BCX_QC,ZB_USDT,HPY_USDT,DOGE_BTC,BCX_BTC,SNT_BTC,DASH_USDT,ZRX_BTC,BTH_QC,KNC_USDT,UBTC_USDT,BTS_USDT,BITE_BTC,DOGE_QC,BAT_QC,EOS_QC,GRAM_BTC"
}
}
},
"api": {
"authenticatedSupport": false,
"authenticatedWebsocketApiSupport": false,
"endpoints": {
"url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API",
"urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API",
"websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API"
},
"credentials": {
"key": "Key",
"secret": "Secret"
},
"credentialsValidator": {
"requiresKey": true,
"requiresSecret": true
}
},
"features": {
"supports": {
"restAPI": true,
"restCapabilities": {
"tickerBatching": true,
"autoPairUpdates": true
},
"websocketAPI": true,
"websocketCapabilities": {}
},
"enabled": {
"autoPairUpdates": true,
"websocketAPI": false
}
},
"bankAccounts": [
{
"enabled": false,
"bankName": "",
"bankAddress": "",
"bankPostalCode": "",
"bankPostalCity": "",
"bankCountry": "",
"accountName": "",
"accountNumber": "",
"swiftCode": "",
"iban": "",
"supportedCurrencies": ""
}
]
},
{
"name": "Bitmex",
"enabled": true,

View File

@@ -22,5 +22,4 @@ lbank,
okcoin,
okx,
poloniex,
yobit,
zb,
yobit,
1 binanceus
22 okcoin
23 okx
24 poloniex
25 yobit
zb

File diff suppressed because it is too large Load Diff