Files
explorer-monorepo/backend/bridge/lifi_provider.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
}