OmniTipper - Architectural Overview
Table of Contents
- System Overview
- Architecture Components
- Data Flow
- Database Schema
- Security Architecture
- External Integrations
- Deployment Architecture
- Key Design Decisions
- Technology Stack
System Overview
OmniTipper is a Discord-based cryptocurrency tipping bot that operates on the Base blockchain. The system enables users to tip, deposit, and withdraw various ERC20 tokens and native ETH through Discord commands, with off-chain transfers for tips and on-chain operations for deposits and withdrawals.
System Diagram
!System Architecture Diagram
Core Principles
- Off-chain tips: Fast, free internal transfers (database-only operations)
- On-chain operations: Deposits and withdrawals interact with the Base blockchain
- Multi-token support: Unlimited ERC20 tokens can be added dynamically
- Single deposit address: Each user has one address that accepts all tokens
- Automatic deposit monitoring: Periodic checks for new deposits with automatic sweeping
- Gas fee management: Automatic ETH funding for gas with tracking and accounting
Architecture Components
1. Bot Layer (src/bot.ts)
The main entry point that orchestrates all components:
- Discord Client: Manages connection to Discord Gateway
- Event Handlers: Processes
messageCreate events for commands
- Command Router: Routes commands to appropriate handlers
- Deposit Monitor: Periodic background task checking for deposits
- Balance Updater: Periodic task updating bot balances from chain
Key Responsibilities:
- Initialize all services on startup
- Handle Discord message events
- Coordinate deposit checking loop
- Manage graceful shutdown
2. Command Handlers (src/commands/)
Modular command processors for user interactions:
User Commands
balances.ts: Display user balances for all supported tokens
tip.ts: Transfer tokens between users (off-chain)
withdraw.ts: Withdraw tokens to external addresses (on-chain)
deposit.ts: Generate/display deposit addresses
help.ts: Display help information
Admin Commands (admin.ts)
addcoin: Add new ERC20 token by contract address
addeth: Add native ETH token
botbalances: View bot's on-chain balances
userbalances: View specific user balances
accounting: Compare bot balances vs user balances (with gas fee accounting)
testprice: Test price conversion functionality
sweepeth: Sweep ETH from user deposit addresses
3. Service Layer (src/services/)
Core business logic services:
Web3Service (web3.ts)
Handles all blockchain interactions:
- Token Operations:
- Balance checking (native ETH and ERC20)
- Token transfers (from bot wallet and deposit addresses)
- Token information retrieval (symbol, name, decimals)
- Deposit Address Management:
- Generate new wallet addresses with private keys
- Transfer tokens from deposit addresses to bot wallet
- Automatic ETH funding for gas when needed
- ETH Sweeping:
- Sweep excess ETH from deposit addresses
- Leave reserve for gas fees
- Update bot balances after sweeping
- Optimization Features:
- JSON-RPC batch requests for balance checks
- Rate limiting to avoid RPS limits
- Gas price caching (10-second cache)
- Batch processing (100 requests per batch)
PriceService (price.ts)
Handles cryptocurrency price conversions:
- CoinMarketCap Integration: Fetches real-time prices
- USD Conversion: Converts USD amounts to token amounts
- Price Caching: 1-minute cache to reduce API calls
- Token Symbol Lookup: Uses database to get token symbols for API queries
MetricsService (metrics.ts)
Prometheus-compatible metrics collection:
- Command Metrics: Tracks command usage by type
- Transaction Metrics: Counts tips, withdrawals, deposits
- RPC Metrics: Tracks RPC calls, errors, and durations
- Gas Fee Metrics: Tracks gas fees by coin and operation type
- Performance Metrics: Tracks operation durations and latencies
- System Metrics: Active users, bot balances, uptime
MetricsServer (metrics-server.ts)
HTTP server exposing Prometheus metrics:
- Metrics Endpoint:
/metrics - Prometheus format
- Health Endpoint:
/health - Health check
- Port: Configurable (default: 9090)
- Format: Prometheus text format (compatible with Grafana)
4. Database Layer (src/database/)
MongoDB-based data persistence:
Connection (connection.ts)
- Manages MongoDB connection lifecycle
- Handles connection errors and reconnection
Models (models.ts)
Database schema and operations:
Collections:
- Users: User accounts, balances, deposit addresses
- Coins: Supported tokens configuration
- BotBalances: Bot's on-chain balances
- DepositTracking: Last checked balances per user/coin
- GasFeeTracking: Recorded gas fees for accounting
Key Features:
- Encrypted private key storage (AES-256-GCM)
- BigInt precision preservation (stored as strings)
- Indexed queries for performance
- Atomic balance updates
5. Utility Layer (src/utils/)
Supporting utilities:
Encryption (encryption.ts)
- AES-256-GCM encryption for private keys
- Key derivation using scrypt
- Authentication tags for integrity
- Encrypted data detection
Logger (logger.ts)
- Multi-level logging (ERROR, WARN, INFO, DEBUG)
- Console and file output
- Date-stamped log files
- Separate error log file
- JSON formatting for objects
Environment (env.ts)
- Environment variable validation
- Required vs optional variable checking
- Type validation (e.g., encryption key length)
Data Flow
1. Tip Flow (Off-chain)
!Tip Flow Diagram
No blockchain interaction - pure database operation
2. Deposit Flow (On-chain)
!Deposit Flow Diagram
3. Withdrawal Flow (On-chain)
!Withdrawal Flow Diagram
4. ERC20 Transfer from Deposit Address Flow
!ERC20 Transfer Flow Diagram
5. Price Conversion Flow
!Price Conversion Flow Diagram
Database Schema
!Database Schema Diagram
Users Collection
{
_id: string; // Discord user ID
username: string; // Discord username
balances: Record<string, string>; // coin symbol -> balance (string for precision)
depositAddress: string | null; // Single address for all tokens
depositPrivateKey: string | null; // Encrypted private key
createdAt: Date;
updatedAt: Date;
}
Indexes:
Coins Collection
{
_id: string; // Token symbol (e.g., "USDC", "ETH")
contractAddress: string | null; // ERC20 contract address (null for native ETH)
decimals: number; // Token decimals
name: string; // Token name
enabled: boolean; // Whether token is enabled
isNative: boolean; // true for native ETH
createdAt: Date;
}
Indexes:
_id (primary key)
contractAddress (for lookups)
enabled (for filtering)
BotBalances Collection
{
coin: string; // Token symbol
balance: string; // On-chain balance (string for precision)
updatedAt: Date;
}
Indexes:
DepositTracking Collection
{
_id: string; // "userId:coinSymbol"
userId: string; // Discord user ID
coin: string; // Token symbol
lastCheckedBalance: string; // Last checked balance
botFundedAmount: string; // ETH funded by bot (not user deposit)
updatedAt: Date;
}
Indexes:
_id (primary key)
userId, coin (compound index for queries)
GasFeeTracking Collection
{
_id: string; // Transaction hash or 'total'
type: 'funding' 'transfer';
amount: string; // Gas fee in ETH (string for precision)
transactionHash?: string; // Transaction hash
depositAddress?: string; // For funding operations
userId?: string; // For withdrawal operations
createdAt: Date;
}
Indexes:
_id (primary key)
type (for filtering by operation type)
createdAt (for time-based queries)
Security Architecture
1. Private Key Encryption
- Algorithm: AES-256-GCM
- Key Derivation: scrypt (from ENCRYPTION_KEY)
- Storage: Encrypted in database
- Access: Decrypted only when needed for transactions
- Key Management: Environment variable (minimum 32 characters)
2. Access Control
- Admin Commands: Restricted to users in
ADMINUSERIDS
- User Commands: Available to all users
- Validation: Command handlers check admin status before execution
3. Input Validation
- Amount Validation: Decimal precision checks
- Address Validation: Ethereum address format validation
- Command Parsing: Safe argument extraction
- Error Handling: Graceful error messages (no sensitive data exposure)
4. Rate Limiting
- RPC Calls: 100ms minimum interval (10 RPS max for batches)
- Batch Processing: 100 requests per batch with 200ms delays
- Gas Price Caching: 10-second cache to reduce calls
- Price Caching: 1-minute cache to reduce API calls
5. Balance Precision
- Storage: All balances stored as strings (preserves BigInt precision)
- Operations: BigInt arithmetic for calculations
- Display: Formatted with proper decimals
6. Transaction Safety
- Deposit Confirmation: System waits for on-chain confirmation (status=1) before crediting users.
- Refund Retry Logic: Failed withdrawals automatically retry refunds with exponential backoff (1s, 2s, 4s...) up to 5 times.
- Race Condition Prevention: Deposit checks are locked to prevent overlapping execution.
External Integrations
1. Discord API
- Library: discord.js v14
- Intents Required:
Guilds: Server information
GuildMessages: Server messages
MessageContent: Read message content (privileged)
DirectMessages: Receive DMs
DirectMessageReactions: DM reactions
GuildMembers: Server members (privileged, required for role-based tipping)
2. Base Blockchain
- RPC Provider: Configurable (default:
https://mainnet.base.org)
- Library: ethers.js v6
- Operations:
- Balance queries (
ethgetBalance, ethcall)
- Token transfers (
eth_sendTransaction)
- Contract interactions (ERC20 standard)
- Transaction receipts
3. CoinMarketCap API
- Endpoint:
/v2/cryptocurrency/quotes/latest
- Authentication: API key (header)
- Usage: Price conversion (USD ↔ tokens)
- Caching: 1-minute cache per token
- Rate Limiting: Handled by caching
4. MongoDB
- Version: MongoDB 7
- Connection: Configurable URL
- Collections: 5 collections (users, coins, botBalances, depositTracking, gasFeeTracking)
- Indexes: Optimized for common queries
Deployment Architecture
Container Architecture
!Container Architecture Diagram
Docker Compose Setup
┌─────────────────────────────────────┐
│ Docker Network │
│ │
│ ┌──────────────┐ ┌─────────────┐ │
│ │ Bot │ │ MongoDB │ │
│ │ Container │◄─┤ Container │ │
│ │ │ │ │ │
│ │ - Bun │ │ - MongoDB 7│ │
│ │ - Node.js │ │ - Volume │ │
│ │ - Source │ │ Mounted │ │
│ └──────┬───────┘ └─────────────┘ │
│ │ │
└─────────┼──────────────────────────┘
│
▼
┌──────────┐
│ Discord │
│ API │
└──────────┘
│
▼
┌──────────┐
│ Base │
│ RPC │
└──────────┘
│
▼
┌──────────┐
│CoinMarket│
│ Cap │
└──────────┘
Container Configuration
Bot Container:
- Base Image:
oven/bun:latest
- Ports: None (outbound only)
- Environment: All config via
.env file
- Restart Policy:
unless-stopped
- Dependencies: MongoDB container
MongoDB Container:
- Base Image:
mongo:7
- Port: 27017 (internal network)
- Volume: Persistent data storage
- Restart Policy:
unless-stopped
Environment Variables
Required:
DISCORDBOTTOKEN: Discord bot token
MONGODB_URL: MongoDB connection string
MONGODBDBNAME: Database name
WALLETPRIVATEKEY: Bot wallet private key
COINMARKETCAPAPIKEY: CoinMarketCap API key
ENCRYPTION_KEY: Encryption key (min 32 chars)
ADMINUSERIDS: Comma-separated Discord user IDs
Optional:
BASERPCURL: Base RPC endpoint (default: https://mainnet.base.org)
LOG_LEVEL: Logging level (default: INFO)
LOGTOFILE: Enable file logging (default: false)
LOG_DIR: Log directory (default: ./logs)
DEPOSITCHECKINTERVAL: Deposit check interval in ms (default: 60000)
METRICS_PORT: Metrics server port (default: 9090)
Key Design Decisions
1. Off-chain Tips
Decision: Tips are database-only operations (no blockchain transactions)
Rationale:
- Instant execution (no gas fees)
- No transaction costs for users
- Scales to high volume
- User balances tracked internally
Trade-offs:
- Requires trust in bot operator
- Funds are custodial until withdrawal
2. Single Deposit Address per User
Decision: One deposit address per user for all tokens
Rationale:
- Simpler user experience
- Easier to manage
- Single private key per user
- Works for all ERC20s and native ETH
Trade-offs:
- All tokens share the same address
- Requires ETH on address for gas (for ERC20 transfers)
3. Automatic ETH Funding
Decision: Bot automatically funds deposit addresses with ETH for gas
Rationale:
- Seamless user experience
- No need for users to manage gas
- ERC20 deposits work automatically
Trade-offs:
- Bot must maintain ETH reserves
- Requires tracking bot-funded amounts
- Gas fees reduce bot's ETH balance
4. ETH Sweeping
Decision: Automatically sweep excess ETH from deposit addresses
Rationale:
- Consolidates funds in bot wallet
- Reduces ETH fragmentation
- Leaves reserve for gas fees
Trade-offs:
- Additional transaction costs
- Must ensure enough ETH remains for gas
5. Batch Balance Checking
Decision: Use JSON-RPC batch requests for balance checks
Rationale:
- Reduces RPC calls dramatically
- Avoids rate limiting
- More efficient than individual calls
Trade-offs:
- More complex implementation
- Batch size limits (100 requests)
6. String-based Balance Storage
Decision: Store all balances as strings in database
Rationale:
- Preserves BigInt precision
- No floating-point errors
- Handles tokens with any decimal places
Trade-offs:
- Requires conversion for arithmetic
- Slightly more complex code
7. Gas Fee Tracking
Decision: Track all gas fees separately for accounting
Rationale:
- Accurate accounting reconciliation
- Can verify bot balances vs user balances
- Transparency for operations
Trade-offs:
- Additional database writes
- More complex accounting logic
8. Recursive Deposit Checking
Decision: Use recursive setTimeout instead of setInterval
Rationale:
- Prevents overlapping checks
- Ensures one check completes before next starts
- Better error handling
Trade-offs:
- Slightly more complex timing logic
- Potential delays if checks take long
Technology Stack
Runtime & Language
- Runtime: Bun (v1.2.17+)
- Language: TypeScript
- Package Manager: Bun
Core Dependencies
- discord.js: v14.14.1 - Discord API client
- ethers: v6.9.2 - Ethereum/Base blockchain interactions
- mongodb: v6.3.0 - MongoDB driver
- dotenv: v16.4.1 - Environment variable management
Infrastructure
- Docker: Containerization
- Docker Compose: Multi-container orchestration
- MongoDB: Database (v7)
External Services
- Discord: Bot platform
- Base Network: EVM-compatible blockchain
- CoinMarketCap: Price data API
Development Tools
- TypeScript: Type safety
- Bun: Fast JavaScript runtime and package manager
Performance Considerations
1. RPC Optimization
- Batch requests (100 per batch)
- Rate limiting (100ms between batches)
- Gas price caching (10 seconds)
- Balance check batching
2. Database Optimization
- Indexed queries
- Atomic updates
- Efficient data structures
- Minimal writes (batch updates where possible)
3. API Optimization
- Price caching (1 minute)
- Batch processing
- Error handling with retries
4. Memory Management
- No long-lived caches
- Efficient BigInt handling
- Proper cleanup on shutdown
Monitoring & Observability
Logging
- Levels: ERROR, WARN, INFO, DEBUG
- Outputs: Console and/or file
- Format: Structured with timestamps
- Rotation: Date-stamped log files
Metrics (Prometheus)
The bot exposes Prometheus-compatible metrics on an HTTP endpoint for integration with Grafana or other monitoring systems.
Metrics Endpoint
- URL:
http://localhost:9090/metrics (configurable via METRICS_PORT)
- Format: Prometheus text format
- Health Check:
http://localhost:9090/health
Available Metrics
Counters:
omnitippercommandstotal{command="..."} - Command execution counts
omnitippertipstotal - Total tips processed
omnitipperwithdrawalstotal - Total withdrawals
omnitipperdepositstotal - Total deposits detected
omnitipperrpccalls_total - RPC call count
omnitipperrpcerrors_total - RPC error count
omnitippererrorstotal{type="..."} - Error counts by type
omnitippergasfees_total{coin="..."} - Total gas fees by coin
omnitippergasfeesbytype_total{type="...",coin="..."} - Gas fees by operation type
omnitipperbalancechecks_total - Balance check count
omnitipperdepositchecks_total - Deposit check count
omnitipperdepositcheckerrorstotal - Deposit check errors
omnitipperpriceapicallstotal - Price API calls
omnitipperpriceapierrorstotal - Price API errors
omnitipperpriceapicachehits_total - Price API cache hits
Gauges:
omnitipperactiveusers - Current active user count
omnitipperbotbalance{coin="..."} - Bot balance per coin (in wei)
omnitipperuptimeseconds - Bot uptime
omnitipperbalancecheckdurationseconds - Average balance check duration
omnitipperdepositcheckdurationseconds - Average deposit check duration
Summaries:
omnitipperrpccalldurationseconds - RPC call duration with quantiles (p50, p95, p99)
Grafana Integration
- Configure Prometheus to scrape the metrics endpoint:
scrape_configs:
static_configs:
- targets: ['bot-container:9090']
- Import Grafana Dashboard or create custom dashboards using the available metrics
- Set up Alerts for:
- High error rates
- RPC call failures
- Balance discrepancies
- Deposit check failures
- Low bot balances
Key Metrics to Monitor
- Deposit check frequency and duration
- RPC call rates and error rates
- Transaction success rates (tips, withdrawals, deposits)
- Balance discrepancies
- Gas fee accumulation
- Error rates by type
- Active user count
- Bot balances per coin
- Price API performance
Health Checks
- Database connectivity
- Discord connection status
- RPC endpoint availability
- CoinMarketCap API status
- Metrics server availability (
/health endpoint)
Future Enhancements
Potential Improvements
- Multi-chain support: Extend to other EVM chains
- Transaction history: Track all user transactions
- Analytics dashboard: Admin interface for metrics
- Webhook notifications: Alternative to DMs
- Rate limiting: Per-user command rate limits
- Multi-signature wallet: Enhanced security
- Automated testing: Comprehensive test suite
- API endpoint: REST API for external integrations
Conclusion
OmniTipper is designed as a scalable, secure, and efficient cryptocurrency tipping bot. The architecture prioritizes:
- User Experience: Simple commands, automatic gas management
- Security: Encrypted private keys, input validation
- Performance: Batch operations, caching, rate limiting
- Reliability: Error handling, graceful shutdown, monitoring
- Maintainability: Modular design, clear separation of concerns
The system is production-ready and can handle high-volume operations while maintaining accuracy and security.