Skip to main content

Building a Telegram Bot with Breeze SDK

This guide demonstrates how to build a fully functional Telegram bot for DeFi yield farming using the Breeze SDK. The bot provides a conversational interface for users to deposit stablecoins, earn yield, and withdraw funds seamlessly.

Functionality

Test it yourself

If the bot will be running on our hardware, then you will be able to test it yourself. You can find it under @breeze_integration_bot handle or under this link.

Code Location

GitHub Repo

Find out the github repository with the full code
The Breeze SDK (@breezebaby/breeze-sdk) abstracts away complex API interactions, providing a clean interface for deposit/withdraw operations and real-time portfolio management.

Breeze SDK Telegram Bot - Complete Implementation Guide

Overview

The Breeze SDK Telegram Bot enables users to:
  • Manage Wallets: Generate new keypairs or import existing ones
  • Real-time Portfolio Tracking: View current balances and yield positions with live APY data
  • Multi-Asset Support: Deposit and withdraw 8 different assets (USDC, USDT, USDS, JupSOL, JLP, mSOL, JitoSOL, SOL)
  • Per-Asset APY Display: See real-time APY for each supported asset
  • Detailed Balance Views: See comprehensive breakdowns of all positions
  • Yield History: Track earnings over time with pagination support
  • Seamless Deposits: Deposit any supported asset into yield-generating strategies
  • Flexible Withdrawals: Withdraw funds with percentage-based or custom amounts
  • Transaction Management: Sign and submit transactions directly through Telegram
Info: The bot uses Strategy ID + Mint for deposits/withdrawals, fetching real-time APY per asset from the Breeze API.

Project Setup

Dependencies

{
  "dependencies": {
    "@breezebaby/breeze-sdk": "^2.0.1",
    "@solana/web3.js": "^1.98.2",
    "@solana/spl-token": "^0.4.13",
    "node-telegram-bot-api": "^0.66.0",
    "bs58": "^6.0.0",
    "dotenv": "^16.5.0"
  },
  "devDependencies": {
    "typescript": "^5.8.3",
    "@types/node": "^24.0.3",
    "@types/node-telegram-bot-api": "^0.64.9",
    "tsx": "^4.21.0"
  }
}

TypeScript Configuration

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ES2020",
    "moduleResolution": "bundler",
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "skipLibCheck": true,
    "outDir": "./dist",
    "rootDir": "./src"
  }
}
Warning: Make sure to set "type": "module" in your package.json to use ES modules.

Core Implementation

Asset Configuration

The bot supports 8 assets with their respective mints and decimals:
interface AssetConfig {
    symbol: string;
    mint: string;
    decimals: number;
}

const AVAILABLE_ASSETS: AssetConfig[] = [
    { symbol: 'USDC', mint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', decimals: 6 },
    { symbol: 'USDT', mint: 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB', decimals: 6 },
    { symbol: 'USDS', mint: 'USDSwr9ApdHk5bvJKMjzff41FfuX8bSxdKcR81vTwcA', decimals: 6 },
    { symbol: 'JupSOL', mint: 'jupSoLaHXQiZZTSfEWMTRRgpnyFm8f6sZdosWBjx93v', decimals: 9 },
    { symbol: 'JLP', mint: '27G8MtK7VtTcCHkpASjSDdkWWYfoqT6ggEuKidVJidD4', decimals: 6 },
    { symbol: 'mSOL', mint: 'mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So', decimals: 9 },
    { symbol: 'JitoSOL', mint: 'J1toso1uCk3RLmjorhTtrVwY9HJ7X8V9yYac6Y7kGCPn', decimals: 9 },
    { symbol: 'SOL', mint: 'So11111111111111111111111111111111111111112', decimals: 9 },
];

Environment Variables

const BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN || process.env.BOT_TOKEN!;
const SOLANA_RPC_URL = process.env.SOLANA_RPC_URL || 'https://api.mainnet-beta.solana.com';
const BREEZE_API_KEY = process.env.BREEZE_API_KEY!;
const STRATEGY_ID = process.env.STRATEGY_ID!;
const BREEZE_API_BASE_URL = 'https://api.breeze.baby';

Strategy Info Type

interface StrategyInfo {
    strategy_id: string;
    strategy_name: string;
    assets: string[];
    apy: number;
    apy_per_asset: Record<string, number>;  // mint -> APY mapping
}

SDK Initialization

import { BreezeSDK } from '@breezebaby/breeze-sdk';

class BreezeBot {
    private bot: TelegramBot;
    private connection: Connection;
    private breezeSDK: BreezeSDK;
    private users: Map<number, UserData> = new Map();
    private strategyInfo: StrategyInfo | null = null;
    private strategyInfoLastFetch: number = 0;
    private readonly STRATEGY_INFO_CACHE_TTL = 60000; // 1 minute cache

    constructor() {
        this.bot = new TelegramBot(BOT_TOKEN, { polling: true });
        this.connection = new Connection(SOLANA_RPC_URL);

        // Initialize Breeze SDK for balance/yield queries
        this.breezeSDK = new BreezeSDK({
            baseUrl: 'https://api.breeze.baby/',
            apiKey: BREEZE_API_KEY
        });

        this.setupHandlers();
        // Fetch strategy info on startup
        this.fetchStrategyInfo();
    }
}

User Data Management

interface UserData {
    keypair?: Keypair;
    publicKey?: string;
    currentMenu?: string;
    selectedAsset?: AssetConfig;  // Currently selected asset for deposit/withdraw
    pendingTransaction?: {
        serializedTx: string;
        type: 'deposit' | 'withdraw';
        amount?: number;
        asset?: string;
    };
}

SDK Methods Overview

The bot uses three main SDK methods:
  1. getStrategyInfo(strategyId) - Fetches APY per asset
  2. getBreezeBalances({ userId, strategyId }) - Fetches user’s balances in the strategy
  3. createDepositTransaction / createWithdrawTransaction - Creates transactions with strategyId + baseAsset

Strategy Info & APY Integration

Fetching Strategy Info

The bot fetches strategy info using the SDK to get real-time APY for each asset:
private async fetchStrategyInfo(): Promise<StrategyInfo | null> {
    const now = Date.now();
    // Use cache if valid
    if (this.strategyInfo && (now - this.strategyInfoLastFetch) < this.STRATEGY_INFO_CACHE_TTL) {
        return this.strategyInfo;
    }

    try {
        // Use SDK method
        this.strategyInfo = await this.breezeSDK.getStrategyInfo(STRATEGY_ID);
        this.strategyInfoLastFetch = now;
        return this.strategyInfo;
    } catch (error) {
        console.error('Error fetching strategy info:', error);
        return this.strategyInfo;
    }
}

// Get APY for a specific mint
private getApyForMint(mint: string): number | null {
    if (!this.strategyInfo?.apy_per_asset) return null;
    return this.strategyInfo.apy_per_asset[mint] ?? null;
}

Fetching Breeze Balances

User balances are fetched using getBreezeBalances with the strategyId:
// Get total Breeze balance
private async getUserCurrentValue(userPublicKey: string): Promise<number> {
    try {
        const data = await this.breezeSDK.getBreezeBalances({
            userId: userPublicKey,
            strategyId: STRATEGY_ID
        });

        let total = 0;
        for (const balance of data.data) {
            total += balance.total_position_value / Math.pow(10, balance.decimals);
        }
        return total;
    } catch (error) {
        console.error('Error fetching user current value:', error);
        return 0;
    }
}

// Get all Breeze balances mapped by symbol
private async getUserBreezeBalances(userPublicKey: string): Promise<Map<string, { balance: number; apy: number }>> {
    const balances = new Map<string, { balance: number; apy: number }>();

    try {
        const data = await this.breezeSDK.getBreezeBalances({
            userId: userPublicKey,
            strategyId: STRATEGY_ID,
            sortBy: 'balance',
            sortOrder: 'desc'
        });

        for (const balance of data.data) {
            const positionValue = balance.total_position_value / Math.pow(10, balance.decimals);
            balances.set(balance.token_symbol, {
                balance: positionValue,
                apy: balance.apy
            });
        }
    } catch (error) {
        console.error('Error fetching user Breeze balances:', error);
    }

    return balances;
}

Deposit Transaction Processing

Deposits use the SDK’s createDepositTransaction with strategyId + baseAsset:
private async processDeposit(chatId: number, percentage?: number, customAmount?: number) {
    const userData = this.users.get(chatId)!;
    const selectedAsset = userData.selectedAsset;

    // ... amount calculation logic ...

    try {
        // Use SDK to create deposit transaction with strategyId + baseAsset
        const data = await this.breezeSDK.createDepositTransaction({
            strategyId: STRATEGY_ID,
            baseAsset: selectedAsset.mint,
            amount: Number(tokenAmount),
            all: isAll,
            userKey: userData.publicKey!,
            payerKey: userData.publicKey!
        });

        // Check if response is string (success) or error object
        if (typeof data === 'object' && 'message' in data) {
            await this.bot.sendMessage(chatId, `❌ Error: ${data.message}`);
            return;
        }

        userData.pendingTransaction = {
            serializedTx: data as string,
            type: 'deposit',
            amount: humanAmount,
            asset: selectedAsset.symbol
        };

        await this.showTransactionConfirmation(chatId, 'deposit', humanAmount, selectedAsset.symbol);
    } catch (error) {
        console.error('Deposit error:', error);
        await this.bot.sendMessage(chatId, '❌ Failed to create deposit transaction. Please try again.');
    }
}

Withdraw Transaction Processing

Withdrawals use the SDK’s createWithdrawTransaction with strategyId + baseAsset:
private async processWithdraw(chatId: number, percentage?: number, customAmount?: number) {
    const userData = this.users.get(chatId)!;
    const selectedAsset = userData.selectedAsset;

    // ... amount calculation logic ...

    try {
        // Use SDK to create withdraw transaction with strategyId + baseAsset
        const data = await this.breezeSDK.createWithdrawTransaction({
            strategyId: STRATEGY_ID,
            baseAsset: selectedAsset.mint,
            amount: tokenAmount,
            all: isAll,
            userKey: userData.publicKey!,
            payerKey: userData.publicKey!
        });

        // Check if response is string (success) or error object
        if (typeof data === 'object' && 'message' in data) {
            await this.bot.sendMessage(chatId, `❌ Error: ${data.message}`);
            return;
        }

        userData.pendingTransaction = {
            serializedTx: data as string,
            type: 'withdraw',
            amount: humanAmount,
            asset: selectedAsset.symbol
        };

        await this.showTransactionConfirmation(chatId, 'withdraw', humanAmount, selectedAsset.symbol);
    } catch (error) {
        console.error('Withdraw error:', error);
        await this.bot.sendMessage(chatId, '❌ Failed to create withdrawal transaction. Please try again.');
    }
}

User Interface Components

Main Dashboard with Per-Asset APY

private async showMainInterface(chatId: number) {
    const userData = this.users.get(chatId)!;
    const publicKey = userData.publicKey!;

    // Fetch strategy info for APY
    await this.fetchStrategyInfo();

    // Get wallet balances for all assets
    const walletBalances = await this.getAllBalances(publicKey);
    const breezeBalance = await this.getUserCurrentValue(publicKey);

    let balanceStr = '';
    for (const asset of AVAILABLE_ASSETS) {
        const balance = walletBalances.get(asset.symbol);
        const apy = this.getApyForMint(asset.mint);
        const apyStr = apy !== null ? ` (${apy.toFixed(2)}% APY)` : '';

        if (asset.symbol === 'SOL') {
            balanceStr += `• ${asset.symbol}: ${balance?.human.toFixed(4) || '0.0000'}${apyStr}\n`;
        } else {
            balanceStr += `• ${asset.symbol}: ${balance?.human.toFixed(2) || '0.00'}${apyStr}\n`;
        }
    }

    const message =
        '🌊 **BREEZE INTEGRATION BOT** 🌊\n\n' +
        `💳 Wallet: \`${publicKey.slice(0, 8)}...${publicKey.slice(-8)}\`\n\n` +
        '💰 **Wallet Balances:**\n' +
        balanceStr + '\n' +
        `🌊 **Breeze Balance:** $${breezeBalance.toFixed(2)}\n\n` +
        '🚀 Ready to earn yield with Breeze!';

    // ... keyboard setup
}

Deposit Interface with Asset Selection

private async showDepositInterface(chatId: number) {
    // Fetch strategy info for APY and available assets
    await this.fetchStrategyInfo();

    const availableAssets = this.strategyInfo?.assets || [];

    let message = '📥 **Deposit to Breeze** 📥\n\n' +
        'Select the asset you want to deposit:\n\n';

    // Build keyboard with all available assets and their APYs
    const assetButtons: TelegramBot.InlineKeyboardButton[][] = [];

    for (const asset of AVAILABLE_ASSETS) {
        const apy = this.getApyForMint(asset.mint);
        const isAvailable = availableAssets.length === 0 || availableAssets.includes(asset.mint);

        if (isAvailable && apy !== null) {
            assetButtons.push([{
                text: `${asset.symbol} (${apy.toFixed(2)}% APY)`,
                callback_data: `deposit_asset_${asset.symbol}`
            }]);
        }
    }

    assetButtons.push([{ text: '🔙 Back', callback_data: 'earn_yield' }]);

    await this.bot.sendMessage(chatId, message, {
        parse_mode: 'Markdown',
        reply_markup: { inline_keyboard: assetButtons }
    });
}

Withdraw Interface with Asset Selection

private async showWithdrawInterface(chatId: number) {
    const userData = this.users.get(chatId)!;

    // Get all Breeze balances
    const breezeBalances = await this.getUserBreezeBalances(userData.publicKey!);

    let message = '📤 **Withdraw from Breeze** 📤\n\n' +
        'Select the asset you want to withdraw:\n\n';

    // Build keyboard with assets that have balances
    const assetButtons: TelegramBot.InlineKeyboardButton[][] = [];

    for (const asset of AVAILABLE_ASSETS) {
        const breezeBal = breezeBalances.get(asset.symbol);
        if (breezeBal && breezeBal.balance > 0) {
            const displayBalance = asset.decimals > 6
                ? breezeBal.balance.toFixed(4)
                : breezeBal.balance.toFixed(2);
            assetButtons.push([{
                text: `${asset.symbol}: ${displayBalance}`,
                callback_data: `withdraw_asset_${asset.symbol}`
            }]);
        }
    }

    assetButtons.push([{ text: '🔙 Back', callback_data: 'earn_yield' }]);

    await this.bot.sendMessage(chatId, message, {
        parse_mode: 'Markdown',
        reply_markup: { inline_keyboard: assetButtons }
    });
}

Callback Handler for Asset Selection

private async handleCallbackQuery(query: TelegramBot.CallbackQuery) {
    const chatId = query.message!.chat.id;
    const data = query.data!;

    await this.bot.answerCallbackQuery(query.id);

    // Handle asset selection for deposit
    if (data.startsWith('deposit_asset_')) {
        const symbol = data.replace('deposit_asset_', '');
        const asset = getAssetBySymbol(symbol);
        if (asset) {
            this.users.get(chatId)!.selectedAsset = asset;
            await this.showDepositAmountSelection(chatId);
        }
        return;
    }

    // Handle asset selection for withdraw
    if (data.startsWith('withdraw_asset_')) {
        const symbol = data.replace('withdraw_asset_', '');
        const asset = getAssetBySymbol(symbol);
        if (asset) {
            this.users.get(chatId)!.selectedAsset = asset;
            await this.showWithdrawAmountSelection(chatId);
        }
        return;
    }

    switch (data) {
        case 'deposit_amount_50':
            await this.processDeposit(chatId, 50);
            break;
        case 'deposit_amount_100':
            await this.processDeposit(chatId, 100);
            break;
        case 'withdraw_amount_50':
            await this.processWithdraw(chatId, 50);
            break;
        case 'withdraw_amount_100':
            await this.processWithdraw(chatId, 100);
            break;
        // ... other cases
    }
}

Transaction Management

Transaction Signing and Submission

private async confirmTransaction(chatId: number) {
    const userData = this.users.get(chatId)!;
    const pendingTx = userData.pendingTransaction;

    if (!pendingTx) {
        await this.bot.sendMessage(chatId, '❌ No pending transaction found.');
        return;
    }

    try {
        // Deserialize and sign the transaction
        const transaction = VersionedTransaction.deserialize(
            Buffer.from(pendingTx.serializedTx, 'base64')
        );

        transaction.sign([userData.keypair!]);

        // Submit to Solana network
        const signature = await this.connection.sendTransaction(transaction);

        await this.bot.sendMessage(chatId, '⏳ Transaction sent! Waiting for confirmation...');

        // Wait for confirmation
        const confirmation = await this.connection.confirmTransaction(signature, 'confirmed');

        if (confirmation.value.err) {
            await this.bot.sendMessage(chatId, '❌ Transaction failed!');
            return;
        }

        const action = pendingTx.type === 'deposit' ? 'deposited to' : 'withdrawn from';
        await this.bot.sendMessage(chatId,
            `🎉 **Successfully ${action} Breeze!**\n\n` +
            `💰 Amount: ${pendingTx.amount?.toFixed(2)} ${pendingTx.asset}\n` +
            `🔗 Transaction: \`${signature}\``,
            { parse_mode: 'Markdown' }
        );

        // Clear pending transaction
        userData.pendingTransaction = undefined;

        setTimeout(() => this.showMainInterface(chatId), 2000);
    } catch (error) {
        console.error('Transaction error:', error);
        await this.bot.sendMessage(chatId, '❌ Failed to process transaction. Please try again.');
    }
}

Key Features

Multi-Asset Support

  • 8 Supported Assets: USDC, USDT, USDS, JupSOL, JLP, mSOL, JitoSOL, SOL
  • Dynamic Asset Lists: Available assets fetched from strategy info
  • Per-Asset APY Display: Real-time APY shown for each asset

Strategy-Based Integration

  • Strategy ID: Single strategy manages all assets
  • Mint-Based Routing: Transactions use strategy_id + base_asset (mint)
  • APY Per Asset: Strategy info provides apy_per_asset mapping

Wallet Management

  • Keypair Generation: Create new Solana keypairs securely
  • Private Key Import: Import existing wallets using base58 encoded private keys
  • Balance Tracking: Real-time SOL and SPL token balance monitoring

Advanced Yield Farming

  • Seamless Deposits: Deposit any supported asset into yield strategies
  • Flexible Withdrawals: Withdraw with percentage options (50%, 100%) or custom amounts
  • Real-time Portfolio: Live tracking of deposited amounts and yield earnings
  • Live APY Data: Real-time yield rates from the strategy info API

Environment Setup

# Required environment variables
TELEGRAM_BOT_TOKEN=your_telegram_bot_token
SOLANA_RPC_URL=https://api.mainnet-beta.solana.com
BREEZE_API_KEY=your_breeze_api_key
STRATEGY_ID=your_strategy_id 

Build and Run

# Install dependencies
npm install

# Build the project
npm run build

# Start the bot (production)
npm start

# Start the bot (development with hot reload)
npm run dev

SDK Methods Used

All interactions with the Breeze API are done through the SDK:

Strategy Info

// Get strategy metadata including APY per asset
const strategyInfo = await breezeSDK.getStrategyInfo(STRATEGY_ID);
// Returns: { strategy_id, strategy_name, assets, apy, apy_per_asset }

Balance Queries

// Get user balances for a specific strategy
const balances = await breezeSDK.getBreezeBalances({
    userId: userPublicKey,
    strategyId: STRATEGY_ID,
    sortBy: 'balance',
    sortOrder: 'desc'
});
// Returns: { data: BreezeBalance[], meta: { page, per_page, total, ... } }

Transactions

// Create deposit transaction
const depositTx = await breezeSDK.createDepositTransaction({
    strategyId: STRATEGY_ID,
    baseAsset: mintAddress,
    amount: tokenAmount,
    all: false,
    userKey: userPublicKey,
    payerKey: userPublicKey
});

// Create withdraw transaction
const withdrawTx = await breezeSDK.createWithdrawTransaction({
    strategyId: STRATEGY_ID,
    baseAsset: mintAddress,
    amount: tokenAmount,
    all: false,
    userKey: userPublicKey,
    payerKey: userPublicKey
});

Type Definitions

interface BreezeBalance {
    strategy_name: string;
    strategy_id: string;
    fund_id: string;
    token_symbol: string;
    decimals: number;
    total_position_value: number;
    total_deposited_value: number;
    yield_earned: number;
    apy: number;
    last_updated: string;
}

interface StrategyInfo {
    strategy_id: string;
    strategy_name: string;
    assets: string[];
    apy: number;
    apy_per_asset: Record<string, number>;
}

Best Practices

Security Considerations

  1. Private Key Handling: Never log or expose private keys in plain text
  2. Environment Variables: Use secure environment variable management
  3. Input Validation: Validate all user inputs before processing
  4. Rate Limiting: Implement rate limiting to prevent abuse

Performance Optimization

  1. Strategy Info Caching: 1-minute TTL cache for strategy info
  2. Connection Pooling: Reuse Solana RPC connections
  3. Async Operations: Use async/await for all I/O operations
  4. Error Recovery: Implement robust error handling and retry logic

Troubleshooting

API Connection Issues

  • Verify API key is correct and active
  • Check network connectivity to Breeze API
  • Ensure proper environment variable configuration
  • Verify STRATEGY_ID is valid

Transaction Failures

  • Check user has sufficient SOL for transaction fees
  • Verify token account exists and has sufficient balance
  • Ensure transaction isn’t expired or malformed
  • Check Solana network status

Balance Discrepancies

  • Strategy info provides real-time APY data
  • Check for pending transactions
  • Verify decimal conversion accuracy

Conclusion

The Breeze SDK Telegram Bot provides a comprehensive interface for multi-asset yield farming:
  • Strategy-Based Architecture: Single strategy ID manages all supported assets
  • Real-Time APY: Per-asset APY fetched via getStrategyInfo()
  • Multi-Asset Support: 8 different assets for deposits and withdrawals
  • Full SDK Integration: All operations use SDK methods:
    • getStrategyInfo() - APY per asset
    • getBreezeBalances() - User balances with strategy filter
    • createDepositTransaction() / createWithdrawTransaction() - Transactions with strategyId + baseAsset
  • Type Safety: Full TypeScript support for robust development
For additional support and advanced integration patterns, consult the Breeze SDK documentation or reach out to the development team.