Skip to content

Commit 24e6e01

Browse files
committed
backend/swap: add implementation of track.
1 parent 03e5f0a commit 24e6e01

File tree

4 files changed

+117
-9
lines changed

4 files changed

+117
-9
lines changed

backend/handlers/handlers.go

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,7 @@ func NewHandlers(
251251
getAPIRouterNoError(apiRouter)("/market/btcdirect/info/{action}/{code}", handlers.getMarketBtcDirectInfo).Methods("GET")
252252
getAPIRouterNoError(apiRouter)("/swap/quote", handlers.getSwapkitQuote).Methods("GET")
253253
getAPIRouterNoError(apiRouter)("/swap/execute", handlers.swapkitSwap).Methods("GET")
254+
getAPIRouterNoError(apiRouter)("/swap/track", handlers.swapkitTrack).Methods("GET")
254255
getAPIRouter(apiRouter)("/market/moonpay/buy-info/{code}", handlers.getMarketMoonpayBuyInfo).Methods("GET")
255256
getAPIRouterNoError(apiRouter)("/market/pocket/api-url/{action}", handlers.getMarketPocketURL).Methods("GET")
256257
getAPIRouterNoError(apiRouter)("/market/pocket/verify-address", handlers.postPocketWidgetVerifyAddress).Methods("POST")
@@ -1704,9 +1705,9 @@ func (handlers *Handlers) getSwapkitQuote(r *http.Request) interface{} {
17041705

17051706
func (handlers *Handlers) swapkitSwap(r *http.Request) interface{} {
17061707
type result struct {
1707-
Success bool `json:"success"`
1708-
Error string `json:"error,omitempty"`
1709-
Swap swapkit.SwapResponse `json:"swap,omitempty"`
1708+
Success bool `json:"success"`
1709+
Error string `json:"error,omitempty"`
1710+
Swap *swapkit.SwapResponse `json:"swap,omitempty"`
17101711
}
17111712

17121713
var request swapkit.SwapRequest
@@ -1730,3 +1731,32 @@ func (handlers *Handlers) swapkitSwap(r *http.Request) interface{} {
17301731
}
17311732

17321733
}
1734+
1735+
func (handlers *Handlers) swapkitTrack(r *http.Request) interface{} {
1736+
type result struct {
1737+
Success bool `json:"success"`
1738+
Error string `json:"error,omitempty"`
1739+
Track *swapkit.TrackResponse `json:"swap,omitempty"`
1740+
}
1741+
1742+
var request swapkit.TrackRequest
1743+
1744+
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
1745+
return result{Success: false, Error: err.Error()}
1746+
}
1747+
1748+
s := swapkit.NewClient("0722e09f-9d3f-4817-a870-069848d03ee9")
1749+
trackResponse, err := s.Track(context.Background(), &request)
1750+
if err != nil {
1751+
return result{
1752+
Success: false,
1753+
Error: err.Error(),
1754+
}
1755+
}
1756+
1757+
return result{
1758+
Success: true,
1759+
Track: trackResponse,
1760+
}
1761+
1762+
}

backend/market/swapkit/client.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,18 @@ import (
88
"io"
99
"net/http"
1010
"time"
11+
12+
"github.com/BitBoxSwiss/bitbox-wallet-app/util/logging"
13+
"github.com/sirupsen/logrus"
1114
)
1215

16+
// Client is a SwapKit client that can be used to interact with
17+
// SwapKit API.
1318
type Client struct {
1419
apiKey string
1520
baseURL string
1621
httpClient *http.Client
22+
log *logrus.Entry
1723
}
1824

1925
func NewClient(apiKey string) *Client {
@@ -23,6 +29,7 @@ func NewClient(apiKey string) *Client {
2329
httpClient: &http.Client{
2430
Timeout: 20 * time.Second,
2531
},
32+
log: logging.Get().WithGroup("swapkit"),
2633
}
2734
}
2835

@@ -46,7 +53,11 @@ func (c *Client) post(ctx context.Context, path string, body any, out any) error
4653
if err != nil {
4754
return fmt.Errorf("http error: %w", err)
4855
}
49-
defer resp.Body.Close()
56+
defer func() {
57+
if err := resp.Body.Close(); err != nil {
58+
c.log.WithError(err).Error("Error closing response body")
59+
}
60+
}()
5061

5162
bodyBytes, err := io.ReadAll(resp.Body)
5263
if err != nil {

backend/market/swapkit/methods.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,20 @@ func (c *Client) Quote(ctx context.Context, req *QuoteRequest) (*QuoteResponse,
1313
return &resp, nil
1414
}
1515

16+
// Swap performs a SwapKit V3 swap request.
1617
func (c *Client) Swap(ctx context.Context, req *SwapRequest) (*SwapResponse, error) {
1718
var resp SwapResponse
1819
if err := c.post(ctx, "/swap", req, &resp); err != nil {
1920
return nil, err
2021
}
2122
return &resp, nil
2223
}
24+
25+
// Track performs a SwapKit track request.
26+
func (c *Client) Track(ctx context.Context, req *TrackRequest) (*TrackResponse, error) {
27+
var resp TrackResponse
28+
if err := c.post(ctx, "/track", req, &resp); err != nil {
29+
return nil, err
30+
}
31+
return &resp, nil
32+
}

backend/market/swapkit/types.go

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package swapkit
22

33
import "encoding/json"
44

5+
// QuoteRequest represents a request to swapkit for a swap quote.
56
type QuoteRequest struct {
67
SellAsset string `json:"sellAsset"`
78
BuyAsset string `json:"buyAsset"`
@@ -13,6 +14,7 @@ type QuoteRequest struct {
1314
MaxExecutionTime *int `json:"maxExecutionTime,omitempty"`
1415
}
1516

17+
// SwapRequest represents a request to swakip to execute a swap.
1618
type SwapRequest struct {
1719
RouteID string `json:"routeId"`
1820
SourceAddress string `json:"sourceAddress"`
@@ -25,13 +27,15 @@ type SwapRequest struct {
2527
OverrideSlippage *bool `json:"overrideSlippage,omitempty"`
2628
}
2729

30+
// QuoteResponse contains info about swaps' quotes.
2831
type QuoteResponse struct {
29-
QuoteID string `json:"quoteId"`
30-
Routes []QuoteRoute `json:"routes"`
31-
ProviderErrors []QuoteError `json:"providerErrors,omitempty"`
32-
Error string `json:"error,omitempty"`
32+
QuoteID string `json:"quoteId"`
33+
Routes []QuoteRoute `json:"routes"`
34+
ProviderErrors []ProviderError `json:"providerErrors,omitempty"`
35+
Error string `json:"error,omitempty"`
3336
}
3437

38+
// SwapResponse is the answer provided by swapkit when asking to execute a swap.
3539
type SwapResponse struct {
3640
RouteID string `json:"routeId"`
3741
Providers []string `json:"providers"`
@@ -53,6 +57,8 @@ type SwapResponse struct {
5357
NextActions []NextAction `json:"nextActions,omitempty"`
5458
}
5559

60+
// QuoteRoute represent a single route to swap coins from
61+
// SellAsset to BuyAsset.
5662
type QuoteRoute struct {
5763
RouteID string `json:"routeId"`
5864
Providers []string `json:"providers"`
@@ -81,6 +87,7 @@ type QuoteRoute struct {
8187
NextActions []NextAction `json:"nextActions,omitempty"`
8288
}
8389

90+
// Fee represents one of the possible fees for executing a swap.
8491
type Fee struct {
8592
Type string `json:"type"`
8693
Amount string `json:"amount"`
@@ -89,14 +96,64 @@ type Fee struct {
8996
Protocol string `json:"protocol"`
9097
}
9198

99+
// NextAction is provided by swap as a convenience field to suggest what
100+
// the next step in a swap workflow could be.
92101
type NextAction struct {
93102
Method string `json:"method"`
94103
URL string `json:"url"`
95104
Payload json.RawMessage `json:"payload,omitempty"`
96105
}
97106

98-
type QuoteError struct {
107+
// ProviderError contains errors specific to a Provider
108+
// (e.g. some provided will only provide quotes for sell amounts
109+
// higher than a certain treshold).
110+
type ProviderError struct {
99111
Provider string `json:"provider"`
100112
ErrorCode string `json:"errorCode"`
101113
Message string `json:"message"`
102114
}
115+
116+
// TrackRequest is used to query swapkit fo track the status of a swap.
117+
type TrackRequest struct {
118+
Hash string `json:"hash"`
119+
ChainID string `json:"chainId"`
120+
}
121+
122+
// TrackResponse represents SwapKit's response for a tracked transaction
123+
type TrackResponse struct {
124+
ChainID string `json:"chainId"`
125+
Hash string `json:"hash"`
126+
Block int64 `json:"block"`
127+
Type string `json:"type"` // swap, token_transfer, etc.
128+
Status string `json:"status"` // not_started, pending, swapping, completed, refunded, failed, unknown
129+
TrackingStatus string `json:"trackingStatus"` // deprecated, status is enough
130+
FromAsset string `json:"fromAsset"`
131+
FromAmount string `json:"fromAmount"`
132+
FromAddress string `json:"fromAddress"`
133+
ToAsset string `json:"toAsset"`
134+
ToAmount string `json:"toAmount"`
135+
ToAddress string `json:"toAddress"`
136+
FinalisedAt int64 `json:"finalisedAt"` // UNIX timestamp
137+
Meta json.RawMessage `json:"meta,omitempty"` // provider, images, etc.
138+
Payload json.RawMessage `json:"payload,omitempty"` // transaction-specific info
139+
Legs []TrackLeg `json:"legs,omitempty"` // individual steps in transaction
140+
}
141+
142+
// TrackLeg represents a step of the transaction
143+
type TrackLeg struct {
144+
ChainID string `json:"chainId"`
145+
Hash string `json:"hash"`
146+
Block int64 `json:"block"`
147+
Type string `json:"type"`
148+
Status string `json:"status"`
149+
TrackingStatus string `json:"trackingStatus"`
150+
FromAsset string `json:"fromAsset"`
151+
FromAmount string `json:"fromAmount"`
152+
FromAddress string `json:"fromAddress"`
153+
ToAsset string `json:"toAsset"`
154+
ToAmount string `json:"toAmount"`
155+
ToAddress string `json:"toAddress"`
156+
FinalisedAt int64 `json:"finalisedAt"`
157+
Meta json.RawMessage `json:"meta,omitempty"`
158+
Payload json.RawMessage `json:"payload,omitempty"`
159+
}

0 commit comments

Comments
 (0)