diff --git a/v2/futures/client.go b/v2/futures/client.go index 67ec79a4..34af7ddf 100644 --- a/v2/futures/client.go +++ b/v2/futures/client.go @@ -698,3 +698,23 @@ func (c *Client) NewGetFeeBurnService() *GetFeeBurnService { func (c *Client) NewFeeBurnService() *FeeBurnService { return &FeeBurnService{c: c} } + +// NewListConvertAssetsService init list convert assets service +func (c *Client) NewListConvertExchangeInfoService() *ListConvertExchangeInfoService { + return &ListConvertExchangeInfoService{c: c} +} + +// NewCreateConvertQuoteService init create convert quote service +func (c *Client) NewCreateConvertQuoteService() *CreateConvertQuoteService { + return &CreateConvertQuoteService{c: c} +} + +// NewCreateConvertService init accept convert quote service +func (c *Client) NewConvertAcceptService() *ConvertAcceptService { + return &ConvertAcceptService{c: c} +} + +// NewGetConvertStatusService init get convert status service +func (c *Client) NewGetConvertStatusService() *ConvertStatusService { + return &ConvertStatusService{c: c} +} diff --git a/v2/futures/convert_service.go b/v2/futures/convert_service.go new file mode 100644 index 00000000..9430a094 --- /dev/null +++ b/v2/futures/convert_service.go @@ -0,0 +1,238 @@ +package futures + +import ( + "context" + "encoding/json" + "net/http" +) + +type ListConvertExchangeInfoService struct { + c *Client + fromAsset string + toAsset string +} + +type ConvertExchangeInfo struct { + FromAsset string `json:"fromAsset"` + ToAsset string `json:"toAsset"` + FromAssetMinAmount string `json:"fromAssetMinAmount"` + FromAssetMaxAmount string `json:"fromAssetMaxAmount"` + ToAssetMinAmount string `json:"toAssetMinAmount"` + ToAssetMaxAmount string `json:"toAssetMaxAmount"` +} + +func (l *ListConvertExchangeInfoService) FromAsset(fromAsset string) *ListConvertExchangeInfoService { + l.fromAsset = fromAsset + return l +} + +func (l *ListConvertExchangeInfoService) ToAsset(toAsset string) *ListConvertExchangeInfoService { + l.toAsset = toAsset + return l +} + +func (l *ListConvertExchangeInfoService) Do(ctx context.Context, opts ...RequestOption) (res []ConvertExchangeInfo, err error) { + r := &request{ + method: http.MethodGet, + endpoint: "/fapi/v1/convert/exchangeInfo", + secType: secTypeNone, + } + if l.fromAsset != "" { + r.setParam("fromAsset", l.fromAsset) + } + if l.toAsset != "" { + r.setParam("toAsset", l.toAsset) + } + + data, _, err := l.c.callAPI(ctx, r, opts...) + if err != nil { + return nil, err + } + res = make([]ConvertExchangeInfo, 0, 50) + if err := json.Unmarshal(data, &res); err != nil { + return nil, err + } + return res, nil +} + +type ConvertValidTime string + +const ( + ConvertValidTime10S ConvertValidTime = "10s" + ConvertValidTime30S ConvertValidTime = "30s" + ConvertValidTime1M ConvertValidTime = "1m" + ConvertValidTime2M ConvertValidTime = "2m" +) + +type CreateConvertQuoteService struct { + c *Client + fromAsset string + toAsset string + fromAmount string + toAmount string + validTime ConvertValidTime +} + +func (c *CreateConvertQuoteService) FromAsset(fromAsset string) *CreateConvertQuoteService { + c.fromAsset = fromAsset + return c +} + +func (c *CreateConvertQuoteService) ToAsset(toAsset string) *CreateConvertQuoteService { + c.toAsset = toAsset + return c +} + +func (c *CreateConvertQuoteService) FromAmount(fromAmount string) *CreateConvertQuoteService { + c.fromAmount = fromAmount + return c +} + +func (c *CreateConvertQuoteService) ToAmount(toAmount string) *CreateConvertQuoteService { + c.toAmount = toAmount + return c +} + +func (c *CreateConvertQuoteService) ValidTime(validTime ConvertValidTime) *CreateConvertQuoteService { + c.validTime = validTime + return c +} + +type ConvertQuote struct { + QuoteId string `json:"quoteId"` + Ratio string `json:"ratio"` + InverseRatio string `json:"inverseRatio"` + ValidTimestamp int64 `json:"validTimestamp"` + ToAmount string `json:"toAmount"` + FromAmount string `json:"fromAmount"` +} + +func (c *CreateConvertQuoteService) Do(ctx context.Context, opts ...RequestOption) (res *ConvertQuote, err error) { + r := &request{ + method: http.MethodPost, + endpoint: "/fapi/v1/convert/getQuote", + secType: secTypeSigned, + } + m := params{ + "fromAsset": c.fromAsset, + "toAsset": c.toAsset, + } + if c.fromAmount != "" { + m["fromAmount"] = c.fromAmount + } + if c.toAmount != "" { + m["toAmount"] = c.toAmount + } + if c.validTime != "" { + m["validTime"] = c.validTime + } + r.setFormParams(m) + data, _, err := c.c.callAPI(ctx, r, opts...) + if err != nil { + return nil, err + } + res = new(ConvertQuote) + if err := json.Unmarshal(data, res); err != nil { + return nil, err + } + return res, nil +} + +type ConvertAcceptService struct { + c *Client + quoteId string +} + +func (c *ConvertAcceptService) QuoteId(quoteId string) *ConvertAcceptService { + c.quoteId = quoteId + return c +} + +type ConvertAcceptStatus string + +const ( + ConvertAcceptStatusProcess ConvertAcceptStatus = "PROCESS" + ConvertAcceptStatusAcceptSuccess ConvertAcceptStatus = "ACCEPT_SUCCESS" + ConvertAcceptStatusSuccess ConvertAcceptStatus = "SUCCESS" + ConvertAcceptStatusFailed ConvertAcceptStatus = "FAILED" +) + +type ConvertResult struct { + OrderId string `json:"orderId"` + CreateTime int64 `json:"createTime"` + OrderStatus ConvertAcceptStatus `json:"orderStatus"` +} + +func (c *ConvertAcceptService) Do(ctx context.Context, opts ...RequestOption) (res *ConvertResult, err error) { + r := &request{ + method: http.MethodPost, + endpoint: "/fapi/v1/convert/acceptQuote", + secType: secTypeSigned, + } + m := params{ + "quoteId": c.quoteId, + } + r.setFormParams(m) + data, _, err := c.c.callAPI(ctx, r, opts...) + if err != nil { + return nil, err + } + res = new(ConvertResult) + if err := json.Unmarshal(data, res); err != nil { + return nil, err + } + return res, nil +} + +type ConvertStatusService struct { + c *Client + quoteId string + orderId string +} + +func (c *ConvertStatusService) QuoteId(quoteId string) *ConvertStatusService { + c.quoteId = quoteId + return c +} + +func (c *ConvertStatusService) OrderId(orderId string) *ConvertStatusService { + c.orderId = orderId + return c +} + +type ConvertStatusResult struct { + OrderId string `json:"orderId"` + OrderStatus ConvertAcceptStatus `json:"orderStatus"` + FromAsset string `json:"fromAsset"` + FromAmount string `json:"fromAmount"` + ToAsset string `json:"toAsset"` + ToAmount string `json:"toAmount"` + Ratio string `json:"ratio"` + InverseRatio string `json:"inverseRatio"` + CreateTime int64 `json:"createTime"` +} + +func (c *ConvertStatusService) Do(ctx context.Context, opts ...RequestOption) (res *ConvertStatusResult, err error) { + r := &request{ + method: http.MethodGet, + endpoint: "/fapi/v1/convert/orderStatus", + secType: secTypeSigned, + } + m := params{ + "quoteId": c.quoteId, + } + if c.orderId != "" { + m["orderId"] = c.orderId + } + r.setParam("quoteId", c.quoteId) + r.setFormParams(m) + data, _, err := c.c.callAPI(ctx, r, opts...) + if err != nil { + return nil, err + } + res = new(ConvertStatusResult) + if err := json.Unmarshal(data, res); err != nil { + return nil, err + } + return res, nil +} diff --git a/v2/futures/convert_service_test.go b/v2/futures/convert_service_test.go new file mode 100644 index 00000000..8695aaa5 --- /dev/null +++ b/v2/futures/convert_service_test.go @@ -0,0 +1,138 @@ +package futures + +import ( + "testing" + + "github.com/stretchr/testify/suite" +) + +type convertServiceTestSuite struct { + baseTestSuite +} + +func TestConvertService(t *testing.T) { + suite.Run(t, new(convertServiceTestSuite)) +} + +func (s *convertServiceTestSuite) TestListConvertExchangeInfo() { + data := []byte(`[ + { + "fromAsset":"BTC", + "toAsset":"USDT", + "fromAssetMinAmount":"0.0004", + "fromAssetMaxAmount":"50", + "toAssetMinAmount":"20", + "toAssetMaxAmount":"2500000" + } +]`) + s.mockDo(data, nil) + defer s.assertDo() + + fromAsset := "BTC" + toAsset := "USDT" + s.assertReq(func(r *request) { + e := newRequest().setParams(params{ + "fromAsset": fromAsset, + "toAsset": toAsset, + }) + s.assertRequestEqual(e, r) + }) + + res, err := s.client.NewListConvertExchangeInfoService().FromAsset(fromAsset).ToAsset(toAsset).Do(newContext()) + s.r().NoError(err) + e := []ConvertExchangeInfo{{ + FromAsset: fromAsset, + ToAsset: toAsset, + FromAssetMinAmount: "0.0004", + FromAssetMaxAmount: "50", + ToAssetMinAmount: "20", + ToAssetMaxAmount: "2500000", + }} + s.assertConvertExchangeInfoEqual(e, res) +} + +func (s *convertServiceTestSuite) TestCreateConvertQuote() { + data := []byte(`{ + "quoteId":"12415572564", + "ratio":"38163.7", + "inverseRatio":"0.0000262", + "validTimestamp":1623319461670, + "toAmount":"3816.37", + "fromAmount":"0.1" +}`) + s.mockDo(data, nil) + defer s.assertDo() + + fromAsset := "BTC" + toAsset := "USDT" + fromAmount := "0.0004" + toAmount := "20" + res, err := s.client.NewCreateConvertQuoteService().FromAsset(fromAsset).ToAsset(toAsset).FromAmount(fromAmount).ToAmount(toAmount).Do(newContext()) + s.r().NoError(err) + s.r().Equal("12415572564", res.QuoteId) + s.r().Equal("38163.7", res.Ratio) + s.r().Equal("0.0000262", res.InverseRatio) + s.r().Equal(int64(1623319461670), res.ValidTimestamp) + s.r().Equal("3816.37", res.ToAmount) + s.r().Equal("0.1", res.FromAmount) +} + +func (s *convertServiceTestSuite) TestAcceptQuote() { + data := []byte(`{ + "orderId":"933256278426274426", + "createTime":1623381330472, + "orderStatus":"PROCESS" +}`) + s.mockDo(data, nil) + defer s.assertDo() + + quoteId := "12415572564" + res, err := s.client.NewConvertAcceptService().QuoteId(quoteId).Do(newContext()) + s.r().NoError(err) + s.r().Equal("933256278426274426", res.OrderId) + s.r().Equal(int64(1623381330472), res.CreateTime) + s.r().Equal(ConvertAcceptStatusProcess, res.OrderStatus) +} + +func (s *convertServiceTestSuite) TestGetConvertStatus() { + data := []byte(`{ + "orderId":"933256278426274426", + "orderStatus":"SUCCESS", + "fromAsset":"BTC", + "fromAmount":"0.00054414", + "toAsset":"USDT", + "toAmount":"20", + "ratio":"36755", + "inverseRatio":"0.00002721", + "createTime":1623381330472 +}`) + s.mockDo(data, nil) + defer s.assertDo() + + orderId := "933256278426274426" + res, err := s.client.NewGetConvertStatusService().OrderId(orderId).Do(newContext()) + s.r().NoError(err) + s.r().Equal("933256278426274426", res.OrderId) + s.r().Equal(ConvertAcceptStatusSuccess, res.OrderStatus) + s.r().Equal("BTC", res.FromAsset) + s.r().Equal("0.00054414", res.FromAmount) + s.r().Equal("USDT", res.ToAsset) + s.r().Equal("20", res.ToAmount) + s.r().Equal("36755", res.Ratio) + s.r().Equal("0.00002721", res.InverseRatio) + s.r().Equal(int64(1623381330472), res.CreateTime) + +} + +func (s *convertServiceTestSuite) assertConvertExchangeInfoEqual(e, a []ConvertExchangeInfo) { + r := s.r() + r.Len(a, len(e)) + for i := range e { + r.Equal(e[i].FromAsset, a[i].FromAsset, "FromAsset") + r.Equal(e[i].ToAsset, a[i].ToAsset, "ToAsset") + r.Equal(e[i].FromAssetMinAmount, a[i].FromAssetMinAmount, "FromAssetMinAmount") + r.Equal(e[i].FromAssetMaxAmount, a[i].FromAssetMaxAmount, "FromAssetMaxAmount") + r.Equal(e[i].ToAssetMinAmount, a[i].ToAssetMinAmount, "ToAssetMinAmount") + r.Equal(e[i].ToAssetMaxAmount, a[i].ToAssetMaxAmount, "ToAssetMaxAmount") + } +}