Files
gocryptotrader/exchanges/bybit/convert.go
Ryan O'Hara-Reid eb60a3c40e bybit: Add convert functions (#1993)
* bybit: Add convert functions

* Update exchanges/bybit/convert.go

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* rm exported type and inline it within func dec

* Update exchanges/bybit/convert.go

Co-authored-by: Scott <gloriousCode@users.noreply.github.com>

* Update exchanges/bybit/convert.go

Co-authored-by: Scott <gloriousCode@users.noreply.github.com>

* glorious: nits

* glorious: catch

* glorious: int -> int64

* bossking: nits

* Update exchanges/bybit/convert.go

Co-authored-by: Gareth Kirwan <gbjkirwan@gmail.com>

* Update exchanges/bybit/convert.go

Co-authored-by: Gareth Kirwan <gbjkirwan@gmail.com>

* Update exchanges/bybit/convert.go

Co-authored-by: Gareth Kirwan <gbjkirwan@gmail.com>

* Update exchanges/bybit/convert_types.go

Co-authored-by: Gareth Kirwan <gbjkirwan@gmail.com>

* Update exchanges/bybit/convert_types.go

Co-authored-by: Gareth Kirwan <gbjkirwan@gmail.com>

* Update exchanges/bybit/convert.go

Co-authored-by: Gareth Kirwan <gbjkirwan@gmail.com>

* gk: nits v1

* gk: mock what I can

* Fix broken things that I broke

* gk: nits

* Update exchanges/bybit/convert_types.go

Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>

* thrasher-: nits and bits

* linter: fix

* thrasher-:nits updoo

* cranktakular: nits

---------

Co-authored-by: Ryan O'Hara-Reid <ryan.oharareid@thrasher.io>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Scott <gloriousCode@users.noreply.github.com>
Co-authored-by: Gareth Kirwan <gbjkirwan@gmail.com>
Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>
2025-10-02 11:20:27 +10:00

147 lines
5.1 KiB
Go

package bybit
import (
"context"
"errors"
"fmt"
"net/http"
"net/url"
"slices"
"strconv"
"strings"
"github.com/thrasher-corp/gocryptotrader/currency"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
)
var supportedAccountTypes = []WalletAccountType{Funding, UTA, Spot, Contract, Inverse}
var (
errUnsupportedAccountType = errors.New("unsupported account type")
errCurrencyCodesEqual = errors.New("from and to currency codes cannot be equal")
errRequestCoinInvalid = errors.New("request coin must match from coin if provided")
errQuoteTransactionIDEmpty = errors.New("quoteTransactionID cannot be empty")
)
// GetConvertCoinList returns a list of coins you can convert to/from
func (e *Exchange) GetConvertCoinList(ctx context.Context, accountType WalletAccountType, coin currency.Code, isCoinToBuy bool) ([]ConvertCoinResponse, error) {
if !slices.Contains(supportedAccountTypes, accountType) {
return nil, fmt.Errorf("%w: %q", errUnsupportedAccountType, accountType)
}
params := url.Values{}
params.Set("accountType", string(accountType))
if isCoinToBuy {
if coin.IsEmpty() {
return nil, currency.ErrCurrencyCodeEmpty
}
params.Set("coin", coin.Upper().String())
params.Set("side", "1")
} else {
params.Set("side", "0")
}
var resp struct {
List []ConvertCoinResponse `json:"coins"`
}
return resp.List, e.SendAuthHTTPRequestV5(ctx, exchange.RestSpot, http.MethodGet, "/v5/asset/exchange/query-coin-list", params, nil, &resp, defaultEPL)
}
// RequestQuote requests a conversion quote between two coins with the specified parameters.
func (e *Exchange) RequestQuote(ctx context.Context, params *RequestQuoteRequest) (*RequestQuoteResponse, error) {
if !slices.Contains(supportedAccountTypes, params.AccountType) {
return nil, fmt.Errorf("%w: %q", errUnsupportedAccountType, params.AccountType)
}
if params.FromCoin.IsEmpty() {
return nil, fmt.Errorf("%w: `from` coin", currency.ErrCurrencyCodeEmpty)
}
if params.ToCoin.IsEmpty() {
return nil, fmt.Errorf("%w: `to` coin", currency.ErrCurrencyCodeEmpty)
}
if params.FromCoin.Equal(params.ToCoin) {
return nil, errCurrencyCodesEqual
}
if !params.RequestCoin.IsEmpty() {
if !params.RequestCoin.Equal(params.FromCoin) {
return nil, errRequestCoinInvalid
}
} else {
params.RequestCoin = params.FromCoin
}
if params.RequestAmount <= 0 {
return nil, fmt.Errorf("%w: %v", order.ErrAmountIsInvalid, params.RequestAmount)
}
params.FromCoin = params.FromCoin.Upper()
params.ToCoin = params.ToCoin.Upper()
params.RequestCoin = params.RequestCoin.Upper()
var resp *RequestQuoteResponse
return resp, e.SendAuthHTTPRequestV5(ctx, exchange.RestSpot, http.MethodPost, "/v5/asset/exchange/quote-apply", nil, params, &resp, defaultEPL)
}
// ConfirmQuote confirms a quote transaction and executes the conversion
func (e *Exchange) ConfirmQuote(ctx context.Context, quoteTransactionID string) (*ConfirmQuoteResponse, error) {
if quoteTransactionID == "" {
return nil, errQuoteTransactionIDEmpty
}
payload := struct {
QuoteTransactionID string `json:"quoteTxId"`
}{
QuoteTransactionID: quoteTransactionID,
}
var resp *ConfirmQuoteResponse
return resp, e.SendAuthHTTPRequestV5(ctx, exchange.RestSpot, http.MethodPost, "/v5/asset/exchange/convert-execute", nil, payload, &resp, defaultEPL)
}
// GetConvertStatus retrieves the status of a conversion transaction
func (e *Exchange) GetConvertStatus(ctx context.Context, accountType WalletAccountType, quoteTransactionID string) (*ConvertStatusResponse, error) {
if !slices.Contains(supportedAccountTypes, accountType) {
return nil, fmt.Errorf("%w: %q", errUnsupportedAccountType, accountType)
}
if quoteTransactionID == "" {
return nil, errQuoteTransactionIDEmpty
}
params := url.Values{}
params.Set("quoteTxId", quoteTransactionID)
params.Set("accountType", string(accountType))
var resp struct {
ConvertStatus *ConvertStatusResponse `json:"result"`
}
return resp.ConvertStatus, e.SendAuthHTTPRequestV5(ctx, exchange.RestSpot, http.MethodGet, "/v5/asset/exchange/convert-result-query", params, nil, &resp, defaultEPL)
}
// GetConvertHistory retrieves the conversion history.
// All params are optional
func (e *Exchange) GetConvertHistory(ctx context.Context, accountTypes []WalletAccountType, index, limit uint64) ([]ConvertHistoryResponse, error) {
atOut := make([]string, 0, len(accountTypes))
for _, accountType := range accountTypes {
if !slices.Contains(supportedAccountTypes, accountType) {
return nil, fmt.Errorf("%w: %q", errUnsupportedAccountType, accountType)
}
atOut = append(atOut, string(accountType))
}
params := url.Values{}
if len(atOut) > 0 {
params.Add("accountType", strings.Join(atOut, ","))
}
if index != 0 {
params.Set("index", strconv.FormatUint(index, 10))
}
if limit != 0 {
params.Set("limit", strconv.FormatUint(limit, 10))
}
var resp struct {
List []ConvertHistoryResponse `json:"list"`
}
return resp.List, e.SendAuthHTTPRequestV5(ctx, exchange.RestSpot, http.MethodGet, "/v5/asset/exchange/query-convert-history", params, nil, &resp, defaultEPL)
}