176 lines
4.5 KiB
Go
176 lines
4.5 KiB
Go
package bridge
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
lifiAPIBase = "https://li.quest"
|
|
lifiTimeout = 10 * time.Second
|
|
)
|
|
|
|
// LiFi-supported chain IDs for SupportsRoute (subset of Li.Fi's 40+ chains)
|
|
var lifiSupportedChains = map[int]bool{
|
|
1: true, // Ethereum Mainnet
|
|
137: true, // Polygon
|
|
10: true, // Optimism
|
|
8453: true, // Base
|
|
42161: true, // Arbitrum One
|
|
56: true, // BNB Chain
|
|
43114: true, // Avalanche
|
|
100: true, // Gnosis Chain
|
|
42220: true, // Celo
|
|
324: true, // zkSync Era
|
|
59144: true, // Linea
|
|
5000: true, // Mantle
|
|
534352: true, // Scroll
|
|
25: true, // Cronos
|
|
250: true, // Fantom
|
|
1111: true, // Wemix
|
|
}
|
|
|
|
// lifiQuoteResponse represents the Li.Fi API quote response structure
|
|
type lifiQuoteResponse struct {
|
|
ID string `json:"id"`
|
|
Type string `json:"type"`
|
|
Tool string `json:"tool"`
|
|
Estimate *struct {
|
|
FromAmount string `json:"fromAmount"`
|
|
ToAmount string `json:"toAmount"`
|
|
ToAmountMin string `json:"toAmountMin"`
|
|
} `json:"estimate"`
|
|
IncludedSteps []struct {
|
|
Type string `json:"type"`
|
|
Tool string `json:"tool"`
|
|
Estimate *struct {
|
|
FromAmount string `json:"fromAmount"`
|
|
ToAmount string `json:"toAmount"`
|
|
} `json:"estimate"`
|
|
} `json:"includedSteps"`
|
|
}
|
|
|
|
// LiFiProvider implements Provider for Li.Fi bridge aggregator
|
|
type LiFiProvider struct {
|
|
apiBase string
|
|
client *http.Client
|
|
}
|
|
|
|
// NewLiFiProvider creates a new Li.Fi bridge provider
|
|
func NewLiFiProvider() *LiFiProvider {
|
|
return &LiFiProvider{
|
|
apiBase: lifiAPIBase,
|
|
client: &http.Client{
|
|
Timeout: lifiTimeout,
|
|
},
|
|
}
|
|
}
|
|
|
|
// Name returns the provider name
|
|
func (p *LiFiProvider) Name() string {
|
|
return "LiFi"
|
|
}
|
|
|
|
// SupportsRoute returns true if Li.Fi supports the fromChain->toChain route
|
|
func (p *LiFiProvider) SupportsRoute(fromChain, toChain int) bool {
|
|
return lifiSupportedChains[fromChain] && lifiSupportedChains[toChain]
|
|
}
|
|
|
|
// GetQuote fetches a bridge quote from the Li.Fi API
|
|
func (p *LiFiProvider) GetQuote(ctx context.Context, req *BridgeRequest) (*BridgeQuote, error) {
|
|
if req.Recipient == "" {
|
|
return nil, fmt.Errorf("recipient address required for Li.Fi")
|
|
}
|
|
|
|
params := url.Values{}
|
|
params.Set("fromChain", strconv.Itoa(req.FromChain))
|
|
params.Set("toChain", strconv.Itoa(req.ToChain))
|
|
params.Set("fromToken", req.FromToken)
|
|
params.Set("toToken", req.ToToken)
|
|
params.Set("fromAmount", req.Amount)
|
|
params.Set("fromAddress", req.Recipient)
|
|
params.Set("toAddress", req.Recipient)
|
|
params.Set("integrator", "explorer-bridge-aggregator")
|
|
|
|
apiURL := fmt.Sprintf("%s/v1/quote?%s", p.apiBase, params.Encode())
|
|
|
|
httpReq, err := http.NewRequestWithContext(ctx, http.MethodGet, apiURL, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resp, err := p.client.Do(httpReq)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
body, _ := io.ReadAll(resp.Body)
|
|
return nil, fmt.Errorf("Li.Fi API error %d: %s", resp.StatusCode, string(body))
|
|
}
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var lifiResp lifiQuoteResponse
|
|
if err := json.Unmarshal(body, &lifiResp); err != nil {
|
|
return nil, fmt.Errorf("failed to parse Li.Fi response: %w", err)
|
|
}
|
|
|
|
if lifiResp.Estimate == nil {
|
|
return nil, fmt.Errorf("Li.Fi response missing estimate")
|
|
}
|
|
|
|
toAmount := lifiResp.Estimate.ToAmount
|
|
if toAmount == "" && len(lifiResp.IncludedSteps) > 0 && lifiResp.IncludedSteps[len(lifiResp.IncludedSteps)-1].Estimate != nil {
|
|
toAmount = lifiResp.IncludedSteps[len(lifiResp.IncludedSteps)-1].Estimate.ToAmount
|
|
}
|
|
if toAmount == "" {
|
|
return nil, fmt.Errorf("Li.Fi response missing toAmount")
|
|
}
|
|
|
|
route := make([]BridgeStep, 0, len(lifiResp.IncludedSteps))
|
|
for _, step := range lifiResp.IncludedSteps {
|
|
stepType := "bridge"
|
|
if step.Type == "swap" {
|
|
stepType = "swap"
|
|
} else if step.Type == "cross" {
|
|
stepType = "bridge"
|
|
}
|
|
route = append(route, BridgeStep{
|
|
Provider: step.Tool,
|
|
From: strconv.Itoa(req.FromChain),
|
|
To: strconv.Itoa(req.ToChain),
|
|
Type: stepType,
|
|
})
|
|
}
|
|
if len(route) == 0 {
|
|
route = append(route, BridgeStep{
|
|
Provider: lifiResp.Tool,
|
|
From: strconv.Itoa(req.FromChain),
|
|
To: strconv.Itoa(req.ToChain),
|
|
Type: lifiResp.Type,
|
|
})
|
|
}
|
|
|
|
return &BridgeQuote{
|
|
Provider: "LiFi",
|
|
FromChain: req.FromChain,
|
|
ToChain: req.ToChain,
|
|
FromAmount: req.Amount,
|
|
ToAmount: toAmount,
|
|
Fee: "0",
|
|
EstimatedTime: "1-5 min",
|
|
Route: route,
|
|
}, nil
|
|
}
|