diff --git a/exchanges/bybit/convert.go b/exchanges/bybit/convert.go new file mode 100644 index 00000000..e9ac3650 --- /dev/null +++ b/exchanges/bybit/convert.go @@ -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) +} diff --git a/exchanges/bybit/convert_test.go b/exchanges/bybit/convert_test.go new file mode 100644 index 00000000..3550fd23 --- /dev/null +++ b/exchanges/bybit/convert_test.go @@ -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]) + } +} diff --git a/exchanges/bybit/convert_types.go b/exchanges/bybit/convert_types.go new file mode 100644 index 00000000..90395265 --- /dev/null +++ b/exchanges/bybit/convert_types.go @@ -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"` +} diff --git a/exchanges/bybit/testdata/http.json b/exchanges/bybit/testdata/http.json index b2cdfdbc..b553b735 100644 --- a/exchanges/bybit/testdata/http.json +++ b/exchanges/bybit/testdata/http.json @@ -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": [ {