import { Events, type IPortalApi, generateTraceId, sdkLogger, } from '@portal-hq/utils' import type { LifiQuoteRequest, LifiQuoteResponse, LifiRoutesRequest, LifiRoutesResponse, LifiStatusRequest, LifiStatusResponse, LifiStepTransactionRequest, LifiStepTransactionResponse, } from '../../../types' const LifiEndpoint = { BASE_PATH: '/api/v3/clients/me/integrations/lifi', routes(): string { return `${this.BASE_PATH}/routes` }, quote(): string { return `${this.BASE_PATH}/quote` }, status(): string { return `${this.BASE_PATH}/status` }, routeStepDetails(): string { return `${this.BASE_PATH}/route-step-details` }, } as const export interface IPortalLifiTradingApi { getRoutes(request: LifiRoutesRequest): Promise getQuote(request: LifiQuoteRequest): Promise getStatus(request: LifiStatusRequest): Promise getRouteStep( request: LifiStepTransactionRequest, ): Promise } export interface PortalLifiTradingApiOptions { api: IPortalApi } export class PortalLifiTradingApi implements IPortalLifiTradingApi { private readonly api: IPortalApi constructor(options: PortalLifiTradingApiOptions) { this.api = options.api } public async getRoutes( request: LifiRoutesRequest, ): Promise { const path = LifiEndpoint.routes() const response = await this.post( path, request, ) await this.trackEvent(Events.LifiGetRoutes, { path, fromChainId: request.fromChainId, toChainId: request.toChainId, fromTokenAddress: request.fromTokenAddress, toTokenAddress: request.toTokenAddress, }) return response } public async getQuote(request: LifiQuoteRequest): Promise { const path = LifiEndpoint.quote() const response = await this.post( path, request, ) await this.trackEvent(Events.LifiGetQuote, { path, fromChain: request.fromChain, toChain: request.toChain, fromToken: request.fromToken, toToken: request.toToken, fromAddress: request.fromAddress, }) return response } public async getStatus( request: LifiStatusRequest, ): Promise { const queryParams = this.buildQueryParams({ txHash: request.txHash, bridge: request.bridge, fromChain: request.fromChain, toChain: request.toChain, }) const path = queryParams ? `${LifiEndpoint.status()}?${queryParams}` : LifiEndpoint.status() const response = await this.get(path) await this.trackEvent(Events.LifiGetStatus, { path: LifiEndpoint.status(), txHash: request.txHash, bridge: request.bridge, fromChain: request.fromChain, toChain: request.toChain, }) return response } public async getRouteStep( request: LifiStepTransactionRequest, ): Promise { const path = LifiEndpoint.routeStepDetails() const response = await this.post< LifiStepTransactionResponse, LifiStepTransactionRequest >(path, request) await this.trackEvent(Events.LifiGetRouteStep, { path, stepId: request.id, stepType: request.type, tool: request.tool, }) return response } private buildQueryParams(params: Record): string { const queryParts: string[] = [] for (const [key, value] of Object.entries(params)) { if (value !== undefined && value !== null) { const encodedValue = encodeURIComponent(String(value)) queryParts.push(`${key}=${encodedValue}`) } } return queryParts.join('&') } private async get(path: string): Promise { return this.api.requests.get(path, { headers: { Authorization: `Bearer ${this.api.apiKey}`, Accept: 'application/json', }, traceId: generateTraceId(), }) } private async post(path: string, body: B): Promise { return this.api.requests.post(path, { headers: { Authorization: `Bearer ${this.api.apiKey}`, Accept: 'application/json', 'Content-Type': 'application/json', }, body, traceId: generateTraceId(), }) } private async trackEvent( event: string, properties: Record, ): Promise { try { await this.api.track(event, properties) } catch (error) { sdkLogger.warn( `[PortalLifiTradingApi] Failed to track event "${event}":`, error, ) } } } export default PortalLifiTradingApi