Skip to main content
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, signAndSendTransaction } 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 {
        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 {
                    await signAndSendTransaction(connection, commitment, transaction, keypair);
                    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 SDK’s getAllClaimablePositions() function returns different types of claimable positions based on the token’s lifecycle and fee sharing configuration:

Regular Positions (No Custom Fee Vault)

These positions are for tokens that don’t use custom fee sharing:
  • Virtual Pool Fees: Fees earned from pre-graduation trading (before the token migrates to DAMM V2)
  • DAMM V2 Pool Fees: Fees earned from post-graduation trading (after the token migrates to the DAMM V2 pool)

Custom Fee Vault V1 Positions

These positions use the Fee Share V1 program for custom fee sharing:
  • Custom Fee Vault: A vault that holds fees for specific claimers (A or B side)
  • Virtual Pool Fees: May include fees from pre-graduation trading
  • DAMM V2 Pool Fees: May include fees from post-graduation trading
  • Uses customFeeVaultClaimerA and customFeeVaultClaimerB to identify claimers
  • Uses customFeeVaultClaimerSide (‘A’ or ‘B’) to determine which side can claim

Custom Fee Vault V2 Positions (Pre-Migration)

These positions use the Fee Share V2 program before token migration:
  • Virtual Pool Fees Only: Fees from pre-graduation trading
  • User BPS: Your share percentage in basis points (10000 = 100%)
  • Claimer Index: Your position in the fee claimers array
  • Token has not yet migrated to DAMM V2 pool

Custom Fee Vault V2 Positions (Post-Migration)

These positions use the Fee Share V2 program after token migration:
  • Virtual Pool Fees: Fees from pre-graduation trading
  • DAMM V2 Pool Fees: Fees from post-graduation trading
  • User BPS: Your share percentage in basis points (10000 = 100%)
  • Claimer Index: Your position in the fee claimers array
  • Token has migrated to DAMM V2 pool (isMigrated: true)

Position Properties

Each position includes:
  • baseMint: The token mint address
  • virtualPoolAddress: Address of the virtual pool (if applicable)
  • virtualPoolClaimableAmount or virtualPoolClaimableLamportsUserShare: Claimable amount from virtual pool
  • dammPoolClaimableAmount or dammPoolClaimableLamportsUserShare: Claimable amount from DAMM V2 pool (if migrated)
  • totalClaimableLamportsUserShare: Total claimable amount for your share
  • isCustomFeeVault: Whether this position uses custom fee sharing
  • programId: The fee share program ID (V1 or V2)
  • isMigrated: Whether the token has migrated to DAMM V2 pool

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 V2 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.