import { Events, type IPortalApi, generateTraceId, sdkLogger, } from '@portal-hq/utils' import type { ZeroExPriceRequest, ZeroExPriceResponse, ZeroExQuoteRequest, ZeroExQuoteResponse, ZeroExSourcesResponse, } from '../../../types' const ZERO_X_BASE_PATH = '/api/v3/clients/me/integrations/0x/swap' const ZeroXEndpoint = { sources: `${ZERO_X_BASE_PATH}/sources`, quote: `${ZERO_X_BASE_PATH}/quote`, price: `${ZERO_X_BASE_PATH}/price`, } as const export interface ZeroXApiKeyOptions { zeroXApiKey?: string } export interface IPortalZeroXApi { getSources( chainId: string, options?: ZeroXApiKeyOptions, ): Promise getQuote( request: ZeroExQuoteRequest, options?: ZeroXApiKeyOptions, ): Promise getPrice( request: ZeroExPriceRequest, options?: ZeroXApiKeyOptions, ): Promise } export interface PortalZeroXApiOptions { api: IPortalApi } export class PortalZeroXApi implements IPortalZeroXApi { private readonly api: IPortalApi constructor(options: PortalZeroXApiOptions) { this.api = options.api } public async getSources( chainId: string, options?: ZeroXApiKeyOptions, ): Promise { const path = ZeroXEndpoint.sources const body = this.appendApiKey({ chainId }, options) const response = await this.post( path, body, ) await this.trackEvent(Events.ZeroXGetSources, { path, chainId, hasZeroXApiKey: Boolean(options?.zeroXApiKey), }) return response } public async getQuote( request: ZeroExQuoteRequest, options?: ZeroXApiKeyOptions, ): Promise { const { chainId, ...requestBody } = request const path = ZeroXEndpoint.quote const body = this.appendApiKey({ ...requestBody, chainId }, options) const response = await this.post( path, body, ) await this.trackEvent(Events.ZeroXGetQuote, { path, chainId, buyToken: request.buyToken, sellToken: request.sellToken, hasZeroXApiKey: Boolean(options?.zeroXApiKey), }) return response } public async getPrice( request: ZeroExPriceRequest, options?: ZeroXApiKeyOptions, ): Promise { const { chainId, ...requestBody } = request const path = ZeroXEndpoint.price const body = this.appendApiKey({ ...requestBody, chainId }, options) const response = await this.post( path, body, ) await this.trackEvent(Events.ZeroXGetPrice, { path, chainId, buyToken: request.buyToken, sellToken: request.sellToken, hasZeroXApiKey: Boolean(options?.zeroXApiKey), }) return response } 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( `[PortalZeroXApi] Failed to track event "${event}":`, error, ) } } private appendApiKey( body: B, options?: ZeroXApiKeyOptions, ) { if (options?.zeroXApiKey) { return { ...body, zeroXApiKey: options.zeroXApiKey } } return body } } export default PortalZeroXApi