package rest import ( "context" "encoding/json" "fmt" "net/http" "strings" "time" ) func (s *Server) queryAIRoutes(ctx context.Context, query string) ([]map[string]any, string) { baseURL := strings.TrimSpace(firstNonEmptyEnv( "TOKEN_AGGREGATION_API_BASE", "TOKEN_AGGREGATION_URL", "TOKEN_AGGREGATION_BASE_URL", )) if baseURL == "" { return nil, "token aggregation api base url is not configured for ai route retrieval" } req, err := http.NewRequestWithContext(ctx, http.MethodGet, strings.TrimRight(baseURL, "/")+"/api/v1/routes/ingestion?fromChainId=138", nil) if err != nil { return nil, "unable to build token aggregation ai request" } client := &http.Client{Timeout: 6 * time.Second} resp, err := client.Do(req) if err != nil { return nil, "token aggregation live routes unavailable: " + err.Error() } defer resp.Body.Close() if resp.StatusCode >= 400 { return nil, fmt.Sprintf("token aggregation live routes returned %d", resp.StatusCode) } var payload struct { Routes []map[string]any `json:"routes"` } if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil { return nil, "unable to decode token aggregation live routes" } if len(payload.Routes) == 0 { return nil, "token aggregation returned no live routes" } matches := filterAIRouteMatches(payload.Routes, query) return matches, "" } func filterAIRouteMatches(routes []map[string]any, query string) []map[string]any { query = strings.ToLower(strings.TrimSpace(query)) matches := make([]map[string]any, 0, 6) for _, route := range routes { if query != "" && !routeMatchesQuery(route, query) { continue } trimmed := map[string]any{ "routeId": route["routeId"], "status": route["status"], "routeType": route["routeType"], "fromChainId": route["fromChainId"], "toChainId": route["toChainId"], "tokenInSymbol": route["tokenInSymbol"], "tokenOutSymbol": route["tokenOutSymbol"], "assetSymbol": route["assetSymbol"], "label": route["label"], "aggregatorFamilies": route["aggregatorFamilies"], "hopCount": route["hopCount"], "bridgeType": route["bridgeType"], "tags": route["tags"], } matches = append(matches, compactAnyMap(trimmed)) if len(matches) >= 6 { break } } if len(matches) == 0 { for _, route := range routes { trimmed := map[string]any{ "routeId": route["routeId"], "status": route["status"], "routeType": route["routeType"], "fromChainId": route["fromChainId"], "toChainId": route["toChainId"], "tokenInSymbol": route["tokenInSymbol"], "tokenOutSymbol": route["tokenOutSymbol"], "assetSymbol": route["assetSymbol"], "label": route["label"], "aggregatorFamilies": route["aggregatorFamilies"], } matches = append(matches, compactAnyMap(trimmed)) if len(matches) >= 4 { break } } } return matches } func normalizeHexString(value string) string { trimmed := strings.TrimSpace(strings.ToLower(value)) return strings.TrimPrefix(trimmed, "0x") } func routeMatchesQuery(route map[string]any, query string) bool { fields := []string{ stringValue(route["routeId"]), stringValue(route["routeType"]), stringValue(route["tokenInSymbol"]), stringValue(route["tokenOutSymbol"]), stringValue(route["assetSymbol"]), stringValue(route["label"]), } for _, field := range fields { if strings.Contains(strings.ToLower(field), query) { return true } } for _, value := range stringSliceValue(route["aggregatorFamilies"]) { if strings.Contains(strings.ToLower(value), query) { return true } } for _, value := range stringSliceValue(route["tags"]) { if strings.Contains(strings.ToLower(value), query) { return true } } for _, symbol := range []string{"cusdt", "cusdc", "cxauc", "ceurt", "usdt", "usdc", "weth"} { if strings.Contains(query, symbol) { if strings.Contains(strings.ToLower(strings.Join(fields, " ")), symbol) { return true } } } return false }