import { LIQUIDITY_STATE_LAYOUT_V4, MAINNET_PROGRAM_ID, MARKET_STATE_LAYOUT_V3, Token } from '@raydium-io/raydium-sdk'; import bs58 from 'bs58'; import { Connection, PublicKey } from '@solana/web3.js'; import { TOKEN_PROGRAM_ID } from '@solana/spl-token'; import { EventEmitter } from 'events'; export class Listeners extends EventEmitter { private subscriptions: number[] = []; constructor(private readonly connection: Connection) { super(); } public async start(config: { walletPublicKey: PublicKey; quoteToken: Token; autoSell: boolean; cacheNewMarkets: boolean; }) { if (config.cacheNewMarkets) { const openBookSubscription = await this.subscribeToOpenBookMarkets(config); this.subscriptions.push(openBookSubscription); } const raydiumSubscription = await this.subscribeToRaydiumPools(config); this.subscriptions.push(raydiumSubscription); if (config.autoSell) { const walletSubscription = await this.subscribeToWalletChanges(config); this.subscriptions.push(walletSubscription); } } private async subscribeToOpenBookMarkets(config: { quoteToken: Token }) { return this.connection.onProgramAccountChange( MAINNET_PROGRAM_ID.OPENBOOK_MARKET, async (updatedAccountInfo) => { this.emit('market', updatedAccountInfo); }, this.connection.commitment, [ { dataSize: MARKET_STATE_LAYOUT_V3.span }, { memcmp: { offset: MARKET_STATE_LAYOUT_V3.offsetOf('quoteMint'), bytes: config.quoteToken.mint.toBase58(), }, }, ], ); } private async subscribeToRaydiumPools(config: { quoteToken: Token }) { return this.connection.onProgramAccountChange( MAINNET_PROGRAM_ID.AmmV4, async (updatedAccountInfo) => { this.emit('pool', updatedAccountInfo); }, this.connection.commitment, [ { dataSize: LIQUIDITY_STATE_LAYOUT_V4.span }, { memcmp: { offset: LIQUIDITY_STATE_LAYOUT_V4.offsetOf('quoteMint'), bytes: config.quoteToken.mint.toBase58(), }, }, { memcmp: { offset: LIQUIDITY_STATE_LAYOUT_V4.offsetOf('marketProgramId'), bytes: MAINNET_PROGRAM_ID.OPENBOOK_MARKET.toBase58(), }, }, { memcmp: { offset: LIQUIDITY_STATE_LAYOUT_V4.offsetOf('status'), bytes: bs58.encode([6, 0, 0, 0, 0, 0, 0, 0]), }, }, ], ); } private async subscribeToWalletChanges(config: { walletPublicKey: PublicKey }) { return this.connection.onProgramAccountChange( TOKEN_PROGRAM_ID, async (updatedAccountInfo) => { this.emit('wallet', updatedAccountInfo); }, this.connection.commitment, [ { dataSize: 165, }, { memcmp: { offset: 32, bytes: config.walletPublicKey.toBase58(), }, }, ], ); } public async stop() { for (let i = this.subscriptions.length; i >= 0; --i) { const subscription = this.subscriptions[i]; await this.connection.removeAccountChangeListener(subscription); this.subscriptions.splice(i, 1); } } }