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

@@ -526,35 +526,44 @@ func (f *FTX) GetAllWalletBalances(ctx context.Context) (AllWalletBalances, erro
}
// FetchDepositAddress gets deposit address for a given coin
func (f *FTX) FetchDepositAddress(ctx context.Context, coin currency.Code) (DepositData, error) {
func (f *FTX) FetchDepositAddress(ctx context.Context, coin currency.Code, chain string) (*DepositData, error) {
resp := struct {
Data DepositData `json:"result"`
}{}
return resp.Data, f.SendAuthHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, getDepositAddress+coin.Upper().String(), nil, &resp)
vals := url.Values{}
if chain != "" {
vals.Set("method", strings.ToLower(chain))
}
path := common.EncodeURLValues(getDepositAddress+coin.Upper().String(), vals)
return &resp.Data, f.SendAuthHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp)
}
// FetchDepositHistory gets deposit history
func (f *FTX) FetchDepositHistory(ctx context.Context) ([]TransactionData, error) {
func (f *FTX) FetchDepositHistory(ctx context.Context) ([]DepositItem, error) {
resp := struct {
Data []TransactionData `json:"result"`
Data []DepositItem `json:"result"`
}{}
return resp.Data, f.SendAuthHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, getDepositHistory, nil, &resp)
}
// FetchWithdrawalHistory gets withdrawal history
func (f *FTX) FetchWithdrawalHistory(ctx context.Context) ([]TransactionData, error) {
func (f *FTX) FetchWithdrawalHistory(ctx context.Context) ([]WithdrawItem, error) {
resp := struct {
Data []TransactionData `json:"result"`
Data []WithdrawItem `json:"result"`
}{}
return resp.Data, f.SendAuthHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, getWithdrawalHistory, nil, &resp)
}
// Withdraw sends a withdrawal request
func (f *FTX) Withdraw(ctx context.Context, coin currency.Code, address, tag, password, code string, size float64) (TransactionData, error) {
func (f *FTX) Withdraw(ctx context.Context, coin currency.Code, address, tag, password, chain, code string, size float64) (*WithdrawItem, error) {
if coin.IsEmpty() || address == "" || size == 0 {
return nil, errors.New("coin, address and size must be specified")
}
req := make(map[string]interface{})
req["coin"] = coin.Upper().String()
req["address"] = address
req["size"] = size
req["address"] = address
if code != "" {
req["code"] = code
}
@@ -564,10 +573,13 @@ func (f *FTX) Withdraw(ctx context.Context, coin currency.Code, address, tag, pa
if password != "" {
req["password"] = password
}
if chain != "" {
req["method"] = chain
}
resp := struct {
Data TransactionData `json:"result"`
Data WithdrawItem `json:"result"`
}{}
return resp.Data, f.SendAuthHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, withdrawRequest, req, &resp)
return &resp.Data, f.SendAuthHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, withdrawRequest, req, &resp)
}
// GetOpenOrders gets open orders
@@ -1145,6 +1157,10 @@ func (f *FTX) StakeRequest(ctx context.Context, coin currency.Code, size float64
// SendAuthHTTPRequest sends an authenticated request
func (f *FTX) SendAuthHTTPRequest(ctx context.Context, ep exchange.URL, method, path string, data, result interface{}) error {
if !f.AllowAuthenticatedRequest() {
return fmt.Errorf("%s %w", f.Name, exchange.ErrAuthenticatedRequestWithoutCredentialsSet)
}
endpoint, err := f.API.Endpoints.GetURL(ep)
if err != nil {
return err
@@ -1295,13 +1311,13 @@ func (f *FTX) RequestForQuotes(ctx context.Context, base, quote currency.Code, a
}
// GetOTCQuoteStatus gets quote status of a quote
func (f *FTX) GetOTCQuoteStatus(ctx context.Context, marketName, quoteID string) ([]QuoteStatusData, error) {
func (f *FTX) GetOTCQuoteStatus(ctx context.Context, marketName, quoteID string) (*QuoteStatusData, error) {
resp := struct {
Data []QuoteStatusData `json:"result"`
Data QuoteStatusData `json:"result"`
}{}
params := url.Values{}
params.Set("market", marketName)
return resp.Data, f.SendAuthHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, getOTCQuoteStatus+quoteID, params, &resp)
return &resp.Data, f.SendAuthHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, getOTCQuoteStatus+quoteID, params, &resp)
}
// AcceptOTCQuote requests for otc quotes

View File

@@ -9,7 +9,6 @@ import (
"time"
"github.com/thrasher-corp/gocryptotrader/config"
"github.com/thrasher-corp/gocryptotrader/core"
"github.com/thrasher-corp/gocryptotrader/currency"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
@@ -370,6 +369,9 @@ func TestGetMarginMarketLendingHistory(t *testing.T) {
t.Errorf("expected %s, got %s", errStartTimeCannotBeAfterEndTime, err)
}
if !areTestAPIKeysSet() {
t.Skip("api keys not set")
}
_, err = f.GetMarginMarketLendingHistory(context.Background(),
currency.USD, tmNow.AddDate(0, 0, -1), tmNow)
if err != nil {
@@ -435,9 +437,12 @@ func TestFetchDepositAddress(t *testing.T) {
if !areTestAPIKeysSet() {
t.Skip()
}
_, err := f.FetchDepositAddress(context.Background(), currency.NewCode("tUsD"))
r, err := f.FetchDepositAddress(context.Background(), currency.NewCode("UsDt"), "trx")
if err != nil {
t.Error(err)
t.Fatal(err)
}
if r.Method != "trx" {
t.Error("expected trx method")
}
}
@@ -469,7 +474,13 @@ func TestWithdraw(t *testing.T) {
t.Skip("skipping test, either api keys or canManipulateRealOrders isnt set correctly")
}
_, err := f.Withdraw(context.Background(),
currency.NewCode("bTc"), core.BitcoinDonationAddress, "", "", "957378", 0.0009)
currency.NewCode("UsDT"),
"TJU9piX2WA8WTvxVKMqpvTzZGhvXQAZKSY",
"",
"",
"trx",
"715913",
-1)
if err != nil {
t.Error(err)
}
@@ -1174,7 +1185,7 @@ func TestGetDepositAddress(t *testing.T) {
if !areTestAPIKeysSet() {
t.Skip("API keys required but not set, skipping test")
}
_, err := f.GetDepositAddress(context.Background(), currency.NewCode("FTT"), "")
_, err := f.GetDepositAddress(context.Background(), currency.NewCode("FTT"), "", "")
if err != nil {
t.Error(err)
}

View File

@@ -223,22 +223,27 @@ type AccountInfoData struct {
// WalletCoinsData stores data about wallet coins
type WalletCoinsData struct {
Bep2Asset interface{} `json:"bep2Asset"`
CanConvert bool `json:"canConvert"`
CanDeposit bool `json:"canDeposit"`
CanWithdraw bool `json:"canWithdraw"`
Collateral bool `json:"collateral"`
CollateralWeight float64 `json:"collateralWeight"`
CreditTo interface{} `json:"creditTo"`
ERC20Contract interface{} `json:"erc20Contract"`
Fiat bool `json:"fiat"`
HasTag bool `json:"hasTag"`
Hidden bool `json:"hidden"`
IsETF bool `json:"isEtf"`
IsToken bool `json:"isToken"`
Methods []interface{}
ID string `json:"id"`
Name string `json:"name"`
USDFungible bool `json:"usdFungible"`
CanDeposit bool `json:"canDeposit"`
CanWithdraw bool `json:"canWithdraw"`
CanConvert bool `json:"canConvert"`
Collateral bool `json:"collateral"`
CollateralWeight float64 `json:"collateralWeight"`
CreditTo string `json:"creditTo"`
ERC20Contract string `json:"erc20Contract"`
BEP2Asset string `json:"bep2Asset"`
TRC20Contract string `json:"trc20Contract"`
SpotMargin bool `json:"spotMargin"`
IndexPrice float64 `json:"indexPrice"`
SPLMint string `json:"splMint"`
Fiat bool `json:"fiat"`
HasTag bool `json:"hasTag"`
Hidden bool `json:"hidden"`
IsETF bool `json:"isEtf"`
IsToken bool `json:"isToken"`
Methods []string `json:"methods"`
ID string `json:"id"`
Name string `json:"name"`
}
// WalletBalance stores balances data
@@ -258,10 +263,12 @@ type AllWalletBalances map[string][]WalletBalance
type DepositData struct {
Address string `json:"address"`
Tag string `json:"tag"`
Method string `json:"method"`
Coin string `json:"coin"`
}
// TransactionData stores data about deposit history
type TransactionData struct {
// DepositItem stores data about deposit history
type DepositItem struct {
Coin string `json:"coin"`
Confirmations int64 `json:"conformations"`
ConfirmedTime time.Time `json:"confirmedTime"`
@@ -272,6 +279,28 @@ type TransactionData struct {
Status string `json:"status"`
Time time.Time `json:"time"`
TxID string `json:"txid"`
Address struct {
Address string `json:"address"`
Tag string `json:"tag"`
Method string `json:"method"`
} `json:"address"`
}
// WithdrawItem stores data about withdraw history
type WithdrawItem struct {
ID int64 `json:"id"`
Coin string `json:"coin"`
Address string `json:"address"`
Tag string `json:"tag"`
Method string `json:"method"`
TXID string `json:"txid"`
Size float64 `json:"size"`
Fee float64 `json:"fee"`
Status string `json:"status"`
Complete time.Time `json:"complete"`
Time time.Time `json:"time"`
Notes string `json:"notes"`
DestinationName string `json:"destinationName"`
}
// OrderData stores open order data

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"
@@ -94,22 +95,26 @@ func (f *FTX) SetDefaults() {
REST: true,
Websocket: true,
RESTCapabilities: protocol.Features{
TickerFetching: true,
TickerBatching: true,
KlineFetching: true,
TradeFetching: true,
OrderbookFetching: true,
AutoPairUpdates: true,
AccountInfo: true,
GetOrder: true,
GetOrders: true,
CancelOrders: true,
CancelOrder: true,
SubmitOrder: true,
TradeFee: true,
FiatDepositFee: true,
FiatWithdrawalFee: true,
CryptoWithdrawalFee: true,
TickerFetching: true,
TickerBatching: true,
KlineFetching: true,
TradeFetching: true,
OrderbookFetching: true,
AutoPairUpdates: true,
AccountInfo: true,
GetOrder: true,
GetOrders: true,
CancelOrders: true,
CancelOrder: true,
SubmitOrder: true,
TradeFee: true,
FiatDepositFee: true,
FiatWithdrawalFee: true,
CryptoWithdrawalFee: true,
CryptoDeposit: true,
CryptoWithdrawal: true,
MultiChainDeposits: true,
MultiChainWithdrawals: true,
},
WebsocketCapabilities: protocol.Features{
OrderbookFetching: true,
@@ -119,7 +124,7 @@ func (f *FTX) SetDefaults() {
GetOrders: true,
GetOrder: true,
},
WithdrawPermissions: exchange.NoAPIWithdrawalMethods,
WithdrawPermissions: exchange.AutoWithdrawCrypto,
Kline: kline.ExchangeCapabilitiesSupported{
DateRanges: true,
Intervals: true,
@@ -491,7 +496,9 @@ func (f *FTX) GetFundingHistory(ctx context.Context) ([]exchange.FundHistory, er
tempData.Fee = depositData[x].Fee
tempData.Timestamp = depositData[x].Time
tempData.ExchangeName = f.Name
tempData.CryptoToAddress = depositData[x].Address.Address
tempData.CryptoTxID = depositData[x].TxID
tempData.CryptoChain = depositData[x].Address.Method
tempData.Status = depositData[x].Status
tempData.Amount = depositData[x].Size
tempData.Currency = depositData[x].Coin
@@ -504,14 +511,16 @@ func (f *FTX) GetFundingHistory(ctx context.Context) ([]exchange.FundHistory, er
}
for y := range withdrawalData {
var tempData exchange.FundHistory
tempData.Fee = depositData[y].Fee
tempData.Timestamp = depositData[y].Time
tempData.Fee = withdrawalData[y].Fee
tempData.Timestamp = withdrawalData[y].Time
tempData.ExchangeName = f.Name
tempData.CryptoTxID = depositData[y].TxID
tempData.Status = depositData[y].Status
tempData.Amount = depositData[y].Size
tempData.Currency = depositData[y].Coin
tempData.TransferID = strconv.FormatInt(depositData[y].ID, 10)
tempData.CryptoToAddress = withdrawalData[y].Address
tempData.CryptoTxID = withdrawalData[y].TXID
tempData.CryptoChain = withdrawalData[y].Method
tempData.Status = withdrawalData[y].Status
tempData.Amount = withdrawalData[y].Size
tempData.Currency = withdrawalData[y].Coin
tempData.TransferID = strconv.FormatInt(withdrawalData[y].ID, 10)
resp = append(resp, tempData)
}
return resp, nil
@@ -824,12 +833,15 @@ func (f *FTX) GetOrderInfo(ctx context.Context, orderID string, pair currency.Pa
}
// GetDepositAddress returns a deposit address for a specified currency
func (f *FTX) GetDepositAddress(ctx context.Context, cryptocurrency currency.Code, _ string) (string, error) {
a, err := f.FetchDepositAddress(ctx, cryptocurrency)
func (f *FTX) GetDepositAddress(ctx context.Context, cryptocurrency currency.Code, _, chain string) (*deposit.Address, error) {
a, err := f.FetchDepositAddress(ctx, cryptocurrency, chain)
if err != nil {
return "", err
return nil, err
}
return a.Address, nil
return &deposit.Address{
Address: a.Address,
Tag: a.Tag,
}, nil
}
// WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is
@@ -843,6 +855,7 @@ func (f *FTX) WithdrawCryptocurrencyFunds(ctx context.Context, withdrawRequest *
withdrawRequest.Crypto.Address,
withdrawRequest.Crypto.AddressTag,
withdrawRequest.TradePassword,
withdrawRequest.Crypto.Chain,
strconv.FormatInt(withdrawRequest.OneTimePassword, 10),
withdrawRequest.Amount)
if err != nil {
@@ -1214,3 +1227,22 @@ func (f *FTX) UpdateOrderExecutionLimits(ctx context.Context, _ asset.Item) erro
}
return f.LoadLimits(limits)
}
// GetAvailableTransferChains returns the available transfer blockchains for the specific
// cryptocurrency
func (f *FTX) GetAvailableTransferChains(ctx context.Context, cryptocurrency currency.Code) ([]string, error) {
coins, err := f.GetCoins(ctx)
if err != nil {
return nil, err
}
var availableChains []string
for x := range coins {
if strings.EqualFold(coins[x].ID, cryptocurrency.String()) {
for y := range coins[x].Methods {
availableChains = append(availableChains, coins[x].Methods[y])
}
}
}
return availableChains, nil
}