mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-23 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:
@@ -30,7 +30,6 @@ const (
|
||||
bitfinexAccountInfo = "account_infos"
|
||||
bitfinexAccountFees = "account_fees"
|
||||
bitfinexAccountSummary = "summary"
|
||||
bitfinexDeposit = "deposit/new"
|
||||
bitfinexBalances = "balances"
|
||||
bitfinexTransfer = "transfer"
|
||||
bitfinexWithdrawal = "withdraw"
|
||||
@@ -77,7 +76,8 @@ const (
|
||||
bitfinexCandles = "candles/trade"
|
||||
bitfinexKeyPermissions = "key_info"
|
||||
bitfinexMarginInfo = "margin_infos"
|
||||
bitfinexDepositMethod = "conf/pub:map:currency:label"
|
||||
bitfinexDepositMethod = "conf/pub:map:tx:method"
|
||||
bitfinexDepositAddress = "auth/w/deposit/address"
|
||||
bitfinexMarginPairs = "conf/pub:list:pair:margin"
|
||||
|
||||
// Bitfinex platform status values
|
||||
@@ -1065,27 +1065,76 @@ func (b *Bitfinex) GetAccountSummary(ctx context.Context) (AccountSummary, error
|
||||
// NewDeposit returns a new deposit address
|
||||
// Method - Example methods accepted: “bitcoin”, “litecoin”, “ethereum”,
|
||||
// “tethers", "ethereumc", "zcash", "monero", "iota", "bcash"
|
||||
// WalletName - accepted: “trading”, “exchange”, “deposit”
|
||||
// WalletName - accepted: "exchange", "margin", "funding" (can also use the old labels
|
||||
// which are "exchange", "trading" and "deposit" respectively). If none is set,
|
||||
// "funding" will be used by default
|
||||
// renew - Default is 0. If set to 1, will return a new unused deposit address
|
||||
func (b *Bitfinex) NewDeposit(ctx context.Context, method, walletName string, renew int) (DepositResponse, error) {
|
||||
if !common.StringDataCompare(AcceptedWalletNames, walletName) {
|
||||
return DepositResponse{},
|
||||
func (b *Bitfinex) NewDeposit(ctx context.Context, method, walletName string, renew uint8) (*Deposit, error) {
|
||||
if walletName == "" {
|
||||
walletName = "funding"
|
||||
} else if !common.StringDataCompare(AcceptedWalletNames, walletName) {
|
||||
return nil,
|
||||
fmt.Errorf("walletname: [%s] is not allowed, supported: %s",
|
||||
walletName,
|
||||
AcceptedWalletNames)
|
||||
}
|
||||
|
||||
response := DepositResponse{}
|
||||
req := make(map[string]interface{})
|
||||
req["method"] = method
|
||||
req["wallet_name"] = walletName
|
||||
req["renew"] = renew
|
||||
req := make(map[string]interface{}, 3)
|
||||
req["wallet"] = walletName
|
||||
req["method"] = strings.ToLower(method)
|
||||
req["op_renew"] = renew
|
||||
var result []interface{}
|
||||
|
||||
return response, b.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost,
|
||||
bitfinexDeposit,
|
||||
err := b.SendAuthenticatedHTTPRequestV2(ctx,
|
||||
exchange.RestSpot,
|
||||
http.MethodPost,
|
||||
bitfinexDepositAddress,
|
||||
req,
|
||||
&response,
|
||||
&result,
|
||||
newDepositAddress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(result) != 8 {
|
||||
return nil, errors.New("expected result to have a len of 8")
|
||||
}
|
||||
|
||||
depositInfo, ok := result[4].([]interface{})
|
||||
if !ok || len(depositInfo) != 6 {
|
||||
return nil, errors.New("unable to get deposit data")
|
||||
}
|
||||
depositMethod, ok := depositInfo[1].(string)
|
||||
if !ok {
|
||||
return nil, errors.New("unable to type assert depositMethod to string")
|
||||
}
|
||||
coin, ok := depositInfo[2].(string)
|
||||
if !ok {
|
||||
return nil, errors.New("unable to type assert coin to string")
|
||||
}
|
||||
var address, poolAddress string
|
||||
if depositInfo[5] == nil {
|
||||
address, ok = depositInfo[4].(string)
|
||||
if !ok {
|
||||
return nil, errors.New("unable to type assert address to string")
|
||||
}
|
||||
} else {
|
||||
poolAddress, ok = depositInfo[4].(string)
|
||||
if !ok {
|
||||
return nil, errors.New("unable to type assert poolAddress to string")
|
||||
}
|
||||
address, ok = depositInfo[5].(string)
|
||||
if !ok {
|
||||
return nil, errors.New("unable to type assert address to string")
|
||||
}
|
||||
}
|
||||
|
||||
return &Deposit{
|
||||
Method: depositMethod,
|
||||
CurrencyCode: coin,
|
||||
Address: address,
|
||||
PoolAddress: poolAddress,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetKeyPermissions checks the permissions of the key being used to generate
|
||||
@@ -1149,10 +1198,10 @@ func (b *Bitfinex) WalletTransfer(ctx context.Context, amount float64, currency,
|
||||
|
||||
// WithdrawCryptocurrency requests a withdrawal from one of your wallets.
|
||||
// For FIAT, use WithdrawFIAT
|
||||
func (b *Bitfinex) WithdrawCryptocurrency(ctx context.Context, wallet, address, paymentID string, amount float64, c currency.Code) (Withdrawal, error) {
|
||||
func (b *Bitfinex) WithdrawCryptocurrency(ctx context.Context, wallet, address, paymentID, curr string, amount float64) (Withdrawal, error) {
|
||||
var response []Withdrawal
|
||||
req := make(map[string]interface{})
|
||||
req["withdraw_type"] = b.ConvertSymbolToWithdrawalType(c)
|
||||
req["withdraw_type"] = strings.ToLower(curr)
|
||||
req["walletselected"] = wallet
|
||||
req["amount"] = strconv.FormatFloat(amount, 'f', -1, 64)
|
||||
req["address"] = address
|
||||
@@ -1666,18 +1715,16 @@ func (b *Bitfinex) SendAuthenticatedHTTPRequestV2(ctx context.Context, ep exchan
|
||||
body = bytes.NewBuffer(payload)
|
||||
}
|
||||
|
||||
// This is done in a weird way because bitfinex doesn't accept unixnano
|
||||
n := strconv.FormatInt(int64(b.Requester.GetNonce(false))*1e9, 10)
|
||||
n := strconv.FormatInt(time.Now().Unix()*1e9, 10)
|
||||
headers := make(map[string]string)
|
||||
headers["Content-Type"] = "application/json"
|
||||
headers["Accept"] = "application/json"
|
||||
headers["bfx-apikey"] = b.API.Credentials.Key
|
||||
headers["bfx-nonce"] = n
|
||||
strPath := "/api" + bitfinexAPIVersion2 + path + string(payload)
|
||||
signStr := strPath + n
|
||||
sig := "/api" + bitfinexAPIVersion2 + path + n + string(payload)
|
||||
hmac, err := crypto.GetHMAC(
|
||||
crypto.HashSHA512_384,
|
||||
[]byte(signStr),
|
||||
[]byte(sig),
|
||||
[]byte(b.API.Credentials.Secret),
|
||||
)
|
||||
if err != nil {
|
||||
@@ -1792,89 +1839,52 @@ func (b *Bitfinex) CalculateTradingFee(i []AccountInfo, purchasePrice, amount fl
|
||||
return (fee / 100) * purchasePrice * amount, err
|
||||
}
|
||||
|
||||
// ConvertSymbolToWithdrawalType You need to have specific withdrawal types to withdraw from Bitfinex
|
||||
func (b *Bitfinex) ConvertSymbolToWithdrawalType(c currency.Code) string {
|
||||
switch c {
|
||||
case currency.BTC:
|
||||
return "bitcoin"
|
||||
case currency.LTC:
|
||||
return "litecoin"
|
||||
case currency.ETH:
|
||||
return "ethereum"
|
||||
case currency.ETC:
|
||||
return "ethereumc"
|
||||
case currency.USDT:
|
||||
return "tetheruso"
|
||||
case currency.ZEC:
|
||||
return "zcash"
|
||||
case currency.XMR:
|
||||
return "monero"
|
||||
case currency.DSH:
|
||||
return "dash"
|
||||
case currency.XRP:
|
||||
return "ripple"
|
||||
case currency.SAN:
|
||||
return "santiment"
|
||||
case currency.OMG:
|
||||
return "omisego"
|
||||
case currency.BCH:
|
||||
return "bcash"
|
||||
case currency.ETP:
|
||||
return "metaverse"
|
||||
case currency.AVT:
|
||||
return "aventus"
|
||||
case currency.EDO:
|
||||
return "eidoo"
|
||||
case currency.BTG:
|
||||
return "bgold"
|
||||
case currency.DATA:
|
||||
return "datacoin"
|
||||
case currency.GNT:
|
||||
return "golem"
|
||||
case currency.SNT:
|
||||
return "status"
|
||||
default:
|
||||
return c.Lower().String()
|
||||
}
|
||||
}
|
||||
|
||||
// ConvertSymbolToDepositMethod returns a converted currency deposit method
|
||||
func (b *Bitfinex) ConvertSymbolToDepositMethod(ctx context.Context, c currency.Code) (string, error) {
|
||||
if err := b.PopulateAcceptableMethods(ctx); err != nil {
|
||||
return "", err
|
||||
}
|
||||
method, ok := AcceptableMethods[c.String()]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("currency %s not supported in method list",
|
||||
c)
|
||||
}
|
||||
|
||||
return strings.ToLower(method), nil
|
||||
}
|
||||
|
||||
// PopulateAcceptableMethods retrieves all accepted currency strings and
|
||||
// populates a map to check
|
||||
func (b *Bitfinex) PopulateAcceptableMethods(ctx context.Context) error {
|
||||
if len(AcceptableMethods) == 0 {
|
||||
var response [][][2]string
|
||||
err := b.SendHTTPRequest(ctx, exchange.RestSpot,
|
||||
bitfinexAPIVersion2+bitfinexDepositMethod,
|
||||
&response,
|
||||
configs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(response) == 0 {
|
||||
return errors.New("response contains no data cannot populate acceptable method map")
|
||||
}
|
||||
|
||||
for i := range response[0] {
|
||||
if len(response[0][i]) != 2 {
|
||||
return errors.New("response contains no data cannot populate acceptable method map")
|
||||
}
|
||||
AcceptableMethods[response[0][i][0]] = response[0][i][1]
|
||||
}
|
||||
if acceptableMethods.loaded() {
|
||||
return nil
|
||||
}
|
||||
|
||||
var response [][][]interface{}
|
||||
err := b.SendHTTPRequest(ctx,
|
||||
exchange.RestSpot,
|
||||
bitfinexAPIVersion2+bitfinexDepositMethod,
|
||||
&response,
|
||||
configs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(response) == 0 {
|
||||
return errors.New("response contains no data cannot populate acceptable method map")
|
||||
}
|
||||
|
||||
data := response[0]
|
||||
storeData := make(map[string][]string)
|
||||
for x := range data {
|
||||
if len(data[x]) == 0 {
|
||||
return fmt.Errorf("data should not be empty")
|
||||
}
|
||||
name, ok := data[x][0].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("unable to type assert name")
|
||||
}
|
||||
|
||||
var availOptions []string
|
||||
options, ok := data[x][1].([]interface{})
|
||||
if !ok {
|
||||
return fmt.Errorf("unable to type assert options")
|
||||
}
|
||||
for x := range options {
|
||||
o, ok := options[x].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("unable to type assert option to string")
|
||||
}
|
||||
availOptions = append(availOptions, o)
|
||||
}
|
||||
storeData[name] = availOptions
|
||||
}
|
||||
acceptableMethods.load(storeData)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -350,7 +350,7 @@ func TestNewDeposit(t *testing.T) {
|
||||
t.Error("NewDeposit() Expected error")
|
||||
}
|
||||
|
||||
_, err = b.NewDeposit(context.Background(), "bitcoin", "exchange", 0)
|
||||
_, err = b.NewDeposit(context.Background(), "ripple", "", 0)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -956,11 +956,13 @@ func TestWithdraw(t *testing.T) {
|
||||
}
|
||||
|
||||
withdrawCryptoRequest := withdraw.Request{
|
||||
Exchange: b.Name,
|
||||
Amount: -1,
|
||||
Currency: currency.BTC,
|
||||
Currency: currency.USDT,
|
||||
Description: "WITHDRAW IT ALL",
|
||||
Crypto: withdraw.CryptoRequest{
|
||||
Address: core.BitcoinDonationAddress,
|
||||
Address: "0x1nv4l1d",
|
||||
Chain: "tetheruse",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1034,14 +1036,12 @@ func TestWithdrawInternationalBank(t *testing.T) {
|
||||
func TestGetDepositAddress(t *testing.T) {
|
||||
t.Parallel()
|
||||
if areTestAPIKeysSet() {
|
||||
_, err := b.GetDepositAddress(context.Background(),
|
||||
currency.BTC, "deposit")
|
||||
_, err := b.GetDepositAddress(context.Background(), currency.USDT, "", "TETHERUSE")
|
||||
if err != nil {
|
||||
t.Error("GetDepositAddress() error", err)
|
||||
}
|
||||
} else {
|
||||
_, err := b.GetDepositAddress(context.Background(),
|
||||
currency.BTC, "deposit")
|
||||
_, err := b.GetDepositAddress(context.Background(), currency.BTC, "deposit", "")
|
||||
if err == nil {
|
||||
t.Error("GetDepositAddress() error cannot be nil")
|
||||
}
|
||||
@@ -1201,22 +1201,6 @@ func TestWsCancelOffer(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertSymbolToDepositMethod(t *testing.T) {
|
||||
s, err := b.ConvertSymbolToDepositMethod(context.Background(), currency.BTC)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if s != "bitcoin" {
|
||||
t.Errorf("expected bitcoin but received %s", s)
|
||||
}
|
||||
|
||||
_, err = b.ConvertSymbolToDepositMethod(context.Background(),
|
||||
currency.NewCode("CATS!"))
|
||||
if err == nil {
|
||||
log.Fatal("error cannot be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateTradablePairs(t *testing.T) {
|
||||
err := b.UpdateTradablePairs(context.Background(), false)
|
||||
if err != nil {
|
||||
@@ -1650,3 +1634,70 @@ func TestReOrderbyID(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPopulateAcceptableMethods(t *testing.T) {
|
||||
t.Parallel()
|
||||
if acceptableMethods.loaded() {
|
||||
// we may have have been loaded from another test, so reset
|
||||
acceptableMethods.m.Lock()
|
||||
acceptableMethods.a = make(map[string][]string)
|
||||
acceptableMethods.m.Unlock()
|
||||
if acceptableMethods.loaded() {
|
||||
t.Error("expected false")
|
||||
}
|
||||
}
|
||||
if err := b.PopulateAcceptableMethods(context.Background()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !acceptableMethods.loaded() {
|
||||
t.Error("acceptable method store should be loaded")
|
||||
}
|
||||
if methods := acceptableMethods.lookup(currency.NewCode("UST")); len(methods) == 0 {
|
||||
t.Error("USDT should have many available methods")
|
||||
}
|
||||
if methods := acceptableMethods.lookup(currency.NewCode("ASdasdasdasd")); len(methods) != 0 {
|
||||
t.Error("non-existent code should return no methods")
|
||||
}
|
||||
// since we're already loaded, this will return nil
|
||||
if err := b.PopulateAcceptableMethods(context.Background()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAvailableTransferChains(t *testing.T) {
|
||||
t.Parallel()
|
||||
r, err := b.GetAvailableTransferChains(context.Background(), currency.USDT)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(r) < 2 {
|
||||
t.Error("there should be many available USDT transfer chains")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccetableMethodStore(t *testing.T) {
|
||||
t.Parallel()
|
||||
var a acceptableMethodStore
|
||||
if a.loaded() {
|
||||
t.Error("should be empty")
|
||||
}
|
||||
data := map[string][]string{
|
||||
"BITCOIN": {"BTC"},
|
||||
"TETHER1": {"UST"},
|
||||
"TETHER2": {"UST"},
|
||||
}
|
||||
a.load(data)
|
||||
if !a.loaded() {
|
||||
t.Error("data should be loaded")
|
||||
}
|
||||
if name := a.lookup(currency.NewCode("BTC")); len(name) != 1 && name[1] != "BITCOIN" {
|
||||
t.Error("incorrect values")
|
||||
}
|
||||
if name := a.lookup(currency.NewCode("UST")); (name[0] != "TETHER1" && name[1] != "TETHER2") &&
|
||||
(name[0] != "TETHER2" && name[1] != "TETHER1") {
|
||||
t.Errorf("incorrect values")
|
||||
}
|
||||
if name := a.lookup(currency.NewCode("PANDA_HORSE")); len(name) != 0 {
|
||||
t.Error("incorrect values")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,11 @@ package bitfinex
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
)
|
||||
|
||||
@@ -50,8 +53,37 @@ var AcceptedOrderType = []string{"market", "limit", "stop", "trailing-stop",
|
||||
var AcceptedWalletNames = []string{"trading", "exchange", "deposit", "margin",
|
||||
"funding"}
|
||||
|
||||
// AcceptableMethods defines a map of currency codes to methods
|
||||
var AcceptableMethods = make(map[string]string)
|
||||
type acceptableMethodStore struct {
|
||||
a map[string][]string
|
||||
m sync.RWMutex
|
||||
}
|
||||
|
||||
// acceptableMethods holds the available acceptable deposit and withdraw methods
|
||||
var acceptableMethods acceptableMethodStore
|
||||
|
||||
func (a *acceptableMethodStore) lookup(curr currency.Code) []string {
|
||||
a.m.RLock()
|
||||
defer a.m.RUnlock()
|
||||
var methods []string
|
||||
for k, v := range a.a {
|
||||
if common.StringDataCompareInsensitive(v, curr.Upper().String()) {
|
||||
methods = append(methods, k)
|
||||
}
|
||||
}
|
||||
return methods
|
||||
}
|
||||
|
||||
func (a *acceptableMethodStore) load(data map[string][]string) {
|
||||
a.m.Lock()
|
||||
defer a.m.Unlock()
|
||||
a.a = data
|
||||
}
|
||||
|
||||
func (a *acceptableMethodStore) loaded() bool {
|
||||
a.m.RLock()
|
||||
defer a.m.RUnlock()
|
||||
return len(a.a) > 0
|
||||
}
|
||||
|
||||
// MarginV2FundingData stores margin funding data
|
||||
type MarginV2FundingData struct {
|
||||
@@ -238,12 +270,12 @@ type Currency struct {
|
||||
Amount float64 `json:"amount,string"`
|
||||
}
|
||||
|
||||
// DepositResponse holds deposit address information
|
||||
type DepositResponse struct {
|
||||
Result string `json:"string"`
|
||||
Method string `json:"method"`
|
||||
Currency string `json:"currency"`
|
||||
Address string `json:"address"`
|
||||
// Deposit holds the deposit address info
|
||||
type Deposit struct {
|
||||
Method string
|
||||
CurrencyCode string
|
||||
Address string // Deposit address (instead of the address, this field will show Tag/Memo/Payment_ID for currencies that require it)
|
||||
PoolAddress string // Pool address (for currencies that require a Tag/Memo/Payment_ID)
|
||||
}
|
||||
|
||||
// KeyPermissions holds the key permissions for the API key set
|
||||
|
||||
@@ -17,6 +17,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"
|
||||
@@ -89,29 +90,32 @@ func (b *Bitfinex) SetDefaults() {
|
||||
REST: true,
|
||||
Websocket: true,
|
||||
RESTCapabilities: protocol.Features{
|
||||
TickerBatching: true,
|
||||
TickerFetching: true,
|
||||
OrderbookFetching: true,
|
||||
AutoPairUpdates: true,
|
||||
AccountInfo: true,
|
||||
CryptoDeposit: true,
|
||||
CryptoWithdrawal: true,
|
||||
FiatWithdraw: true,
|
||||
GetOrder: true,
|
||||
GetOrders: true,
|
||||
CancelOrders: true,
|
||||
CancelOrder: true,
|
||||
SubmitOrder: true,
|
||||
SubmitOrders: true,
|
||||
DepositHistory: true,
|
||||
WithdrawalHistory: true,
|
||||
TradeFetching: true,
|
||||
UserTradeHistory: true,
|
||||
TradeFee: true,
|
||||
FiatDepositFee: true,
|
||||
FiatWithdrawalFee: true,
|
||||
CryptoDepositFee: true,
|
||||
CryptoWithdrawalFee: true,
|
||||
TickerBatching: true,
|
||||
TickerFetching: true,
|
||||
OrderbookFetching: true,
|
||||
AutoPairUpdates: true,
|
||||
AccountInfo: true,
|
||||
CryptoDeposit: true,
|
||||
CryptoWithdrawal: true,
|
||||
FiatWithdraw: true,
|
||||
GetOrder: true,
|
||||
GetOrders: true,
|
||||
CancelOrders: true,
|
||||
CancelOrder: true,
|
||||
SubmitOrder: true,
|
||||
SubmitOrders: true,
|
||||
DepositHistory: true,
|
||||
WithdrawalHistory: true,
|
||||
TradeFetching: true,
|
||||
UserTradeHistory: true,
|
||||
TradeFee: true,
|
||||
FiatDepositFee: true,
|
||||
FiatWithdrawalFee: true,
|
||||
CryptoDepositFee: true,
|
||||
CryptoWithdrawalFee: true,
|
||||
MultiChainDeposits: true,
|
||||
MultiChainWithdrawals: true,
|
||||
MultiChainDepositRequiresChainSet: true,
|
||||
},
|
||||
WebsocketCapabilities: protocol.Features{
|
||||
AccountBalance: true,
|
||||
@@ -731,18 +735,39 @@ func (b *Bitfinex) GetOrderInfo(ctx context.Context, orderID string, pair curren
|
||||
}
|
||||
|
||||
// GetDepositAddress returns a deposit address for a specified currency
|
||||
func (b *Bitfinex) GetDepositAddress(ctx context.Context, c currency.Code, accountID string) (string, error) {
|
||||
func (b *Bitfinex) GetDepositAddress(ctx context.Context, c currency.Code, accountID, chain string) (*deposit.Address, error) {
|
||||
if accountID == "" {
|
||||
accountID = "deposit"
|
||||
accountID = "funding"
|
||||
}
|
||||
|
||||
method, err := b.ConvertSymbolToDepositMethod(ctx, c)
|
||||
if err != nil {
|
||||
return "", err
|
||||
if c == currency.USDT {
|
||||
// USDT is UST on Bitfinex
|
||||
c = currency.NewCode("UST")
|
||||
}
|
||||
|
||||
if err := b.PopulateAcceptableMethods(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
methods := acceptableMethods.lookup(c)
|
||||
if len(methods) == 0 {
|
||||
return nil, errors.New("unsupported currency")
|
||||
}
|
||||
method := methods[0]
|
||||
if len(methods) > 1 && chain != "" {
|
||||
method = chain
|
||||
} else if len(methods) > 1 && chain == "" {
|
||||
return nil, fmt.Errorf("a chain must be specified, %s available", methods)
|
||||
}
|
||||
|
||||
resp, err := b.NewDeposit(ctx, method, accountID, 0)
|
||||
return resp.Address, err
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &deposit.Address{
|
||||
Address: resp.Address,
|
||||
Tag: resp.PoolAddress,
|
||||
}, err
|
||||
}
|
||||
|
||||
// WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is submitted
|
||||
@@ -750,6 +775,31 @@ func (b *Bitfinex) WithdrawCryptocurrencyFunds(ctx context.Context, withdrawRequ
|
||||
if err := withdrawRequest.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := b.PopulateAcceptableMethods(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tmpCurr := withdrawRequest.Currency
|
||||
if tmpCurr == currency.USDT {
|
||||
// USDT is UST on Bitfinex
|
||||
tmpCurr = currency.NewCode("UST")
|
||||
}
|
||||
|
||||
methods := acceptableMethods.lookup(tmpCurr)
|
||||
if len(methods) == 0 {
|
||||
return nil, errors.New("no transfer methods returned for currency")
|
||||
}
|
||||
method := methods[0]
|
||||
if len(methods) > 1 && withdrawRequest.Crypto.Chain != "" {
|
||||
if !common.StringDataCompareInsensitive(methods, withdrawRequest.Crypto.Chain) {
|
||||
return nil, fmt.Errorf("invalid chain %s supplied, %v available", withdrawRequest.Crypto.Chain, methods)
|
||||
}
|
||||
method = withdrawRequest.Crypto.Chain
|
||||
} else if len(methods) > 1 && withdrawRequest.Crypto.Chain == "" {
|
||||
return nil, fmt.Errorf("a chain must be specified, %s available", methods)
|
||||
}
|
||||
|
||||
// Bitfinex has support for three types, exchange, margin and deposit
|
||||
// As this is for trading, I've made the wrapper default 'exchange'
|
||||
// TODO: Discover an automated way to make the decision for wallet type to withdraw from
|
||||
@@ -757,9 +807,9 @@ func (b *Bitfinex) WithdrawCryptocurrencyFunds(ctx context.Context, withdrawRequ
|
||||
resp, err := b.WithdrawCryptocurrency(ctx,
|
||||
walletType,
|
||||
withdrawRequest.Crypto.Address,
|
||||
withdrawRequest.Description,
|
||||
withdrawRequest.Amount,
|
||||
withdrawRequest.Currency)
|
||||
withdrawRequest.Crypto.AddressTag,
|
||||
method,
|
||||
withdrawRequest.Amount)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1119,3 +1169,22 @@ func (b *Bitfinex) fixCasing(in currency.Pair, a asset.Item) (string, error) {
|
||||
runes[0] = unicode.ToLower(runes[0])
|
||||
return string(runes), nil
|
||||
}
|
||||
|
||||
// GetAvailableTransferChains returns the available transfer blockchains for the specific
|
||||
// cryptocurrency
|
||||
func (b *Bitfinex) GetAvailableTransferChains(ctx context.Context, cryptocurrency currency.Code) ([]string, error) {
|
||||
if err := b.PopulateAcceptableMethods(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if cryptocurrency == currency.USDT {
|
||||
// USDT is UST on Bitfinex
|
||||
cryptocurrency = currency.NewCode("UST")
|
||||
}
|
||||
|
||||
availChains := acceptableMethods.lookup(cryptocurrency)
|
||||
if len(availChains) == 0 {
|
||||
return nil, fmt.Errorf("unable to find any available chains")
|
||||
}
|
||||
return availChains, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user