In this guide, you’ll learn how to launch a Solana token with shared fees using the Bags TypeScript SDK with Node.js. This allows you to split fees between yourself and another wallet (like a fee claimer) when your token is traded.

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 some SOL for transactions.
  • A token image URL ready for use.
  • The Twitter username of the fee claimer (required).
  • 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 Token Launch with Shared Fees Script

Here is the complete script for launching a token with shared fees. Save it as launch-token-with-shared-fees.ts. The script handles everything from creating the token’s metadata and fetching its image from a URL to configuring the launch with shared fees and submitting the final transaction to the Solana blockchain.
import dotenv from "dotenv";
dotenv.config({ quiet: true });

import { BagsSDK } from "@bagsfm/bags-sdk";
import {
  Keypair,
  VersionedTransaction,
  LAMPORTS_PER_SOL,
  PublicKey,
  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");

const INITIAL_BUY_LAMPORTS = 0.01 * LAMPORTS_PER_SOL; // 0.01 SOL

async function signAndSendTransaction(serializedTx: string, keypair: Keypair): Promise<string> {
  const connection = sdk.state.getConnection();
  const commitment = sdk.state.getCommitment();
  
  const txBuffer = bs58.decode(serializedTx);
  const transaction = VersionedTransaction.deserialize(txBuffer);
  transaction.sign([keypair]);

  const blockhash = await connection.getLatestBlockhash(commitment);
  
  const signature = await connection.sendTransaction(transaction, {
    skipPreflight: true,
    maxRetries: 0,
  });
  
  const confirmation = await connection.confirmTransaction({
    blockhash: blockhash.blockhash,
    lastValidBlockHeight: blockhash.lastValidBlockHeight,
    signature: signature,
  }, commitment);

  if (confirmation.value.err) {
    throw new Error(`Transaction failed: ${confirmation.value.err}`);
  }
  console.log("✅ Transaction confirmed:", signature);
  return signature;
}

async function getFeeShareWallet(
  twitterUsername: string
): Promise<PublicKey | null> {
  try {
    console.log(
      `🔍 Looking up fee share wallet for Twitter user: @${twitterUsername}`
    );
    const feeShareWallet = await sdk.state.getLaunchWalletForTwitterUsername(
      twitterUsername
    );
    console.log(`✨ Found fee share wallet: ${feeShareWallet.toString()}`);
    return feeShareWallet;
  } catch (error) {
    console.error(
      `❌ Failed to get fee share wallet for @${twitterUsername}:`,
      error
    );
    return null;
  }
}

export async function launchTokenWithSharedFees(
  imageUrl: string,
  name: string,
  symbol: string,
  description: string,
  feeClaimerTwitterHandle: string,
  creatorFeeBps: number = 1000, // 10% for creator
  feeClaimerFeeBps: number = 9000, // 90% for fee claimer
  telegram?: string,
  twitter?: string,
  website?: 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(`🚀 Creating token $${symbol} with shared fees using wallet ${keypair.publicKey.toBase58()}`);
    console.log(`📝 Token: ${name} (${symbol})`);
    console.log(`📄 Description: ${description}`);
    console.log(`👤 Fee Claimer: @${feeClaimerTwitterHandle}`);
    console.log(
      `💰 Fee Split: Creator ${creatorFeeBps / 100}% | Fee Claimer ${
        feeClaimerFeeBps / 100
      }%`
    );

    // Step 1: Create token info
    console.log("📝 Creating token info and metadata...");

    // Fetch image url to file like object
    const image = await fetch(imageUrl).then(res => res.blob());

    const tokenInfo = await sdk.tokenLaunch.createTokenInfoAndMetadata({
      image,
      name,
      symbol,
      description,
      telegram,
      twitter,
      website,
    });
    console.log("✨ Successfully created token info and metadata!");
    console.log("🪙 Token mint:", tokenInfo.tokenMint);

    // Step 2: Get fee share wallet (required for shared fees)
    if (!feeClaimerTwitterHandle) {
      throw new Error("Fee claimer Twitter handle is required for shared fees");
    }

    console.log(
      `🔍 Looking up fee share wallet for Twitter user: @${feeClaimerTwitterHandle}`
    );
    const feeShareWallet = await getFeeShareWallet(feeClaimerTwitterHandle);
    if (!feeShareWallet) {
      throw new Error(
        `Could not find fee share wallet for Twitter user @${feeClaimerTwitterHandle}`
      );
    }

    // Step 3: Create launch config with shared fees
    console.log("⚙️  Creating launch config with shared fees...");
    const feeShareConfig = await sdk.config.createFeeShareConfig({
      users: [
        {
          wallet: keypair.publicKey,
          bps: creatorFeeBps,
        },
        {
          wallet: feeShareWallet,
          bps: feeClaimerFeeBps,
        },
      ],
      payer: keypair.publicKey,
      baseMint: new PublicKey(tokenInfo.tokenMint),
      quoteMint: new PublicKey("So11111111111111111111111111111111111111112"), // wSOL mint (required)
    });

    const configResult = {
      configKey: feeShareConfig.configKey.toString(),
      tx: feeShareConfig.transaction
        ? bs58.encode(feeShareConfig.transaction.serialize())
        : null,
    };

    console.log("🔧 Launch config created successfully!");

    if (configResult.tx) {
      console.log("🔐 Signing and sending config transaction...");
      await signAndSendTransaction(configResult.tx, keypair);
    } else {
      console.log("♻️  Config already exists, reusing existing configuration");
    }

    const configKey = configResult.configKey;

    console.log("🔑 Config Key:", configKey);

    // Step 4: Create launch transaction
    console.log("🎯 Creating token launch transaction...");
    const launchTx = await sdk.tokenLaunch.createLaunchTransaction({
      metadataUrl: tokenInfo.tokenMetadata,
      tokenMint: new PublicKey(tokenInfo.tokenMint),
      launchWallet: keypair.publicKey,
      initialBuyLamports: INITIAL_BUY_LAMPORTS,
      configKey: new PublicKey(configKey),
    });

    // Step 6: Send final transaction
    console.log("📡 Sending final transaction...");
    const finalSignature = await signAndSendTransaction(bs58.encode(launchTx.serialize()), keypair);

    console.log("🎉 Token launched successfully!");
    console.log("🪙 Token Mint:", tokenInfo.tokenMint);
    console.log("🔑 Launch Signature:", finalSignature);
    console.log("📄 Metadata URI:", tokenInfo.tokenMetadata);
    console.log("💰 Shared Fees Configuration:");
    console.log(
      `  👤 Creator (${keypair.publicKey.toString()}): ${creatorFeeBps / 100}%`
    );
    console.log(
      `  🤝 Fee Claimer (${feeShareWallet.toString()}): ${feeClaimerFeeBps / 100}%`
    );
    console.log(`🌐 View your token at: https://bags.fm/${tokenInfo.tokenMint}`);

    return {
      tokenMint: tokenInfo.tokenMint,
      signature: finalSignature,
      tokenMetadata: tokenInfo.tokenMetadata,
      tokenLaunch: tokenInfo.tokenLaunch,
      feeShareWallet: feeShareWallet.toString(),
      feeSplit: {
        creator: creatorFeeBps,
        feeClaimer: feeClaimerFeeBps,
      },
    };
  } catch (error) {
    console.error("🚨 Token launch failed:", error);
    throw error;
  }
}

// Example usage
launchTokenWithSharedFees(
  "https://img.freepik.com/premium-vector/white-abstract-vactor-background-design_665257-153.jpg",
  "My Shared Token",
  "MST",
  "This token has shared fees with a fee claimer!",
  "elonmusk", // Twitter username of the fee claimer (required)
  1000, // 10% for creator
  9000, // 90% for fee claimer
  "https://t.me/mysharedtoken",
  "https://twitter.com/mysharedtoken",
  "https://mysharedtoken.com"
).then((result) => {
  console.log("🎊 Launch completed successfully!");
  console.log(`🌐 View your token at: https://bags.fm/${result.tokenMint}`);
}).catch((error) => {
  console.error("🚨 Unexpected error occurred:", error);
});

3. Understanding Shared Fees

When you launch a token with shared fees, the trading fees are split between two wallets:

Requirements for Fee Share Configuration

  • Exactly 2 users: The fee share must be between exactly 2 wallets
  • 100% total: The basis points (bps) must sum to exactly 10000 (100%)
  • wSOL quote mint: Currently only wSOL is supported as the quote token
  • BAGS base mint: The token being launched must be a BAGS token

Fee Split Configuration

  • Creator Fee: The percentage of fees that go to the token creator (you)
  • Fee Claimer Fee: The percentage of fees that go to the fee claimer wallet

Fee Claimer Wallet

The fee claimer wallet is automatically determined by the Twitter username you provide. The Bags API maintains a mapping of Twitter usernames to their associated Solana wallets.

Example Fee Splits

  • 90/10 Split: 90% to fee claimer, 10% to creator
  • 50/50 Split: Equal split between both parties
  • 80/20 Split: 80% to fee claimer, 20% to creator

4. Running Your Script

To launch your token with shared fees, edit the function call in launch-token-with-shared-fees.ts with your token’s details:
launchTokenWithSharedFees(
  "https://your-image-url.com/token-image.png",
  "My Token Name",
  "MTN",
  "Token description here",
  "feeclaimer_username", // Twitter username of fee claimer (required)
  1000, // 10% for creator
  9000, // 90% for fee claimer
  "https://t.me/mytoken",
  "https://twitter.com/mytoken",
  "https://mytoken.com"
);
Then, run the script from your terminal:
npx ts-node launch-token-with-shared-fees.ts

5. Fee Claiming After Launch

After launching a token with shared fees, both the creator and fee claimer can claim their portion of the fees using the Claim Fees Guide. The fee claimer will need to:
  1. Use their wallet to check for claimable positions
  2. Generate and sign claim transactions
  3. Submit the transactions to collect their fees

6. Troubleshooting

Common Issues

  • Invalid Twitter Username: Ensure the fee claimer’s Twitter username is correct and exists
  • Fee Share Wallet Not Found: The Twitter user may not have a registered fee share wallet
  • Invalid Fee Split: The sum of creatorFeeBps and feeClaimerFeeBps should equal 10000 (100%)
  • Insufficient SOL: Your wallet needs SOL for transaction fees

Error Handling

The script includes comprehensive error handling for:
  • API authentication issues
  • Invalid Twitter usernames
  • Network connectivity problems
  • Transaction failures
  • Rate limiting
For more details, see the API Reference.