import { Address, Hex, PublicClient } from 'viem'; import { SwapQuoter, OrderBuilder, getFeePercentage, DutchOrder, } from '@prex0/prex-structs'; import { PrexApiService } from '../api'; import { PrexClient } from '../prex-client'; import { PagingOptions } from '../types'; import { querySwapHistory } from '../graph/swap-history'; interface QuoteSwapParams { tokenIn: Address; tokenOut: Address; amount: bigint; tradeType: 'EXACT_INPUT' | 'EXACT_OUTPUT'; swapper: Address; recipient: Address; slippageTolerance?: bigint; } export interface SwapParams { tokenIn: Address; tokenOut: Address; amount: bigint; tradeType: 'EXACT_INPUT' | 'EXACT_OUTPUT'; route: Hex; } export class SwapAction { private quoter: SwapQuoter; constructor( private client: PrexClient, private evmClient: PublicClient, private apiService: PrexApiService, public auctionDuration: number = 120, public decayDuration: number = 600 ) { this.quoter = new SwapQuoter(this.evmClient); } async getFeeTiers(chainId: number) { const config = await this.client.loadConfig(chainId); return config.feeTiers; } public async quoteSwap(params: QuoteSwapParams): Promise<{ quote: bigint; route: Hex; order: DutchOrder; }> { const chainId = this.evmClient.chain?.id; if (!chainId) { throw new Error('Chain ID not found'); } const feeTiers = await this.getFeeTiers(chainId); // TODO: update nonce const { amountCalculated, route } = await this.quoter.estimateSwap( params.tokenIn, params.tokenOut, params.amount, params.tradeType ); const feeRatio = getFeePercentage( { ...params, amountCalculated, }, feeTiers ); const orderBuilder = new OrderBuilder( chainId, params.swapper, params.recipient, params.tokenIn, params.tokenOut, params.tradeType, params.amount, amountCalculated, params.slippageTolerance || 2000n, this.auctionDuration, this.decayDuration, feeRatio || 400n ); const order = orderBuilder.build(this.client.getPermit2Nonce()); if (params.tradeType === 'EXACT_OUTPUT') { return { quote: order.params.input.startAmount, route: route as Hex, order, }; } else { return { quote: order.params.outputs[0].startAmount, route: route as Hex, order, }; } } async swap(order: DutchOrder, route: Hex) { if (!this.client.signer) { throw new Error('Signer not found'); } order.params.nonce = await this.client.updatePermit2Nonce( order.params.reactor, order.params.swapper ); const { domain, types, message } = order.permitData(); const signature = await this.client.signer.signTypedData( { domain, types, message, primaryType: 'PermitWitnessTransferFrom', }, order.params.swapper ); const receipt = await this.apiService.swap( order.toJSON(), signature, route ); await this.client.fetchBalance(order.params.input.token as Address); await this.client.fetchBalance(order.params.outputs[0].token as Address); return receipt; } async getSwapHistory( query?: { user: Address }, pagingOptions?: PagingOptions ) { if (!this.client.user) { throw new Error('User not initialized'); } return await querySwapHistory( this.apiService, query || { user: this.client.user.address, }, pagingOptions?.offset || 0, pagingOptions?.limit || 10 ); } }