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>
This commit is contained in:
Ryan O'Hara-Reid
2025-10-02 11:20:27 +10:00
committed by GitHub
parent bb122dcafa
commit eb60a3c40e
4 changed files with 912 additions and 0 deletions

146
exchanges/bybit/convert.go Normal file
View File

@@ -0,0 +1,146 @@
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)
}

View File

@@ -0,0 +1,151 @@
package bybit
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
"github.com/thrasher-corp/gocryptotrader/types"
)
func TestGetConvertCoinList(t *testing.T) {
t.Parallel()
_, err := e.GetConvertCoinList(t.Context(), "", currency.EMPTYCODE, true)
assert.ErrorIs(t, err, errUnsupportedAccountType)
_, err = e.GetConvertCoinList(t.Context(), UTA, currency.EMPTYCODE, true)
assert.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
if !mockTests {
sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
}
buylist, err := e.GetConvertCoinList(t.Context(), UTA, currency.USDT, true)
require.NoError(t, err)
assert.NotEmpty(t, buylist)
sellList, err := e.GetConvertCoinList(t.Context(), UTA, currency.EMPTYCODE, false)
require.NoError(t, err)
assert.NotEmpty(t, sellList)
}
func TestRequestQuote(t *testing.T) {
t.Parallel()
_, err := e.RequestQuote(t.Context(), &RequestQuoteRequest{})
assert.ErrorIs(t, err, errUnsupportedAccountType)
_, err = e.RequestQuote(t.Context(), &RequestQuoteRequest{AccountType: UTA})
assert.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
_, err = e.RequestQuote(t.Context(), &RequestQuoteRequest{AccountType: UTA, FromCoin: currency.BTC})
assert.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
_, err = e.RequestQuote(t.Context(), &RequestQuoteRequest{
AccountType: UTA,
FromCoin: currency.BTC,
ToCoin: currency.BTC,
})
assert.ErrorIs(t, err, errCurrencyCodesEqual)
_, err = e.RequestQuote(t.Context(), &RequestQuoteRequest{
AccountType: UTA,
FromCoin: currency.BTC,
ToCoin: currency.USDT,
RequestCoin: currency.WOO,
})
assert.ErrorIs(t, err, errRequestCoinInvalid)
_, err = e.RequestQuote(t.Context(), &RequestQuoteRequest{
AccountType: UTA,
FromCoin: currency.BTC,
ToCoin: currency.USDT,
})
assert.ErrorIs(t, err, order.ErrAmountIsInvalid)
if !mockTests {
sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
}
quote, err := e.RequestQuote(t.Context(), &RequestQuoteRequest{
AccountType: UTA,
FromCoin: currency.XRP,
ToCoin: currency.USDT,
RequestAmount: 0.0088,
})
require.NoError(t, err)
assert.NotEmpty(t, quote.QuoteTransactionID)
}
func TestConfirmQuote(t *testing.T) {
t.Parallel()
_, err := e.ConfirmQuote(t.Context(), "")
assert.ErrorIs(t, err, errQuoteTransactionIDEmpty)
if !mockTests {
sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
}
quote, err := e.ConfirmQuote(t.Context(), "10175108571334212336947200")
require.NoError(t, err)
assert.NotEmpty(t, quote.QuoteTransactionID)
}
func TestGetConvertStatus(t *testing.T) {
t.Parallel()
_, err := e.GetConvertStatus(t.Context(), "", "")
assert.ErrorIs(t, err, errUnsupportedAccountType)
_, err = e.GetConvertStatus(t.Context(), UTA, "")
assert.ErrorIs(t, err, errQuoteTransactionIDEmpty)
if !mockTests {
sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
}
status, err := e.GetConvertStatus(t.Context(), UTA, "10414247553864074960678912")
require.NoError(t, err)
assert.NotEmpty(t, status)
}
func TestGetConvertHistory(t *testing.T) {
t.Parallel()
_, err := e.GetConvertHistory(t.Context(), []WalletAccountType{""}, 0, 0)
assert.ErrorIs(t, err, errUnsupportedAccountType)
if !mockTests {
sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
}
history, err := e.GetConvertHistory(t.Context(), []WalletAccountType{UTA}, 0, 0)
require.NoError(t, err)
assert.NotEmpty(t, history)
if mockTests {
require.Equal(t, 4, len(history), "GetConvertHistory must return 4 items in mock test")
exp := ConvertHistoryResponse{
AccountType: UTA,
ExchangeTransactionID: "104231555340214158196736",
UserID: "74199870",
FromCoin: currency.NewCode("UXLINK"),
FromCoinType: "crypto",
FromAmount: 7.9952,
ToCoin: currency.USDT,
ToCoinType: "crypto",
ToAmount: 2.84509740190888,
ExchangeStatus: "success",
ExtendedInfo: ExtendedInfoHistoryResponse{},
ConvertRate: 0.35585068565,
CreatedAt: types.Time(time.UnixMilli(1754880224953)),
}
require.Equal(t, exp, history[0])
}
}

View File

@@ -0,0 +1,117 @@
package bybit
import (
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/encoding/json"
"github.com/thrasher-corp/gocryptotrader/types"
)
// WalletAccountTypes
const (
Funding WalletAccountType = "eb_convert_funding"
UTA WalletAccountType = "eb_convert_uta"
Spot WalletAccountType = "eb_convert_spot"
Contract WalletAccountType = "eb_convert_contract"
Inverse WalletAccountType = "eb_convert_inverse"
)
// WalletAccountType represents the different types of wallet accounts
type WalletAccountType string
// ConvertCoinResponse represents a coin that can be converted
type ConvertCoinResponse struct {
Coin currency.Code `json:"coin"`
FullName string `json:"fullName"`
Icon string `json:"icon"`
IconNight string `json:"iconNight"`
AccuracyLength uint8 `json:"accuracyLength"`
CoinType string `json:"coinType"`
Balance types.Number `json:"balance"`
BalanceInUSDT types.Number `json:"uBalance"`
SingleFromMinLimit types.Number `json:"singleFromMinLimit"` // The minimum amount of fromCoin per transaction
SingleFromMaxLimit types.Number `json:"singleFromMaxLimit"` // The maximum amount of fromCoin per transaction
DisableFrom bool `json:"disableFrom"` // true: the coin is disabled to be fromCoin, false: the coin is allowed to be fromCoin
DisableTo bool `json:"disableTo"` // true: the coin is disabled to be toCoin, false: the coin is allowed to be toCoin
TimePeriod int64 `json:"timePeriod"`
SingleToMinLimit types.Number `json:"singleToMinLimit"`
SingleToMaxLimit types.Number `json:"singleToMaxLimit"`
DailyFromMinLimit types.Number `json:"dailyFromMinLimit"`
DailyFromMaxLimit types.Number `json:"dailyFromMaxLimit"`
DailyToMinLimit types.Number `json:"dailyToMinLimit"`
DailyToMaxLimit types.Number `json:"dailyToMaxLimit"`
}
// RequestQuoteRequest holds the parameters for requesting a quote
type RequestQuoteRequest struct {
RequestID string `json:"requestId,omitempty"`
AccountType WalletAccountType `json:"accountType"`
FromCoin currency.Code `json:"fromCoin"`
ToCoin currency.Code `json:"toCoin"`
RequestCoin currency.Code `json:"requestCoin"` // Must be same as FromCoin
RequestAmount types.Number `json:"requestAmount"`
FromCoinType string `json:"fromCoinType,omitempty"` // "crypto"
ToCoinType string `json:"toCoinType,omitempty"` // "crypto"
ParamType string `json:"paramType,omitempty"` // "opFrom", mainly used for API broker user
ParamValue string `json:"paramValue,omitempty"` // Broker ID, mainly used for API broker user
}
// RequestQuoteResponse represents a response for a request a quote
type RequestQuoteResponse struct {
QuoteTransactionID string `json:"quoteTxId"` // Quote transaction ID. It is system generated, and it is used to confirm quote and query the result of transaction
ExchangeRate types.Number `json:"exchangeRate"`
FromCoin currency.Code `json:"fromCoin"`
FromCoinType string `json:"fromCoinType"`
ToCoin currency.Code `json:"toCoin"`
ToCoinType string `json:"toCoinType"`
FromAmount types.Number `json:"fromAmount"`
ToAmount types.Number `json:"toAmount"`
ExpiredTime types.Time `json:"expiredTime"` // The expiry time for this quote (15 seconds)
RequestID string `json:"requestId"`
ExtendedTaxAndFee json.RawMessage `json:"extTaxAndFee"` // Compliance-related field. Currently returns an empty array, which may be used in the future
}
// ConfirmQuoteResponse represents a response for confirming a quote
type ConfirmQuoteResponse struct {
ExchangeStatus string `json:"exchangeStatus"`
QuoteTransactionID string `json:"quoteTxId"`
}
// ConvertStatusResponse represents the response for a conversion status query
type ConvertStatusResponse struct {
AccountType WalletAccountType `json:"accountType"`
ExchangeTransactionID string `json:"exchangeTxId"`
UserID string `json:"userId"`
FromCoin currency.Code `json:"fromCoin"`
FromCoinType string `json:"fromCoinType"`
FromAmount types.Number `json:"fromAmount"`
ToCoin currency.Code `json:"toCoin"`
ToCoinType string `json:"toCoinType"`
ToAmount types.Number `json:"toAmount"`
ExchangeStatus string `json:"exchangeStatus"`
ExtendedInfo json.RawMessage `json:"extInfo"` // Reserved field, ignored for now
ConvertRate types.Number `json:"convertRate"`
CreatedAt types.Time `json:"createdAt"`
}
// ConvertHistoryResponse represents a response for conversion history
type ConvertHistoryResponse struct {
AccountType WalletAccountType `json:"accountType"`
ExchangeTransactionID string `json:"exchangeTxId"`
UserID string `json:"userId"`
FromCoin currency.Code `json:"fromCoin"`
FromCoinType string `json:"fromCoinType"`
FromAmount types.Number `json:"fromAmount"`
ToCoin currency.Code `json:"toCoin"`
ToCoinType string `json:"toCoinType"`
ToAmount types.Number `json:"toAmount"`
ExchangeStatus string `json:"exchangeStatus"`
ExtendedInfo ExtendedInfoHistoryResponse `json:"extInfo"`
ConvertRate types.Number `json:"convertRate"`
CreatedAt types.Time `json:"createdAt"`
}
// ExtendedInfoHistoryResponse represents the extended information for conversion history
type ExtendedInfoHistoryResponse struct {
ParamType string `json:"paramType"`
ParamValue string `json:"paramValue"`
}

View File

@@ -10676,6 +10676,89 @@
}
]
},
"/v5/asset/exchange/convert-execute": {
"POST": [
{
"data": {
"result": {
"exchangeStatus": "processing",
"quoteTxId": "10175108571334212336947200"
},
"retCode": 0,
"retExtInfo": {},
"retMsg": "ok",
"time": 1758693503803
},
"queryString": "",
"bodyParams": "{\"quoteTxId\":\"10175108571334212336947200\"}",
"headers": {
"Content-Type": [
"application/json"
],
"X-Bapi-Api-Key": [
""
],
"X-Bapi-Recv-Window": [
"5000"
],
"X-Bapi-Sign": [
"8ac32a4d321aae758172178e0626903e90960372d0e8361e6920f5e7c1e2b1d4"
],
"X-Bapi-Timestamp": [
"1758693503260"
]
}
}
]
},
"/v5/asset/exchange/convert-result-query": {
"GET": [
{
"data": {
"result": {
"result": {
"accountType": "eb_convert_uta",
"convertRate": "0.046765815395",
"createdAt": "1754528298061",
"exchangeStatus": "success",
"exchangeTxId": "10414247553864074960678912",
"extInfo": {},
"fromAmount": "49.96",
"fromCoin": "PIRATE",
"fromCoinType": "crypto",
"toAmount": "2.3364201371342",
"toCoin": "USDT",
"toCoinType": "crypto",
"userId": "74199870"
}
},
"retCode": 0,
"retExtInfo": {},
"retMsg": "ok",
"time": 1758260135835
},
"queryString": "accountType=eb_convert_uta\u0026quoteTxId=10414247553864074960678912",
"bodyParams": "",
"headers": {
"Content-Type": [
"application/x-www-form-urlencoded"
],
"X-Bapi-Api-Key": [
""
],
"X-Bapi-Recv-Window": [
"5000"
],
"X-Bapi-Sign": [
"af98ed1cf332f7d3fbdbbeecf47e5e9aae011388ac6bf40a73b3337843124f2e"
],
"X-Bapi-Timestamp": [
"1758260135697"
]
}
}
]
},
"/v5/asset/exchange/order-record": {
"GET": [
{
@@ -10711,6 +10794,421 @@
}
]
},
"/v5/asset/exchange/query-coin-list": {
"GET": [
{
"data": {
"result": {
"coins": [
{
"accuracyLength": 8,
"balance": "",
"coin": "AGLD",
"coinType": "crypto",
"dailyFromMaxLimit": "0",
"dailyFromMinLimit": "0",
"dailyToMaxLimit": "0",
"dailyToMinLimit": "0",
"disableFrom": false,
"disableTo": false,
"fullName": "AGLD",
"icon": "https://s1.bycsi.com/app/assets/token/71c30ccc0c343d2f6ab830262ac7a5f8.svg",
"iconNight": "https://s1.bycsi.com/app/assets/token/8eb2f2348cc917081be4c28b797bd930.svg",
"singleFromMaxLimit": "15200",
"singleFromMinLimit": "0.015",
"singleToMaxLimit": "0",
"singleToMinLimit": "0",
"timePeriod": 0,
"uBalance": ""
},
{
"accuracyLength": 8,
"balance": "",
"coin": "AGLA",
"coinType": "crypto",
"dailyFromMaxLimit": "0",
"dailyFromMinLimit": "0",
"dailyToMaxLimit": "0",
"dailyToMinLimit": "0",
"disableFrom": false,
"disableTo": false,
"fullName": "AGLA",
"icon": "https://s1.bycsi.com/app/assets/token/1857d5971bed49d9b411032f54243128.svg",
"iconNight": "https://s1.bycsi.com/app/assets/token/5bc128beb4db5e6091b3c4211e7e4c4e.svg",
"singleFromMaxLimit": "9907900",
"singleFromMinLimit": "10",
"singleToMaxLimit": "0",
"singleToMinLimit": "0",
"timePeriod": 0,
"uBalance": ""
},
{
"accuracyLength": 8,
"balance": "",
"coin": "NEAR",
"coinType": "crypto",
"dailyFromMaxLimit": "0",
"dailyFromMinLimit": "0",
"dailyToMaxLimit": "0",
"dailyToMinLimit": "0",
"disableFrom": false,
"disableTo": false,
"fullName": "NEAR",
"icon": "https://www.bybit.com/bycsi-root/app/assets/token/3fafab371c0c1e42362813593034ee42.svg",
"iconNight": "https://www.bybit.com/bycsi-root/app/assets/token/3fafab371c0c1e42362813593034ee42.svg",
"singleFromMaxLimit": "4000",
"singleFromMinLimit": "0.004",
"singleToMaxLimit": "0",
"singleToMinLimit": "0",
"timePeriod": 0,
"uBalance": ""
},
{
"accuracyLength": 8,
"balance": "",
"coin": "AIOZ",
"coinType": "crypto",
"dailyFromMaxLimit": "0",
"dailyFromMinLimit": "0",
"dailyToMaxLimit": "0",
"dailyToMinLimit": "0",
"disableFrom": false,
"disableTo": false,
"fullName": "AIOZ",
"icon": "https://s1.bycsi.com/app/assets/token/603ff078bc4c032bc93bbcba375008ac.svg",
"iconNight": "https://s1.bycsi.com/app/assets/token/1ff40bb0bdcaae69fdc0cdb27b4d769d.svg",
"singleFromMaxLimit": "30400",
"singleFromMinLimit": "0.03",
"singleToMaxLimit": "0",
"singleToMinLimit": "0",
"timePeriod": 0,
"uBalance": ""
},
{
"accuracyLength": 8,
"balance": "",
"coin": "WLD",
"coinType": "crypto",
"dailyFromMaxLimit": "0",
"dailyFromMinLimit": "0",
"dailyToMaxLimit": "0",
"dailyToMinLimit": "0",
"disableFrom": false,
"disableTo": false,
"fullName": "WLD",
"icon": "https://s1.bycsi.com/app/assets/token/ba1f955e2b609920ebc2fce92feaa006.svg",
"iconNight": "https://s1.bycsi.com/app/assets/token/ba1f955e2b609920ebc2fce92feaa006.svg",
"singleFromMaxLimit": "7000",
"singleFromMinLimit": "0.007",
"singleToMaxLimit": "0",
"singleToMinLimit": "0",
"timePeriod": 0,
"uBalance": ""
}
]
},
"retCode": 0,
"retExtInfo": {},
"retMsg": "ok",
"time": 1758256916514
},
"queryString": "accountType=eb_convert_uta\u0026coin=USDT\u0026side=1",
"bodyParams": "",
"headers": {
"Content-Type": [
"application/x-www-form-urlencoded"
],
"X-Bapi-Api-Key": [
""
],
"X-Bapi-Recv-Window": [
"5000"
],
"X-Bapi-Sign": [
"3b10c29cba3b135d12d1ebda5f752976c84c3f443350f3719e517a72cb262292"
],
"X-Bapi-Timestamp": [
"1758259225338"
]
}
},
{
"data": {
"result": {
"coins": [
{
"accuracyLength": 8,
"balance": "0",
"coin": "AGLD",
"coinType": "crypto",
"dailyFromMaxLimit": "0",
"dailyFromMinLimit": "0",
"dailyToMaxLimit": "0",
"dailyToMinLimit": "0",
"disableFrom": false,
"disableTo": false,
"fullName": "AGLD",
"icon": "https://s1.bycsi.com/app/assets/token/71c30ccc0c343d2f6ab830262ac7a5f8.svg",
"iconNight": "https://s1.bycsi.com/app/assets/token/8eb2f2348cc917081be4c28b797bd930.svg",
"singleFromMaxLimit": "15200",
"singleFromMinLimit": "0.015",
"singleToMaxLimit": "0",
"singleToMinLimit": "0",
"timePeriod": 0,
"uBalance": "0"
},
{
"accuracyLength": 8,
"balance": "",
"coin": "AGLA",
"coinType": "crypto",
"dailyFromMaxLimit": "0",
"dailyFromMinLimit": "0",
"dailyToMaxLimit": "0",
"dailyToMinLimit": "0",
"disableFrom": false,
"disableTo": false,
"fullName": "AGLA",
"icon": "https://s1.bycsi.com/app/assets/token/1857d5971bed49d9b411032f54243128.svg",
"iconNight": "https://s1.bycsi.com/app/assets/token/5bc128beb4db5e6091b3c4211e7e4c4e.svg",
"singleFromMaxLimit": "9907900",
"singleFromMinLimit": "10",
"singleToMaxLimit": "0",
"singleToMinLimit": "0",
"timePeriod": 0,
"uBalance": ""
},
{
"accuracyLength": 8,
"balance": "0",
"coin": "NEAR",
"coinType": "crypto",
"dailyFromMaxLimit": "0",
"dailyFromMinLimit": "0",
"dailyToMaxLimit": "0",
"dailyToMinLimit": "0",
"disableFrom": false,
"disableTo": false,
"fullName": "NEAR",
"icon": "https://www.bybit.com/bycsi-root/app/assets/token/3fafab371c0c1e42362813593034ee42.svg",
"iconNight": "https://www.bybit.com/bycsi-root/app/assets/token/3fafab371c0c1e42362813593034ee42.svg",
"singleFromMaxLimit": "4000",
"singleFromMinLimit": "0.004",
"singleToMaxLimit": "0",
"singleToMinLimit": "0",
"timePeriod": 0,
"uBalance": "0"
},
{
"accuracyLength": 8,
"balance": "0.0052",
"coin": "AIOZ",
"coinType": "crypto",
"dailyFromMaxLimit": "0",
"dailyFromMinLimit": "0",
"dailyToMaxLimit": "0",
"dailyToMinLimit": "0",
"disableFrom": false,
"disableTo": false,
"fullName": "AIOZ",
"icon": "https://s1.bycsi.com/app/assets/token/603ff078bc4c032bc93bbcba375008ac.svg",
"iconNight": "https://s1.bycsi.com/app/assets/token/1ff40bb0bdcaae69fdc0cdb27b4d769d.svg",
"singleFromMaxLimit": "30400",
"singleFromMinLimit": "0.03",
"singleToMaxLimit": "0",
"singleToMinLimit": "0",
"timePeriod": 0,
"uBalance": "0.0017732"
},
{
"accuracyLength": 8,
"balance": "0",
"coin": "WLD",
"coinType": "crypto",
"dailyFromMaxLimit": "0",
"dailyFromMinLimit": "0",
"dailyToMaxLimit": "0",
"dailyToMinLimit": "0",
"disableFrom": false,
"disableTo": false,
"fullName": "WLD",
"icon": "https://s1.bycsi.com/app/assets/token/ba1f955e2b609920ebc2fce92feaa006.svg",
"iconNight": "https://s1.bycsi.com/app/assets/token/ba1f955e2b609920ebc2fce92feaa006.svg",
"singleFromMaxLimit": "7000",
"singleFromMinLimit": "0.007",
"singleToMaxLimit": "0",
"singleToMinLimit": "0",
"timePeriod": 0,
"uBalance": "0"
}
]
},
"retCode": 0,
"retExtInfo": {},
"retMsg": "ok",
"time": 1758256841574
},
"queryString": "accountType=eb_convert_uta\u0026side=0",
"bodyParams": "",
"headers": {
"Content-Type": [
"application/x-www-form-urlencoded"
],
"X-Bapi-Api-Key": [
""
],
"X-Bapi-Recv-Window": [
"5000"
],
"X-Bapi-Sign": [
"3e2ae23929ba5d2855dc20cb0ad81d6b975411ffc727ce52f3f9e21c966fbe0e"
],
"X-Bapi-Timestamp": [
"1758259225549"
]
}
}
]
},
"/v5/asset/exchange/query-convert-history": {
"GET": [
{
"data": {
"result": {
"list": [
{
"accountType": "eb_convert_uta",
"convertRate": "0.35585068565",
"createdAt": "1754880224953",
"exchangeStatus": "success",
"exchangeTxId": "104231555340214158196736",
"extInfo": {},
"fromAmount": "7.9952",
"fromCoin": "UXLINK",
"fromCoinType": "crypto",
"toAmount": "2.84509740190888",
"toCoin": "USDT",
"toCoinType": "crypto",
"userId": "74199870"
},
{
"accountType": "eb_convert_uta",
"convertRate": "0.76800370605",
"createdAt": "1754880224437",
"exchangeStatus": "success",
"exchangeTxId": "10164127555340212031606784",
"extInfo": {},
"fromAmount": "1.002",
"fromCoin": "ME",
"fromCoinType": "crypto",
"toAmount": "0.7695397134621",
"toCoin": "USDT",
"toCoinType": "crypto",
"userId": "74199870"
},
{
"accountType": "eb_convert_uta",
"convertRate": "1.470836214",
"createdAt": "1754880223928",
"exchangeStatus": "success",
"exchangeTxId": "101214251555340209864749056",
"extInfo": {},
"fromAmount": "1.9984",
"fromCoin": "VIRTUAL",
"fromCoinType": "crypto",
"toAmount": "2.9393190900576",
"toCoin": "USDT",
"toCoinType": "crypto",
"userId": "74199870"
},
{
"accountType": "eb_convert_uta",
"convertRate": "0.046765815395",
"createdAt": "1754528298061",
"exchangeStatus": "success",
"exchangeTxId": "10414247553864074960678912",
"extInfo": {},
"fromAmount": "49.96",
"fromCoin": "PIRATE",
"fromCoinType": "crypto",
"toAmount": "2.3364201371342",
"toCoin": "USDT",
"toCoinType": "crypto",
"userId": "74199870"
}
]
},
"retCode": 0,
"retExtInfo": {},
"retMsg": "ok",
"time": 1758260485735
},
"queryString": "accountType=eb_convert_uta",
"bodyParams": "",
"headers": {
"Content-Type": [
"application/x-www-form-urlencoded"
],
"X-Bapi-Api-Key": [
""
],
"X-Bapi-Recv-Window": [
"5000"
],
"X-Bapi-Sign": [
"d48d942ee6ed14a088beb153bc0d069048834a983ddf1258b3512e90a5980ffb"
],
"X-Bapi-Timestamp": [
"1758260485632"
]
}
}
]
},
"/v5/asset/exchange/quote-apply": {
"POST": [
{
"data": {
"result": {
"exchangeRate": "2.848499537750000000",
"expiredTime": "1758693506127",
"extTaxAndFee": [],
"fromAmount": "0.0088",
"fromCoin": "XRP",
"fromCoinType": "crypto",
"quoteTxId": "10175108571334212336947200",
"requestId": "",
"toAmount": "0.0250667959322",
"toCoin": "USDT",
"toCoinType": "crypto"
},
"retCode": 0,
"retExtInfo": {},
"retMsg": "ok",
"time": 1758693491155
},
"queryString": "",
"bodyParams": "{\"accountType\":\"eb_convert_uta\",\"fromCoin\":\"XRP\",\"toCoin\":\"USDT\",\"requestAmount\":\"0.0088\",\"requestCoin\":\"XRP\"}",
"headers": {
"Content-Type": [
"application/json"
],
"X-Bapi-Api-Key": [
""
],
"X-Bapi-Recv-Window": [
"5000"
],
"X-Bapi-Sign": [
"48767e64f637c452bc48304d368f214a80ff31aa3b014e60eb7f384b18b25240"
],
"X-Bapi-Timestamp": [
"1758693491006"
]
}
}
]
},
"/v5/asset/settlement-record": {
"GET": [
{