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
🥈 **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.