Discord Economy Bot Guide 2025

Build engaging virtual currency systems, leveling mechanics, and interactive economies that keep your community active and invested

Back to Blog

Economy bots are among the most engaging features you can add to your Discord server. They gamify user interaction, encourage participation, and create lasting community engagement. In 2025, with advanced database systems and sophisticated game mechanics, creating compelling economy systems has never been more accessible.

Why Economy Bots Work

Economy systems tap into fundamental human psychology: progression, achievement, and competition. They transform passive server members into active participants by providing tangible rewards for engagement.

Core Economy Features

Virtual Currency

Server-specific or global currency systems with earning mechanisms

Leveling System

XP-based progression with rewards and status recognition

Shop & Inventory

Purchasable items, roles, and collectibles

Games & Gambling

Mini-games and risk/reward mechanics

Database Design and Setup

1Database Schema

Design a robust database structure to handle users, currency, and transactions:

-- SQLite Database Schema for Economy Bot

-- Users table with economy data
CREATE TABLE users (
    id TEXT PRIMARY KEY,
    username TEXT,
    guild_id TEXT,
    balance INTEGER DEFAULT 0,
    bank_balance INTEGER DEFAULT 0,
    level INTEGER DEFAULT 1,
    xp INTEGER DEFAULT 0,
    daily_last_claimed DATE,
    weekly_last_claimed DATE,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

-- Items/Shop inventory
CREATE TABLE items (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT UNIQUE,
    description TEXT,
    price INTEGER,
    category TEXT,
    role_id TEXT,
    emoji TEXT,
    is_available BOOLEAN DEFAULT true,
    max_quantity INTEGER DEFAULT -1
);

-- User inventories
CREATE TABLE user_inventory (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    user_id TEXT,
    item_id INTEGER,
    quantity INTEGER DEFAULT 1,
    purchased_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users (id),
    FOREIGN KEY (item_id) REFERENCES items (id)
);

-- Transaction history
CREATE TABLE transactions (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    user_id TEXT,
    type TEXT, -- 'earn', 'spend', 'transfer', 'gamble'
    amount INTEGER,
    description TEXT,
    timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users (id)
);

-- Leaderboards and rankings
CREATE TABLE leaderboards (
    user_id TEXT PRIMARY KEY,
    guild_id TEXT,
    total_earned INTEGER DEFAULT 0,
    commands_used INTEGER DEFAULT 0,
    gambles_won INTEGER DEFAULT 0,
    gambles_lost INTEGER DEFAULT 0,
    FOREIGN KEY (user_id) REFERENCES users (id)
);

2Database Connection Setup

const sqlite3 = require('sqlite3').verbose();
const { open } = require('sqlite');

class EconomyDatabase {
    constructor() {
        this.db = null;
    }
    
    async initialize() {
        this.db = await open({
            filename: './economy.db',
            driver: sqlite3.Database
        });
        
        // Enable WAL mode for better performance
        await this.db.exec('PRAGMA journal_mode = WAL;');
        await this.db.exec('PRAGMA synchronous = NORMAL;');
        await this.db.exec('PRAGMA cache_size = 1000;');
        
        console.log('Economy database connected successfully');
    }
    
    async getUser(userId, guildId) {
        let user = await this.db.get(
            'SELECT * FROM users WHERE id = ? AND guild_id = ?',
            [userId, guildId]
        );
        
        if (!user) {
            user = await this.createUser(userId, guildId);
        }
        
        return user;
    }
    
    async createUser(userId, guildId, username = 'Unknown') {
        await this.db.run(
            'INSERT INTO users (id, guild_id, username) VALUES (?, ?, ?)',
            [userId, guildId, username]
        );
        
        return await this.getUser(userId, guildId);
    }
    
    async updateBalance(userId, guildId, amount, type = 'earn') {
        const user = await this.getUser(userId, guildId);
        const newBalance = user.balance + amount;
        
        // Prevent negative balances
        if (newBalance < 0) {
            throw new Error('Insufficient balance');
        }
        
        await this.db.run(
            'UPDATE users SET balance = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? AND guild_id = ?',
            [newBalance, userId, guildId]
        );
        
        // Log transaction
        await this.logTransaction(userId, type, amount, `Balance ${type}: ${amount}`);
        
        return newBalance;
    }
    
    async logTransaction(userId, type, amount, description) {
        await this.db.run(
            'INSERT INTO transactions (user_id, type, amount, description) VALUES (?, ?, ?, ?)',
            [userId, type, amount, description]
        );
    }
}

Currency System Implementation

3Basic Currency Commands

const { SlashCommandBuilder } = require('discord.js');

// Balance command
const balanceCommand = {
    data: new SlashCommandBuilder()
        .setName('balance')
        .setDescription('Check your current balance')
        .addUserOption(option =>
            option.setName('user')
                .setDescription('Check another user\'s balance')
                .setRequired(false)),
    
    async execute(interaction) {
        const targetUser = interaction.options.getUser('user') || interaction.user;
        const user = await db.getUser(targetUser.id, interaction.guild.id);
        
        const embed = new EmbedBuilder()
            .setTitle(`💰 ${targetUser.username}'s Balance`)
            .setColor('#f59e0b')
            .addFields(
                { name: 'Wallet', value: `🪙 ${user.balance.toLocaleString()}`, inline: true },
                { name: 'Bank', value: `🏦 ${user.bank_balance.toLocaleString()}`, inline: true },
                { name: 'Total', value: `💎 ${(user.balance + user.bank_balance).toLocaleString()}`, inline: true }
            )
            .setThumbnail(targetUser.displayAvatarURL())
            .setTimestamp();
            
        await interaction.reply({ embeds: [embed] });
    }
};

// Daily reward command
const dailyCommand = {
    data: new SlashCommandBuilder()
        .setName('daily')
        .setDescription('Claim your daily reward'),
    
    async execute(interaction) {
        const user = await db.getUser(interaction.user.id, interaction.guild.id);
        const now = new Date();
        const today = now.toDateString();
        
        // Check if already claimed today
        if (user.daily_last_claimed === today) {
            const tomorrow = new Date(now);
            tomorrow.setDate(tomorrow.getDate() + 1);
            tomorrow.setHours(0, 0, 0, 0);
            
            const timeLeft = tomorrow - now;
            const hours = Math.floor(timeLeft / (1000 * 60 * 60));
            const minutes = Math.floor((timeLeft % (1000 * 60 * 60)) / (1000 * 60));
            
            return await interaction.reply({
                content: `⏰ You've already claimed your daily reward! Come back in ${hours}h ${minutes}m`,
                ephemeral: true
            });
        }
        
        // Calculate daily reward (base + streak bonus)
        const baseReward = 100;
        const streakBonus = this.calculateStreakBonus(user);
        const totalReward = baseReward + streakBonus;
        
        // Award the daily reward
        await db.updateBalance(interaction.user.id, interaction.guild.id, totalReward, 'daily');
        await db.db.run(
            'UPDATE users SET daily_last_claimed = ? WHERE id = ? AND guild_id = ?',
            [today, interaction.user.id, interaction.guild.id]
        );
        
        const embed = new EmbedBuilder()
            .setTitle('🎁 Daily Reward Claimed!')
            .setColor('#10b981')
            .setDescription(`You earned **${totalReward.toLocaleString()}** coins!`)
            .addFields(
                { name: 'Base Reward', value: `🪙 ${baseReward}`, inline: true },
                { name: 'Streak Bonus', value: `⚡ ${streakBonus}`, inline: true },
                { name: 'New Balance', value: `💰 ${(user.balance + totalReward).toLocaleString()}`, inline: true }
            )
            .setTimestamp();
            
        await interaction.reply({ embeds: [embed] });
    },
    
    calculateStreakBonus(user) {
        // Implementation for streak calculation
        const yesterday = new Date();
        yesterday.setDate(yesterday.getDate() - 1);
        
        if (user.daily_last_claimed === yesterday.toDateString()) {
            // Continue streak
            return Math.min(user.daily_streak * 10, 200); // Max 200 bonus
        } else {
            // Reset streak
            return 0;
        }
    }
};

Leveling System

4XP and Level Management

class LevelingSystem {
    static calculateXPNeeded(level) {
        // Progressive XP requirement: level^2 * 100
        return Math.floor(Math.pow(level, 2) * 100);
    }
    
    static calculateLevelFromXP(xp) {
        // Reverse calculation to determine level from total XP
        let level = 1;
        let totalXPNeeded = 0;
        
        while (totalXPNeeded <= xp) {
            level++;
            totalXPNeeded += this.calculateXPNeeded(level);
        }
        
        return level - 1;
    }
    
    static async awardXP(userId, guildId, amount) {
        const user = await db.getUser(userId, guildId);
        const newXP = user.xp + amount;
        const newLevel = this.calculateLevelFromXP(newXP);
        
        // Check if user leveled up
        const leveledUp = newLevel > user.level;
        
        await db.db.run(
            'UPDATE users SET xp = ?, level = ? WHERE id = ? AND guild_id = ?',
            [newXP, newLevel, userId, guildId]
        );
        
        if (leveledUp) {
            return {
                leveledUp: true,
                oldLevel: user.level,
                newLevel: newLevel,
                reward: await this.getLevelUpReward(newLevel)
            };
        }
        
        return { leveledUp: false };
    }
    
    static async getLevelUpReward(level) {
        // Calculate coin reward for leveling up
        const baseReward = 50;
        const multiplier = Math.floor(level / 5) + 1; // Increases every 5 levels
        const milestone = level % 10 === 0 ? level * 10 : 0; // Bonus every 10 levels
        
        return (baseReward * multiplier) + milestone;
    }
}

// XP earning through message activity
client.on('messageCreate', async (message) => {
    if (message.author.bot || !message.guild) return;
    
    // Rate limiting: only award XP once per minute per user
    const lastXPTime = userXPCooldowns.get(message.author.id) || 0;
    const now = Date.now();
    
    if (now - lastXPTime < 60000) return; // 1 minute cooldown
    
    userXPCooldowns.set(message.author.id, now);
    
    // Award random XP (15-25 per message)
    const xpGained = Math.floor(Math.random() * 11) + 15;
    const result = await LevelingSystem.awardXP(
        message.author.id,
        message.guild.id,
        xpGained
    );
    
    // Send level up message
    if (result.leveledUp) {
        const embed = new EmbedBuilder()
            .setTitle('🎉 Level Up!')
            .setColor('#8b5cf6')
            .setDescription(`${message.author} reached **Level ${result.newLevel}**!`)
            .addFields(
                { name: 'Reward', value: `🪙 ${result.reward} coins`, inline: true },
                { name: 'XP Gained', value: `✨ ${xpGained} XP`, inline: true }
            )
            .setThumbnail(message.author.displayAvatarURL());
            
        await message.channel.send({ embeds: [embed] });
        
        // Award level up coins
        await db.updateBalance(message.author.id, message.guild.id, result.reward, 'level_up');
    }
});

Shop and Inventory System

5Shop Implementation

// Shop command with pagination
const shopCommand = {
    data: new SlashCommandBuilder()
        .setName('shop')
        .setDescription('Browse the server shop')
        .addStringOption(option =>
            option.setName('category')
                .setDescription('Filter by category')
                .addChoices(
                    { name: 'Roles', value: 'roles' },
                    { name: 'Items', value: 'items' },
                    { name: 'Collectibles', value: 'collectibles' }
                )),
    
    async execute(interaction) {
        const category = interaction.options.getString('category');
        const items = await this.getShopItems(category);
        
        if (items.length === 0) {
            return await interaction.reply({
                content: '🏪 The shop is empty right now. Check back later!',
                ephemeral: true
            });
        }
        
        const embed = this.createShopEmbed(items, 0);
        const row = this.createShopButtons(items.length);
        
        await interaction.reply({ embeds: [embed], components: [row] });
    },
    
    async getShopItems(category = null) {
        let query = 'SELECT * FROM items WHERE is_available = true';
        const params = [];
        
        if (category) {
            query += ' AND category = ?';
            params.push(category);
        }
        
        query += ' ORDER BY price ASC';
        
        return await db.db.all(query, params);
    },
    
    createShopEmbed(items, page) {
        const itemsPerPage = 5;
        const startIndex = page * itemsPerPage;
        const endIndex = startIndex + itemsPerPage;
        const pageItems = items.slice(startIndex, endIndex);
        
        const embed = new EmbedBuilder()
            .setTitle('🏪 Server Shop')
            .setColor('#06b6d4')
            .setDescription('Use `/buy ` to purchase items')
            .setFooter({ text: `Page ${page + 1} of ${Math.ceil(items.length / itemsPerPage)}` });
        
        pageItems.forEach(item => {
            const stockText = item.max_quantity === -1 ? 'Unlimited' : 
                             `${item.max_quantity} available`;
            
            embed.addFields({
                name: `${item.emoji} ${item.name}`,
                value: `${item.description}\n**Price:** 🪙 ${item.price.toLocaleString()}\n**Stock:** ${stockText}`,
                inline: false
            });
        });
        
        return embed;
    }
};

// Buy command
const buyCommand = {
    data: new SlashCommandBuilder()
        .setName('buy')
        .setDescription('Purchase an item from the shop')
        .addStringOption(option =>
            option.setName('item')
                .setDescription('Item name to purchase')
                .setRequired(true)
                .setAutocomplete(true))
        .addIntegerOption(option =>
            option.setName('quantity')
                .setDescription('Quantity to purchase')
                .setMinValue(1)
                .setMaxValue(10)),
    
    async execute(interaction) {
        const itemName = interaction.options.getString('item');
        const quantity = interaction.options.getInteger('quantity') || 1;
        
        const item = await db.db.get(
            'SELECT * FROM items WHERE LOWER(name) = LOWER(?) AND is_available = true',
            [itemName]
        );
        
        if (!item) {
            return await interaction.reply({
                content: `❌ Item "${itemName}" not found in shop.`,
                ephemeral: true
            });
        }
        
        const user = await db.getUser(interaction.user.id, interaction.guild.id);
        const totalCost = item.price * quantity;
        
        // Check if user has enough balance
        if (user.balance < totalCost) {
            return await interaction.reply({
                content: `❌ Insufficient funds! You need 🪙 ${totalCost.toLocaleString()} but only have 🪙 ${user.balance.toLocaleString()}.`,
                ephemeral: true
            });
        }
        
        // Check stock availability
        if (item.max_quantity !== -1) {
            const currentStock = await this.getCurrentStock(item.id);
            if (currentStock < quantity) {
                return await interaction.reply({
                    content: `❌ Not enough stock! Only ${currentStock} available.`,
                    ephemeral: true
                });
            }
        }
        
        // Process purchase
        try {
            await db.db.run('BEGIN TRANSACTION');
            
            // Deduct balance
            await db.updateBalance(interaction.user.id, interaction.guild.id, -totalCost, 'purchase');
            
            // Add to inventory
            await db.db.run(
                'INSERT INTO user_inventory (user_id, item_id, quantity) VALUES (?, ?, ?)',
                [interaction.user.id, item.id, quantity]
            );
            
            // Apply role if it's a role item
            if (item.role_id) {
                const role = interaction.guild.roles.cache.get(item.role_id);
                if (role) {
                    await interaction.member.roles.add(role);
                }
            }
            
            await db.db.run('COMMIT');
            
            const embed = new EmbedBuilder()
                .setTitle('✅ Purchase Successful!')
                .setColor('#10b981')
                .setDescription(`You bought **${quantity}x ${item.emoji} ${item.name}**`)
                .addFields(
                    { name: 'Cost', value: `🪙 ${totalCost.toLocaleString()}`, inline: true },
                    { name: 'Remaining Balance', value: `🪙 ${(user.balance - totalCost).toLocaleString()}`, inline: true }
                )
                .setTimestamp();
                
            await interaction.reply({ embeds: [embed] });
            
        } catch (error) {
            await db.db.run('ROLLBACK');
            console.error('Purchase failed:', error);
            await interaction.reply({
                content: '❌ Purchase failed. Please try again.',
                ephemeral: true
            });
        }
    }
};

Mini-Games and Gambling Features

6Casino Games

// Coinflip game
const coinflipCommand = {
    data: new SlashCommandBuilder()
        .setName('coinflip')
        .setDescription('Bet on heads or tails')
        .addStringOption(option =>
            option.setName('choice')
                .setDescription('Choose heads or tails')
                .setRequired(true)
                .addChoices(
                    { name: 'Heads', value: 'heads' },
                    { name: 'Tails', value: 'tails' }
                ))
        .addIntegerOption(option =>
            option.setName('amount')
                .setDescription('Amount to bet')
                .setRequired(true)
                .setMinValue(10)),
    
    async execute(interaction) {
        const choice = interaction.options.getString('choice');
        const betAmount = interaction.options.getInteger('amount');
        
        const user = await db.getUser(interaction.user.id, interaction.guild.id);
        
        if (user.balance < betAmount) {
            return await interaction.reply({
                content: `❌ Insufficient balance! You need 🪙 ${betAmount.toLocaleString()}`,
                ephemeral: true
            });
        }
        
        // Flip the coin
        const result = Math.random() < 0.5 ? 'heads' : 'tails';
        const won = choice === result;
        const winAmount = won ? betAmount : -betAmount;
        
        // Update balance
        await db.updateBalance(interaction.user.id, interaction.guild.id, winAmount, 'coinflip');
        
        // Update leaderboard stats
        await this.updateGamblingStats(interaction.user.id, won);
        
        const embed = new EmbedBuilder()
            .setTitle('🪙 Coinflip Result')
            .setColor(won ? '#10b981' : '#ef4444')
            .setDescription(`The coin landed on **${result}**!`)
            .addFields(
                { name: 'Your Choice', value: choice, inline: true },
                { name: 'Result', value: won ? '✅ You Won!' : '❌ You Lost!', inline: true },
                { name: 'Amount', value: `${won ? '+' : ''}🪙 ${winAmount.toLocaleString()}`, inline: true }
            )
            .setTimestamp();
            
        await interaction.reply({ embeds: [embed] });
    }
};

// Slot machine game
const slotsCommand = {
    data: new SlashCommandBuilder()
        .setName('slots')
        .setDescription('Play the slot machine')
        .addIntegerOption(option =>
            option.setName('bet')
                .setDescription('Amount to bet')
                .setRequired(true)
                .setMinValue(25)),
    
    async execute(interaction) {
        const betAmount = interaction.options.getInteger('bet');
        const user = await db.getUser(interaction.user.id, interaction.guild.id);
        
        if (user.balance < betAmount) {
            return await interaction.reply({
                content: `❌ Insufficient balance! You need 🪙 ${betAmount.toLocaleString()}`,
                ephemeral: true
            });
        }
        
        // Slot machine symbols with weights
        const symbols = [
            { emoji: '🍒', weight: 30, multiplier: 2 },
            { emoji: '🍋', weight: 25, multiplier: 3 },
            { emoji: '🍊', weight: 20, multiplier: 4 },
            { emoji: '🍇', weight: 15, multiplier: 6 },
            { emoji: '💎', weight: 8, multiplier: 10 },
            { emoji: '⭐', weight: 2, multiplier: 25 }
        ];
        
        // Spin the reels
        const reels = [
            this.weightedRandom(symbols),
            this.weightedRandom(symbols),
            this.weightedRandom(symbols)
        ];
        
        // Calculate winnings
        const { winAmount, multiplier } = this.calculateSlotWinnings(reels, betAmount);
        const totalWinnings = winAmount - betAmount;
        
        // Update balance
        await db.updateBalance(interaction.user.id, interaction.guild.id, totalWinnings, 'slots');
        
        const resultEmoji = totalWinnings > 0 ? '🎉' : totalWinnings === 0 ? '😐' : '😞';
        const resultText = totalWinnings > 0 ? 'Jackpot!' : 
                          totalWinnings === 0 ? 'Break Even' : 'Better luck next time!';
        
        const embed = new EmbedBuilder()
            .setTitle('🎰 Slot Machine')
            .setColor(totalWinnings > 0 ? '#10b981' : totalWinnings === 0 ? '#f59e0b' : '#ef4444')
            .setDescription(`${reels.map(r => r.emoji).join(' | ')}\n\n${resultEmoji} ${resultText}`)
            .addFields(
                { name: 'Bet', value: `🪙 ${betAmount.toLocaleString()}`, inline: true },
                { name: 'Won', value: `🪙 ${winAmount.toLocaleString()}`, inline: true },
                { name: 'Net', value: `${totalWinnings >= 0 ? '+' : ''}🪙 ${totalWinnings.toLocaleString()}`, inline: true }
            );
            
        if (multiplier > 1) {
            embed.addFields({ name: 'Multiplier', value: `${multiplier}x`, inline: true });
        }
        
        await interaction.reply({ embeds: [embed] });
    },
    
    weightedRandom(symbols) {
        const totalWeight = symbols.reduce((sum, s) => sum + s.weight, 0);
        let random = Math.random() * totalWeight;
        
        for (const symbol of symbols) {
            random -= symbol.weight;
            if (random <= 0) return symbol;
        }
        
        return symbols[0]; // Fallback
    },
    
    calculateSlotWinnings(reels, betAmount) {
        // Three of a kind
        if (reels[0].emoji === reels[1].emoji && reels[1].emoji === reels[2].emoji) {
            return {
                winAmount: betAmount * reels[0].multiplier,
                multiplier: reels[0].multiplier
            };
        }
        
        // Two of a kind (smaller payout)
        const pairs = [
            [reels[0], reels[1]],
            [reels[0], reels[2]],
            [reels[1], reels[2]]
        ];
        
        for (const [a, b] of pairs) {
            if (a.emoji === b.emoji) {
                return {
                    winAmount: Math.floor(betAmount * (a.multiplier * 0.3)),
                    multiplier: a.multiplier * 0.3
                };
            }
        }
        
        // No match
        return { winAmount: 0, multiplier: 0 };
    }
};

Leaderboards and Competition

User: /leaderboard wealth
Bot: 🏆 Wealth Leaderboard
🥇 **PlayerOne** - 1,234,567 coins
🥈 **TopGamer** - 987,654 coins
🥉 **CoinCollector** - 654,321 coins
4️⃣ **LuckyPlayer** - 432,198 coins
5️⃣ **EconomyKing** - 321,987 coins

7Leaderboard System

const leaderboardCommand = {
    data: new SlashCommandBuilder()
        .setName('leaderboard')
        .setDescription('View server leaderboards')
        .addStringOption(option =>
            option.setName('type')
                .setDescription('Leaderboard type')
                .setRequired(true)
                .addChoices(
                    { name: 'Wealth (Total Money)', value: 'wealth' },
                    { name: 'Level', value: 'level' },
                    { name: 'XP', value: 'xp' },
                    { name: 'Daily Streaks', value: 'streaks' }
                )),
    
    async execute(interaction) {
        const type = interaction.options.getString('type');
        const leaderboard = await this.getLeaderboard(type, interaction.guild.id);
        
        if (leaderboard.length === 0) {
            return await interaction.reply({
                content: '📊 No data available for leaderboard yet!',
                ephemeral: true
            });
        }
        
        const embed = this.createLeaderboardEmbed(type, leaderboard, interaction.guild);
        await interaction.reply({ embeds: [embed] });
    },
    
    async getLeaderboard(type, guildId, limit = 10) {
        let query;
        let orderBy;
        
        switch (type) {
            case 'wealth':
                query = 'SELECT id, username, (balance + bank_balance) as total_wealth FROM users';
                orderBy = 'total_wealth DESC';
                break;
            case 'level':
                query = 'SELECT id, username, level FROM users';
                orderBy = 'level DESC, xp DESC';
                break;
            case 'xp':
                query = 'SELECT id, username, xp FROM users';
                orderBy = 'xp DESC';
                break;
            case 'streaks':
                query = 'SELECT id, username, daily_streak FROM users';
                orderBy = 'daily_streak DESC';
                break;
        }
        
        query += ` WHERE guild_id = ? ORDER BY ${orderBy} LIMIT ?`;
        
        return await db.db.all(query, [guildId, limit]);
    },
    
    createLeaderboardEmbed(type, data, guild) {
        const medals = ['🥇', '🥈', '🥉'];
        const numbers = ['4️⃣', '5️⃣', '6️⃣', '7️⃣', '8️⃣', '9️⃣', '🔟'];
        
        const embed = new EmbedBuilder()
            .setTitle(`🏆 ${this.getLeaderboardTitle(type)}`)
            .setColor('#f59e0b')
            .setThumbnail(guild.iconURL())
            .setTimestamp();
        
        let description = '';
        
        data.forEach((user, index) => {
            const position = index < 3 ? medals[index] : numbers[index - 3] || `${index + 1}️⃣`;
            const value = this.formatLeaderboardValue(type, user);
            
            description += `${position} **${user.username}** - ${value}\n`;
        });
        
        embed.setDescription(description);
        
        return embed;
    },
    
    getLeaderboardTitle(type) {
        const titles = {
            wealth: 'Wealth Leaderboard',
            level: 'Level Leaderboard',
            xp: 'XP Leaderboard',
            streaks: 'Daily Streak Leaderboard'
        };
        
        return titles[type] || 'Leaderboard';
    },
    
    formatLeaderboardValue(type, user) {
        switch (type) {
            case 'wealth':
                return `🪙 ${user.total_wealth.toLocaleString()} coins`;
            case 'level':
                return `⭐ Level ${user.level}`;
            case 'xp':
                return `✨ ${user.xp.toLocaleString()} XP`;
            case 'streaks':
                return `🔥 ${user.daily_streak} days`;
            default:
                return 'N/A';
        }
    }
};

Advanced Features and Monetization

Economy Balance

Maintain healthy economy balance by implementing coin sinks (ways to remove currency) alongside sources. Without proper balance, inflation can ruin the economy experience.

Economy Management Tools

// Admin commands for economy management
const adminEconomyCommand = {
    data: new SlashCommandBuilder()
        .setName('economy-admin')
        .setDescription('Economy administration tools')
        .setDefaultMemberPermissions(PermissionFlagsBits.Administrator)
        .addSubcommand(subcommand =>
            subcommand
                .setName('give')
                .setDescription('Give coins to a user')
                .addUserOption(option =>
                    option.setName('user')
                        .setDescription('User to give coins to')
                        .setRequired(true))
                .addIntegerOption(option =>
                    option.setName('amount')
                        .setDescription('Amount of coins to give')
                        .setRequired(true)))
        .addSubcommand(subcommand =>
            subcommand
                .setName('take')
                .setDescription('Take coins from a user')
                .addUserOption(option =>
                    option.setName('user')
                        .setDescription('User to take coins from')
                        .setRequired(true))
                .addIntegerOption(option =>
                    option.setName('amount')
                        .setDescription('Amount of coins to take')
                        .setRequired(true)))
        .addSubcommand(subcommand =>
            subcommand
                .setName('reset')
                .setDescription('Reset a user\'s economy data')
                .addUserOption(option =>
                    option.setName('user')
                        .setDescription('User to reset')
                        .setRequired(true)))
        .addSubcommand(subcommand =>
            subcommand
                .setName('stats')
                .setDescription('View economy statistics')),
    
    async execute(interaction) {
        const subcommand = interaction.options.getSubcommand();
        
        switch (subcommand) {
            case 'give':
                await this.handleGive(interaction);
                break;
            case 'take':
                await this.handleTake(interaction);
                break;
            case 'reset':
                await this.handleReset(interaction);
                break;
            case 'stats':
                await this.handleStats(interaction);
                break;
        }
    },
    
    async handleStats(interaction) {
        const stats = await db.db.get(`
            SELECT 
                COUNT(*) as total_users,
                SUM(balance + bank_balance) as total_currency,
                AVG(balance + bank_balance) as avg_wealth,
                MAX(balance + bank_balance) as max_wealth,
                AVG(level) as avg_level,
                MAX(level) as max_level
            FROM users 
            WHERE guild_id = ?
        `, [interaction.guild.id]);
        
        const transactions = await db.db.get(`
            SELECT COUNT(*) as total_transactions
            FROM transactions t
            JOIN users u ON t.user_id = u.id
            WHERE u.guild_id = ?
        `, [interaction.guild.id]);
        
        const embed = new EmbedBuilder()
            .setTitle('📊 Economy Statistics')
            .setColor('#06b6d4')
            .addFields(
                { name: 'Total Users', value: stats.total_users.toLocaleString(), inline: true },
                { name: 'Total Currency', value: `🪙 ${stats.total_currency.toLocaleString()}`, inline: true },
                { name: 'Average Wealth', value: `🪙 ${Math.floor(stats.avg_wealth).toLocaleString()}`, inline: true },
                { name: 'Richest User', value: `🪙 ${stats.max_wealth.toLocaleString()}`, inline: true },
                { name: 'Average Level', value: `⭐ ${stats.avg_level.toFixed(1)}`, inline: true },
                { name: 'Highest Level', value: `⭐ ${stats.max_level}`, inline: true },
                { name: 'Total Transactions', value: transactions.total_transactions.toLocaleString(), inline: true }
            )
            .setTimestamp();
            
        await interaction.reply({ embeds: [embed], ephemeral: true });
    }
};

Performance and Scaling

Database Optimization Tips

  • Use database indexes on frequently queried columns (user_id, guild_id)
  • Implement connection pooling for high-traffic bots
  • Regular database maintenance and cleanup of old transactions
  • Consider Redis for caching frequently accessed data
  • Implement rate limiting to prevent database overload

8Caching and Optimization

const NodeCache = require('node-cache');
const userCache = new NodeCache({ stdTTL: 300 }); // 5 minute cache

class OptimizedEconomyDatabase extends EconomyDatabase {
    async getUser(userId, guildId) {
        const cacheKey = `user_${userId}_${guildId}`;
        let user = userCache.get(cacheKey);
        
        if (!user) {
            user = await super.getUser(userId, guildId);
            userCache.set(cacheKey, user);
        }
        
        return user;
    }
    
    async updateBalance(userId, guildId, amount, type = 'earn') {
        const result = await super.updateBalance(userId, guildId, amount, type);
        
        // Update cache
        const cacheKey = `user_${userId}_${guildId}`;
        const user = userCache.get(cacheKey);
        if (user) {
            user.balance += amount;
            userCache.set(cacheKey, user);
        }
        
        return result;
    }
    
    // Batch operations for better performance
    async batchUpdateBalances(updates) {
        await this.db.run('BEGIN TRANSACTION');
        
        try {
            for (const { userId, guildId, amount, type } of updates) {
                await this.updateBalance(userId, guildId, amount, type);
            }
            
            await this.db.run('COMMIT');
        } catch (error) {
            await this.db.run('ROLLBACK');
            throw error;
        }
    }
}

Security and Anti-Abuse Measures

9Economy Security

class EconomySecurity {
    static detectAnomalies(userId, action, amount) {
        const userActivity = this.getUserActivity(userId);
        
        // Flag suspicious patterns
        const flags = [];
        
        // Rapid successive commands
        if (userActivity.commandsInLastMinute > 20) {
            flags.push('RAPID_COMMANDS');
        }
        
        // Unusually large transactions
        if (amount > userActivity.averageTransaction * 10) {
            flags.push('LARGE_TRANSACTION');
        }
        
        // Gambling addiction patterns
        if (action.includes('gamble') && userActivity.lossStreak > 10) {
            flags.push('POTENTIAL_PROBLEM_GAMBLING');
        }
        
        return flags;
    }
    
    static async implementCooldowns(userId, command) {
        const cooldowns = {
            'daily': 86400000,    // 24 hours
            'weekly': 604800000,  // 7 days
            'work': 3600000,      // 1 hour
            'rob': 1800000,       // 30 minutes
            'gamble': 30000       // 30 seconds
        };
        
        const lastUsed = await this.getLastCommandTime(userId, command);
        const cooldown = cooldowns[command] || 0;
        const now = Date.now();
        
        if (now - lastUsed < cooldown) {
            const timeLeft = cooldown - (now - lastUsed);
            throw new Error(`Command on cooldown. Try again in ${this.formatTime(timeLeft)}`);
        }
        
        await this.setLastCommandTime(userId, command, now);
    }
    
    static validateTransaction(fromUser, toUser, amount) {
        // Prevent negative amounts
        if (amount <= 0) {
            throw new Error('Amount must be positive');
        }
        
        // Prevent self-transactions
        if (fromUser.id === toUser.id) {
            throw new Error('Cannot transfer to yourself');
        }
        
        // Check minimum transfer amount
        if (amount < 10) {
            throw new Error('Minimum transfer amount is 10 coins');
        }
        
        // Check maximum transfer amount (anti-money laundering)
        if (amount > 1000000) {
            throw new Error('Maximum transfer amount is 1,000,000 coins');
        }
        
        return true;
    }
}

Economy Bot Success Tips

  • Start simple with basic currency and gradually add features
  • Balance earning rates with spending opportunities
  • Regular events and seasonal content keep users engaged
  • Monitor economy health with analytics and adjust rates
  • Consider integration with existing popular bots like MEE6 or Carl-bot
  • Use Friendify's economy features for rapid deployment and proven mechanics

Deployment and Maintenance

Economy bots require ongoing maintenance and monitoring:

  • Regular Database Backups: User economy data is critical
  • Economy Monitoring: Track inflation and user engagement
  • Seasonal Events: Double XP weekends, special shop items
  • Balance Adjustments: Fine-tune earning and spending rates
  • Bug Bounties: Reward users who find economy exploits

Economy bots create lasting engagement by tapping into progression psychology. With proper implementation and balance, they become the cornerstone of active Discord communities, encouraging daily participation and fostering healthy competition among members.

← Back to All Posts