Copy import { YoinkSDK } from "yoink-sdk";
import { Connection, Keypair, LAMPORTS_PER_SOL, PublicKey } from "@solana/web3.js";
import { AnchorProvider } from "@coral-xyz/anchor";
import NodeWallet from "@coral-xyz/anchor/dist/cjs/nodewallet";
import { getAccount, getAssociatedTokenAddress } from "@solana/spl-token";
interface BotConfig {
// Target Token Settings
targetTokens: string[]; // Specific token mints to monitor
maxMarketCap: number; // SOL - max market cap to trade
minBuyers: number; // Minimum number of buyers required
onlyIncompleteCarves: boolean; // Only trade incomplete bonding curves
// Trading Parameters
buyAmount: number; // SOL amount per buy
maxSlippage: number; // Slippage tolerance in basis points
maxPriceImpact: number; // Maximum price impact percentage
profitTarget: number; // Take profit percentage
stopLoss: number; // Stop loss percentage
// Bot Behavior
checkInterval: number; // Check interval in milliseconds
maxPositions: number; // Maximum concurrent positions
enableMomentumTrading: boolean; // Enable momentum-based trading
}
interface Position {
mint: string;
balance: number;
entryPrice: number;
entryTime: number;
entryMarketCap: number;
stopLossPrice: number;
takeProfitPrice: number;
}
class BondingCurveTradingBot {
private sdk: YoinkSDK;
private keypair: Keypair;
private config: BotConfig;
private positions: Map<string, Position>;
private isRunning: boolean = false;
private marketData: Map<string, any[]> = new Map(); // Price history
constructor(keypair: Keypair, config: Partial<BotConfig>) {
// Initialize connection and provider
const connection = new Connection(config.rpcUrl || "https://staging-rpc.dev2.eclipsenetwork.xyz");
const wallet = new NodeWallet(keypair);
const provider = new AnchorProvider(connection, wallet, { commitment: "confirmed" });
this.sdk = new YoinkSDK(provider);
this.keypair = keypair;
this.positions = new Map();
this.config = {
targetTokens: config.targetTokens || [],
maxMarketCap: config.maxMarketCap || 50, // 50 SOL max market cap
minBuyers: config.minBuyers || 10,
onlyIncompleteCarves: config.onlyIncompleteCarves !== false,
buyAmount: config.buyAmount || 0.01, // 0.01 SOL per trade
maxSlippage: config.maxSlippage || 500, // 5%
maxPriceImpact: config.maxPriceImpact || 3, // 3%
profitTarget: config.profitTarget || 25, // 25% profit target
stopLoss: config.stopLoss || -15, // 15% stop loss
checkInterval: config.checkInterval || 10000, // 10 seconds
maxPositions: config.maxPositions || 5,
enableMomentumTrading: config.enableMomentumTrading !== false,
}
async start() {
console.log('π€ Starting Bonding Curve Trading Bot...');
console.log('Wallet:', this.keypair.publicKey.toBase58());
try {
// Test connection
const global = await this.sdk.getGlobalAccount();
console.log('β
Connected to Yoink protocol');
console.log('Protocol fee:', Number(global.feeBasisPoints) / 100, '%');
console.log('π Bot Configuration:');
console.log(' Max Market Cap:', this.config.maxMarketCap, 'SOL');
console.log(' Min Buyers:', this.config.minBuyers);
console.log(' Buy Amount:', this.config.buyAmount, 'SOL');
console.log(' Profit Target:', this.config.profitTarget, '%');
console.log(' Stop Loss:', this.config.stopLoss, '%');
console.log(' Max Positions:', this.config.maxPositions);
this.isRunning = true;
await this.monitorAndTrade();
} catch (error) {
console.error('β Failed to start bot:', error);
}
}
async monitorAndTrade() {
while (this.isRunning) {
try {
console.log('π Scanning market...');
// Check existing positions first
await this.checkExistingPositions();
// Look for new opportunities
if (this.positions.size < this.config.maxPositions) {
await this.scanForOpportunities();
}
await this.sleep(this.config.checkInterval);
} catch (error) {
console.error('β Monitoring error:', error);
await this.sleep(5000);
}
}
async checkExistingPositions() {
for (const [mintStr, position] of this.positions.entries()) {
try {
const mint = new PublicKey(mintStr);
const curve = await this.sdk.getBondingCurveAccount(mint);
if (!curve) continue;
const currentPrice = curve.getPricePerToken();
const profitLoss = ((currentPrice - position.entryPrice) / position.entryPrice) * 100;
console.log(`π ${mintStr.slice(0, 8)}... P&L: ${profitLoss.toFixed(2)}%`);
// Check exit conditions
const shouldSell =
profitLoss >= this.config.profitTarget ||
profitLoss <= this.config.stopLoss ||
curve.complete;
if (shouldSell) {
const reason = curve.complete ? 'Curve completed' :
profitLoss >= this.config.profitTarget ? 'Profit target hit' :
'Stop loss triggered';
console.log(`π° Selling ${mintStr.slice(0, 8)}... (${reason})`);
await this.sellPosition(mint, position);
}
} catch (error) {
console.error(`β Error checking position ${mintStr}:`, error);
}
}
}
async scanForOpportunities() {
for (const mintStr of this.config.targetTokens) {
try {
const mint = new PublicKey(mintStr);
await this.analyzeToken(mint);
} catch (error) {
console.error(`β Error analyzing ${mintStr}:`, error);
}
}
}
async analyzeToken(mint: PublicKey) {
try {
const curve = await this.sdk.getBondingCurveAccount(mint);
if (!curve) return;
const marketCap = Number(curve.getMarketCapSOL()) / LAMPORTS_PER_SOL;
const totalBuyers = Number(curve.totalBuyers);
const isComplete = curve.complete;
const currentPrice = curve.getPricePerToken();
// Store price history for momentum analysis
const history = this.marketData.get(mint.toBase58()) || [];
history.push({
timestamp: Date.now(),
price: currentPrice,
marketCap,
buyers: totalBuyers
});
// Keep only last 20 data points
if (history.length > 20) history.shift();
this.marketData.set(mint.toBase58(), history);
console.log(`π ${mint.toBase58().slice(0, 8)}...`);
console.log(` Market Cap: ${marketCap.toFixed(4)} SOL`);
console.log(` Buyers: ${totalBuyers}`);
console.log(` Complete: ${isComplete}`);
// Check if we should buy
const shouldBuy = this.shouldBuyToken(curve, history);
if (shouldBuy && !this.positions.has(mint.toBase58())) {
await this.buyToken(mint, curve);
}
} catch (error) {
console.error(`β Analysis error for ${mint.toBase58()}:`, error);
}
}
shouldBuyToken(curve: any, history: any[]): boolean {
const marketCap = Number(curve.getMarketCapSOL()) / LAMPORTS_PER_SOL;
const totalBuyers = Number(curve.totalBuyers);
// Basic filters
if (marketCap > this.config.maxMarketCap) return false;
if (totalBuyers < this.config.minBuyers) return false;
if (this.config.onlyIncompleteCarves && curve.complete) return false;
// Momentum analysis
if (this.config.enableMomentumTrading && history.length >= 3) {
const recent = history.slice(-3);
const buyerGrowth = recent[2].buyers - recent[0].buyers;
const priceGrowth = ((recent[2].price - recent[0].price) / recent[0].price) * 100;
// Look for positive momentum
if (buyerGrowth > 2 && priceGrowth > 5) {
console.log(`π Momentum detected: +${buyerGrowth} buyers, +${priceGrowth.toFixed(2)}% price`);
return true;
}
}
// Basic entry conditions
return marketCap < 20 && totalBuyers > 15; // Conservative entry
}
async buyToken(mint: PublicKey, curve: any) {
try {
const buyAmount = BigInt(this.config.buyAmount * LAMPORTS_PER_SOL);
// Get quote first
const quote = await this.sdk.getBuyQuote(mint, buyAmount, BigInt(this.config.maxSlippage));
if (quote.priceImpact > this.config.maxPriceImpact) {
console.log(`β οΈ Price impact too high: ${quote.priceImpact.toFixed(2)}%`);
return;
}
console.log(`π’ Buying ${mint.toBase58().slice(0, 8)}...`);
console.log(` Quote: ${quote.tokenAmount} tokens`);
console.log(` Price impact: ${quote.priceImpact.toFixed(2)}%`);
const result = await this.sdk.buy(
this.keypair,
mint,
buyAmount,
BigInt(this.config.maxSlippage),
{ unitLimit: 400000, unitPrice: 100000 }
);
if (result.success) {
const position: Position = {
mint: mint.toBase58(),
balance: Number(quote.tokenAmount) / Math.pow(10, 6),
entryPrice: curve.getPricePerToken(),
entryTime: Date.now(),
entryMarketCap: Number(curve.getMarketCapSOL()) / LAMPORTS_PER_SOL,
stopLossPrice: curve.getPricePerToken() * (1 + this.config.stopLoss / 100),
takeProfitPrice: curve.getPricePerToken() * (1 + this.config.profitTarget / 100),
};
this.positions.set(mint.toBase58(), position);
console.log(`β
Position opened! Signature: ${result.signature}`);
} else {
console.error(`β Buy failed:`, result.error);
}
} catch (error) {
console.error('β Buy execution error:', error);
}
}
async sellPosition(mint: PublicKey, position: Position) {
try {
const tokenAmount = BigInt(Math.floor(position.balance * Math.pow(10, 6)));
const result = await this.sdk.sell(
this.keypair,
mint,
tokenAmount,
BigInt(this.config.maxSlippage)
);
if (result.success) {
const curve = await this.sdk.getBondingCurveAccount(mint);
const exitPrice = curve?.getPricePerToken() || 0;
const profitLoss = ((exitPrice - position.entryPrice) / position.entryPrice) * 100;
const holdTime = (Date.now() - position.entryTime) / 1000 / 60; // minutes
console.log(`β
Position closed! Signature: ${result.signature}`);
console.log(`π° Final P&L: ${profitLoss.toFixed(2)}%`);
console.log(`β±οΈ Hold time: ${holdTime.toFixed(1)} minutes`);
this.positions.delete(mint.toBase58());
} else {
console.error(`β Sell failed:`, result.error);
}
} catch (error) {
console.error('β Sell execution error:', error);
}
}
displayStatus() {
console.log('\\nπ Bot Status:');
console.log(`Active positions: ${this.positions.size}/${this.config.maxPositions}`);
console.log(`Monitoring ${this.config.targetTokens.length} tokens`);
if (this.positions.size > 0) {
console.log('\\nπΌ Current Positions:');
this.positions.forEach(position => {
const holdTime = (Date.now() - position.entryTime) / 1000 / 60;
console.log(` ${position.mint.slice(0, 8)}... (${holdTime.toFixed(1)}m ago)`);
});
}
console.log('β'.repeat(50));
}
stop() {
console.log('π Stopping trading bot...');
this.isRunning = false;
}
sleep(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// Usage Example
async function main() {
const keypair = Keypair.generate(); // Use your funded keypair
const bot = new BondingCurveTradingBot(keypair, {
targetTokens: [
"9BSxAV9iRuiT3W7kwhFEkmzfoMo7xZTBdFGRF793JRbC",
// Add more token mints to monitor
],
maxMarketCap: 30, // Max 30 SOL market cap
minBuyers: 8, // Min 8 buyers
onlyIncompleteCarves: true, // Only trade incomplete curves
buyAmount: 0.005, // 0.005 SOL per trade
maxSlippage: 400, // 4% slippage
maxPriceImpact: 2.5, // 2.5% max price impact
profitTarget: 20, // 20% profit target
stopLoss: -12, // 12% stop loss
checkInterval: 8000, // Check every 8 seconds
maxPositions: 3, // Max 3 concurrent positions
enableMomentumTrading: true, // Enable momentum analysis
});
await bot.start();
// Display status every 30 seconds
setInterval(() => {
bot.displayStatus();
}, 30000);
// Stop after 2 hours (for demo)
setTimeout(() => {
bot.stop();
}, 2 * 60 * 60 * 1000);
}
main().catch(console.error);