sell-token.ts•6.87 kB
import { LAMPORTS_PER_SOL, PublicKey } from "@solana/web3.js";
import { initializeSDK, createMcpResponse } from "./get-token-info.js";
import { getOrCreateKeypair, getSPLBalance, rootDir } from "./utils.js";
import path from "path";
import fs from "fs";
import dotenv from "dotenv";
dotenv.config({ path: path.join(rootDir, ".env") });
const DEFAULT_PRIORITY_FEES = {
  unitLimit: 250000,
  unitPrice: 250000,
};
export async function sellToken(
  tokenAddress: string,
  sellAmount: number = 0,
  accountName: string = "default",
  slippageBasisPoints: number = 100
) {
  try {
    console.error("Starting sellToken function");
    const { sdk, connection } = initializeSDK();
    console.error("SDK initialized");
    const keysFolder = path.resolve(rootDir, ".keys");
    console.error(`Using keys folder path relative to script: ${keysFolder}`);
    console.error(
      `Checking if keys folder exists: ${fs.existsSync(keysFolder)}`
    );
    if (!fs.existsSync(keysFolder)) {
      console.error(`Creating keys folder: ${keysFolder}`);
      try {
        fs.mkdirSync(keysFolder, { recursive: true });
        console.error(`Keys folder created successfully`);
      } catch (mkdirError: any) {
        console.error(`Error creating keys folder:`, mkdirError);
        return {
          success: false,
          error: `Error creating keys folder: ${
            mkdirError.message || JSON.stringify(mkdirError)
          }`,
        };
      }
    }
    console.error(`Getting or creating keypair from folder: ${keysFolder}`);
    const account = getOrCreateKeypair(keysFolder, accountName);
    console.log(`Using account: ${account.publicKey.toString()}`);
    const mintPublicKey = new PublicKey(tokenAddress);
    console.log(`Token address: ${tokenAddress}`);
    const tokenBalance = await getSPLBalance(
      connection,
      mintPublicKey,
      account.publicKey
    );
    console.log(`Current token balance: ${tokenBalance}`);
    if (!tokenBalance || tokenBalance === 0) {
      const errorMessage = `No tokens to sell. Account ${account.publicKey.toString()} has 0 tokens of ${tokenAddress}.`;
      console.error(errorMessage);
      return { success: false, error: errorMessage };
    }
    const amountToSell =
      sellAmount === 0 ? tokenBalance : Math.min(sellAmount, tokenBalance);
    console.log(`Amount to sell: ${amountToSell}`);
    const initialSolBalance = await connection.getBalance(account.publicKey);
    console.log(
      `Initial SOL balance: ${initialSolBalance / LAMPORTS_PER_SOL} SOL`
    );
    console.log(`Selling ${amountToSell} tokens...`);
    const result = await sdk.sell(
      account,
      mintPublicKey,
      BigInt(amountToSell * Math.pow(10, 6)),
      BigInt(slippageBasisPoints),
      DEFAULT_PRIORITY_FEES
    );
    if (!result.success) {
      console.error(`Failed to sell token:`, result.error);
      return {
        success: false,
        error: result.error
          ? typeof result.error === "object"
            ? JSON.stringify(result.error)
            : result.error
          : "Unknown error",
      };
    }
    console.log(`Transaction successful: ${result.signature}`);
    const newSolBalance = await connection.getBalance(account.publicKey);
    console.log(`New SOL balance: ${newSolBalance / LAMPORTS_PER_SOL} SOL`);
    const solReceived = (newSolBalance - initialSolBalance) / LAMPORTS_PER_SOL;
    console.log(`SOL received: ${solReceived} SOL`);
    const newTokenBalance =
      (await getSPLBalance(connection, mintPublicKey, account.publicKey)) || 0;
    console.log(`New token balance: ${newTokenBalance}`);
    return {
      success: true,
      tokenAddress,
      tokensSold: amountToSell,
      solReceived,
      newTokenBalance,
      signature: result.signature,
      pumpfunUrl: `https://pump.fun/${tokenAddress}`,
    };
  } catch (error: any) {
    console.error("Error selling token:", error);
    console.error("Error stack:", error.stack);
    let errorMessage = "Unknown error";
    if (error) {
      if (typeof error === "object") {
        if (error.message) {
          errorMessage = error.message;
        } else {
          try {
            errorMessage = JSON.stringify(error);
          } catch (e) {
            errorMessage = "Error object could not be stringified";
          }
        }
      } else {
        errorMessage = String(error);
      }
    }
    return { success: false, error: errorMessage };
  }
}
export function formatSellResult(
  result: ReturnType<typeof sellToken> extends Promise<infer T> ? T : never
) {
  if (!result.success) {
    return `Error selling token: ${result.error}`;
  }
  return [
    `Successfully sold token!`,
    `Token Address: ${result.tokenAddress}`,
    `Tokens Sold: ${result.tokensSold}`,
    `SOL Received: ${result.solReceived} SOL`,
    `Remaining Token Balance: ${result.newTokenBalance}`,
    `Transaction Signature: ${result.signature}`,
    `Pump.fun URL: ${result.pumpfunUrl}`,
  ].join("\n");
}
async function main() {
  const args = process.argv.slice(2);
  if (args.length < 1) {
    console.error(
      "Usage: node sell-token.js <token_address> [sell_amount] [account_name] [slippage_basis_points]"
    );
    console.error(
      "Example: node sell-token.js G5e2XonmccmdKc98g3eNQe5oBYGw9m8xdMUvVtcZpump 1000 default 100"
    );
    console.error(
      "Note: If sell_amount is 0 or not provided, all tokens will be sold."
    );
    process.exit(1);
  }
  const tokenAddress = args[0];
  const sellAmount = args[1] ? parseFloat(args[1]) : 0;
  const accountName = args[2] || "default";
  const slippageBasisPoints = args[3] ? parseInt(args[3]) : 100;
  if (isNaN(sellAmount) || sellAmount < 0) {
    console.error("Sell amount must be a non-negative number");
    process.exit(1);
  }
  if (isNaN(slippageBasisPoints) || slippageBasisPoints < 0) {
    console.error("Slippage basis points must be a non-negative integer");
    process.exit(1);
  }
  const sellAmountText = sellAmount === 0 ? "ALL" : sellAmount;
  console.log(
    `Selling ${sellAmountText} tokens of ${tokenAddress} with account ${accountName} and slippage ${slippageBasisPoints} basis points`
  );
  try {
    const result = await sellToken(
      tokenAddress,
      sellAmount,
      accountName,
      slippageBasisPoints
    );
    console.log("\nResult:");
    const formattedResult = formatSellResult(result);
    console.log(formattedResult);
    const mcpResponse = createMcpResponse(formattedResult);
    console.log("\nMCP Response (for reference):");
    console.log(JSON.stringify(mcpResponse, null, 2));
  } catch (error: any) {
    console.error("Error in main:", error);
  }
}
const isMainModule = import.meta.url === `file://${process.argv[1]}`;
if (isMainModule) {
  main().catch(console.error);
}
export default {
  sellToken,
  formatSellResult,
};