Skip to content

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

Conversation

prasanthlrb
Copy link

@prasanthlrb prasanthlrb commented May 24, 2025

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.

Summary by CodeRabbit

  • New Features

    • Introduced a TypeScript-based trading bot system for Solana, including modules for wallet management, blockchain client, bonding curve calculations, trading operations (buy/sell), event processing, and listeners for blocks and logs.
    • Added support for dynamic and fixed priority fee management, with plugins to estimate transaction fees based on network conditions.
    • Implemented event-driven trading via real-time listeners for new token events using WebSocket and log subscriptions.
    • Provided comprehensive configuration and modular structure for extensibility.
  • Bug Fixes

    • Not applicable.
  • Chores

    • Migrated the codebase from Python to TypeScript, removing all Python scripts, utilities, and tests.
    • Removed Python-specific configuration files and dependencies.
    • Added TypeScript project configuration and package management files.
  • Tests

    • Removed all Python-based test scripts.
  • Documentation

    • Not applicable.

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.
Copy link
Contributor

coderabbitai bot commented May 24, 2025

Walkthrough

This 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

File(s) Change Summary
.gitignore, pyproject.toml, package.json, tsconfig.json Python config and ignore files removed; TypeScript project config and minimal .gitignore added
src/core/client.py, src/core/curve.py, src/core/pubkeys.py, src/core/wallet.py Python core blockchain, curve, pubkeys, and wallet modules removed
src/core/client.ts, src/core/curve.ts, src/core/pubkeys.ts, src/core/wallet.ts TypeScript core modules for Solana client, bonding curve, pubkeys, and wallet added
src/core/priority_fee/* (Python) All Python priority fee plugin, dynamic, fixed, and manager modules removed
src/core/priority_fee/* (TypeScript) TypeScript modules for priority fee plugins, dynamic/fixed fee, and manager added
src/core/index.ts TypeScript index module re-exporting all core modules added
src/cleanup/* (Python) Python cleanup manager and modes modules deleted
src/config_loader.py Python config loader and validator deleted
src/monitoring/* (Python) All Python listeners, event processors, and base classes for monitoring deleted
src/monitoring/* (TypeScript) TypeScript modules for base listener, block/logs event processors, block/logs listeners, and index added
src/trading/* (Python) All Python trading base, buyer, seller, and main trader classes deleted
src/trading/* (TypeScript) TypeScript modules for trading base, buyer, seller, trader, and index added
src/utils/logger.py Python logging utility deleted
learning-examples/*, tests/* All Python learning examples and test scripts deleted
src/geyser/proto/* Protobuf schemas for Geyser and Solana storage deleted

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)
Loading

Suggested labels

enhancement

Poem

A hop, a skip, a leap so bold,
From Python burrows to TypeScript warren,
The old code’s gone, the new is gold,
With listeners, traders, and bots now foreign.
The rabbit blinks, its whiskers twitch—
“Let’s trade on Solana, with code that’s rich!”
🐇✨

Note

⚡️ AI Code Reviews for VS Code, Cursor, Windsurf

CodeRabbit 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.
Learn more here.

✨ Finishing Touches
  • 📝 Generate Docstrings

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need 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)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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 empty Trader 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 and parseSellTx 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

📥 Commits

Reviewing files that changed from the base of the PR and between 9495d25 and 8e4c6be.

⛔ 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 moderate

Length 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 and bondingCurveManager 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 and TOKEN_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 and TOKEN_PROGRAM_ID accounts in src/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_authority

Action: 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.

Comment on lines +18 to +24
public async getPriorityFee(): Promise<number | null> {
if (this.fixedFee === 0) {
return null;
}
return this.fixedFee;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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.

Suggested change
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.

Comment on lines +9 to +12
constructor(privateKey: string) {
this._privateKey = privateKey;
this._keypair = this.loadKeypair(privateKey);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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.

Suggested change
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.

Comment on lines +26 to +29
private loadKeypair(privateKey: string): Keypair {
const privateKeyBytes = base58.decode(privateKey);
return Keypair.fromSecretKey(privateKeyBytes);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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.

Suggested change
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"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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.

Suggested change
"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>;
Copy link
Contributor

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.

Suggested change
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.

Comment on lines +13 to +24
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
}
Copy link
Contributor

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.

Comment on lines +257 to +294
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.
}
}
Copy link
Contributor

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.

Comment on lines +18 to +19
import *s fs from 'fs/promises'; // For async file operations
import *s path from 'path';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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.

Comment on lines +479 to +520
// 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}`);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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.

Suggested change
// 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.

Comment on lines +173 to +188
// 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
);
Copy link
Contributor

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.

@akegaviar
Copy link
Member

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.

@akegaviar akegaviar closed this May 24, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants