-
Notifications
You must be signed in to change notification settings - Fork 258
feat: Convert Python project to Node.js/TypeScript (Phase 1) #103
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Convert Python project to Node.js/TypeScript (Phase 1) #103
Conversation
This commit includes the initial conversion of the Solana trading bot from Python to Node.js/TypeScript. Key changes: - Initialized a Node.js/TypeScript project with necessary dependencies (@solana/web3.js, typescript). - Copied IDLs, bot configurations, and relevant project files. - Removed original Python source files and Python-specific project files. - Converted core Solana interaction logic from Python (src/core/*) to TypeScript (src/core/*), including client, wallet, curve calculations, pubkey constants, and priority fee management. - Based on your feedback, all gRPC and Geyser-related components (Geyser listeners, .proto files, gRPC generated code) have been excluded from this conversion. - Converted RPC/log-based monitoring components (src/monitoring/*) to TypeScript, including BlockListener, LogsListener, and their event processors. - Converted trading logic (src/trading/*) to TypeScript, including base trading classes, buyer, seller, and the main trader orchestration class. - Created index.ts files for easier module imports in core, monitoring, and trading directories. The project now has a foundational TypeScript structure for core Solana interactions, RPC/log-based event monitoring, and trading logic. Further steps will involve converting utility functions, configuration loading, implementing tests, and setting up the bot runner. The gRPC/Geyser functionality was intentionally skipped due to difficulties in the automated environment for gRPC code generation and your subsequent request to focus on RPC-based features.
WalkthroughThis change migrates the codebase from a Python-based Solana trading bot to a TypeScript implementation. All Python modules, scripts, tests, configuration files, and protobuf schemas are deleted. In their place, new TypeScript modules are introduced for blockchain interaction, event processing, trading logic, and configuration. TypeScript project configuration files are added. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Trader (TS)
participant SolanaClient (TS)
participant Wallet (TS)
participant Listener (TS)
participant BondingCurveManager (TS)
participant PriorityFeeManager (TS)
User->>Trader (TS): start()
Trader (TS)->>Listener (TS): start(callback)
Listener (TS)-->>Trader (TS): onTokenDetected(tokenInfo)
Trader (TS)->>BondingCurveManager (TS): getCurveState(tokenInfo.bondingCurve)
BondingCurveManager (TS)-->>Trader (TS): BondingCurveState
Trader (TS)->>Wallet (TS): getAssociatedTokenAddress(tokenInfo.mint)
Wallet (TS)-->>Trader (TS): associatedTokenAccount
Trader (TS)->>PriorityFeeManager (TS): calculatePriorityFee(accounts)
PriorityFeeManager (TS)-->>Trader (TS): priorityFee
Trader (TS)->>SolanaClient (TS): buildAndSendTransaction(...)
SolanaClient (TS)-->>Trader (TS): txSignature
Trader (TS)-->>User: TradeResult (success/failure)
Suggested labels
Poem
Note ⚡️ AI Code Reviews for VS Code, Cursor, WindsurfCodeRabbit now has a plugin for VS Code, Cursor and Windsurf. This brings AI code reviews directly in the code editor. Each commit is reviewed immediately, finding bugs before the PR is raised. Seamless context handoff to your AI code agent ensures that you can easily incorporate review feedback. ✨ Finishing Touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 23
♻️ Duplicate comments (2)
src/monitoring/logsListener.ts (1)
8-11
: Replace empty interface with type alias.Same issue as in
baseListener.ts
- the emptyTrader
interface should be replaced with a type alias.-// Placeholder for Trader -interface Trader { - // Define methods and properties of Trader if known -} +// Placeholder for Trader +type Trader = Record<string, never>;🧰 Tools
🪛 Biome (1.9.4)
[error] 8-11: An empty interface is equivalent to {}.
Safe fix: Use a type alias instead.
(lint/suspicious/noEmptyInterface)
src/core/priority_fee/dynamicFee.ts (1)
5-10
: Consider using a proper logging framework.Same logging concern as in other files - consider using a production-ready logging framework.
🧹 Nitpick comments (14)
package.json (1)
16-22
: Consider if gRPC development dependencies are needed.Given that the PR objectives mention excluding gRPC/Geyser components, consider whether the gRPC-related development dependencies (
grpc_tools_node_protoc_ts
,grpc-tools
) are still needed.If gRPC functionality is truly excluded, these dependencies can be removed:
"@types/node": "^20.12.12", "@types/ws": "^8.18.1", - "grpc_tools_node_protoc_ts": "^5.3.3", - "grpc-tools": "^1.13.0", "typescript": "^5.4.5"src/monitoring/baseListener.ts (1)
19-29
: Consider making constructor parameters readonly.For better encapsulation and to prevent accidental modification of dependencies, consider making the constructor parameters readonly.
constructor( - solanaClient: SolanaClient, - wallet: Wallet, - trader: Trader, // Using placeholder Trader interface - callback: EventCallback + readonly solanaClient: SolanaClient, + readonly wallet: Wallet, + readonly trader: Trader, + readonly callback: EventCallback ) { - this.solanaClient = solanaClient; - this.wallet = wallet; - this.trader = trader; - this.callback = callback; + // Properties are automatically assigned when using readonly parameters }src/monitoring/logsListener.ts (2)
13-17
: Consider using a proper logging framework.The simple console-based logger may not be suitable for production environments. Consider using a proper logging framework that supports log levels, formatting, and output destinations.
// Consider using a logging library like winston or pino import { createLogger, format, transports } from 'winston'; const logger = createLogger({ level: 'info', format: format.combine( format.timestamp(), format.label({ label: 'LogsListener' }), format.printf(({ timestamp, label, level, message }) => `${timestamp} [${label}] ${level}: ${message}` ) ), transports: [ new transports.Console(), new transports.File({ filename: 'logs/listener.log' }) ] });
101-103
: Improve error handling in callback.The error handling for the callback could be more robust with additional context and potentially retry logic.
- this.callback(tokenInfo).catch(e => { - logger.error(`Error in token callback for ${tokenInfo.name} (sig: ${tokenInfo.signature}): ${e.message}`); - }); + this.callback(tokenInfo).catch(e => { + logger.error(`Error in token callback for ${tokenInfo.name} (sig: ${tokenInfo.signature}): ${e.message}`, { + tokenInfo, + error: e, + stack: e.stack + }); + // Consider implementing retry logic or dead letter queue here + });src/core/priority_fee/dynamicFee.ts (1)
12-31
: Add comprehensive documentation for quantile calculation.The quantile calculation is complex and would benefit from more detailed documentation explaining the mathematical approach.
-// Helper function to calculate quantiles, similar to Python's statistics.quantiles +/** + * Calculates quantiles using linear interpolation, similar to Python's statistics.quantiles. + * + * @param data Array of numeric values to calculate quantiles from + * @param n Number of quantiles to calculate (e.g., 4 for quartiles, 10 for deciles) + * @returns Array of n-1 quantile values (e.g., for n=10, returns 9 decile values) + * + * Algorithm: + * 1. Sort data in ascending order + * 2. For each quantile i from 1 to n-1: + * - Calculate position: (data.length - 1) * (i / n) + * - Use linear interpolation between adjacent data points + */ function calculateQuantiles(data: number[], n: number): number[] {src/core/pubkeys.ts (1)
3-17
: Add JSDoc documentation for better code documentation.Consider adding JSDoc comments to explain the purpose of each constant, especially for domain-specific ones like pumpFun constants.
+/** Number of lamports in one SOL */ export const lamportsPerSol: number = 1_000_000_000; + +/** Standard token decimals for most SPL tokens */ export const tokenDecimals: number = 6; +/** Solana system program for basic operations */ export const systemProgramId: PublicKey = new PublicKey("11111111111111111111111111111111"); + +/** SPL Token program for token operations */ export const tokenProgramId: PublicKey = new PublicKey("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"); + +/** Associated Token Account program */ export const associatedTokenProgramId: PublicKey = new PublicKey("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"); + +/** Rent sysvar for calculating rent exemption */ export const rentSysvarId: PublicKey = new PublicKey("SysvarRent111111111111111111111111111111111"); + +/** Wrapped SOL mint address */ export const solMintId: PublicKey = new PublicKey("So11111111111111111111111111111111111111112"); +/** PumpFun program ID for token creation and trading */ export const pumpFunProgramId: PublicKey = new PublicKey("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"); + +/** PumpFun global state account */ export const pumpFunGlobal: PublicKey = new PublicKey("4wTV1YmiEkRvAtNtsSGPtUrqRYQMe5SKy2uB4Jjaxnjf"); + +/** PumpFun event authority for program events */ export const pumpFunEventAuthority: PublicKey = new PublicKey("Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1"); + +/** PumpFun fee recipient account */ export const pumpFunFeeRecipient: PublicKey = new PublicKey("CebN5WGQ4jvEPvsVU4EoHEpgzq1VV7AbicfhtW4xC9iM"); + -// LIQUIDITY_MIGRATOR is not a valid PublicKey, so it's kept as a string. +/** PumpFun liquidity migrator (kept as string - verify if this should be a PublicKey) */ export const pumpFunLiquidityMigrator: string = "39azUYFWPz3VHgKCf3VChUwbpURdCHRxjWVowf5jUJjg";src/monitoring/blockListener.ts (1)
234-245
: Improve conditional check with optional chaining.The transaction processing logic is sound, but the conditional check can be simplified.
Apply this improvement as suggested by static analysis:
private processBlockTransactions( transactions: Array<{ transaction: [string, string]; meta: any }> ): void { for (const tx of transactions) { - if (tx && tx.transaction && tx.transaction[0]) { // tx.transaction[0] is the tx data string + if (tx?.transaction?.[0]) { // tx.transaction[0] is the tx data string const tokenInfo = this.eventProcessor.processTransaction(tx.transaction[0]); if (tokenInfo) { this.handleFoundToken(tokenInfo); } } } }🧰 Tools
🪛 Biome (1.9.4)
[error] 238-238: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
src/core/priority_fee/manager.ts (1)
21-63
: Clean up commented-out code.The constructor implementation is well-documented and structured, but contains commented-out code that should be removed.
private enableDynamicFee: boolean; private enableFixedFee: boolean; - // private fixedFeeValue: number; // Renamed from fixed_fee to avoid confusion with FixedPriorityFee class private extraFeePercentage: number; // Renamed from extra_fee private hardCapMicroLamports: number; // Renamed from hard_cap // ... later in constructor ... this.enableDynamicFee = enableDynamicFee; this.enableFixedFee = enableFixedFee; - // this.fixedFeeValue = fixedFee; // Storing the raw value if needed, though plugin holds it too this.extraFeePercentage = extraFee;src/core/client.ts (1)
147-154
: Make compute unit limit configurable.The hardcoded compute unit limit of 72,000 may not be suitable for all operations. Make it configurable.
public async buildAndSendTransaction( instructions: TransactionInstruction[], signerKeypair: Keypair, skipPreflight: boolean = true, maxRetries: number = 3, - priorityFeeMicroLamports: number | null = null + priorityFeeMicroLamports: number | null = null, + computeUnitLimit: number = 72_000 ): Promise<string> { logger.info(`Priority fee in microlamports: ${priorityFeeMicroLamports ?? 0}`); const allInstructions = []; if (priorityFeeMicroLamports !== null && priorityFeeMicroLamports > 0) { allInstructions.push( - ComputeBudgetProgram.setComputeUnitLimit({ units: 72_000 }) // Default from Python + ComputeBudgetProgram.setComputeUnitLimit({ units: computeUnitLimit }) );src/monitoring/logsEventProcessor.ts (2)
26-27
: Remove unused empty interface.The
Trader
interface is empty and unused. Remove it to clean up the code.-// Placeholder for Trader interface -interface Trader {}🧰 Tools
🪛 Biome (1.9.4)
[error] 26-27: An empty interface is equivalent to {}.
Safe fix: Use a type alias instead.
(lint/suspicious/noEmptyInterface)
191-206
: Implement the buy and sell transaction parsers.The
parseBuyTx
andparseSellTx
methods are placeholders. These should be implemented for complete functionality.Would you like me to help implement these methods based on the expected log structure for buy and sell transactions? I can analyze sample logs to determine the parsing logic needed.
src/trading/seller.ts (1)
95-95
: Fix formatting for consistency.The division operator should have consistent spacing.
- this.logger.info(`Minimum SOL output (with ${this.slippageBps/100}% slippage): ${(minSolOutputLamports / lamportsPerSol).toFixed(8)} SOL`); + this.logger.info(`Minimum SOL output (with ${this.slippageBps / 100}% slippage): ${(minSolOutputLamports / lamportsPerSol).toFixed(8)} SOL`);src/trading/trader.ts (2)
22-24
: Implement cleanup functions for production readiness.The cleanup functions are currently placeholders. These are critical for proper resource management and should be implemented before production use.
Would you like me to help implement these cleanup functions or create issues to track their implementation?
174-185
: Remove redundant switch case.The 'blocks' case is redundant since it performs the same action as the default case.
- case 'blocks': default: this.tokenListener = new BlockListener( this.solanaClient, // BlockListener now takes SolanaClient this.wallet, this, // Pass the Trader instance (this) tokenCallback, this.config.wssEndpoint, pumpFunProgramId ); logger.info("Using blockSubscribe listener for token monitoring"); break;
🧰 Tools
🪛 Biome (1.9.4)
[error] 174-174: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (15)
learning-examples/listen-new-tokens/generated/__init__.py
is excluded by!**/generated/**
learning-examples/listen-new-tokens/generated/geyser_pb2.py
is excluded by!**/generated/**
learning-examples/listen-new-tokens/generated/geyser_pb2.pyi
is excluded by!**/generated/**
learning-examples/listen-new-tokens/generated/geyser_pb2_grpc.py
is excluded by!**/generated/**
learning-examples/listen-new-tokens/generated/solana_storage_pb2.py
is excluded by!**/generated/**
learning-examples/listen-new-tokens/generated/solana_storage_pb2.pyi
is excluded by!**/generated/**
learning-examples/listen-new-tokens/generated/solana_storage_pb2_grpc.py
is excluded by!**/generated/**
package-lock.json
is excluded by!**/package-lock.json
src/geyser/generated/geyser_pb2.py
is excluded by!**/generated/**
src/geyser/generated/geyser_pb2.pyi
is excluded by!**/generated/**
src/geyser/generated/geyser_pb2_grpc.py
is excluded by!**/generated/**
src/geyser/generated/solana_storage_pb2.py
is excluded by!**/generated/**
src/geyser/generated/solana_storage_pb2.pyi
is excluded by!**/generated/**
src/geyser/generated/solana_storage_pb2_grpc.py
is excluded by!**/generated/**
trades/trades.log
is excluded by!**/*.log
📒 Files selected for processing (86)
.gitignore
(1 hunks)learning-examples/blockSubscribe-transactions/raw_create_tx_from_blockSubscribe.json
(0 hunks)learning-examples/blockSubscribe_extract_transactions.py
(0 hunks)learning-examples/bonding-curve-progress/get_bonding_curve_status.py
(0 hunks)learning-examples/bonding-curve-progress/get_graduating_tokens.py
(0 hunks)learning-examples/bonding-curve-progress/poll_bonding_curve_progress.py
(0 hunks)learning-examples/calculate_discriminator.py
(0 hunks)learning-examples/cleanup_accounts.py
(0 hunks)learning-examples/compute_associated_bonding_curve.py
(0 hunks)learning-examples/decode_from_blockSubscribe.py
(0 hunks)learning-examples/decode_from_getAccountInfo.py
(0 hunks)learning-examples/decode_from_getTransaction.py
(0 hunks)learning-examples/decoded_buy_tx_from_getTransaction.json
(0 hunks)learning-examples/decoded_create_tx_from_getTransaction.json
(0 hunks)learning-examples/fetch_price.py
(0 hunks)learning-examples/listen-migrations/compare_migration_listeners.py
(0 hunks)learning-examples/listen-migrations/listen_blocksubscribe_old_raydium.py
(0 hunks)learning-examples/listen-migrations/listen_logsubscribe.py
(0 hunks)learning-examples/listen-migrations/listen_programsubscribe.py
(0 hunks)learning-examples/listen-new-tokens/compare_listeners.py
(0 hunks)learning-examples/listen-new-tokens/listen_blocksubscribe.py
(0 hunks)learning-examples/listen-new-tokens/listen_geyser.py
(0 hunks)learning-examples/listen-new-tokens/listen_logsubscribe+abc.py
(0 hunks)learning-examples/listen-new-tokens/listen_logsubscribe.py
(0 hunks)learning-examples/listen-new-tokens/listen_pumpportal.py
(0 hunks)learning-examples/manual_buy.py
(0 hunks)learning-examples/manual_sell.py
(0 hunks)learning-examples/pumpswap/get_pumpswap_pools.py
(0 hunks)learning-examples/pumpswap/manual_buy_pumpswap.py
(0 hunks)learning-examples/pumpswap/manual_sell_pumpswap.py
(0 hunks)learning-examples/raw_bondingCurve_from_getAccountInfo.json
(0 hunks)learning-examples/raw_buy_tx_from_getTransaction.json
(0 hunks)learning-examples/raw_create_tx_from_getTransaction.json
(0 hunks)package.json
(1 hunks)pyproject.toml
(0 hunks)src/bot_runner.py
(0 hunks)src/cleanup/manager.py
(0 hunks)src/cleanup/modes.py
(0 hunks)src/config_loader.py
(0 hunks)src/core/client.py
(0 hunks)src/core/client.ts
(1 hunks)src/core/curve.py
(0 hunks)src/core/curve.ts
(1 hunks)src/core/index.ts
(1 hunks)src/core/priority_fee/__init__.py
(0 hunks)src/core/priority_fee/dynamicFee.ts
(1 hunks)src/core/priority_fee/dynamic_fee.py
(0 hunks)src/core/priority_fee/fixedFee.ts
(1 hunks)src/core/priority_fee/fixed_fee.py
(0 hunks)src/core/priority_fee/index.ts
(1 hunks)src/core/priority_fee/manager.py
(0 hunks)src/core/priority_fee/manager.ts
(1 hunks)src/core/pubkeys.py
(0 hunks)src/core/pubkeys.ts
(1 hunks)src/core/wallet.py
(0 hunks)src/core/wallet.ts
(1 hunks)src/geyser/proto/geyser.proto
(0 hunks)src/geyser/proto/solana-storage.proto
(0 hunks)src/monitoring/baseListener.ts
(1 hunks)src/monitoring/base_listener.py
(0 hunks)src/monitoring/blockEventProcessor.ts
(1 hunks)src/monitoring/blockListener.ts
(1 hunks)src/monitoring/block_event_processor.py
(0 hunks)src/monitoring/block_listener.py
(0 hunks)src/monitoring/geyser_event_processor.py
(0 hunks)src/monitoring/geyser_listener.py
(0 hunks)src/monitoring/index.ts
(1 hunks)src/monitoring/logsEventProcessor.ts
(1 hunks)src/monitoring/logsListener.ts
(1 hunks)src/monitoring/logs_event_processor.py
(0 hunks)src/monitoring/logs_listener.py
(0 hunks)src/trading/base.py
(0 hunks)src/trading/baseTrader.ts
(1 hunks)src/trading/buyer.py
(0 hunks)src/trading/buyer.ts
(1 hunks)src/trading/index.ts
(1 hunks)src/trading/seller.py
(0 hunks)src/trading/seller.ts
(1 hunks)src/trading/trader.py
(0 hunks)src/trading/trader.ts
(1 hunks)src/utils/logger.py
(0 hunks)tests/compare_listeners.py
(0 hunks)tests/test_block_listener.py
(0 hunks)tests/test_geyser_listener.py
(0 hunks)tests/test_logs_listener.py
(0 hunks)tsconfig.json
(1 hunks)
💤 Files with no reviewable changes (63)
- src/core/priority_fee/init.py
- src/monitoring/logs_event_processor.py
- src/monitoring/block_listener.py
- learning-examples/calculate_discriminator.py
- learning-examples/raw_bondingCurve_from_getAccountInfo.json
- src/core/priority_fee/fixed_fee.py
- src/monitoring/base_listener.py
- learning-examples/blockSubscribe-transactions/raw_create_tx_from_blockSubscribe.json
- learning-examples/decoded_buy_tx_from_getTransaction.json
- learning-examples/raw_buy_tx_from_getTransaction.json
- learning-examples/cleanup_accounts.py
- learning-examples/listen-new-tokens/listen_logsubscribe.py
- learning-examples/pumpswap/get_pumpswap_pools.py
- learning-examples/blockSubscribe_extract_transactions.py
- learning-examples/listen-new-tokens/listen_blocksubscribe.py
- src/config_loader.py
- src/bot_runner.py
- learning-examples/listen-new-tokens/listen_logsubscribe+abc.py
- src/core/curve.py
- src/core/pubkeys.py
- src/core/wallet.py
- tests/test_logs_listener.py
- learning-examples/decode_from_blockSubscribe.py
- learning-examples/listen-migrations/listen_logsubscribe.py
- learning-examples/compute_associated_bonding_curve.py
- src/utils/logger.py
- src/cleanup/manager.py
- learning-examples/raw_create_tx_from_getTransaction.json
- src/core/priority_fee/dynamic_fee.py
- learning-examples/decoded_create_tx_from_getTransaction.json
- learning-examples/fetch_price.py
- learning-examples/bonding-curve-progress/poll_bonding_curve_progress.py
- learning-examples/decode_from_getTransaction.py
- src/monitoring/logs_listener.py
- src/cleanup/modes.py
- learning-examples/listen-migrations/listen_programsubscribe.py
- pyproject.toml
- learning-examples/listen-new-tokens/listen_geyser.py
- src/monitoring/geyser_event_processor.py
- src/trading/base.py
- learning-examples/bonding-curve-progress/get_bonding_curve_status.py
- src/monitoring/geyser_listener.py
- src/geyser/proto/solana-storage.proto
- src/trading/buyer.py
- learning-examples/bonding-curve-progress/get_graduating_tokens.py
- tests/compare_listeners.py
- learning-examples/decode_from_getAccountInfo.py
- src/core/client.py
- tests/test_block_listener.py
- learning-examples/listen-migrations/compare_migration_listeners.py
- learning-examples/manual_buy.py
- learning-examples/pumpswap/manual_buy_pumpswap.py
- src/monitoring/block_event_processor.py
- src/trading/seller.py
- src/core/priority_fee/manager.py
- src/trading/trader.py
- learning-examples/listen-new-tokens/compare_listeners.py
- learning-examples/manual_sell.py
- learning-examples/pumpswap/manual_sell_pumpswap.py
- src/geyser/proto/geyser.proto
- tests/test_geyser_listener.py
- learning-examples/listen-migrations/listen_blocksubscribe_old_raydium.py
- learning-examples/listen-new-tokens/listen_pumpportal.py
🧰 Additional context used
🧬 Code Graph Analysis (4)
src/core/priority_fee/fixedFee.ts (1)
src/core/priority_fee/manager.ts (1)
PriorityFeePlugin
(17-19)
src/core/client.ts (1)
src/core/wallet.ts (1)
pubkey
(14-16)
src/monitoring/blockEventProcessor.ts (1)
src/core/pubkeys.ts (1)
pumpFunProgramId
(12-12)
src/trading/baseTrader.ts (3)
src/core/client.ts (1)
SolanaClient
(37-259)src/core/wallet.ts (1)
Wallet
(5-30)src/core/pubkeys.ts (2)
pumpFunProgramId
(12-12)pumpFunFeeRecipient
(15-15)
🪛 Biome (1.9.4)
src/monitoring/baseListener.ts
[error] 4-8: An empty interface is equivalent to {}.
Safe fix: Use a type alias instead.
(lint/suspicious/noEmptyInterface)
src/monitoring/logsListener.ts
[error] 8-11: An empty interface is equivalent to {}.
Safe fix: Use a type alias instead.
(lint/suspicious/noEmptyInterface)
src/monitoring/blockListener.ts
[error] 9-12: An empty interface is equivalent to {}.
Safe fix: Use a type alias instead.
(lint/suspicious/noEmptyInterface)
[error] 238-238: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
src/monitoring/logsEventProcessor.ts
[error] 26-27: An empty interface is equivalent to {}.
Safe fix: Use a type alias instead.
(lint/suspicious/noEmptyInterface)
[error] 70-70: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
src/trading/trader.ts
[error] 18-18: expected as
but instead found s
Remove s
(parse)
[error] 18-18: expected from
but instead found fs
Remove fs
(parse)
[error] 18-18: Expected a semicolon or an implicit semicolon after a statement, but found none
An explicit or implicit semicolon is expected here...
...Which is required to end this statement
(parse)
[error] 18-18: Expected a semicolon or an implicit semicolon after a statement, but found none
An explicit or implicit semicolon is expected here...
...Which is required to end this statement
(parse)
[error] 19-19: expected as
but instead found s
Remove s
(parse)
[error] 19-19: expected from
but instead found path
Remove path
(parse)
[error] 19-19: Expected a semicolon or an implicit semicolon after a statement, but found none
An explicit or implicit semicolon is expected here...
...Which is required to end this statement
(parse)
[error] 19-19: Expected a semicolon or an implicit semicolon after a statement, but found none
An explicit or implicit semicolon is expected here...
...Which is required to end this statement
(parse)
[error] 174-174: Useless case clause.
because the default clause is present:
Unsafe fix: Remove the useless case.
(lint/complexity/noUselessSwitchCase)
[error] 19-19: Shouldn't redeclare 's'. Consider to delete it or rename it.
's' is defined here:
(lint/suspicious/noRedeclare)
🔇 Additional comments (31)
.gitignore (1)
1-7
: LGTM! Gitignore correctly updated for TypeScript migration.The simplification from Python-specific patterns to Node.js/TypeScript essentials aligns perfectly with the migration objectives. The remaining entries appropriately cover Node.js artifacts, environment files, and build outputs.
src/core/priority_fee/index.ts (1)
1-4
: LGTM! Clean barrel export pattern implemented correctly.The re-exports are properly structured and follow TypeScript best practices for module organization. This provides a clean interface for consuming priority fee functionality.
tsconfig.json (1)
1-14
: LGTM! Well-configured TypeScript setup for Node.js project.The configuration follows TypeScript best practices with appropriate compiler options:
- ES2020 target provides modern JavaScript features
- Strict mode ensures type safety
- CommonJS modules work well with Node.js ecosystem
- Sensible directory structure with src/dist separation
src/core/index.ts (1)
1-6
: LGTM! Core module exports properly organized.The barrel export pattern provides a clean interface for core functionality. The comment on line 5 helpfully clarifies the priority_fee export path, making the module structure clear for consumers.
src/core/priority_fee/fixedFee.ts (2)
3-12
: LGTM! Clean implementation of fixed priority fee logic.The class structure and constructor implementation are well-designed. The private field encapsulation and clear parameter documentation follow TypeScript best practices.
19-23
: LGTM! Correct logic for fixed fee calculation.The logic correctly returns
null
for zero fees (indicating no priority fee) and the fixed value otherwise. This aligns with the expected behavior for a fixed priority fee plugin.src/trading/index.ts (1)
1-4
: LGTM! Clean barrel export implementation.The barrel export pattern is implemented correctly with helpful comments indicating what each module exports. This creates a clean, centralized entry point for importing trading components.
src/monitoring/index.ts (1)
1-5
: LGTM! Consistent barrel export pattern.The monitoring index follows the same clean barrel export pattern as the trading module, providing a centralized entry point for all monitoring components.
src/core/wallet.ts (1)
22-24
: LGTM! Correct implementation of associated token address derivation.The
getAssociatedTokenAddress
method correctly uses the SPL Token library to derive the associated token account address.package.json (1)
10-14
:❓ Verification inconclusive
Verify dependency versions for security and compatibility.
Ensure that the specified versions are secure and compatible with your target environment.
🏁 Script executed:
#!/bin/bash # Check for security advisories and latest versions echo "Checking @solana/web3.js..." npm view @solana/web3.js version time echo "Checking @grpc/grpc-js..." npm view @grpc/grpc-js version time echo "Checking ws..." npm view ws version time # Check for known vulnerabilities npm audit --audit-level moderateLength of output: 65675
Verify and Align Dependency Versions
We ran
npm view @solana/web3.js version
and found the latest stable release is 1.98.2. Your range^1.91.8
will already pick up this patch, but consider bumping to^1.98.2
if you need the newest fixes or APIs.Please also verify that the following dependencies are up-to-date and free of critical advisories in your target environment:
- @grpc/grpc-js (currently
^1.13.4
)- @grpc/proto-loader (currently
^0.7.15
)- ws (currently
^8.18.2
)src/core/curve.ts (3)
1-8
: LGTM!The imports are well-structured and the discriminator constant is properly defined with a clear explanatory comment.
10-28
: LGTM!The interface and Borsh layout are properly aligned with appropriate types for on-chain data structure parsing.
75-109
: LGTM!The BondingCurveManager class is well-implemented with proper async operations, error handling, and defensive programming including division by zero checks.
src/monitoring/blockListener.ts (3)
41-108
: LGTM!The BlockListener class implements proper lifecycle management with idempotency checks, graceful shutdown, and comprehensive cleanup of resources.
109-231
: LGTM!The connection management implementation is robust with proper WebSocket lifecycle handling, automatic reconnection, health checks, and comprehensive error handling.
247-268
: LGTM!The token filtering logic is well-implemented with clear filter criteria, informative logging, and proper error handling for callback invocation.
src/core/priority_fee/manager.ts (3)
1-19
: LGTM!The imports are appropriate and the PriorityFeePlugin interface provides a clean abstraction for fee calculation strategies.
65-95
: LGTM!The priority fee calculation logic is well-implemented with proper validation, percentage calculations, hard cap enforcement, and appropriate null handling.
97-143
: LGTM!The base fee calculation logic is comprehensive with excellent fallback handling, informative logging, and proper edge case management for zero/negative fees.
src/trading/baseTrader.ts (4)
1-17
: LGTM!The imports are appropriate and the TokenInfo interface is comprehensive, properly typed, and well-suited for representing token metadata in the trading system.
19-50
: LGTM!The helper functions provide excellent validation, proper type conversion between plain objects and TokenInfo instances, and appropriate error handling for missing required fields.
52-64
: LGTM!The TradeResult interface is well-designed for representing trade outcomes. The temporary use of 'any' for TradingConfig is appropriately acknowledged and disabled in ESLint.
67-115
: LGTM!The TradingBase class provides an excellent foundation for trading operations with proper dependency injection, clear placeholder methods, and correct identification of relevant accounts for priority fee calculations.
src/trading/seller.ts (4)
1-30
: LGTM! Imports and constants are properly structured.The imports are well-organized and the sell discriminator is correctly defined as a little-endian hex string matching the Python implementation.
33-39
: Well-designed interface with appropriate mandatory fields.Making
priorityFeeManager
andbondingCurveManager
mandatory is a good design decision as these are essential for seller operations.
41-58
: Constructor properly initializes all required fields.Note that the default slippage of 25% (2500 bps) is quite high, but this matches the Python implementation and may be appropriate for volatile token markets.
146-148
:❓ Verification inconclusive
Verify account ordering matches on-chain program requirements.
The comments indicate that
creator_vault
andTOKEN_PROGRAM_ID
positions differ from the buy instruction. Please verify this account ordering against the pump.fun program's sell instruction requirements.
🏁 Script executed:
#!/bin/bash # Search for any documentation or tests that might confirm the account ordering for sell transactions rg -A 10 -B 5 "sell.*instruction.*account" --type-add 'test:*.test.{ts,js}' --type test rg -A 10 -B 5 "creator_vault.*TOKEN_PROGRAM_ID|TOKEN_PROGRAM_ID.*creator_vault"Length of output: 320
Confirm sell transaction account ordering against on-chain IDL
It looks like the
creatorVault
andTOKEN_PROGRAM_ID
accounts insrc/trading/seller.ts
(lines 146–148) aren’t in the same positions as the buy instruction, which could cause the sell to fail at runtime:{ pubkey: tokenInfo.creatorVault, isSigner: false, isWritable: true }, // creator_vault (differs from buy order)
{ pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, // moved from index 8→9
{ pubkey: pumpFunEventAuthority, isSigner: false, isWritable: false }, // event_authorityAction: Cross-check this sequence against the pump.fun program’s Anchor IDL (or on-chain spec) for the
sell
instruction to ensure the account array matches exactly.src/trading/trader.ts (1)
27-39
: GeyserListener placeholder aligns with PR objectives.The placeholder implementation is acceptable as the PR objectives state that gRPC/Geyser functionality was intentionally omitted from this phase of conversion.
src/trading/buyer.ts (3)
1-35
: LGTM! Imports and constants are well-organized.The buy discriminator is correctly defined as a little-endian hex string.
78-148
: Well-implemented buy execution logic.The method properly handles both extreme fast mode and normal mode, with good validation of token price and proper error handling. The slippage calculation is correct.
192-224
: Excellent implementation of transaction building and sending.The use of BigInt for token amounts ensures precision, and the integration with priority fee manager is well done. The error handling and retry logic are properly implemented.
public async getPriorityFee(): Promise<number | null> { | ||
if (this.fixedFee === 0) { | ||
return null; | ||
} | ||
return this.fixedFee; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix method signature to match PriorityFeePlugin interface.
The getPriorityFee
method signature doesn't match the interface contract. According to the interface in manager.ts
, it should accept an optional accounts
parameter.
+import { PublicKey } from '@solana/web3.js';
import { PriorityFeePlugin } from './manager';
- public async getPriorityFee(): Promise<number | null> {
+ public async getPriorityFee(accounts?: PublicKey[]): Promise<number | null> {
The accounts
parameter isn't used in fixed fee calculation, but it must be present to satisfy the interface contract.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
public async getPriorityFee(): Promise<number | null> { | |
if (this.fixedFee === 0) { | |
return null; | |
} | |
return this.fixedFee; | |
} | |
} | |
import { PublicKey } from '@solana/web3.js'; | |
import { PriorityFeePlugin } from './manager'; | |
export class FixedPriorityFee implements PriorityFeePlugin { | |
constructor(private fixedFee: number) {} | |
public async getPriorityFee(accounts?: PublicKey[]): Promise<number | null> { | |
if (this.fixedFee === 0) { | |
return null; | |
} | |
return this.fixedFee; | |
} | |
} |
🤖 Prompt for AI Agents
In src/core/priority_fee/fixedFee.ts around lines 18 to 24, update the
getPriorityFee method signature to include an optional accounts parameter to
match the PriorityFeePlugin interface defined in manager.ts. Although the
accounts parameter is not used in the fixed fee calculation, it must be added to
the method signature to satisfy the interface contract.
constructor(privateKey: string) { | ||
this._privateKey = privateKey; | ||
this._keypair = this.loadKeypair(privateKey); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add error handling for invalid private keys.
The constructor should handle potential errors from invalid private keys to prevent runtime crashes.
constructor(privateKey: string) {
+ if (!privateKey || typeof privateKey !== 'string') {
+ throw new Error('Private key must be a non-empty string');
+ }
this._privateKey = privateKey;
this._keypair = this.loadKeypair(privateKey);
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
constructor(privateKey: string) { | |
this._privateKey = privateKey; | |
this._keypair = this.loadKeypair(privateKey); | |
} | |
constructor(privateKey: string) { | |
if (!privateKey || typeof privateKey !== 'string') { | |
throw new Error('Private key must be a non-empty string'); | |
} | |
this._privateKey = privateKey; | |
this._keypair = this.loadKeypair(privateKey); | |
} |
🤖 Prompt for AI Agents
In src/core/wallet.ts around lines 9 to 12, the constructor assigns the private
key and loads the keypair without handling errors from invalid private keys.
Wrap the call to loadKeypair in a try-catch block to catch any exceptions thrown
due to invalid keys. If an error occurs, handle it gracefully by either throwing
a custom error with a clear message or setting the object to a safe state to
prevent runtime crashes.
private loadKeypair(privateKey: string): Keypair { | ||
const privateKeyBytes = base58.decode(privateKey); | ||
return Keypair.fromSecretKey(privateKeyBytes); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add error handling for keypair creation.
The loadKeypair
method should handle potential errors from base58 decoding and keypair creation to provide meaningful error messages.
private loadKeypair(privateKey: string): Keypair {
+ try {
const privateKeyBytes = base58.decode(privateKey);
+ if (privateKeyBytes.length !== 64) {
+ throw new Error('Private key must be 64 bytes');
+ }
return Keypair.fromSecretKey(privateKeyBytes);
+ } catch (error) {
+ throw new Error(`Invalid private key: ${error instanceof Error ? error.message : 'Unknown error'}`);
+ }
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
private loadKeypair(privateKey: string): Keypair { | |
const privateKeyBytes = base58.decode(privateKey); | |
return Keypair.fromSecretKey(privateKeyBytes); | |
} | |
private loadKeypair(privateKey: string): Keypair { | |
try { | |
const privateKeyBytes = base58.decode(privateKey); | |
if (privateKeyBytes.length !== 64) { | |
throw new Error('Private key must be 64 bytes'); | |
} | |
return Keypair.fromSecretKey(privateKeyBytes); | |
} catch (error) { | |
throw new Error(`Invalid private key: ${error instanceof Error ? error.message : 'Unknown error'}`); | |
} | |
} |
🤖 Prompt for AI Agents
In src/core/wallet.ts around lines 26 to 29, the loadKeypair method lacks error
handling for potential failures during base58 decoding and keypair creation.
Wrap the decoding and keypair creation logic in a try-catch block, catch any
errors thrown, and throw a new error with a clear, descriptive message
indicating the failure to load the keypair along with the original error
details.
"scripts": { | ||
"build": "tsc", | ||
"start": "node dist/index.js", | ||
"generate-proto": "npx grpc_tools_node_protoc --plugin=protoc-gen-ts=./node_modules/.bin/protoc-gen-ts --ts_out=grpc_js:./src/geyser/generated --js_out=import_style=commonjs,binary:./src/geyser/generated --grpc_out=grpc_js:./src/geyser/generated -I ./proto ./proto/*.proto" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove or update the generate-proto script to align with PR objectives.
The PR objectives state that "all gRPC and Geyser-related components have been excluded from this conversion," but the generate-proto
script references proto files and geyser directories. This creates an inconsistency.
Either remove the script if gRPC functionality is truly excluded:
- "generate-proto": "npx grpc_tools_node_protoc --plugin=protoc-gen-ts=./node_modules/.bin/protoc-gen-ts --ts_out=grpc_js:./src/geyser/generated --js_out=import_style=commonjs,binary:./src/geyser/generated --grpc_out=grpc_js:./src/geyser/generated -I ./proto ./proto/*.proto"
Or update the PR objectives to clarify the intended scope of gRPC functionality.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
"generate-proto": "npx grpc_tools_node_protoc --plugin=protoc-gen-ts=./node_modules/.bin/protoc-gen-ts --ts_out=grpc_js:./src/geyser/generated --js_out=import_style=commonjs,binary:./src/geyser/generated --grpc_out=grpc_js:./src/geyser/generated -I ./proto ./proto/*.proto" |
🤖 Prompt for AI Agents
In package.json at line 8, the generate-proto script references gRPC proto files
and geyser directories, which conflicts with the PR objective of excluding all
gRPC and Geyser-related components. To fix this, either remove the
generate-proto script entirely if gRPC functionality is no longer part of the
project, or update the PR objectives to explicitly state that gRPC components
remain included. Choose the appropriate action based on the intended scope of
the PR.
} | ||
|
||
// Define a more specific type for the callback if possible | ||
export type EventCallback = (data: any) => Promise<void>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Improve type safety for EventCallback.
The EventCallback
type uses any
which reduces type safety. Consider using a more specific type or generic.
-export type EventCallback = (data: any) => Promise<void>;
+export type EventCallback<T = unknown> = (data: T) => Promise<void>;
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
export type EventCallback = (data: any) => Promise<void>; | |
export type EventCallback<T = unknown> = (data: T) => Promise<void>; |
🤖 Prompt for AI Agents
In src/monitoring/baseListener.ts at line 11, the EventCallback type currently
uses 'any' for its data parameter, which reduces type safety. Refactor
EventCallback to be a generic type that accepts a type parameter for the data,
allowing callers to specify the exact type of data expected. This will improve
type safety and maintain flexibility.
export interface TokenInfo { | ||
name: string; | ||
symbol: string; | ||
uri: string; | ||
mint: PublicKey; | ||
bondingCurve: PublicKey; | ||
associatedBondingCurve: PublicKey; | ||
user: PublicKey; // The account that initiated the create, likely the fee payer | ||
creator: PublicKey; | ||
creatorVault: PublicKey; | ||
signature?: string; // Optional: include signature for context | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Extract common types and utilities to avoid duplication.
The TokenInfo
interface and findCreatorVault
method are duplicated from blockEventProcessor.ts
. Extract these to a shared module.
Create a new file src/monitoring/types.ts
:
// src/monitoring/types.ts
import { PublicKey } from '@solana/web3.js';
export interface TokenInfo {
name: string;
symbol: string;
uri: string;
mint: PublicKey;
bondingCurve: PublicKey;
associatedBondingCurve: PublicKey;
user: PublicKey;
creator: PublicKey;
creatorVault: PublicKey;
signature?: string;
}
Create a new file src/monitoring/utils.ts
:
// src/monitoring/utils.ts
import { PublicKey } from '@solana/web3.js';
import { Buffer } from 'buffer';
export function findCreatorVault(creator: PublicKey, pumpProgramId: PublicKey): PublicKey {
const [derivedAddress] = PublicKey.findProgramAddressSync(
[Buffer.from("creator-vault"), creator.toBuffer()],
pumpProgramId
);
return derivedAddress;
}
Then import from these shared modules in both processors.
Also applies to: 176-182
🤖 Prompt for AI Agents
In src/monitoring/logsEventProcessor.ts around lines 13 to 24 and also lines 176
to 182, the TokenInfo interface and findCreatorVault function are duplicated
from blockEventProcessor.ts. To fix this, create a new shared file
src/monitoring/types.ts containing the TokenInfo interface and another file
src/monitoring/utils.ts containing the findCreatorVault function as described.
Then, remove the duplicated definitions from both logsEventProcessor.ts and
blockEventProcessor.ts and import these shared types and utilities from the new
modules instead.
private async waitForToken(): Promise<TokenInfo | null> { | ||
let foundToken: TokenInfo | null = null; | ||
const tokenFoundPromise = new Promise<TokenInfo | null>((resolve) => { | ||
const originalCallback = this.tokenListener.callback; // Assuming BaseListener stores it | ||
this.tokenListener.callback = async (token: TokenInfo) => { // Override callback temporarily | ||
const tokenKey = token.mint.toBase58(); | ||
if (!this.processedTokens.has(tokenKey)) { | ||
this.tokenDiscoveredTimestamps.set(tokenKey, monotonic()); | ||
this.processedTokens.add(tokenKey); // Mark as processed for this wait context | ||
foundToken = token; | ||
resolve(token); // Resolve the promise | ||
} | ||
// If originalCallback needs to be called for other purposes, do it here. | ||
// await originalCallback(token); | ||
}; | ||
}); | ||
|
||
await this.tokenListener.start(this.config.matchString || undefined, this.config.creatorAddressFilter || undefined); | ||
logger.info(`Waiting for a suitable token (timeout: ${this.config.tokenWaitTimeoutSec}s)...`); | ||
|
||
try { | ||
const result = await Promise.race([ | ||
tokenFoundPromise, | ||
sleep(this.config.tokenWaitTimeoutSec! * 1000).then(() => null) // Timeout | ||
]); | ||
|
||
if (result) { | ||
logger.info(`Found token: ${result.symbol} (${result.mint.toBase58()})`); | ||
} else { | ||
logger.info(`Timed out after waiting ${this.config.tokenWaitTimeoutSec}s for a token`); | ||
} | ||
return result; | ||
|
||
} finally { | ||
await this.tokenListener.stop(); // Stop listener after waiting | ||
// Restore original callback if necessary, though for single token mode, trader might exit. | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider safer callback management in waitForToken.
The current implementation overrides the listener's callback without restoring it, which could cause issues if the listener is reused. Consider using a separate event emitter or promise-based approach instead of modifying the listener's callback directly.
🤖 Prompt for AI Agents
In src/trading/trader.ts around lines 257 to 294, the method waitForToken
overrides the tokenListener's callback without restoring the original, risking
side effects if the listener is reused. Refactor to avoid directly modifying the
listener's callback by either using a separate event emitter to listen for
tokens or by implementing a promise-based approach that does not alter the
listener's internal callback. Ensure the original callback remains intact and
the token detection logic is handled externally to prevent interference with
other parts of the system.
import *s fs from 'fs/promises'; // For async file operations | ||
import *s path from 'path'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix critical syntax errors in import statements.
The import statements have incorrect syntax with *s
instead of * as
.
-import *s fs from 'fs/promises'; // For async file operations
-import *s path from 'path';
+import * as fs from 'fs/promises'; // For async file operations
+import * as path from 'path';
🧰 Tools
🪛 Biome (1.9.4)
[error] 18-18: expected as
but instead found s
Remove s
(parse)
[error] 18-18: expected from
but instead found fs
Remove fs
(parse)
[error] 18-18: Expected a semicolon or an implicit semicolon after a statement, but found none
An explicit or implicit semicolon is expected here...
...Which is required to end this statement
(parse)
[error] 18-18: Expected a semicolon or an implicit semicolon after a statement, but found none
An explicit or implicit semicolon is expected here...
...Which is required to end this statement
(parse)
[error] 19-19: expected as
but instead found s
Remove s
(parse)
[error] 19-19: expected from
but instead found path
Remove path
(parse)
[error] 19-19: Expected a semicolon or an implicit semicolon after a statement, but found none
An explicit or implicit semicolon is expected here...
...Which is required to end this statement
(parse)
[error] 19-19: Expected a semicolon or an implicit semicolon after a statement, but found none
An explicit or implicit semicolon is expected here...
...Which is required to end this statement
(parse)
[error] 19-19: Shouldn't redeclare 's'. Consider to delete it or rename it.
's' is defined here:
(lint/suspicious/noRedeclare)
🤖 Prompt for AI Agents
In src/trading/trader.ts around lines 18 to 19, the import statements use
incorrect syntax `*s` instead of `* as`. Correct the import statements by
replacing `*s` with `* as` to properly import the modules using the ES module
syntax.
// Corresponds to _log_trade | ||
private async logTrade( | ||
action: 'buy' | 'sell', | ||
tokenInfo: TokenInfo, | ||
price: number, // Price per token in SOL | ||
solAmount: number, // Total SOL value of the trade (for buy, SOL spent; for sell, SOL received) | ||
txSignature: string | ||
): Promise<void> { | ||
try { | ||
const tradesDir = path.join(process.cwd(), "trades"); | ||
await fs.mkdir(tradesDir, { recursive: true }); | ||
|
||
const logEntry = { | ||
timestamp: new Date().toISOString(), | ||
action: action, | ||
tokenAddress: tokenInfo.mint.toBase58(), | ||
symbol: tokenInfo.symbol, | ||
name: tokenInfo.name, // Added name for more context | ||
pricePerTokenSol: price, | ||
totalSolAmount: solAmount, // Amount of SOL involved in this trade | ||
// For buy, tokenAmount = solAmount / price. For sell, tokenAmount = actual tokens sold. | ||
// The `buyResult.amount` and `sellResult.amount` from Buyer/Seller are token amounts. | ||
// Let's clarify: the Python code logs `amount` which seems to be SOL amount for buy, token amount for sell. | ||
// The TradeResult interface has `amount` which is token amount. | ||
// Let's assume `amount` here means token quantity. | ||
tokenQuantity: solAmount, // This needs to be token quantity, not SOL amount. | ||
// The python code used `buy_result.amount` which was token amount. | ||
// And `sell_result.amount` which was token amount. | ||
// So `amount` param here should be token quantity. | ||
txHash: txSignature, | ||
}; | ||
// Adjusting the log based on the `amount` parameter meaning token quantity: | ||
logEntry.tokenQuantity = amount; // Assuming `amount` parameter is token quantity | ||
logEntry.totalSolAmount = price * amount; | ||
|
||
|
||
const logFilePath = path.join(tradesDir, "trades.log"); | ||
await fs.appendFile(logFilePath, JSON.stringify(logEntry) + "\n"); | ||
} catch (e: any) { | ||
logger.error(`Failed to log trade: ${e.message}`); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix critical variable reference error in logTrade method.
The method has a parameter named solAmount
but references an undefined variable amount
on lines 511-512. Additionally, the parameter semantics are confusing - the method signature suggests solAmount
is "Total SOL value" but the implementation treats it as token quantity.
private async logTrade(
action: 'buy' | 'sell',
tokenInfo: TokenInfo,
price: number, // Price per token in SOL
- solAmount: number, // Total SOL value of the trade (for buy, SOL spent; for sell, SOL received)
+ tokenAmount: number, // Amount of tokens bought/sold
txSignature: string
): Promise<void> {
try {
const tradesDir = path.join(process.cwd(), "trades");
await fs.mkdir(tradesDir, { recursive: true });
const logEntry = {
timestamp: new Date().toISOString(),
action: action,
tokenAddress: tokenInfo.mint.toBase58(),
symbol: tokenInfo.symbol,
name: tokenInfo.name,
pricePerTokenSol: price,
- totalSolAmount: solAmount, // Amount of SOL involved in this trade
- // For buy, tokenAmount = solAmount / price. For sell, tokenAmount = actual tokens sold.
- // The `buyResult.amount` and `sellResult.amount` from Buyer/Seller are token amounts.
- // Let's clarify: the Python code logs `amount` which seems to be SOL amount for buy, token amount for sell.
- // The TradeResult interface has `amount` which is token amount.
- // Let's assume `amount` here means token quantity.
- tokenQuantity: solAmount, // This needs to be token quantity, not SOL amount.
- // The python code used `buy_result.amount` which was token amount.
- // And `sell_result.amount` which was token amount.
- // So `amount` param here should be token quantity.
+ tokenQuantity: tokenAmount,
+ totalSolAmount: price * tokenAmount,
txHash: txSignature,
};
- // Adjusting the log based on the `amount` parameter meaning token quantity:
- logEntry.tokenQuantity = amount; // Assuming `amount` parameter is token quantity
- logEntry.totalSolAmount = price * amount;
const logFilePath = path.join(tradesDir, "trades.log");
await fs.appendFile(logFilePath, JSON.stringify(logEntry) + "\n");
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
// Corresponds to _log_trade | |
private async logTrade( | |
action: 'buy' | 'sell', | |
tokenInfo: TokenInfo, | |
price: number, // Price per token in SOL | |
solAmount: number, // Total SOL value of the trade (for buy, SOL spent; for sell, SOL received) | |
txSignature: string | |
): Promise<void> { | |
try { | |
const tradesDir = path.join(process.cwd(), "trades"); | |
await fs.mkdir(tradesDir, { recursive: true }); | |
const logEntry = { | |
timestamp: new Date().toISOString(), | |
action: action, | |
tokenAddress: tokenInfo.mint.toBase58(), | |
symbol: tokenInfo.symbol, | |
name: tokenInfo.name, // Added name for more context | |
pricePerTokenSol: price, | |
totalSolAmount: solAmount, // Amount of SOL involved in this trade | |
// For buy, tokenAmount = solAmount / price. For sell, tokenAmount = actual tokens sold. | |
// The `buyResult.amount` and `sellResult.amount` from Buyer/Seller are token amounts. | |
// Let's clarify: the Python code logs `amount` which seems to be SOL amount for buy, token amount for sell. | |
// The TradeResult interface has `amount` which is token amount. | |
// Let's assume `amount` here means token quantity. | |
tokenQuantity: solAmount, // This needs to be token quantity, not SOL amount. | |
// The python code used `buy_result.amount` which was token amount. | |
// And `sell_result.amount` which was token amount. | |
// So `amount` param here should be token quantity. | |
txHash: txSignature, | |
}; | |
// Adjusting the log based on the `amount` parameter meaning token quantity: | |
logEntry.tokenQuantity = amount; // Assuming `amount` parameter is token quantity | |
logEntry.totalSolAmount = price * amount; | |
const logFilePath = path.join(tradesDir, "trades.log"); | |
await fs.appendFile(logFilePath, JSON.stringify(logEntry) + "\n"); | |
} catch (e: any) { | |
logger.error(`Failed to log trade: ${e.message}`); | |
} | |
} | |
// Corresponds to _log_trade | |
private async logTrade( | |
action: 'buy' | 'sell', | |
tokenInfo: TokenInfo, | |
price: number, // Price per token in SOL | |
tokenAmount: number, // Amount of tokens bought/sold | |
txSignature: string | |
): Promise<void> { | |
try { | |
const tradesDir = path.join(process.cwd(), "trades"); | |
await fs.mkdir(tradesDir, { recursive: true }); | |
const logEntry = { | |
timestamp: new Date().toISOString(), | |
action: action, | |
tokenAddress: tokenInfo.mint.toBase58(), | |
symbol: tokenInfo.symbol, | |
name: tokenInfo.name, // Added name for more context | |
pricePerTokenSol: price, | |
tokenQuantity: tokenAmount, | |
totalSolAmount: price * tokenAmount, | |
txHash: txSignature, | |
}; | |
const logFilePath = path.join(tradesDir, "trades.log"); | |
await fs.appendFile(logFilePath, JSON.stringify(logEntry) + "\n"); | |
} catch (e: any) { | |
logger.error(`Failed to log trade: ${e.message}`); | |
} | |
} |
🤖 Prompt for AI Agents
In src/trading/trader.ts between lines 479 and 520, the logTrade method
incorrectly references an undefined variable `amount` instead of the existing
parameter `solAmount`. To fix this, replace all uses of `amount` with the
correct variable representing token quantity. Also, clarify the method
parameters by renaming `solAmount` to `tokenQuantity` to reflect its actual
meaning, and add a new parameter for the total SOL amount if needed. Update the
logEntry construction accordingly to use these corrected and clearly named
parameters.
// Create ATA instruction - this is often included to ensure the user's token account exists. | ||
// The instruction should ideally be idempotent or checked for existence beforehand if strictness is needed. | ||
// For pump.fun, often the buy instruction itself handles ATA creation if it's the first interaction. | ||
// However, the Python code explicitly adds an idempotent ATA creation. | ||
// createAssociatedTokenAccountInstruction is not idempotent by default if account exists. | ||
// A common pattern is to just send it. If it fails because account exists, tx fails. | ||
// For true idempotency like Python's create_idempotent_associated_token_account, | ||
// you might need to check if account exists first using getAccountInfo, or use a specific instruction if available. | ||
// For now, we'll include it as per the Python code's intention. | ||
// Note: The payer for create ATA is typically the user/wallet. | ||
const ataInstruction = createAssociatedTokenAccountInstruction( | ||
this.wallet.pubkey, // payer | ||
userAssociatedTokenAccount, // ata | ||
this.wallet.pubkey, // owner | ||
tokenInfo.mint // mint | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Address ATA creation idempotency issue.
The current implementation uses createAssociatedTokenAccountInstruction
which will fail if the ATA already exists. As noted in the comment, this is not idempotent and could cause transaction failures.
Consider checking if the ATA exists before including the creation instruction:
Alternatively, you could catch and handle the specific error when the ATA already exists, or use a pattern like:
const ataInfo = await this.solanaClient.connection.getAccountInfo(userAssociatedTokenAccount);
if (!ataInfo) {
instructions.push(ataInstruction);
}
🏁 Script executed:
#!/bin/bash
# Search for examples of idempotent ATA creation patterns in the codebase
rg -A 5 -B 5 "getAccountInfo.*getAssociatedTokenAddress|idempotent.*token.*account"
ast-grep --pattern 'createAssociatedTokenAccountInstruction($$$)'
Length of output: 1726
Ensure idempotent ATA creation
The current use of createAssociatedTokenAccountInstruction
will throw if the ATA already exists, causing the entire transaction to fail. To make this idempotent, either check for the account first or switch to the SPL-Token library’s idempotent instruction.
Locations to update:
- File:
src/trading/buyer.ts
- Around lines 173–188 in
sendBuyTransaction
Suggested changes:
Option A: Pre-check existence and only add the instruction when missing
- const ataInstruction = createAssociatedTokenAccountInstruction(
- this.wallet.pubkey,
- userAssociatedTokenAccount,
- this.wallet.pubkey,
- tokenInfo.mint
- );
- instructions.push(ataInstruction);
+ // Only create the ATA if it doesn’t already exist
+ const ataInfo = await this.solanaClient.connection.getAccountInfo(userAssociatedTokenAccount);
+ if (!ataInfo) {
+ instructions.push(
+ createAssociatedTokenAccountInstruction(
+ this.wallet.pubkey, // payer
+ userAssociatedTokenAccount, // ata
+ this.wallet.pubkey, // owner
+ tokenInfo.mint // mint
+ )
+ );
+ }
Option B: Use the idempotent ATA‐creation helper (if supported by your @solana/spl-token
version)
import {
createIdempotentAssociatedTokenAccountInstruction
} from "@solana/spl-token";
instructions.push(
createIdempotentAssociatedTokenAccountInstruction(
this.wallet.pubkey,
userAssociatedTokenAccount,
this.wallet.pubkey,
tokenInfo.mint
)
);
Either approach will prevent “account exists” errors and ensure your buy transaction is robust.
🤖 Prompt for AI Agents
In src/trading/buyer.ts around lines 173 to 188, the current use of
createAssociatedTokenAccountInstruction is not idempotent and will cause the
transaction to fail if the associated token account (ATA) already exists. To fix
this, either check if the ATA exists first by calling getAccountInfo on
userAssociatedTokenAccount and only push the creation instruction if it does not
exist, or replace createAssociatedTokenAccountInstruction with
createIdempotentAssociatedTokenAccountInstruction from @solana/spl-token if your
version supports it. This change will ensure the ATA creation step is idempotent
and prevent errors from duplicate account creation attempts.
Thanks @prasanthlrb . Apparently nodejs/typescript when handled by you is 236% superior to Python. I suggest you start your own fork and rake the stars. |
This commit includes the initial conversion of the Solana trading bot from Python to Node.js/TypeScript.
Key changes:
The project now has a foundational TypeScript structure for core Solana interactions, RPC/log-based event monitoring, and trading logic.
Further steps will involve converting utility functions, configuration loading, implementing tests, and setting up the bot runner.
The gRPC/Geyser functionality was intentionally skipped due to difficulties in the automated environment for gRPC code generation and your subsequent request to focus on RPC-based features.
Summary by CodeRabbit
New Features
Bug Fixes
Chores
Tests
Documentation