import { Address, isAddressEqual } from 'viem'; import { SwapOrder, PUMPUM_POINT_TOKEN_ADDRESS, PUMPUM_FAN_CONTROLLER_ADDRESS, PUMPUM_POINT_MARKET_ADDRESS, IssueTokenOrder, BuyPointOrder, } from '@prex0/prex-structs'; import { PrexApiService } from '../../api'; import { PrexClient } from '../../prex-client'; import { quoteExecuteOrder } from '../../evm-api/pumpum/quote-order'; import { PERMIT2_ADDRESS } from '../../constants'; import { queryPumTokens } from '../../graph/pum-tokens'; import { queryPumActionHistory, queryPumTimeline, } from '../../graph/pum-action-history'; import { PumpumActionInterface, QuoteSwapParams, } from '../../interfaces/prex-client-interface'; import { PagingOptions } from '../../types'; import { getMarketInfo } from '../../evm-api/pumpum/get-market-info'; import { queryPumTokenPrice } from '../../graph/pum-token-price'; const AN_HOUR_IN_SECONDS = 3600; export class PumpumAction implements PumpumActionInterface { constructor(private client: PrexClient, private apiService: PrexApiService) {} async quoteSwap(quoteParams: QuoteSwapParams) { const isBuy = this.isBuy(quoteParams); const communityToken = this.getCommunityToken(quoteParams); const slippageTolerance = quoteParams.slippageTolerance || 2000n; let [amountIn, amountOut] = await quoteExecuteOrder( this.client.evmChainClient, communityToken, isBuy, quoteParams.tradeType === 'EXACT_INPUT', quoteParams.amount ); if (quoteParams.tradeType === 'EXACT_INPUT') { amountOut = (amountOut * (1000000n - slippageTolerance)) / 1000000n; } else { amountIn = (amountIn * (1000000n + slippageTolerance)) / 1000000n; } return new SwapOrder( { sender: quoteParams.swapper, recipient: quoteParams.recipient, deadline: getDeadline(), isBuy, communityToken, amountIn, amountOut, isExactIn: quoteParams.tradeType === 'EXACT_INPUT', dispatcher: PUMPUM_FAN_CONTROLLER_ADDRESS, nonce: 0n, }, this.apiService.chainId, PERMIT2_ADDRESS ); } isBuy(quoteParams: QuoteSwapParams) { return isAddressEqual(quoteParams.tokenIn, PUMPUM_POINT_TOKEN_ADDRESS); } getCommunityToken(quoteParams: QuoteSwapParams) { const isBuy = this.isBuy(quoteParams); if (isBuy) { return quoteParams.tokenOut; } return quoteParams.tokenIn; } async executeSwap(order: SwapOrder) { if (!this.client.signer) { throw new Error('Signer not found'); } order.params.nonce = await this.client.updatePermit2Nonce( order.params.dispatcher, order.params.sender ); const { domain, types, message } = order.permitData(); const signature = await this.client.signer.signTypedData( { domain, types, message, primaryType: 'PermitWitnessTransferFrom', }, order.params.sender ); const receipt = await this.apiService.postExecutePumSwap( order.serialize(), signature ); await this.client.fetchBalance(order.params.communityToken); await this.client.fetchBalance(PUMPUM_POINT_TOKEN_ADDRESS); return receipt; } async issueTokens({ name, symbol, amount, metadata, }: { name: string; symbol: string; amount: bigint; metadata?: string; }) { if (!this.client.signer || !this.client.user) { throw new Error('Signer or user not found'); } const nonce = await this.client.updatePermit2Nonce( PUMPUM_FAN_CONTROLLER_ADDRESS, this.client.user.address ); const order = new IssueTokenOrder( { sender: this.client.user.address, dispatcher: PUMPUM_FAN_CONTROLLER_ADDRESS, nonce, deadline: getDeadline(), name, symbol, amount, metadata: metadata || '{}', }, this.apiService.chainId, PERMIT2_ADDRESS ); const { domain, types, message } = order.permitData(); const signature = await this.client.signer.signTypedData( { domain, types, message, primaryType: 'PermitWitnessTransferFrom', }, order.params.sender ); const receipt = await this.apiService.postIssuePumToken( order.serialize(), signature ); await this.client.fetchBalance(PUMPUM_POINT_TOKEN_ADDRESS); await this.client.fetchBalance(receipt.result); return receipt; } async buy({ dai, amount }: { dai: Address; amount: bigint }) { if (!this.client.signer || !this.client.user) { throw new Error('Signer or user not found'); } const nonce = await this.client.updatePermit2Nonce( PUMPUM_POINT_MARKET_ADDRESS, this.client.user.address ); const order = new BuyPointOrder( { dispatcher: PUMPUM_POINT_MARKET_ADDRESS, buyer: this.client.user.address, nonce, deadline: getDeadline(), amount, }, this.apiService.chainId, PERMIT2_ADDRESS ); const { domain, types, message } = order.permitData(dai); const signature = await this.client.signer.signTypedData( { domain, types, message, primaryType: 'PermitWitnessTransferFrom', }, order.params.buyer ); const receipt = await this.apiService.postBuyPoint( order.serialize(), signature ); await this.client.fetchBalance(PUMPUM_POINT_TOKEN_ADDRESS); return receipt; } async getPumTokens() { return queryPumTokens(this.apiService); } async getPumTimeline(pageOptions: PagingOptions) { return await queryPumTimeline(this.apiService, pageOptions); } async getPumActionHistory(user: Address, pageOptions: PagingOptions) { return queryPumActionHistory(this.apiService, user, pageOptions); } async getPumTokenPrice( token: Address, interval: 'HOUR' | 'DAY', pageOptions: PagingOptions ) { return queryPumTokenPrice(this.apiService, { token, interval, pageOptions, }); } async getMarketInfo(communityToken: Address) { return getMarketInfo(this.client.evmChainClient, communityToken); } } function getDeadline() { return BigInt(Math.floor(Date.now() / 1000 + AN_HOUR_IN_SECONDS)); }