In this guide, you’ll learn how to retrieve claimable positions and claim fees for a specific token using the Bags TypeScript SDK with Node.js. We’ll show you how to filter claimable positions for a specific token mint and generate claim transactions to collect your fees.

Prerequisites

Before starting, make sure you have:
  • Completed our TypeScript and Node.js Setup Guide.
  • Got your API key from the Bags Developer Portal.
  • A Solana wallet with claimable positions (from token launches, liquidity pools, etc.).
  • Installed the additional dependencies for this guide:
    npm install @solana/web3.js bs58
    

1. Set Up Environment Variables

This guide requires your wallet’s private key. Add it to your base .env file:
# .env
BAGS_API_KEY=your_api_key_here
SOLANA_RPC_URL=https://api.mainnet-beta.solana.com
PRIVATE_KEY=your_base58_encoded_private_key_here  # Required for this guide
You can export your private key from wallets like Bags, Phantom, or Backpack.

2. The Fee Claiming Script

Here is a comprehensive script to fetch claimable positions for a specific token and claim fees. You can save this as claim-fees.ts. This script uses the Bags SDK’s fee service to get all claimable positions, filter them for a specific token mint, and generate claim transactions.
// claim-fees.ts
import dotenv from "dotenv";
dotenv.config({ quiet: true });

import { BagsSDK } from "@bagsfm/bags-sdk";
import { Keypair, LAMPORTS_PER_SOL, Connection } from "@solana/web3.js";
import bs58 from "bs58";

// Initialize SDK
const BAGS_API_KEY = process.env.BAGS_API_KEY;
const SOLANA_RPC_URL = process.env.SOLANA_RPC_URL;
const PRIVATE_KEY = process.env.PRIVATE_KEY;

if (!BAGS_API_KEY || !SOLANA_RPC_URL || !PRIVATE_KEY) {
    throw new Error("BAGS_API_KEY, SOLANA_RPC_URL, and PRIVATE_KEY are required");
}

const connection = new Connection(SOLANA_RPC_URL);
const sdk = new BagsSDK(BAGS_API_KEY, connection, "processed");

async function claimFeesForToken(tokenMint: string) {
    try {
        const PRIVATE_KEY = process.env.PRIVATE_KEY;

        if (!PRIVATE_KEY) {
            throw new Error("PRIVATE_KEY is not set");
        }

        const keypair = Keypair.fromSecretKey(bs58.decode(PRIVATE_KEY));

        console.log(`💰 Claiming fees for token ${tokenMint} with wallet ${keypair.publicKey.toBase58()}`);

        const connection = sdk.state.getConnection();
        const commitment = sdk.state.getCommitment();

        console.log("🔍 Fetching all claimable positions...");

        // Get all claimable positions for the wallet
        const allPositions = await sdk.fee.getAllClaimablePositions(keypair.publicKey);

        if (allPositions.length === 0) {
            console.log("❌ No claimable positions found for this wallet.");
            return;
        }

        console.log(`📋 Found ${allPositions.length} total claimable position(s)`);

        // Filter positions for the specific token mint
        const targetPositions = allPositions.filter(position => position.baseMint === tokenMint);

        if (targetPositions.length === 0) {
            console.log(`❌ No claimable positions found for token mint: ${tokenMint}`);
            console.log("Available token mints:");
            allPositions.forEach((position, index) => {
                console.log(`   ${index + 1}. ${position.baseMint}`);
            });
            return;
        }

        console.log(`✅ Found ${targetPositions.length} claimable position(s) for target token`);

        // Display position details
        targetPositions.forEach((position, index) => {
            console.log(`\n📊 Position ${index + 1}:`);
            console.log(`   🪙 Token: ${position.baseMint}`);
            console.log(`   🏊 Virtual Pool: ${position.virtualPoolAddress}`);

            if (position.virtualPoolClaimableAmount) {
                const virtualAmount = Number(position.virtualPoolClaimableAmount) / LAMPORTS_PER_SOL;
                console.log(`   💰 Virtual Pool Claimable: ${virtualAmount.toFixed(6)} SOL`);
            }

            if (position.dammPoolClaimableAmount) {
                const dammAmount = Number(position.dammPoolClaimableAmount) / LAMPORTS_PER_SOL;
                console.log(`   💰 DAMM Pool Claimable: ${dammAmount.toFixed(6)} SOL`);
            }

            if (position.isCustomFeeVault) {
                const customFeeVaultBalance = Number(position.customFeeVaultBalance) / LAMPORTS_PER_SOL;
                const bps = position.customFeeVaultBps;

                const claimableAmount = customFeeVaultBalance * (bps / 10000);

                console.log(`   🏦 Custom Fee Vault: Yes`);
                console.log(`   📍 Claimer Side: ${position.customFeeVaultClaimerSide}`);
                console.log(`   💰 Custom Fee Vault Claimable: ${claimableAmount.toFixed(6)} SOL`);
            }
        });

        console.log("\n🎯 Creating claim transactions...");

        // Process each target position
        for (let i = 0; i < targetPositions.length; i++) {
            const position = targetPositions[i];
            console.log(`\n⚙️  Processing position ${i + 1}/${targetPositions.length}...`);

            // Generate claim transactions for this position
            const claimTransactions = await sdk.fee.getClaimTransaction(
                keypair.publicKey,
                position
            );

            if (!claimTransactions || claimTransactions.length === 0) {
                console.log(`⚠️  No claim transactions generated for this position.`);
                continue;
            }

            console.log(`✨ Generated ${claimTransactions.length} claim transaction(s)`);

            // Sign and send transactions
            console.log(`🔑 Signing and sending transactions...`);

            for (let j = 0; j < claimTransactions.length; j++) {
                const transaction = claimTransactions[j];

                try {
                    transaction.sign([keypair]);

                    const blockhash = await connection.getLatestBlockhash(commitment);

                    const txSignature = await connection.sendTransaction(transaction, { 
                        maxRetries: 0, 
                        skipPreflight: true 
                    });

                    console.log(`🔑 Confirming transaction signature: ${txSignature}`);

                    const confirmed = await connection.confirmTransaction({
                        blockhash: blockhash.blockhash,
                        lastValidBlockHeight: blockhash.lastValidBlockHeight,
                        signature: txSignature,
                    }, commitment);

                    if (confirmed.value.err) {
                        console.error(`💥 Error confirming transaction ${j + 1}:`, confirmed.value.err);
                        throw new Error("Error confirming transaction");
                    }
                    else {
                        console.log(`✅ Transaction ${j + 1} confirmed successfully!`);
                    }
                } catch (txError) {
                    console.error(`🚨 Failed to send transaction ${j + 1}:`, txError);
                }
            }
        }

        console.log("🎉 Fee claiming process completed!");
    }
    catch (error) {
        console.error("🚨 Unexpected error occurred:", error);
    }
}

claimFeesForToken("TOKEN_MINT");

3. Understanding Claimable Positions

The script retrieves different types of claimable positions:

Virtual Pool Fees

  • Fees earned from pre graduation trading

DAMM Pool Fees

  • Fees earned from post graduation trading

Custom Fee Vaults

  • Fees earned from custom fee sharing arrangements
  • Uses custom fee vault balance and basis points (BPS) to determine claimable fees

4. Running the Script

To check and claim fees for the specified token, simply run the script from your terminal:
npx ts-node claim-fees.ts
The script will automatically use the wallet associated with your private key and claim fees for the specific token mint defined in the claimFeesForToken() function call. You can change the token mint address by modifying the last line of the script.

5. Automatic Fee Claiming

The script automatically signs and sends all claim transactions for the specified token using your private key. This means:
  • Token-specific claiming: Only processes positions for the specified token mint
  • No manual intervention required: All transactions are signed and submitted automatically
  • Batch processing: Multiple claim transactions are processed sequentially
  • Error handling: Failed transactions are logged but don’t stop the process
  • Fallback information: If no positions exist for the target token, it shows available token mints
⚠️ Security Warning: Never commit private keys to version control or share them publicly.

6. Transaction Types

The SDK generates different types of claim transactions based on the position:
  • Virtual Pool Claims: Simple fee claims from token trading
  • DAMM Pool Claims: Complex transactions involving position NFTs and pool data
  • Custom Vault Claims: Specialized transactions for custom fee arrangements
Each transaction type requires specific parameters that the SDK automatically handles based on the position data.

7. Error Handling

The script includes comprehensive error handling for:
  • Invalid API keys
  • Network connectivity issues
  • Invalid wallet addresses
  • Transaction failures
  • Rate limiting
Check the console output for detailed error messages and troubleshooting hints.