mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-06-06 07:26:47 +00:00
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:
@@ -69,7 +69,7 @@ func (w *CurrencyDetails) loadPairs(data map[string]Ticker) error {
|
||||
}
|
||||
|
||||
// loadCodes loads currency codes from currency map
|
||||
func (w *CurrencyDetails) loadCodes(data map[string]Currencies) error {
|
||||
func (w *CurrencyDetails) loadCodes(data map[string]*Currencies) error {
|
||||
if data == nil {
|
||||
return errCannotLoadNoData
|
||||
}
|
||||
|
||||
@@ -108,25 +108,25 @@ func TestWsCurrencyMap(t *testing.T) {
|
||||
t.Fatal("expecting USDT_BTC pair")
|
||||
}
|
||||
|
||||
maid, err := m.GetCode(127)
|
||||
eth, err := m.GetCode(267)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("expected: %v but received: %v", nil, err)
|
||||
}
|
||||
|
||||
if maid.String() != "MAID" {
|
||||
if eth.String() != "ETH" {
|
||||
t.Fatal("unexpected value")
|
||||
}
|
||||
|
||||
txFee, err := m.GetWithdrawalTXFee(maid)
|
||||
txFee, err := m.GetWithdrawalTXFee(eth)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if txFee != 80 {
|
||||
if txFee == 0 {
|
||||
t.Fatal("unexpected value")
|
||||
}
|
||||
|
||||
_, err = m.GetDepositAddress(maid)
|
||||
_, err = m.GetDepositAddress(eth)
|
||||
if !errors.Is(err, errNoDepositAddress) {
|
||||
t.Fatalf("expected: %v but received: %v", errNoDepositAddress, err)
|
||||
}
|
||||
@@ -140,7 +140,7 @@ func TestWsCurrencyMap(t *testing.T) {
|
||||
t.Fatal("unexpected deposit address")
|
||||
}
|
||||
|
||||
wdEnabled, err := m.IsWithdrawAndDepositsEnabled(maid)
|
||||
wdEnabled, err := m.IsWithdrawAndDepositsEnabled(eth)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("expected: %v but received: %v", nil, err)
|
||||
}
|
||||
@@ -149,7 +149,7 @@ func TestWsCurrencyMap(t *testing.T) {
|
||||
t.Fatal("unexpected results")
|
||||
}
|
||||
|
||||
tEnabled, err := m.IsTradingEnabledForCurrency(maid)
|
||||
tEnabled, err := m.IsTradingEnabledForCurrency(eth)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("expected: %v but received: %v", nil, err)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/common/crypto"
|
||||
@@ -220,12 +221,16 @@ func (p *Poloniex) GetChartData(ctx context.Context, currencyPair string, start,
|
||||
}
|
||||
|
||||
// GetCurrencies returns information about currencies
|
||||
func (p *Poloniex) GetCurrencies(ctx context.Context) (map[string]Currencies, error) {
|
||||
func (p *Poloniex) GetCurrencies(ctx context.Context) (map[string]*Currencies, error) {
|
||||
type Response struct {
|
||||
Data map[string]Currencies
|
||||
Data map[string]*Currencies
|
||||
}
|
||||
resp := Response{}
|
||||
return resp.Data, p.SendHTTPRequest(ctx, exchange.RestSpot, "/public?command=returnCurrencies", &resp.Data)
|
||||
return resp.Data, p.SendHTTPRequest(ctx,
|
||||
exchange.RestSpot,
|
||||
"/public?command=returnCurrencies&includeMultiChainCurrencies=true",
|
||||
&resp.Data,
|
||||
)
|
||||
}
|
||||
|
||||
// GetLoanOrders returns the list of loan offers and demands for a given
|
||||
@@ -577,12 +582,15 @@ func (p *Poloniex) MoveOrder(ctx context.Context, orderID int64, rate, amount fl
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Withdraw withdraws a currency to a specific delegated address
|
||||
// Withdraw withdraws a currency to a specific delegated address.
|
||||
// For currencies where there are multiple networks to choose from (like USDT or BTC),
|
||||
// you can specify the chain by setting the "currency" parameter to be a multiChain currency
|
||||
// name, like USDTTRON, USDTETH, or BTCTRON
|
||||
func (p *Poloniex) Withdraw(ctx context.Context, currency, address string, amount float64) (*Withdraw, error) {
|
||||
result := &Withdraw{}
|
||||
values := url.Values{}
|
||||
|
||||
values.Set("currency", currency)
|
||||
values.Set("currency", strings.ToUpper(currency))
|
||||
values.Set("amount", strconv.FormatFloat(amount, 'f', -1, 64))
|
||||
values.Set("address", address)
|
||||
|
||||
@@ -869,7 +877,7 @@ func (p *Poloniex) SendAuthenticatedHTTPRequest(ctx context.Context, ep exchange
|
||||
headers := make(map[string]string)
|
||||
headers["Content-Type"] = "application/x-www-form-urlencoded"
|
||||
headers["Key"] = p.API.Credentials.Key
|
||||
values.Set("nonce", strconv.FormatInt(time.Now().UnixNano(), 10))
|
||||
values.Set("nonce", p.Requester.GetNonce(true).String())
|
||||
values.Set("command", endpoint)
|
||||
|
||||
hmac, err := crypto.GetHMAC(crypto.HashSHA512,
|
||||
@@ -888,6 +896,7 @@ func (p *Poloniex) SendAuthenticatedHTTPRequest(ctx context.Context, ep exchange
|
||||
Body: bytes.NewBufferString(values.Encode()),
|
||||
Result: result,
|
||||
AuthRequest: true,
|
||||
NonceEnabled: true,
|
||||
Verbose: p.Verbose,
|
||||
HTTPDebugging: p.HTTPDebugging,
|
||||
HTTPRecording: p.HTTPRecording,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -459,9 +459,9 @@ func TestWithdraw(t *testing.T) {
|
||||
Exchange: p.Name,
|
||||
Crypto: withdraw.CryptoRequest{
|
||||
Address: core.BitcoinDonationAddress,
|
||||
FeeAmount: 1,
|
||||
FeeAmount: 0,
|
||||
},
|
||||
Amount: 0.00001337,
|
||||
Amount: -1,
|
||||
Currency: currency.LTC,
|
||||
Description: "WITHDRAW IT ALL",
|
||||
TradePassword: "Password",
|
||||
@@ -477,8 +477,8 @@ func TestWithdraw(t *testing.T) {
|
||||
t.Errorf("Withdraw failed to be placed: %v", err)
|
||||
case !areTestAPIKeysSet() && !mockTests && err == nil:
|
||||
t.Error("Expecting an error when no keys are set")
|
||||
case mockTests && err != nil:
|
||||
t.Error(err)
|
||||
case mockTests && err == nil:
|
||||
t.Error("should error due to invalid amount")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -513,7 +513,7 @@ func TestWithdrawInternationalBank(t *testing.T) {
|
||||
|
||||
func TestGetDepositAddress(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := p.GetDepositAddress(context.Background(), currency.DASH, "")
|
||||
_, err := p.GetDepositAddress(context.Background(), currency.USDT, "", "USDTETH")
|
||||
switch {
|
||||
case areTestAPIKeysSet() && err != nil:
|
||||
t.Error("GetDepositAddress()", err)
|
||||
@@ -524,6 +524,17 @@ func TestGetDepositAddress(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateNewAddress(t *testing.T) {
|
||||
t.Parallel()
|
||||
if !areTestAPIKeysSet() {
|
||||
t.Skip("api keys not set, skipping test")
|
||||
}
|
||||
_, err := p.GenerateNewAddress(context.Background(), currency.XRP.String())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestWsAuth dials websocket, sends login request.
|
||||
// Will receive a message only on failure
|
||||
func TestWsAuth(t *testing.T) {
|
||||
@@ -1060,3 +1071,11 @@ func TestUpdateTickers(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAvailableTransferChains(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := p.GetAvailableTransferChains(context.Background(), currency.USDT)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,14 +115,23 @@ type ChartData struct {
|
||||
|
||||
// Currencies contains currency information
|
||||
type Currencies struct {
|
||||
ID float64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
TxFee float64 `json:"txFee,string"`
|
||||
MinConfirmations int64 `json:"minConf"`
|
||||
DepositAddress string `json:"depositAddress"`
|
||||
WithdrawalDepositDisabled uint8 `json:"disabled"`
|
||||
Delisted uint8 `json:"delisted"`
|
||||
Frozen uint8 `json:"frozen"`
|
||||
ID float64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
HumanType string `json:"humanType"`
|
||||
CurrencyType string `json:"currencyType"`
|
||||
TxFee float64 `json:"txFee,string"`
|
||||
MinConfirmations int64 `json:"minConf"`
|
||||
DepositAddress string `json:"depositAddress"`
|
||||
WithdrawalDepositDisabled uint8 `json:"disabled"`
|
||||
Frozen uint8 `json:"frozen"`
|
||||
HexColour string `json:"hexColor"`
|
||||
Blockchain string `json:"blockchain"`
|
||||
Delisted uint8 `json:"delisted"`
|
||||
ParentChain string `json:"parentChain"`
|
||||
IsMultiChain uint8 `json:"isMultiChain"`
|
||||
IsChildChain uint8 `json:"isChildChain"`
|
||||
ChildChains []string `json:"childChains"`
|
||||
IsGeofenced uint8 `json:"isGeofenced"`
|
||||
}
|
||||
|
||||
// LoanOrder holds loan order information
|
||||
|
||||
@@ -2,6 +2,7 @@ package poloniex
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
@@ -15,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"
|
||||
@@ -78,25 +80,27 @@ func (p *Poloniex) SetDefaults() {
|
||||
REST: true,
|
||||
Websocket: true,
|
||||
RESTCapabilities: protocol.Features{
|
||||
TickerBatching: true,
|
||||
TickerFetching: true,
|
||||
KlineFetching: true,
|
||||
TradeFetching: true,
|
||||
OrderbookFetching: true,
|
||||
AutoPairUpdates: true,
|
||||
AccountInfo: true,
|
||||
GetOrder: true,
|
||||
GetOrders: true,
|
||||
CancelOrder: true,
|
||||
CancelOrders: true,
|
||||
SubmitOrder: true,
|
||||
DepositHistory: true,
|
||||
WithdrawalHistory: true,
|
||||
UserTradeHistory: true,
|
||||
CryptoDeposit: true,
|
||||
CryptoWithdrawal: true,
|
||||
TradeFee: true,
|
||||
CryptoWithdrawalFee: true,
|
||||
TickerBatching: true,
|
||||
TickerFetching: true,
|
||||
KlineFetching: true,
|
||||
TradeFetching: true,
|
||||
OrderbookFetching: true,
|
||||
AutoPairUpdates: true,
|
||||
AccountInfo: true,
|
||||
GetOrder: true,
|
||||
GetOrders: true,
|
||||
CancelOrder: true,
|
||||
CancelOrders: true,
|
||||
SubmitOrder: true,
|
||||
DepositHistory: true,
|
||||
WithdrawalHistory: true,
|
||||
UserTradeHistory: true,
|
||||
CryptoDeposit: true,
|
||||
CryptoWithdrawal: true,
|
||||
TradeFee: true,
|
||||
CryptoWithdrawalFee: true,
|
||||
MultiChainDeposits: true,
|
||||
MultiChainWithdrawals: true,
|
||||
},
|
||||
WebsocketCapabilities: protocol.Features{
|
||||
TickerFetching: true,
|
||||
@@ -691,19 +695,72 @@ func (p *Poloniex) GetOrderInfo(ctx context.Context, orderID string, pair curren
|
||||
}
|
||||
|
||||
// GetDepositAddress returns a deposit address for a specified currency
|
||||
func (p *Poloniex) GetDepositAddress(ctx context.Context, cryptocurrency currency.Code, _ string) (string, error) {
|
||||
a, err := p.GetDepositAddresses(ctx)
|
||||
func (p *Poloniex) GetDepositAddress(ctx context.Context, cryptocurrency currency.Code, _, chain string) (*deposit.Address, error) {
|
||||
depositAddrs, err := p.GetDepositAddresses(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
address, ok := a.Addresses[cryptocurrency.Upper().String()]
|
||||
// Some coins use a main address, so we must use this in conjunction with the returned
|
||||
// deposit address to produce the full deposit address and tag
|
||||
currencies, err := p.GetCurrencies(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
coinParams, ok := currencies[cryptocurrency.Upper().String()]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("cannot find deposit address for %s",
|
||||
cryptocurrency)
|
||||
return nil, fmt.Errorf("unable to find currency %s in map", cryptocurrency)
|
||||
}
|
||||
|
||||
return address, nil
|
||||
// Handle coins with payment ID's like XRP
|
||||
var address, tag string
|
||||
if coinParams.CurrencyType == "address-payment-id" && coinParams.DepositAddress != "" {
|
||||
address = coinParams.DepositAddress
|
||||
tag, ok = depositAddrs.Addresses[cryptocurrency.Upper().String()]
|
||||
if !ok {
|
||||
newAddr, err := p.GenerateNewAddress(ctx, cryptocurrency.Upper().String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tag = newAddr
|
||||
}
|
||||
return &deposit.Address{
|
||||
Address: address,
|
||||
Tag: tag,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Handle coins like BTC or multichain coins
|
||||
targetCurrency := cryptocurrency.String()
|
||||
if chain != "" && !strings.EqualFold(chain, cryptocurrency.String()) {
|
||||
targetCurrency = chain
|
||||
}
|
||||
|
||||
address, ok = depositAddrs.Addresses[strings.ToUpper(targetCurrency)]
|
||||
if !ok {
|
||||
if len(coinParams.ChildChains) > 1 && chain != "" && !common.StringDataCompare(coinParams.ChildChains, targetCurrency) {
|
||||
// rather than assume, return an error
|
||||
return nil, fmt.Errorf("currency %s has %v chains available, one of these must be specified",
|
||||
cryptocurrency,
|
||||
coinParams.ChildChains)
|
||||
}
|
||||
|
||||
coinParams, ok = currencies[strings.ToUpper(targetCurrency)]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unable to find currency %s in map", cryptocurrency)
|
||||
}
|
||||
if coinParams.WithdrawalDepositDisabled == 1 {
|
||||
return nil, fmt.Errorf("deposits and withdrawals for %v are currently disabled", targetCurrency)
|
||||
}
|
||||
|
||||
newAddr, err := p.GenerateNewAddress(ctx, targetCurrency)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
address = newAddr
|
||||
}
|
||||
return &deposit.Address{Address: address}, nil
|
||||
}
|
||||
|
||||
// WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is
|
||||
@@ -712,7 +769,12 @@ func (p *Poloniex) WithdrawCryptocurrencyFunds(ctx context.Context, withdrawRequ
|
||||
if err := withdrawRequest.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v, err := p.Withdraw(ctx, withdrawRequest.Currency.String(), withdrawRequest.Crypto.Address, withdrawRequest.Amount)
|
||||
|
||||
targetCurrency := withdrawRequest.Currency.String()
|
||||
if withdrawRequest.Crypto.Chain != "" {
|
||||
targetCurrency = withdrawRequest.Crypto.Chain
|
||||
}
|
||||
v, err := p.Withdraw(ctx, targetCurrency, withdrawRequest.Crypto.Address, withdrawRequest.Amount)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -910,3 +972,19 @@ func (p *Poloniex) GetHistoricCandles(ctx context.Context, pair currency.Pair, a
|
||||
func (p *Poloniex) GetHistoricCandlesExtended(ctx context.Context, pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
return p.GetHistoricCandles(ctx, pair, a, start, end, interval)
|
||||
}
|
||||
|
||||
// GetAvailableTransferChains returns the available transfer blockchains for the specific
|
||||
// cryptocurrency
|
||||
func (p *Poloniex) GetAvailableTransferChains(ctx context.Context, cryptocurrency currency.Code) ([]string, error) {
|
||||
currencies, err := p.GetCurrencies(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
curr, ok := currencies[cryptocurrency.Upper().String()]
|
||||
if !ok {
|
||||
return nil, errors.New("unable to locate currency in map")
|
||||
}
|
||||
|
||||
return curr.ChildChains, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user