exchanges/engine: Add multichain deposit/withdrawal support (#794)

* Add exchange multichain support

* Start tidying up

* Add multichain transfer support for Bitfinex and fix poloniex bug

* Add Coinbene multichain support

* Start adjusting the deposit address manager

* Fix deposit tests and further enhancements

* Cleanup

* Add bypass flag, expand tests plus error coverage for Huobi

Adjust helpers

* Address nitterinos

* BFX wd changes

* Address nitterinos

* Minor fixes rebasing on master

* Fix BFX acceptableMethods test

* Add some TO-DOs for 2 tests WRT races

* Fix acceptableMethods test round 2

* Address nitterinos
This commit is contained in:
Adrian Gallagher
2021-10-15 15:55:38 +11:00
committed by GitHub
parent b093a7df19
commit 0c00b7e1df
145 changed files with 46329 additions and 5507 deletions

View File

@@ -59,10 +59,6 @@ const (
marginAccountInfo = "/sapi/v1/margin/account"
// Withdraw API endpoints
withdrawEndpoint = "/wapi/v3/withdraw.html"
depositHistory = "/wapi/v3/depositHistory.html"
withdrawalHistory = "/wapi/v3/withdrawHistory.html"
depositAddress = "/wapi/v3/depositAddress.html"
accountStatus = "/wapi/v3/accountStatus.html"
systemStatus = "/wapi/v3/systemStatus.html"
dustLog = "/wapi/v3/userAssetDribbletLog.html"
@@ -71,7 +67,15 @@ const (
undocumentedInterestHistory = "/gateway-api/v1/public/isolated-margin/pair/vip-level"
undocumentedCrossMarginInterestHistory = "/gateway-api/v1/friendly/margin/vip/spec/list-all"
defaultRecvWindow = 5 * time.Second
// Wallet endpoints
allCoinsInfo = "/sapi/v1/capital/config/getall"
withdrawEndpoint = "/sapi/v1/capital/withdraw/apply"
depositHistory = "/sapi/v1/capital/deposit/hisrec"
withdrawHistory = "/sapi/v1/capital/withdraw/history"
depositAddress = "/sapi/v1/capital/deposit/address"
defaultRecvWindow = 5 * time.Second
binanceSAPITimeLayout = "2006-01-02 15:04:05"
)
// GetInterestHistory gets interest history for currency/currencies provided
@@ -906,98 +910,185 @@ func getCryptocurrencyWithdrawalFee(c currency.Code) float64 {
return WithdrawalFees[c]
}
// WithdrawCrypto sends cryptocurrency to the address of your choosing
func (b *Binance) WithdrawCrypto(ctx context.Context, asset, address, addressTag, name, amount string) (string, error) {
var resp WithdrawResponse
params := url.Values{}
params.Set("asset", asset)
params.Set("address", address)
params.Set("amount", amount)
if len(name) > 0 {
params.Set("name", name)
}
if len(addressTag) > 0 {
params.Set("addressTag", addressTag)
}
// GetAllCoinsInfo returns details about all supported coins
func (b *Binance) GetAllCoinsInfo(ctx context.Context) ([]CoinInfo, error) {
var resp []CoinInfo
if err := b.SendAuthHTTPRequest(ctx,
exchange.RestSpotSupplementary,
http.MethodPost, withdrawEndpoint,
params, spotDefaultRate, &resp); err != nil {
http.MethodGet,
allCoinsInfo,
nil,
spotDefaultRate,
&resp); err != nil {
return nil, err
}
return resp, nil
}
// WithdrawCrypto sends cryptocurrency to the address of your choosing
func (b *Binance) WithdrawCrypto(ctx context.Context, cryptoAsset, withdrawOrderID, network, address, addressTag, name, amount string, transactionFeeFlag bool) (string, error) {
if cryptoAsset == "" || address == "" || amount == "" {
return "", errors.New("asset, address and amount must not be empty")
}
params := url.Values{}
params.Set("coin", cryptoAsset)
params.Set("address", address)
params.Set("amount", amount)
// optional params
if withdrawOrderID != "" {
params.Set("withdrawOrderId", withdrawOrderID)
}
if network != "" {
params.Set("network", network)
}
if addressTag != "" {
params.Set("addressTag", addressTag)
}
if transactionFeeFlag {
params.Set("transactionFeeFlag", "true")
}
if name != "" {
params.Set("name", url.QueryEscape(name))
}
var resp WithdrawResponse
if err := b.SendAuthHTTPRequest(ctx,
exchange.RestSpotSupplementary,
http.MethodPost,
withdrawEndpoint,
params,
spotDefaultRate,
&resp); err != nil {
return "", err
}
if !resp.Success {
return resp.ID, errors.New(resp.Msg)
if resp.ID == "" {
return "", errors.New("ID is nil")
}
return resp.ID, nil
}
// WithdrawStatus gets the status of recent withdrawals
// DepositHistory returns the deposit history based on the supplied params
// status `param` used as string to prevent default value 0 (for int) interpreting as EmailSent status
func (b *Binance) WithdrawStatus(ctx context.Context, c currency.Code, status string, startTime, endTime int64) ([]WithdrawStatusResponse, error) {
var response struct {
Success bool `json:"success"`
WithdrawList []WithdrawStatusResponse `json:"withdrawList"`
}
func (b *Binance) DepositHistory(ctx context.Context, c currency.Code, status string, startTime, endTime time.Time, offset, limit int) ([]DepositHistory, error) {
var response []DepositHistory
params := url.Values{}
params.Set("asset", c.String())
if !c.IsEmpty() {
params.Set("coin", c.String())
}
if status != "" {
i, err := strconv.Atoi(status)
if err != nil {
return response.WithdrawList, fmt.Errorf("wrong param (status): %s. Error: %v", status, err)
return nil, fmt.Errorf("wrong param (status): %s. Error: %v", status, err)
}
switch i {
case EmailSent, Cancelled, AwaitingApproval, Rejected, Processing, Failure, Completed:
default:
return response.WithdrawList, fmt.Errorf("wrong param (status): %s", status)
return nil, fmt.Errorf("wrong param (status): %s", status)
}
params.Set("status", status)
}
if startTime > 0 {
params.Set("startTime", strconv.FormatInt(startTime, 10))
if !startTime.IsZero() {
params.Set("startTime", strconv.FormatInt(startTime.UTC().Unix(), 10))
}
if endTime > 0 {
params.Set("endTime", strconv.FormatInt(endTime, 10))
if !endTime.IsZero() {
params.Set("endTime", strconv.FormatInt(endTime.UTC().Unix(), 10))
}
if offset != 0 {
params.Set("offset", strconv.Itoa(offset))
}
if limit != 0 {
params.Set("limit", strconv.Itoa(limit))
}
if err := b.SendAuthHTTPRequest(ctx,
exchange.RestSpotSupplementary,
http.MethodGet, withdrawalHistory,
params, spotDefaultRate,
http.MethodGet,
depositHistory,
params,
spotDefaultRate,
&response); err != nil {
return response.WithdrawList, err
return nil, err
}
return response.WithdrawList, nil
return response, nil
}
// WithdrawHistory gets the status of recent withdrawals
// status `param` used as string to prevent default value 0 (for int) interpreting as EmailSent status
func (b *Binance) WithdrawHistory(ctx context.Context, c currency.Code, status string, startTime, endTime time.Time, offset, limit int) ([]WithdrawStatusResponse, error) {
params := url.Values{}
if !c.IsEmpty() {
params.Set("coin", c.String())
}
if status != "" {
i, err := strconv.Atoi(status)
if err != nil {
return nil, fmt.Errorf("wrong param (status): %s. Error: %v", status, err)
}
switch i {
case EmailSent, Cancelled, AwaitingApproval, Rejected, Processing, Failure, Completed:
default:
return nil, fmt.Errorf("wrong param (status): %s", status)
}
params.Set("status", status)
}
if !startTime.IsZero() {
params.Set("startTime", strconv.FormatInt(startTime.UTC().Unix(), 10))
}
if !endTime.IsZero() {
params.Set("endTime", strconv.FormatInt(endTime.UTC().Unix(), 10))
}
if offset != 0 {
params.Set("offset", strconv.Itoa(offset))
}
if limit != 0 {
params.Set("limit", strconv.Itoa(limit))
}
var withdrawStatus []WithdrawStatusResponse
if err := b.SendAuthHTTPRequest(ctx,
exchange.RestSpotSupplementary,
http.MethodGet,
withdrawHistory,
params,
spotDefaultRate,
&withdrawStatus); err != nil {
return nil, err
}
return withdrawStatus, nil
}
// GetDepositAddressForCurrency retrieves the wallet address for a given currency
func (b *Binance) GetDepositAddressForCurrency(ctx context.Context, currency string) (string, error) {
resp := struct {
Address string `json:"address"`
Success bool `json:"success"`
AddressTag string `json:"addressTag"`
}{}
func (b *Binance) GetDepositAddressForCurrency(ctx context.Context, currency, chain string) (*DepositAddress, error) {
params := url.Values{}
params.Set("asset", currency)
params.Set("status", "true")
params.Set("coin", currency)
if chain != "" {
params.Set("network", chain)
}
params.Set("recvWindow", "10000")
return resp.Address,
b.SendAuthHTTPRequest(ctx,
exchange.RestSpotSupplementary,
http.MethodGet, depositAddress,
params, spotDefaultRate, &resp)
var d DepositAddress
return &d,
b.SendAuthHTTPRequest(ctx, exchange.RestSpotSupplementary, http.MethodGet, depositAddress, params, spotDefaultRate, &d)
}
// GetWsAuthStreamKey will retrieve a key to use for authorised WS streaming

View File

@@ -1,4 +1,5 @@
//+build mock_test_off
//go:build mock_test_off
// +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

View File

@@ -27,10 +27,13 @@ const (
canManipulateRealOrders = false
)
var b Binance
// this lock guards against orderbook tests race
var binanceOrderBookLock = &sync.Mutex{}
var (
b Binance
// this lock guards against orderbook tests race
binanceOrderBookLock = &sync.Mutex{}
// this pair is used to ensure that endpoints match it correctly
testPairMapping = currency.NewPair(currency.DOGE, currency.USDT)
)
func areTestAPIKeysSet() bool {
return b.ValidateAPICredentials()
@@ -53,22 +56,31 @@ func TestUServerTime(t *testing.T) {
}
}
func TestParseSAPITime(t *testing.T) {
t.Parallel()
tm, err := time.Parse(binanceSAPITimeLayout, "2021-05-27 03:56:46")
if err != nil {
t.Fatal(tm)
}
tm = tm.UTC()
if tm.Year() != 2021 ||
tm.Month() != 5 ||
tm.Day() != 27 ||
tm.Hour() != 3 ||
tm.Minute() != 56 ||
tm.Second() != 46 {
t.Fatal("incorrect values")
}
}
func TestUpdateTicker(t *testing.T) {
t.Parallel()
spotPairs, err := b.FetchTradablePairs(context.Background(), asset.Spot)
r, err := b.UpdateTicker(context.Background(), testPairMapping, asset.Spot)
if err != nil {
t.Error(err)
}
if len(spotPairs) == 0 {
t.Error("no tradable pairs")
}
spotCP, err := currency.NewPairFromString(spotPairs[0])
if err != nil {
t.Error(err)
}
_, err = b.UpdateTicker(context.Background(), spotCP, asset.Spot)
if err != nil {
t.Error(err)
if r.Pair.Base != currency.DOGE && r.Pair.Quote != currency.USDT {
t.Error("invalid pair values")
}
tradablePairs, err := b.FetchTradablePairs(context.Background(), asset.CoinMarginedFutures)
if err != nil {
@@ -1945,6 +1957,17 @@ func TestModifyOrder(t *testing.T) {
}
}
func TestGetAllCoinsInfo(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() && !mockTests {
t.Skip("API keys not set")
}
_, err := b.GetAllCoinsInfo(context.Background())
if err != nil {
t.Error(err)
}
}
func TestWithdraw(t *testing.T) {
t.Parallel()
if areTestAPIKeysSet() && !canManipulateRealOrders && !mockTests {
@@ -1953,7 +1976,7 @@ func TestWithdraw(t *testing.T) {
withdrawCryptoRequest := withdraw.Request{
Exchange: b.Name,
Amount: 0.00001337,
Amount: -1,
Currency: currency.BTC,
Description: "WITHDRAW IT ALL",
Crypto: withdraw.CryptoRequest{
@@ -1968,8 +1991,20 @@ func TestWithdraw(t *testing.T) {
t.Error("Withdraw() error", err)
case !areTestAPIKeysSet() && err == nil && !mockTests:
t.Error("Withdraw() expecting an error when no keys are set")
case mockTests && err != nil:
}
}
func TestDepositHistory(t *testing.T) {
t.Parallel()
if areTestAPIKeysSet() && !canManipulateRealOrders && !mockTests {
t.Skip("API keys set, canManipulateRealOrders false, skipping test")
}
_, err := b.DepositHistory(context.Background(), currency.ETH, "", time.Time{}, time.Time{}, 0, 10000)
switch {
case areTestAPIKeysSet() && err != nil:
t.Error(err)
case !areTestAPIKeysSet() && err == nil && !mockTests:
t.Error("expecting an error when no keys are set")
}
}
@@ -1978,7 +2013,7 @@ func TestWithdrawHistory(t *testing.T) {
if areTestAPIKeysSet() && !canManipulateRealOrders && !mockTests {
t.Skip("API keys set, canManipulateRealOrders false, skipping test")
}
_, err := b.GetWithdrawalsHistory(context.Background(), currency.XBT)
_, err := b.GetWithdrawalsHistory(context.Background(), currency.ETH)
switch {
case areTestAPIKeysSet() && err != nil:
t.Error("GetWithdrawalsHistory() error", err)
@@ -2007,7 +2042,7 @@ func TestWithdrawInternationalBank(t *testing.T) {
func TestGetDepositAddress(t *testing.T) {
t.Parallel()
_, err := b.GetDepositAddress(context.Background(), currency.BTC, "")
_, err := b.GetDepositAddress(context.Background(), currency.USDT, "", currency.BNB.String())
switch {
case areTestAPIKeysSet() && err != nil:
t.Error("GetDepositAddress() error", err)
@@ -2414,6 +2449,19 @@ func TestGetRecentTrades(t *testing.T) {
}
}
func TestGetAvailableTransferChains(t *testing.T) {
t.Parallel()
_, err := b.GetAvailableTransferChains(context.Background(), currency.BTC)
switch {
case areTestAPIKeysSet() && err != nil:
t.Error(err)
case !areTestAPIKeysSet() && err == nil && !mockTests:
t.Error("error cannot be nil")
case mockTests && err != nil:
t.Error(err)
}
}
func TestSeedLocalCache(t *testing.T) {
t.Parallel()
err := b.SeedLocalCache(context.Background(), currency.NewPair(currency.BTC, currency.USDT))
@@ -2428,7 +2476,7 @@ func TestGenerateSubscriptions(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if len(subs) != 4 {
if len(subs) != 8 {
t.Fatal("unexpected subscription length")
}
}

View File

@@ -68,6 +68,42 @@ type ExchangeInfo struct {
} `json:"symbols"`
}
// CoinInfo stores information about all supported coins
type CoinInfo struct {
Coin string `json:"coin"`
DepositAllEnable bool `json:"depositAllEnable"`
WithdrawAllEnable bool `json:"withdrawAllEnable"`
Free float64 `json:"free,string"`
Freeze float64 `json:"freeze,string"`
IPOAble float64 `json:"ipoable,string"`
IPOing float64 `json:"ipoing,string"`
IsLegalMoney bool `json:"isLegalMoney"`
Locked float64 `json:"locked,string"`
Name string `json:"name"`
NetworkList []struct {
AddressRegex string `json:"addressRegex"`
Coin string `json:"coin"`
DepositDescription string `json:"depositDesc"` // shown only when "depositEnable" is false
DepositEnable bool `json:"depositEnable"`
IsDefault bool `json:"isDefault"`
MemoRegex string `json:"memoRegex"`
MinimumConfirmation uint16 `json:"minConfirm"`
Name string `json:"name"`
Network string `json:"network"`
ResetAddressStatus bool `json:"resetAddressStatus"`
SpecialTips string `json:"specialTips"`
UnlockConfirm uint16 `json:"unLockConfirm"`
WithdrawDescription string `json:"withdrawDesc"` // shown only when "withdrawEnable" is false
WithdrawEnable bool `json:"withdrawEnable"`
WithdrawFee float64 `json:"withdrawFee,string"`
WithdrawMinimum float64 `json:"withdrawMin,string"`
WithdrawMaximum float64 `json:"withdrawMax,string"`
} `json:"networkList"`
Storage float64 `json:"storage,string"`
Trading bool `json:"trading"`
Withdrawing float64 `json:"withdrawing,string"`
}
// OrderBookDataRequestParams represents Klines request data.
type OrderBookDataRequestParams struct {
Symbol currency.Pair `json:"symbol"` // Required field; example LTCBTC,BTCUSDT
@@ -648,24 +684,47 @@ var WithdrawalFees = map[currency.Code]float64{
currency.PIVX: 0.02,
}
// DepositHistory stores deposit history info
type DepositHistory struct {
Amount float64 `json:"amount,string"`
Coin string `json:"coin"`
Network string `json:"network"`
Status uint8 `json:"status"`
Address string `json:"address"`
AddressTag string `json:"adressTag"`
TransactionID string `json:"txId"`
InsertTime float64 `json:"insertTime"`
TransferType uint8 `json:"transferType"`
ConfirmTimes string `json:"confirmTimes"`
}
// WithdrawResponse contains status of withdrawal request
type WithdrawResponse struct {
Success bool `json:"success"`
Msg string `json:"msg"`
ID string `json:"id"`
ID string `json:"id"`
}
// WithdrawStatusResponse defines a withdrawal status response
type WithdrawStatusResponse struct {
Amount float64 `json:"amount"`
TransactionFee float64 `json:"transactionFee"`
Address string `json:"address"`
TxID string `json:"txId"`
ID string `json:"id"`
Asset string `json:"asset"`
ApplyTime int64 `json:"applyTime"`
Status int64 `json:"status"`
Network string `json:"network"`
Address string `json:"address"`
Amount float64 `json:"amount,string"`
ApplyTime string `json:"applyTime"`
Coin string `json:"coin"`
ID string `json:"id"`
WithdrawOrderID string `json:"withdrawOrderId"`
Network string `json:"network"`
TransferType uint8 `json:"transferType"`
Status int64 `json:"status"`
TransactionFee float64 `json:"transactionFee,string"`
TransactionID string `json:"txId"`
ConfirmNumber int64 `json:"confirmNo"`
}
// DepositAddress stores the deposit address info
type DepositAddress struct {
Address string `json:"address"`
Coin string `json:"coin"`
Tag string `json:"tag"`
URL string `json:"url"`
}
// UserAccountStream contains a key to maintain an authorised

View File

@@ -16,6 +16,7 @@ import (
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/deposit"
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
@@ -118,25 +119,27 @@ func (b *Binance) SetDefaults() {
REST: true,
Websocket: true,
RESTCapabilities: protocol.Features{
TickerBatching: true,
TickerFetching: true,
KlineFetching: true,
OrderbookFetching: true,
AutoPairUpdates: true,
AccountInfo: true,
CryptoDeposit: true,
CryptoWithdrawal: true,
GetOrder: true,
GetOrders: true,
CancelOrders: true,
CancelOrder: true,
SubmitOrder: true,
DepositHistory: true,
WithdrawalHistory: true,
TradeFetching: true,
UserTradeHistory: true,
TradeFee: true,
CryptoWithdrawalFee: true,
TickerBatching: true,
TickerFetching: true,
KlineFetching: true,
OrderbookFetching: true,
AutoPairUpdates: true,
AccountInfo: true,
CryptoDeposit: true,
CryptoWithdrawal: true,
GetOrder: true,
GetOrders: true,
CancelOrders: true,
CancelOrder: true,
SubmitOrder: true,
DepositHistory: true,
WithdrawalHistory: true,
TradeFetching: true,
UserTradeHistory: true,
TradeFee: true,
CryptoWithdrawalFee: true,
MultiChainDeposits: true,
MultiChainWithdrawals: true,
},
WebsocketCapabilities: protocol.Features{
TradeFetching: true,
@@ -431,27 +434,40 @@ func (b *Binance) UpdateTickers(ctx context.Context, a asset.Item) error {
if err != nil {
return err
}
for y := range tick {
cp, err := currency.NewPairFromString(tick[y].Symbol)
if err != nil {
return err
}
err = ticker.ProcessTicker(&ticker.Price{
Last: tick[y].LastPrice,
High: tick[y].HighPrice,
Low: tick[y].LowPrice,
Bid: tick[y].BidPrice,
Ask: tick[y].AskPrice,
Volume: tick[y].Volume,
QuoteVolume: tick[y].QuoteVolume,
Open: tick[y].OpenPrice,
Close: tick[y].PrevClosePrice,
Pair: cp,
ExchangeName: b.Name,
AssetType: a,
})
if err != nil {
return err
pairs, err := b.GetEnabledPairs(a)
if err != nil {
return err
}
for i := range pairs {
for y := range tick {
pairFmt, err := b.FormatExchangeCurrency(pairs[i], a)
if err != nil {
return err
}
if tick[y].Symbol != pairFmt.String() {
continue
}
err = ticker.ProcessTicker(&ticker.Price{
Last: tick[y].LastPrice,
High: tick[y].HighPrice,
Low: tick[y].LowPrice,
Bid: tick[y].BidPrice,
Ask: tick[y].AskPrice,
Volume: tick[y].Volume,
QuoteVolume: tick[y].QuoteVolume,
Open: tick[y].OpenPrice,
Close: tick[y].PrevClosePrice,
Pair: pairFmt,
ExchangeName: b.Name,
AssetType: a,
})
if err != nil {
return err
}
}
}
case asset.USDTMarginedFutures:
@@ -522,10 +538,6 @@ func (b *Binance) UpdateTicker(ctx context.Context, p currency.Pair, a asset.Ite
if err != nil {
return nil, err
}
cp, err := currency.NewPairFromString(tick.Symbol)
if err != nil {
return nil, err
}
err = ticker.ProcessTicker(&ticker.Price{
Last: tick.LastPrice,
High: tick.HighPrice,
@@ -536,7 +548,7 @@ func (b *Binance) UpdateTicker(ctx context.Context, p currency.Pair, a asset.Ite
QuoteVolume: tick.QuoteVolume,
Open: tick.OpenPrice,
Close: tick.PrevClosePrice,
Pair: cp,
Pair: p,
ExchangeName: b.Name,
AssetType: a,
})
@@ -548,10 +560,6 @@ func (b *Binance) UpdateTicker(ctx context.Context, p currency.Pair, a asset.Ite
if err != nil {
return nil, err
}
cp, err := currency.NewPairFromString(tick[0].Symbol)
if err != nil {
return nil, err
}
err = ticker.ProcessTicker(&ticker.Price{
Last: tick[0].LastPrice,
High: tick[0].HighPrice,
@@ -560,7 +568,7 @@ func (b *Binance) UpdateTicker(ctx context.Context, p currency.Pair, a asset.Ite
QuoteVolume: tick[0].QuoteVolume,
Open: tick[0].OpenPrice,
Close: tick[0].PrevClosePrice,
Pair: cp,
Pair: p,
ExchangeName: b.Name,
AssetType: a,
})
@@ -572,10 +580,6 @@ func (b *Binance) UpdateTicker(ctx context.Context, p currency.Pair, a asset.Ite
if err != nil {
return nil, err
}
cp, err := currency.NewPairFromString(tick[0].Symbol)
if err != nil {
return nil, err
}
err = ticker.ProcessTicker(&ticker.Price{
Last: tick[0].LastPrice,
High: tick[0].HighPrice,
@@ -584,7 +588,7 @@ func (b *Binance) UpdateTicker(ctx context.Context, p currency.Pair, a asset.Ite
QuoteVolume: tick[0].QuoteVolume,
Open: tick[0].OpenPrice,
Close: tick[0].PrevClosePrice,
Pair: cp,
Pair: p,
ExchangeName: b.Name,
AssetType: a,
})
@@ -763,7 +767,6 @@ func (b *Binance) FetchAccountInfo(ctx context.Context, assetType asset.Item) (a
if err != nil {
return b.UpdateAccountInfo(ctx, assetType)
}
return acc, nil
}
@@ -775,21 +778,26 @@ func (b *Binance) GetFundingHistory(ctx context.Context) ([]exchange.FundHistory
// GetWithdrawalsHistory returns previous withdrawals data
func (b *Binance) GetWithdrawalsHistory(ctx context.Context, c currency.Code) (resp []exchange.WithdrawalHistory, err error) {
w, err := b.WithdrawStatus(ctx, c, "", 0, 0)
w, err := b.WithdrawHistory(ctx, c, "", time.Time{}, time.Time{}, 0, 10000)
if err != nil {
return nil, err
}
for i := range w {
tm, err := time.Parse(binanceSAPITimeLayout, w[i].ApplyTime)
if err != nil {
return nil, err
}
resp = append(resp, exchange.WithdrawalHistory{
Status: strconv.FormatInt(w[i].Status, 10),
TransferID: w[i].ID,
Currency: w[i].Asset,
Currency: w[i].Coin,
Amount: w[i].Amount,
Fee: w[i].TransactionFee,
CryptoToAddress: w[i].Address,
CryptoTxID: w[i].TxID,
Timestamp: time.Unix(w[i].ApplyTime/1000, 0),
CryptoTxID: w[i].TransactionID,
CryptoChain: w[i].Network,
Timestamp: tm,
})
}
@@ -1216,8 +1224,16 @@ func (b *Binance) GetOrderInfo(ctx context.Context, orderID string, pair currenc
}
// GetDepositAddress returns a deposit address for a specified currency
func (b *Binance) GetDepositAddress(ctx context.Context, cryptocurrency currency.Code, _ string) (string, error) {
return b.GetDepositAddressForCurrency(ctx, cryptocurrency.String())
func (b *Binance) GetDepositAddress(ctx context.Context, cryptocurrency currency.Code, _, chain string) (*deposit.Address, error) {
addr, err := b.GetDepositAddressForCurrency(ctx, cryptocurrency.String(), chain)
if err != nil {
return nil, err
}
return &deposit.Address{
Address: addr.Address,
Tag: addr.Tag,
}, nil
}
// WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is
@@ -1229,9 +1245,13 @@ func (b *Binance) WithdrawCryptocurrencyFunds(ctx context.Context, withdrawReque
amountStr := strconv.FormatFloat(withdrawRequest.Amount, 'f', -1, 64)
v, err := b.WithdrawCrypto(ctx,
withdrawRequest.Currency.String(),
"", // withdrawal order ID
withdrawRequest.Crypto.Chain,
withdrawRequest.Crypto.Address,
withdrawRequest.Crypto.AddressTag,
withdrawRequest.Description, amountStr)
withdrawRequest.Description,
amountStr,
false)
if err != nil {
return nil, err
}
@@ -1730,3 +1750,22 @@ func (b *Binance) UpdateOrderExecutionLimits(ctx context.Context, a asset.Item)
}
return b.LoadLimits(limits)
}
// GetAvailableTransferChains returns the available transfer blockchains for the specific
// cryptocurrency
func (b *Binance) GetAvailableTransferChains(ctx context.Context, cryptocurrency currency.Code) ([]string, error) {
coinInfo, err := b.GetAllCoinsInfo(ctx)
if err != nil {
return nil, err
}
var availableChains []string
for x := range coinInfo {
if strings.EqualFold(coinInfo[x].Coin, cryptocurrency.String()) {
for y := range coinInfo[x].NetworkList {
availableChains = append(availableChains, coinInfo[x].NetworkList[y].Network)
}
}
}
return availableChains, nil
}