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 }