From 72e6f20e748ea5e1eb0bea5ca81c4c21e69f7c5e Mon Sep 17 00:00:00 2001 From: philip Date: Fri, 31 Oct 2025 14:20:46 -0400 Subject: [PATCH 1/3] feat: add IPC tFIL faucet with Docker support and deployment instructions - Introduced a complete faucet application for distributing test FIL tokens on the IPC testnet. - Added Docker support for easy deployment and management. - Included comprehensive documentation for setup, configuration, and usage. - Implemented rate limiting and health check endpoints for enhanced security and reliability. - Created frontend using Vue 3 and backend with Express, integrated with MetaMask for user interaction. --- faucet/.gitignore | 31 ++ faucet/DEPLOY.md | 421 +++++++++++++++++++++++ faucet/Dockerfile | 52 +++ faucet/Makefile | 101 ++++++ faucet/QUICKSTART.md | 105 ++++++ faucet/README.md | 526 +++++++++++++++++++++++++++++ faucet/SETUP.md | 315 +++++++++++++++++ faucet/backend/package.json | 18 + faucet/backend/src/index.js | 268 +++++++++++++++ faucet/docker-compose.yml | 35 ++ faucet/env-template.txt | 63 ++++ faucet/frontend/index.html | 14 + faucet/frontend/package.json | 23 ++ faucet/frontend/postcss.config.js | 7 + faucet/frontend/public/favicon.svg | 11 + faucet/frontend/src/App.vue | 387 +++++++++++++++++++++ faucet/frontend/src/main.js | 6 + faucet/frontend/src/style.css | 25 ++ faucet/frontend/tailwind.config.js | 30 ++ faucet/frontend/vite.config.js | 20 ++ faucet/nginx.conf.example | 99 ++++++ faucet/package.json | 19 ++ faucet/scripts/check-balance.js | 74 ++++ faucet/scripts/generate-wallet.js | 36 ++ faucet/scripts/package.json | 11 + 25 files changed, 2697 insertions(+) create mode 100644 faucet/.gitignore create mode 100644 faucet/DEPLOY.md create mode 100644 faucet/Dockerfile create mode 100644 faucet/Makefile create mode 100644 faucet/QUICKSTART.md create mode 100644 faucet/README.md create mode 100644 faucet/SETUP.md create mode 100644 faucet/backend/package.json create mode 100644 faucet/backend/src/index.js create mode 100644 faucet/docker-compose.yml create mode 100644 faucet/env-template.txt create mode 100644 faucet/frontend/index.html create mode 100644 faucet/frontend/package.json create mode 100644 faucet/frontend/postcss.config.js create mode 100644 faucet/frontend/public/favicon.svg create mode 100644 faucet/frontend/src/App.vue create mode 100644 faucet/frontend/src/main.js create mode 100644 faucet/frontend/src/style.css create mode 100644 faucet/frontend/tailwind.config.js create mode 100644 faucet/frontend/vite.config.js create mode 100644 faucet/nginx.conf.example create mode 100644 faucet/package.json create mode 100644 faucet/scripts/check-balance.js create mode 100644 faucet/scripts/generate-wallet.js create mode 100644 faucet/scripts/package.json diff --git a/faucet/.gitignore b/faucet/.gitignore new file mode 100644 index 000000000..4500fe1da --- /dev/null +++ b/faucet/.gitignore @@ -0,0 +1,31 @@ +# Environment files +.env +.env.local + +# Dependencies +node_modules/ +frontend/node_modules/ +backend/node_modules/ + +# Build output +frontend/dist/ +backend/dist/ + +# Logs +*.log +npm-debug.log* +logs/ + +# IDE +.vscode/ +.idea/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Docker +.dockerignore + diff --git a/faucet/DEPLOY.md b/faucet/DEPLOY.md new file mode 100644 index 000000000..6fe6d823d --- /dev/null +++ b/faucet/DEPLOY.md @@ -0,0 +1,421 @@ +# Deployment Guide for GCP + +This guide walks you through deploying the IPC tFIL faucet on Google Cloud Platform. + +## Prerequisites + +- GCP account with billing enabled +- `gcloud` CLI installed and configured +- Basic knowledge of GCP Compute Engine + +## Quick Deployment + +### 1. Create a GCP VM Instance + +```bash +# Create a VM instance +gcloud compute instances create ipc-faucet \ + --zone=us-central1-a \ + --machine-type=e2-small \ + --image-family=ubuntu-2204-lts \ + --image-project=ubuntu-os-cloud \ + --boot-disk-size=20GB \ + --tags=http-server,https-server,faucet-server +``` + +### 2. SSH into the VM + +```bash +gcloud compute ssh ipc-faucet --zone=us-central1-a +``` + +### 3. Install Dependencies + +```bash +# Update system +sudo apt update && sudo apt upgrade -y + +# Install Docker +curl -fsSL https://get.docker.com -o get-docker.sh +sudo sh get-docker.sh +sudo usermod -aG docker $USER + +# Install Docker Compose +sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose +sudo chmod +x /usr/local/bin/docker-compose + +# Install Git +sudo apt install -y git + +# Log out and back in +exit +``` + +### 4. Clone and Configure + +```bash +# SSH back in +gcloud compute ssh ipc-faucet --zone=us-central1-a + +# Clone the repository +git clone https://github.com/consensus-shipyard/ipc.git +cd ipc/faucet + +# Create .env file +nano .env +``` + +Add your configuration: +```bash +PRIVATE_KEY=0xYOUR_PRIVATE_KEY_HERE +RPC_URL=http://node-1.test.ipc.space:8545 +FAUCET_AMOUNT=1 +RATE_LIMIT_WINDOW=86400000 +RATE_LIMIT_MAX=1 +PORT=3001 +ENABLE_CORS=false +SERVE_STATIC=true +``` + +Save with `Ctrl+X`, then `Y`, then `Enter`. + +### 5. Configure Firewall + +```bash +# Create firewall rule for port 3001 +gcloud compute firewall-rules create allow-ipc-faucet \ + --allow tcp:3001 \ + --source-ranges 0.0.0.0/0 \ + --target-tags faucet-server \ + --description "Allow access to IPC faucet on port 3001" +``` + +### 6. Deploy the Faucet + +```bash +# Build and start +docker-compose up -d + +# Check status +docker-compose ps + +# View logs +docker-compose logs -f +``` + +### 7. Verify Deployment + +```bash +# Get external IP +EXTERNAL_IP=$(gcloud compute instances describe ipc-faucet --zone=us-central1-a --format='get(networkInterfaces[0].accessConfigs[0].natIP)') +echo "Faucet URL: http://$EXTERNAL_IP:3001" + +# Test health endpoint +curl http://$EXTERNAL_IP:3001/api/health +``` + +Open the URL in your browser! + +## Production Setup with HTTPS + +### 1. Set Up Domain + +Point your domain to the VM's external IP: +```bash +# Get external IP +gcloud compute instances describe ipc-faucet --zone=us-central1-a --format='get(networkInterfaces[0].accessConfigs[0].natIP)' +``` + +Create an A record pointing to this IP. + +### 2. Install Nginx and Certbot + +```bash +sudo apt update +sudo apt install -y nginx certbot python3-certbot-nginx +``` + +### 3. Configure Nginx + +```bash +# Copy the example config +sudo cp nginx.conf.example /etc/nginx/sites-available/ipc-faucet + +# Edit and replace YOUR_DOMAIN +sudo nano /etc/nginx/sites-available/ipc-faucet + +# Enable the site +sudo ln -s /etc/nginx/sites-available/ipc-faucet /etc/nginx/sites-enabled/ + +# Test configuration +sudo nginx -t + +# Reload nginx +sudo systemctl reload nginx +``` + +### 4. Get SSL Certificate + +```bash +sudo certbot --nginx -d your-domain.com +``` + +Follow the prompts. Certbot will automatically configure SSL. + +### 5. Update Firewall for HTTPS + +```bash +# The http-server and https-server tags should already allow 80/443 +# If not, create rules: +gcloud compute firewall-rules create allow-http \ + --allow tcp:80 \ + --target-tags http-server + +gcloud compute firewall-rules create allow-https \ + --allow tcp:443 \ + --target-tags https-server +``` + +### 6. Test HTTPS + +Visit `https://your-domain.com` in your browser! + +## Monitoring and Maintenance + +### Set Up Monitoring + +```bash +# Install monitoring script +cd ~/ipc/faucet +cat > monitor-faucet.sh << 'EOF' +#!/bin/bash +LOGFILE="/home/$USER/faucet-monitor.log" +cd /home/$USER/ipc/faucet + +echo "=== Faucet Monitor $(date) ===" >> $LOGFILE + +# Check if container is running +if docker-compose ps | grep -q "Up"; then + echo "Status: Running" >> $LOGFILE +else + echo "Status: DOWN - Restarting..." >> $LOGFILE + docker-compose up -d >> $LOGFILE 2>&1 +fi + +# Check balance +docker-compose logs | grep "Faucet balance" | tail -1 >> $LOGFILE + +# Check for errors +ERROR_COUNT=$(docker-compose logs --tail=100 | grep -c "Error") +echo "Recent errors: $ERROR_COUNT" >> $LOGFILE + +echo "" >> $LOGFILE +EOF + +chmod +x monitor-faucet.sh +``` + +### Set Up Cron Job + +```bash +# Edit crontab +crontab -e + +# Add these lines: +# Check faucet status every hour +0 * * * * /home/$USER/ipc/faucet/monitor-faucet.sh + +# Restart faucet daily at 3 AM (optional) +0 3 * * * cd /home/$USER/ipc/faucet && docker-compose restart +``` + +### View Logs + +```bash +# Real-time logs +docker-compose logs -f + +# Last 100 lines +docker-compose logs --tail=100 + +# Monitor log +tail -f ~/faucet-monitor.log +``` + +### Check Balance + +```bash +cd ~/ipc/faucet +cd scripts && npm install && cd .. +node scripts/check-balance.js +``` + +## Backup and Recovery + +### Backup Configuration + +```bash +# Backup .env file +cp ~/ipc/faucet/.env ~/ipc-faucet-backup.env + +# Store securely (not on the same VM!) +gcloud compute scp ~/ipc-faucet-backup.env your-local-machine:~/backups/ +``` + +### Update Deployment + +```bash +cd ~/ipc/faucet +git pull +docker-compose down +docker-compose build --no-cache +docker-compose up -d +``` + +### Disaster Recovery + +If the VM fails: + +1. Create a new VM following steps 1-3 +2. Restore your `.env` file +3. Deploy as per steps 4-6 + +## Cost Optimization + +### Recommended Instance Types + +- **e2-micro** ($5-7/month): Good for low traffic (< 100 requests/day) +- **e2-small** ($13-15/month): Recommended for moderate traffic +- **e2-medium** ($25-30/month): High traffic + +### Set Up Budget Alerts + +```bash +# Create budget alert (via GCP Console recommended) +# Compute Engine > Budgets & Alerts +# Set alert at 50%, 90%, 100% of budget +``` + +### Auto-shutdown for Testing + +```bash +# Stop VM when not needed +gcloud compute instances stop ipc-faucet --zone=us-central1-a + +# Start when needed +gcloud compute instances start ipc-faucet --zone=us-central1-a +``` + +## Security Best Practices + +### 1. Restrict SSH Access + +```bash +# Update firewall to allow SSH only from your IP +gcloud compute firewall-rules create allow-ssh-restricted \ + --allow tcp:22 \ + --source-ranges YOUR_IP_ADDRESS/32 \ + --target-tags faucet-server +``` + +### 2. Enable OS Login + +```bash +gcloud compute instances add-metadata ipc-faucet \ + --zone=us-central1-a \ + --metadata enable-oslogin=TRUE +``` + +### 3. Regular Updates + +```bash +# Set up automatic security updates +sudo apt install -y unattended-upgrades +sudo dpkg-reconfigure -plow unattended-upgrades +``` + +### 4. Rotate Private Key + +Periodically rotate your faucet wallet: +1. Generate new wallet +2. Transfer remaining funds to new wallet +3. Update `.env` with new private key +4. Restart: `docker-compose restart` + +## Troubleshooting + +### Container Won't Start + +```bash +# Check logs +docker-compose logs + +# Rebuild +docker-compose down +docker-compose build --no-cache +docker-compose up -d +``` + +### Out of Memory + +```bash +# Check memory usage +free -h + +# Increase swap +sudo fallocate -l 2G /swapfile +sudo chmod 600 /swapfile +sudo mkswap /swapfile +sudo swapon /swapfile +echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab +``` + +### High CPU Usage + +```bash +# Check container stats +docker stats + +# Scale down if needed +# Consider rate limiting or smaller instance +``` + +## Useful Commands + +```bash +# Restart faucet +docker-compose restart + +# View real-time logs +docker-compose logs -f + +# Check container status +docker-compose ps + +# Stop faucet +docker-compose down + +# Start faucet +docker-compose up -d + +# Update and restart +git pull && docker-compose down && docker-compose build --no-cache && docker-compose up -d + +# Check disk space +df -h + +# Clean up Docker +docker system prune -a +``` + +## Support + +For issues or questions: +- Check logs: `docker-compose logs -f` +- Review README.md +- Check IPC documentation: https://docs.ipc.space + +--- + +**Your faucet should now be production-ready on GCP! ๐Ÿš€** + diff --git a/faucet/Dockerfile b/faucet/Dockerfile new file mode 100644 index 000000000..e1e4e0a19 --- /dev/null +++ b/faucet/Dockerfile @@ -0,0 +1,52 @@ +# Multi-stage build for IPC tFIL Faucet + +# Stage 1: Build frontend +FROM node:20-alpine AS frontend-builder + +WORKDIR /app/frontend + +# Copy frontend package files +COPY frontend/package*.json ./ + +# Install dependencies +RUN npm ci + +# Copy frontend source +COPY frontend/ ./ + +# Build frontend +RUN npm run build + +# Stage 2: Setup backend and runtime +FROM node:20-alpine + +WORKDIR /app + +# Install production dependencies +COPY backend/package*.json ./ +RUN npm ci --only=production + +# Copy backend source +COPY backend/src ./src + +# Copy built frontend +COPY --from=frontend-builder /app/frontend/dist ./frontend/dist + +# Create directory for logs +RUN mkdir -p /app/logs + +# Expose port +EXPOSE 3001 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD node -e "require('http').get('http://localhost:3001/api/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1))" + +# Set environment +ENV NODE_ENV=production +ENV SERVE_STATIC=true +ENV ENABLE_CORS=false + +# Start the application +CMD ["node", "src/index.js"] + diff --git a/faucet/Makefile b/faucet/Makefile new file mode 100644 index 000000000..910b95420 --- /dev/null +++ b/faucet/Makefile @@ -0,0 +1,101 @@ +.PHONY: help install dev build start stop restart logs clean docker-build docker-up docker-down docker-logs generate-wallet check-balance + +help: ## Show this help message + @echo 'Usage: make [target]' + @echo '' + @echo 'Available targets:' + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-20s\033[0m %s\n", $$1, $$2}' + +install: ## Install all dependencies + @echo "๐Ÿ“ฆ Installing dependencies..." + npm install + cd frontend && npm install + cd backend && npm install + cd scripts && npm install + +dev: ## Start development servers + @echo "๐Ÿš€ Starting development servers..." + npm run dev + +build: ## Build frontend for production + @echo "๐Ÿ”จ Building frontend..." + cd frontend && npm run build + +start: ## Start backend in production mode + @echo "โ–ถ๏ธ Starting backend..." + cd backend && npm start + +stop: ## Stop all processes + @echo "โน๏ธ Stopping processes..." + @pkill -f "node.*src/index.js" || true + @pkill -f "vite" || true + +restart: stop start ## Restart the application + +logs: ## View application logs (requires Docker) + docker-compose logs -f + +clean: ## Clean build artifacts and dependencies + @echo "๐Ÿงน Cleaning..." + rm -rf node_modules + rm -rf frontend/node_modules + rm -rf frontend/dist + rm -rf backend/node_modules + rm -rf scripts/node_modules + rm -rf logs + +docker-build: ## Build Docker image + @echo "๐Ÿณ Building Docker image..." + docker-compose build + +docker-up: ## Start Docker containers + @echo "๐Ÿณ Starting Docker containers..." + docker-compose up -d + +docker-down: ## Stop Docker containers + @echo "๐Ÿณ Stopping Docker containers..." + docker-compose down + +docker-logs: ## View Docker logs + docker-compose logs -f + +docker-restart: docker-down docker-up ## Restart Docker containers + +docker-rebuild: docker-down ## Rebuild and restart Docker containers + @echo "๐Ÿณ Rebuilding Docker containers..." + docker-compose build --no-cache + docker-compose up -d + +generate-wallet: ## Generate a new wallet for the faucet + @echo "๐Ÿ” Generating new wallet..." + @cd scripts && npm install > /dev/null 2>&1 && node generate-wallet.js + +check-balance: ## Check faucet wallet balance + @echo "๐Ÿ’ฐ Checking faucet balance..." + @cd scripts && npm install > /dev/null 2>&1 && node check-balance.js + +setup: install generate-wallet ## Initial setup (install deps and generate wallet) + @echo "" + @echo "โœ… Setup complete!" + @echo "" + @echo "Next steps:" + @echo "1. Fund the generated wallet address with tFIL" + @echo "2. Copy the private key to .env file" + @echo "3. Run 'make dev' for development or 'make docker-up' for production" + @echo "" + +test-health: ## Test faucet health endpoint + @curl -s http://localhost:3001/api/health | json_pp || curl -s http://localhost:3001/api/health + +test-config: ## Test faucet config endpoint + @curl -s http://localhost:3001/api/config | json_pp || curl -s http://localhost:3001/api/config + +status: ## Show faucet status + @echo "๐Ÿ“Š Faucet Status" + @echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" + @echo "Docker Containers:" + @docker-compose ps || echo " Docker not running or not configured" + @echo "" + @echo "Health Check:" + @curl -s http://localhost:3001/api/health | json_pp || echo " Service not responding" + diff --git a/faucet/QUICKSTART.md b/faucet/QUICKSTART.md new file mode 100644 index 000000000..5c4e30f92 --- /dev/null +++ b/faucet/QUICKSTART.md @@ -0,0 +1,105 @@ +# ๐Ÿš€ Quick Start Guide + +Get your IPC tFIL faucet running in 5 minutes! + +## For Local Development + +```bash +# 1. Install dependencies +cd faucet +make install + +# 2. Generate a wallet +make generate-wallet + +# 3. Create .env file +cp env-template.txt .env +nano .env # Add your PRIVATE_KEY + +# 4. Fund your wallet with tFIL +# (Transfer tFIL to the address from step 2) + +# 5. Start development servers +make dev + +# Visit http://localhost:3000 +``` + +## For Production (Docker) + +```bash +# 1. Create .env file +cd faucet +nano .env +``` + +Add this: +```env +PRIVATE_KEY=0xYOUR_PRIVATE_KEY_HERE +RPC_URL=http://node-1.test.ipc.space:8545 +FAUCET_AMOUNT=1 +RATE_LIMIT_WINDOW=86400000 +RATE_LIMIT_MAX=1 +``` + +```bash +# 2. Start with Docker +make docker-up + +# 3. Check logs +make docker-logs + +# Visit http://localhost:3001 +``` + +## For GCP Deployment + +```bash +# 1. Create VM +gcloud compute instances create ipc-faucet \ + --zone=us-central1-a \ + --machine-type=e2-small \ + --image-family=ubuntu-2204-lts \ + --image-project=ubuntu-os-cloud + +# 2. SSH in +gcloud compute ssh ipc-faucet --zone=us-central1-a + +# 3. Install Docker +curl -fsSL https://get.docker.com | sudo sh +sudo usermod -aG docker $USER + +# 4. Clone and configure +git clone https://github.com/consensus-shipyard/ipc.git +cd ipc/faucet +nano .env # Add configuration + +# 5. Start faucet +docker-compose up -d + +# 6. Configure firewall +gcloud compute firewall-rules create allow-ipc-faucet \ + --allow tcp:3001 \ + --source-ranges 0.0.0.0/0 +``` + +## Helpful Commands + +```bash +make help # Show all commands +make check-balance # Check wallet balance +make docker-logs # View logs +make docker-restart # Restart faucet +make status # Show faucet status +``` + +## Need Help? + +- ๐Ÿ“– Full docs: See [README.md](README.md) +- ๐Ÿ› ๏ธ Setup guide: See [SETUP.md](SETUP.md) +- โ˜๏ธ GCP deployment: See [DEPLOY.md](DEPLOY.md) + +--- + +**Made with โค๏ธ for the IPC community** + diff --git a/faucet/README.md b/faucet/README.md new file mode 100644 index 000000000..4edb0a646 --- /dev/null +++ b/faucet/README.md @@ -0,0 +1,526 @@ +# IPC tFIL Faucet + +A modern, production-ready faucet for distributing test FIL tokens on the IPC testnet. Built with Vue 3, Tailwind CSS, and Express. + +![Faucet Preview](https://img.shields.io/badge/Vue-3.x-4FC08D?logo=vue.js&logoColor=white) +![Tailwind CSS](https://img.shields.io/badge/Tailwind-3.x-38B2AC?logo=tailwind-css&logoColor=white) +![Express](https://img.shields.io/badge/Express-4.x-000000?logo=express&logoColor=white) +![Docker](https://img.shields.io/badge/Docker-Ready-2496ED?logo=docker&logoColor=white) + +## Features + +โœจ **Modern UI** +- Clean, responsive design with Tailwind CSS +- Beautiful gradient backgrounds and animations +- Dark theme optimized for crypto applications + +๐Ÿ” **Secure & Robust** +- IP-based rate limiting +- Address-based rate limiting +- Configurable distribution amounts +- Environment-based configuration + +๐ŸฆŠ **Web3 Integration** +- MetaMask wallet connection +- Network switcher for easy testnet setup +- Address validation +- Transaction status tracking + +๐Ÿณ **Production Ready** +- Docker containerization +- Health checks +- Structured logging +- Easy GCP VM deployment + +## Quick Start + +### Prerequisites + +- Node.js 18+ and npm +- Docker and Docker Compose (for containerized deployment) +- A funded wallet with tFIL tokens +- Access to IPC testnet RPC endpoint + +### Local Development + +1. **Clone and install dependencies:** + +\`\`\`bash +cd faucet +npm run install:all +\`\`\` + +2. **Configure the faucet:** + +Create a `.env` file in the root directory: + +\`\`\`bash +# Required: Your faucet wallet private key +PRIVATE_KEY=0x1234567890abcdef... + +# RPC endpoint +RPC_URL=http://node-1.test.ipc.space:8545 + +# Amount to send per request (in FIL) +FAUCET_AMOUNT=1 + +# Rate limiting (24 hours in milliseconds) +RATE_LIMIT_WINDOW=86400000 +RATE_LIMIT_MAX=1 + +# Server port +PORT=3001 + +# Development settings +ENABLE_CORS=true +SERVE_STATIC=false +\`\`\` + +3. **Start the development servers:** + +\`\`\`bash +npm run dev +\`\`\` + +This will start: +- Frontend on http://localhost:3000 +- Backend on http://localhost:3001 + +### Docker Deployment (Recommended for Production) + +1. **Create `.env` file:** + +\`\`\`bash +PRIVATE_KEY=your_private_key_here +RPC_URL=http://node-1.test.ipc.space:8545 +FAUCET_AMOUNT=1 +RATE_LIMIT_WINDOW=86400000 +RATE_LIMIT_MAX=1 +\`\`\` + +2. **Build and run with Docker Compose:** + +\`\`\`bash +docker-compose up -d +\`\`\` + +The faucet will be available on http://localhost:3001 + +3. **Check logs:** + +\`\`\`bash +docker-compose logs -f +\`\`\` + +4. **Stop the faucet:** + +\`\`\`bash +docker-compose down +\`\`\` + +## GCP VM Deployment + +### Option 1: Using Docker Compose (Recommended) + +1. **SSH into your GCP VM:** + +\`\`\`bash +gcloud compute ssh your-vm-name --zone=your-zone +\`\`\` + +2. **Install Docker and Docker Compose:** + +\`\`\`bash +# Install Docker +curl -fsSL https://get.docker.com -o get-docker.sh +sudo sh get-docker.sh +sudo usermod -aG docker $USER + +# Install Docker Compose +sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose +sudo chmod +x /usr/local/bin/docker-compose + +# Log out and back in for group changes to take effect +exit +\`\`\` + +3. **Clone the repository:** + +\`\`\`bash +git clone https://github.com/your-org/ipc.git +cd ipc/faucet +\`\`\` + +4. **Create `.env` file:** + +\`\`\`bash +nano .env +# Add your configuration (see example above) +\`\`\` + +5. **Start the faucet:** + +\`\`\`bash +docker-compose up -d +\`\`\` + +6. **Configure firewall:** + +\`\`\`bash +# Allow port 3001 +gcloud compute firewall-rules create allow-faucet \ + --allow tcp:3001 \ + --source-ranges 0.0.0.0/0 \ + --description "Allow IPC faucet access" +\`\`\` + +7. **Access your faucet:** + +Visit `http://YOUR_VM_EXTERNAL_IP:3001` + +### Option 2: Using Systemd Service + +1. **Build the application:** + +\`\`\`bash +cd ipc/faucet +npm run install:all +cd frontend && npm run build +\`\`\` + +2. **Create systemd service:** + +\`\`\`bash +sudo nano /etc/systemd/system/ipc-faucet.service +\`\`\` + +Add the following content: + +\`\`\`ini +[Unit] +Description=IPC tFIL Faucet +After=network.target + +[Service] +Type=simple +User=your_username +WorkingDirectory=/home/your_username/ipc/faucet/backend +Environment=NODE_ENV=production +Environment=SERVE_STATIC=true +EnvironmentFile=/home/your_username/ipc/faucet/.env +ExecStart=/usr/bin/node src/index.js +Restart=always +RestartSec=10 + +[Install] +WantedBy=multi-user.target +\`\`\` + +3. **Enable and start the service:** + +\`\`\`bash +sudo systemctl daemon-reload +sudo systemctl enable ipc-faucet +sudo systemctl start ipc-faucet +sudo systemctl status ipc-faucet +\`\`\` + +## Setting Up Your Faucet Wallet + +### Creating a New Wallet + +1. **Generate a new wallet:** + +\`\`\`bash +# Using ethers.js CLI or any Ethereum wallet tool +node -e "const ethers = require('ethers'); const wallet = ethers.Wallet.createRandom(); console.log('Address:', wallet.address); console.log('Private Key:', wallet.privateKey);" +\`\`\` + +2. **Fund the wallet:** + +Transfer tFIL tokens to the generated address. The amount depends on how many requests you expect to serve. + +**Example calculation:** +- 1 tFIL per request +- 1000 expected requests +- Total needed: 1000 tFIL + buffer for gas fees = ~1010 tFIL + +3. **Secure your private key:** + +Store your private key securely: +- Use environment variables (never commit to git) +- Use secret management services (GCP Secret Manager, AWS Secrets Manager, etc.) +- Limit access to the server + +### Using an Existing Wallet + +If you already have a wallet with tFIL: + +1. **Export private key from MetaMask:** + - Click on account details + - Click "Export Private Key" + - Enter your password + - Copy the private key + +2. **Add to `.env` file:** + \`\`\` + PRIVATE_KEY=0xYOUR_PRIVATE_KEY_HERE + \`\`\` + +## Configuration Options + +### Environment Variables + +| Variable | Description | Default | Required | +|----------|-------------|---------|----------| +| `PRIVATE_KEY` | Faucet wallet private key | - | โœ… Yes | +| `RPC_URL` | IPC testnet RPC endpoint | `http://node-1.test.ipc.space:8545` | No | +| `FAUCET_AMOUNT` | Amount of tFIL per request | `1` | No | +| `RATE_LIMIT_WINDOW` | Rate limit window in ms | `86400000` (24h) | No | +| `RATE_LIMIT_MAX` | Max requests per window per IP | `1` | No | +| `PORT` | Server port | `3001` | No | +| `ENABLE_CORS` | Enable CORS | `true` | No | +| `SERVE_STATIC` | Serve frontend files | `false` (dev), `true` (prod) | No | + +### Customizing Rate Limits + +**Per hour instead of 24 hours:** +\`\`\`bash +RATE_LIMIT_WINDOW=3600000 # 1 hour in milliseconds +RATE_LIMIT_MAX=1 +\`\`\` + +**Multiple requests per day:** +\`\`\`bash +RATE_LIMIT_WINDOW=86400000 # 24 hours +RATE_LIMIT_MAX=3 # 3 requests per 24 hours +\`\`\` + +**Higher distribution amount:** +\`\`\`bash +FAUCET_AMOUNT=5 # 5 tFIL per request +\`\`\` + +## Monitoring + +### Health Check + +\`\`\`bash +curl http://localhost:3001/api/health +\`\`\` + +Response: +\`\`\`json +{ + "status": "ok", + "configured": true, + "network": "http://node-1.test.ipc.space:8545" +} +\`\`\` + +### Check Faucet Balance + +The backend logs the faucet balance on startup: + +\`\`\`bash +docker-compose logs faucet | grep "Faucet balance" +\`\`\` + +### Logs + +**Docker Compose:** +\`\`\`bash +docker-compose logs -f +\`\`\` + +**Systemd:** +\`\`\`bash +sudo journalctl -u ipc-faucet -f +\`\`\` + +## Security Best Practices + +1. **Private Key Security** + - Never commit private keys to version control + - Use environment variables or secret management services + - Rotate keys periodically + - Use a dedicated wallet for the faucet + +2. **Rate Limiting** + - Adjust rate limits based on your token supply + - Monitor for abuse patterns + - Consider adding CAPTCHA for additional protection + +3. **Network Security** + - Use HTTPS with reverse proxy (Nginx, Caddy) + - Configure firewall rules appropriately + - Keep dependencies updated + +4. **Monitoring** + - Set up alerts for low faucet balance + - Monitor request patterns + - Log suspicious activity + +## Troubleshooting + +### Faucet not sending tokens + +1. Check if private key is configured: +\`\`\`bash +docker-compose logs | grep "WARNING" +\`\`\` + +2. Verify wallet has sufficient balance: +\`\`\`bash +docker-compose logs | grep "balance" +\`\`\` + +3. Check RPC connection: +\`\`\`bash +curl http://node-1.test.ipc.space:8545 -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' +\`\`\` + +### Rate limit errors + +Rate limits are per IP and per address. Wait for the rate limit window to expire, or adjust the configuration. + +### MetaMask connection issues + +1. Make sure MetaMask is installed +2. Check that you're on the correct network +3. Use the "Switch to IPC Testnet" button to add the network + +### Docker build failures + +1. Ensure Docker is running: +\`\`\`bash +docker info +\`\`\` + +2. Check Docker Compose version: +\`\`\`bash +docker-compose --version +\`\`\` + +3. Rebuild from scratch: +\`\`\`bash +docker-compose down +docker-compose build --no-cache +docker-compose up -d +\`\`\` + +## Project Structure + +\`\`\` +faucet/ +โ”œโ”€โ”€ frontend/ # Vue 3 frontend +โ”‚ โ”œโ”€โ”€ src/ +โ”‚ โ”‚ โ”œโ”€โ”€ App.vue # Main application component +โ”‚ โ”‚ โ”œโ”€โ”€ main.js # Entry point +โ”‚ โ”‚ โ””โ”€โ”€ style.css # Global styles (Tailwind) +โ”‚ โ”œโ”€โ”€ public/ +โ”‚ โ”‚ โ””โ”€โ”€ favicon.svg # Faucet icon +โ”‚ โ”œโ”€โ”€ index.html +โ”‚ โ”œโ”€โ”€ package.json +โ”‚ โ”œโ”€โ”€ vite.config.js +โ”‚ โ””โ”€โ”€ tailwind.config.js +โ”œโ”€โ”€ backend/ # Express backend +โ”‚ โ”œโ”€โ”€ src/ +โ”‚ โ”‚ โ””โ”€โ”€ index.js # Main server file +โ”‚ โ””โ”€โ”€ package.json +โ”œโ”€โ”€ Dockerfile # Multi-stage Docker build +โ”œโ”€โ”€ docker-compose.yml # Docker Compose configuration +โ”œโ”€โ”€ .dockerignore +โ”œโ”€โ”€ .gitignore +โ”œโ”€โ”€ package.json # Root package file +โ””โ”€โ”€ README.md # This file +\`\`\` + +## API Reference + +### GET `/api/health` + +Health check endpoint. + +**Response:** +\`\`\`json +{ + "status": "ok", + "configured": true, + "network": "http://node-1.test.ipc.space:8545" +} +\`\`\` + +### GET `/api/config` + +Get faucet configuration. + +**Response:** +\`\`\`json +{ + "amount": "1", + "rateLimit": "1 request per 24 hours per address", + "network": "http://node-1.test.ipc.space:8545" +} +\`\`\` + +### POST `/api/request` + +Request tFIL tokens. + +**Request Body:** +\`\`\`json +{ + "address": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb" +} +\`\`\` + +**Success Response:** +\`\`\`json +{ + "success": true, + "txHash": "0x123abc...", + "amount": "1", + "blockNumber": 12345 +} +\`\`\` + +**Error Response:** +\`\`\`json +{ + "success": false, + "error": "Rate limit exceeded" +} +\`\`\` + +## Contributing + +Contributions are welcome! Please follow these guidelines: + +1. Fork the repository +2. Create a feature branch +3. Make your changes +4. Test thoroughly +5. Submit a pull request + +## License + +This project is part of the IPC (InterPlanetary Consensus) project. + +## Support + +- Documentation: https://docs.ipc.space +- Issues: https://github.com/consensus-shipyard/ipc/issues +- Community: [IPC Discord/Forum] + +## Changelog + +### v1.0.0 (2024-10-31) +- Initial release +- Vue 3 frontend with Tailwind CSS +- Express backend with rate limiting +- MetaMask integration +- Network switcher +- Docker support +- GCP deployment ready + diff --git a/faucet/SETUP.md b/faucet/SETUP.md new file mode 100644 index 000000000..b740e595b --- /dev/null +++ b/faucet/SETUP.md @@ -0,0 +1,315 @@ +# Quick Setup Guide + +This guide will help you get your IPC tFIL faucet up and running in minutes. + +## Step 1: Prepare Your Wallet + +### Option A: Create a New Wallet (Recommended) + +\`\`\`bash +# Generate a new wallet using Node.js +node -e "const ethers = require('ethers'); const wallet = ethers.Wallet.createRandom(); console.log('Address:', wallet.address); console.log('Private Key:', wallet.privateKey);" +\`\`\` + +**Save the output securely!** + +Example output: +\`\`\` +Address: 0x1234567890abcdef1234567890abcdef12345678 +Private Key: 0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890 +\`\`\` + +### Option B: Use Existing Wallet + +Export your private key from MetaMask: +1. Open MetaMask +2. Click on the account menu (three dots) +3. Account Details โ†’ Export Private Key +4. Enter your password +5. Copy the private key + +### Fund Your Wallet + +Transfer tFIL to your faucet wallet address. Calculate how much you need: + +\`\`\` +Amount needed = (Expected requests ร— Amount per request) + Gas buffer +Example: (1000 requests ร— 1 tFIL) + 10 tFIL gas = 1010 tFIL +\`\`\` + +## Step 2: Configure the Faucet + +Create a `.env` file in the `faucet/` directory: + +\`\`\`bash +cd faucet +nano .env +\`\`\` + +Add the following configuration: + +\`\`\`bash +# YOUR FAUCET WALLET PRIVATE KEY (keep this secret!) +PRIVATE_KEY=0xYOUR_PRIVATE_KEY_HERE + +# IPC Testnet RPC +RPC_URL=http://node-1.test.ipc.space:8545 + +# Amount to distribute per request (in tFIL) +FAUCET_AMOUNT=1 + +# Rate limiting: 1 request per 24 hours +RATE_LIMIT_WINDOW=86400000 +RATE_LIMIT_MAX=1 + +# Server configuration +PORT=3001 +ENABLE_CORS=false +SERVE_STATIC=true +\`\`\` + +**Save and exit** (Ctrl+X, then Y, then Enter) + +## Step 3: Deploy with Docker + +### Install Docker (if not already installed) + +\`\`\`bash +# Install Docker +curl -fsSL https://get.docker.com -o get-docker.sh +sudo sh get-docker.sh + +# Install Docker Compose +sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose +sudo chmod +x /usr/local/bin/docker-compose + +# Add your user to docker group (to run without sudo) +sudo usermod -aG docker $USER + +# Log out and back in for changes to take effect +exit +\`\`\` + +### Build and Run + +\`\`\`bash +# Navigate to faucet directory +cd /path/to/ipc/faucet + +# Build and start the faucet +docker-compose up -d + +# Check if it's running +docker-compose ps + +# View logs +docker-compose logs -f +\`\`\` + +You should see output like: +\`\`\` +โœ… Wallet initialized + Address: 0x1234... +๐Ÿ’ฐ Faucet balance: 1000.0 tFIL + Can serve ~1000 requests +โœ… Server running on port 3001 +\`\`\` + +## Step 4: Configure Firewall (GCP) + +### Using gcloud CLI: + +\`\`\`bash +gcloud compute firewall-rules create allow-ipc-faucet \ + --allow tcp:3001 \ + --source-ranges 0.0.0.0/0 \ + --description "Allow access to IPC tFIL faucet" +\`\`\` + +### Using GCP Console: + +1. Go to VPC Network โ†’ Firewall +2. Click "CREATE FIREWALL RULE" +3. Name: `allow-ipc-faucet` +4. Direction: Ingress +5. Targets: All instances in the network +6. Source IP ranges: `0.0.0.0/0` +7. Protocols and ports: tcp:3001 +8. Click CREATE + +## Step 5: Access Your Faucet + +### Find Your External IP: + +\`\`\`bash +# On GCP VM +curl -H "Metadata-Flavor: Google" http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/access-configs/0/external-ip +\`\`\` + +Or check in GCP Console: Compute Engine โ†’ VM Instances + +### Access the faucet: + +Open your browser and go to: +\`\`\` +http://YOUR_EXTERNAL_IP:3001 +\`\`\` + +## Step 6: Test the Faucet + +1. **Open the faucet URL in your browser** +2. **Click "Connect MetaMask"** +3. **Click "Switch to IPC Testnet"** (if not already connected) +4. **Click "Request 1 tFIL"** +5. **Wait for confirmation** + +You should see a success message with a transaction hash! + +## Step 7: Set Up Monitoring (Optional) + +### Set up automatic restarts: + +Docker Compose is already configured with `restart: unless-stopped`, so the faucet will automatically restart if it crashes or after server reboots. + +### Monitor balance: + +Create a simple monitoring script: + +\`\`\`bash +nano /home/$USER/check-faucet-balance.sh +\`\`\` + +Add: +\`\`\`bash +#!/bin/bash +docker-compose -f /path/to/ipc/faucet/docker-compose.yml logs | grep "Faucet balance" | tail -1 +\`\`\` + +Make executable: +\`\`\`bash +chmod +x /home/$USER/check-faucet-balance.sh +\`\`\` + +### Set up a cron job to check balance daily: + +\`\`\`bash +crontab -e +\`\`\` + +Add: +\`\`\` +0 9 * * * /home/$USER/check-faucet-balance.sh >> /home/$USER/faucet-balance.log 2>&1 +\`\`\` + +## Useful Commands + +### Check faucet status: +\`\`\`bash +docker-compose ps +\`\`\` + +### View logs: +\`\`\`bash +docker-compose logs -f +\`\`\` + +### Restart faucet: +\`\`\`bash +docker-compose restart +\`\`\` + +### Stop faucet: +\`\`\`bash +docker-compose down +\`\`\` + +### Update faucet: +\`\`\`bash +git pull +docker-compose down +docker-compose build --no-cache +docker-compose up -d +\`\`\` + +### Check faucet health: +\`\`\`bash +curl http://localhost:3001/api/health +\`\`\` + +## Troubleshooting + +### Faucet not accessible from browser: + +1. Check if Docker container is running: + \`\`\`bash + docker-compose ps + \`\`\` + +2. Check firewall rules: + \`\`\`bash + gcloud compute firewall-rules list | grep faucet + \`\`\` + +3. Test locally on the VM: + \`\`\`bash + curl http://localhost:3001/api/health + \`\`\` + +### Faucet not sending tokens: + +1. Check balance: + \`\`\`bash + docker-compose logs | grep balance + \`\`\` + +2. Verify private key is set: + \`\`\`bash + docker-compose logs | grep "Wallet initialized" + \`\`\` + +3. Test RPC connection: + \`\`\`bash + curl -X POST http://node-1.test.ipc.space:8545 \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' + \`\`\` + +### Rate limit issues: + +Rate limits are tracked in-memory. If you restart the container, rate limits reset. To modify rate limits, update `.env` and restart: + +\`\`\`bash +docker-compose down +docker-compose up -d +\`\`\` + +## Security Checklist + +- [ ] Private key is stored in `.env` (not committed to git) +- [ ] `.env` file has restrictive permissions: `chmod 600 .env` +- [ ] Firewall is configured properly +- [ ] Faucet wallet is separate from other wallets +- [ ] Balance monitoring is set up +- [ ] Regular backups of configuration +- [ ] Docker and system packages are up to date + +## Next Steps + +- Set up HTTPS with a reverse proxy (Nginx or Caddy) +- Configure a domain name for easier access +- Set up monitoring and alerting +- Consider adding CAPTCHA for additional abuse prevention + +## Need Help? + +- Check the main README.md for detailed documentation +- Review logs: `docker-compose logs -f` +- Visit IPC documentation: https://docs.ipc.space +- Report issues on GitHub + +--- + +**Your faucet should now be running! ๐ŸŽ‰** + +Access it at: `http://YOUR_EXTERNAL_IP:3001` + diff --git a/faucet/backend/package.json b/faucet/backend/package.json new file mode 100644 index 000000000..0e387e714 --- /dev/null +++ b/faucet/backend/package.json @@ -0,0 +1,18 @@ +{ + "name": "ipc-faucet-backend", + "version": "1.0.0", + "type": "module", + "scripts": { + "dev": "node --watch src/index.js", + "start": "node src/index.js", + "build": "echo 'No build step required for backend'" + }, + "dependencies": { + "express": "^4.18.3", + "express-rate-limit": "^7.1.5", + "cors": "^2.8.5", + "ethers": "^6.11.1", + "dotenv": "^16.4.5" + } +} + diff --git a/faucet/backend/src/index.js b/faucet/backend/src/index.js new file mode 100644 index 000000000..5996684b0 --- /dev/null +++ b/faucet/backend/src/index.js @@ -0,0 +1,268 @@ +import express from 'express' +import cors from 'cors' +import rateLimit from 'express-rate-limit' +import { ethers } from 'ethers' +import dotenv from 'dotenv' +import { fileURLToPath } from 'url' +import { dirname, join } from 'path' + +const __filename = fileURLToPath(import.meta.url) +const __dirname = dirname(__filename) + +// Load .env from the parent directory (faucet/.env) +dotenv.config({ path: join(__dirname, '../../.env') }) + +const app = express() +const PORT = process.env.PORT || 3001 + +// Configuration +const config = { + rpcUrl: process.env.RPC_URL || 'http://node-1.test.ipc.space:8545', + privateKey: process.env.PRIVATE_KEY, + amount: process.env.FAUCET_AMOUNT || '1', // Amount in FIL + rateLimitWindow: parseInt(process.env.RATE_LIMIT_WINDOW || '86400000'), // 24 hours in ms + rateLimitMax: parseInt(process.env.RATE_LIMIT_MAX || '1'), + enableCors: process.env.ENABLE_CORS !== 'false', + serveStatic: process.env.SERVE_STATIC === 'true' +} + +// Middleware +app.use(express.json()) + +if (config.enableCors) { + app.use(cors()) +} + +// Rate limiting per IP +const ipLimiter = rateLimit({ + windowMs: config.rateLimitWindow, + max: config.rateLimitMax, + message: { error: 'Too many requests from this IP, please try again later' }, + standardHeaders: true, + legacyHeaders: false, +}) + +// Rate limiting per address +const addressLimitStore = new Map() + +function checkAddressRateLimit(address) { + const now = Date.now() + const lastRequest = addressLimitStore.get(address.toLowerCase()) + + if (lastRequest && (now - lastRequest) < config.rateLimitWindow) { + const timeLeft = config.rateLimitWindow - (now - lastRequest) + const hoursLeft = Math.ceil(timeLeft / (1000 * 60 * 60)) + return { + allowed: false, + error: `This address has already requested tokens. Please try again in ${hoursLeft} hour(s).` + } + } + + return { allowed: true } +} + +function recordAddressRequest(address) { + addressLimitStore.set(address.toLowerCase(), Date.now()) +} + +// Cleanup old entries every hour +setInterval(() => { + const now = Date.now() + const cutoff = now - config.rateLimitWindow + + for (const [address, timestamp] of addressLimitStore.entries()) { + if (timestamp < cutoff) { + addressLimitStore.delete(address) + } + } +}, 3600000) // 1 hour + +// Provider setup +let provider +let wallet +let isConfigured = false + +function initializeWallet() { + try { + if (!config.privateKey) { + console.warn('โš ๏ธ WARNING: No PRIVATE_KEY configured. Faucet will not be able to send tokens.') + console.warn('โš ๏ธ Please set PRIVATE_KEY in your .env file') + return false + } + + provider = new ethers.JsonRpcProvider(config.rpcUrl) + wallet = new ethers.Wallet(config.privateKey, provider) + isConfigured = true + + console.log('โœ… Wallet initialized') + console.log(` Address: ${wallet.address}`) + + return true + } catch (error) { + console.error('โŒ Error initializing wallet:', error.message) + return false + } +} + +// Routes +app.get('/api/health', (req, res) => { + res.json({ + status: 'ok', + configured: isConfigured, + network: config.rpcUrl + }) +}) + +app.get('/api/config', (req, res) => { + res.json({ + amount: config.amount, + rateLimit: `1 request per ${config.rateLimitWindow / (1000 * 60 * 60)} hours per address`, + network: config.rpcUrl + }) +}) + +app.post('/api/request', ipLimiter, async (req, res) => { + try { + const { address } = req.body + + // Validation + if (!address) { + return res.status(400).json({ + success: false, + error: 'Address is required' + }) + } + + if (!ethers.isAddress(address)) { + return res.status(400).json({ + success: false, + error: 'Invalid Ethereum address' + }) + } + + if (!isConfigured) { + return res.status(500).json({ + success: false, + error: 'Faucet is not configured. Please contact the administrator.' + }) + } + + // Check address rate limit + const rateLimitCheck = checkAddressRateLimit(address) + if (!rateLimitCheck.allowed) { + return res.status(429).json({ + success: false, + error: rateLimitCheck.error + }) + } + + // Check faucet balance + const balance = await provider.getBalance(wallet.address) + const amountWei = ethers.parseEther(config.amount) + + if (balance < amountWei) { + return res.status(503).json({ + success: false, + error: 'Faucet is currently out of funds. Please contact the administrator.' + }) + } + + console.log(`๐Ÿ“ค Sending ${config.amount} tFIL to ${address}`) + + // Send transaction + const tx = await wallet.sendTransaction({ + to: address, + value: amountWei + }) + + console.log(` Transaction hash: ${tx.hash}`) + console.log(` Waiting for confirmation...`) + + // Wait for confirmation + const receipt = await tx.wait() + + console.log(`โœ… Transaction confirmed in block ${receipt.blockNumber}`) + + // Record the request + recordAddressRequest(address) + + res.json({ + success: true, + txHash: tx.hash, + amount: config.amount, + blockNumber: receipt.blockNumber + }) + + } catch (error) { + console.error('โŒ Error processing request:', error) + + let errorMessage = 'Failed to process request' + + if (error.code === 'INSUFFICIENT_FUNDS') { + errorMessage = 'Faucet has insufficient funds' + } else if (error.code === 'NETWORK_ERROR') { + errorMessage = 'Network error. Please try again later.' + } else if (error.message) { + errorMessage = error.message + } + + res.status(500).json({ + success: false, + error: errorMessage + }) + } +}) + +// Serve static files in production +if (config.serveStatic) { + const staticPath = join(__dirname, '../../frontend/dist') + app.use(express.static(staticPath)) + + app.get('*', (req, res) => { + res.sendFile(join(staticPath, 'index.html')) + }) +} + +// Start server +async function start() { + console.log('๐Ÿš€ Starting IPC tFIL Faucet Backend...') + console.log('') + console.log('Configuration:') + console.log(` RPC URL: ${config.rpcUrl}`) + console.log(` Amount per request: ${config.amount} tFIL`) + console.log(` Rate limit: ${config.rateLimitMax} request(s) per ${config.rateLimitWindow / (1000 * 60 * 60)} hour(s)`) + console.log(` Port: ${PORT}`) + console.log('') + + const initialized = initializeWallet() + + if (initialized) { + // Check and display balance + try { + const balance = await provider.getBalance(wallet.address) + const balanceFIL = ethers.formatEther(balance) + console.log(`๐Ÿ’ฐ Faucet balance: ${balanceFIL} tFIL`) + + const maxRequests = Math.floor(parseFloat(balanceFIL) / parseFloat(config.amount)) + console.log(` Can serve ~${maxRequests} requests`) + } catch (error) { + console.error('โš ๏ธ Could not fetch balance:', error.message) + } + } + + console.log('') + + app.listen(PORT, () => { + console.log(`โœ… Server running on port ${PORT}`) + console.log(` Health check: http://localhost:${PORT}/api/health`) + console.log('') + + if (!initialized) { + console.log('โš ๏ธ IMPORTANT: Configure PRIVATE_KEY to enable token distribution') + console.log('') + } + }) +} + +start() + diff --git a/faucet/docker-compose.yml b/faucet/docker-compose.yml new file mode 100644 index 000000000..a89d47adf --- /dev/null +++ b/faucet/docker-compose.yml @@ -0,0 +1,35 @@ +version: '3.8' + +services: + faucet: + build: + context: . + dockerfile: Dockerfile + container_name: ipc-faucet + restart: unless-stopped + ports: + - "3001:3001" + environment: + - NODE_ENV=production + - PORT=3001 + - RPC_URL=${RPC_URL:-http://node-1.test.ipc.space:8545} + - PRIVATE_KEY=${PRIVATE_KEY} + - FAUCET_AMOUNT=${FAUCET_AMOUNT:-1} + - RATE_LIMIT_WINDOW=${RATE_LIMIT_WINDOW:-86400000} + - RATE_LIMIT_MAX=${RATE_LIMIT_MAX:-1} + - SERVE_STATIC=true + - ENABLE_CORS=false + volumes: + - ./logs:/app/logs + healthcheck: + test: ["CMD", "node", "-e", "require('http').get('http://localhost:3001/api/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1))"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 5s + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "3" + diff --git a/faucet/env-template.txt b/faucet/env-template.txt new file mode 100644 index 000000000..6d4de24f5 --- /dev/null +++ b/faucet/env-template.txt @@ -0,0 +1,63 @@ +# IPC tFIL Faucet Configuration Template +# Copy this file to .env and fill in your values + +# ============================================================================= +# REQUIRED CONFIGURATION +# ============================================================================= + +# Faucet wallet private key (KEEP THIS SECRET!) +# Generate a new wallet: node -e "const ethers = require('ethers'); const wallet = ethers.Wallet.createRandom(); console.log('Address:', wallet.address); console.log('Private Key:', wallet.privateKey);" +PRIVATE_KEY=0xYOUR_PRIVATE_KEY_HERE + +# ============================================================================= +# NETWORK CONFIGURATION +# ============================================================================= + +# IPC testnet RPC endpoint +RPC_URL=http://node-1.test.ipc.space:8545 + +# ============================================================================= +# FAUCET SETTINGS +# ============================================================================= + +# Amount of tFIL to send per request +FAUCET_AMOUNT=1 + +# Rate limiting settings +# RATE_LIMIT_WINDOW: Time window in milliseconds (default: 24 hours) +# RATE_LIMIT_MAX: Maximum requests per window per IP +RATE_LIMIT_WINDOW=86400000 +RATE_LIMIT_MAX=1 + +# ============================================================================= +# SERVER CONFIGURATION +# ============================================================================= + +# Port for the backend server +PORT=3001 + +# Enable CORS (set to false in production if serving static files) +ENABLE_CORS=false + +# Serve static frontend files (set to true in production/Docker) +SERVE_STATIC=true + +# ============================================================================= +# COMMON CONFIGURATIONS +# ============================================================================= + +# For 1 hour rate limit: +# RATE_LIMIT_WINDOW=3600000 +# RATE_LIMIT_MAX=1 + +# For multiple requests per day: +# RATE_LIMIT_WINDOW=86400000 +# RATE_LIMIT_MAX=3 + +# For higher distribution: +# FAUCET_AMOUNT=5 + +# For development: +# ENABLE_CORS=true +# SERVE_STATIC=false + diff --git a/faucet/frontend/index.html b/faucet/frontend/index.html new file mode 100644 index 000000000..c8d8f123d --- /dev/null +++ b/faucet/frontend/index.html @@ -0,0 +1,14 @@ + + + + + + + IPC tFIL Faucet + + +
+ + + + diff --git a/faucet/frontend/package.json b/faucet/frontend/package.json new file mode 100644 index 000000000..3c8e6dda1 --- /dev/null +++ b/faucet/frontend/package.json @@ -0,0 +1,23 @@ +{ + "name": "ipc-faucet-frontend", + "version": "1.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "vue": "^3.4.21", + "ethers": "^6.11.1", + "axios": "^1.6.7" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.0.4", + "autoprefixer": "^10.4.18", + "postcss": "^8.4.35", + "tailwindcss": "^3.4.1", + "vite": "^5.1.5" + } +} + diff --git a/faucet/frontend/postcss.config.js b/faucet/frontend/postcss.config.js new file mode 100644 index 000000000..b4a6220e2 --- /dev/null +++ b/faucet/frontend/postcss.config.js @@ -0,0 +1,7 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} + diff --git a/faucet/frontend/public/favicon.svg b/faucet/frontend/public/favicon.svg new file mode 100644 index 000000000..aa6f11ee2 --- /dev/null +++ b/faucet/frontend/public/favicon.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/faucet/frontend/src/App.vue b/faucet/frontend/src/App.vue new file mode 100644 index 000000000..eedfd8bf6 --- /dev/null +++ b/faucet/frontend/src/App.vue @@ -0,0 +1,387 @@ + + + + + + diff --git a/faucet/frontend/src/main.js b/faucet/frontend/src/main.js new file mode 100644 index 000000000..216546d74 --- /dev/null +++ b/faucet/frontend/src/main.js @@ -0,0 +1,6 @@ +import { createApp } from 'vue' +import './style.css' +import App from './App.vue' + +createApp(App).mount('#app') + diff --git a/faucet/frontend/src/style.css b/faucet/frontend/src/style.css new file mode 100644 index 000000000..3cea26ed0 --- /dev/null +++ b/faucet/frontend/src/style.css @@ -0,0 +1,25 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +body { + @apply bg-gradient-to-br from-slate-900 via-blue-900 to-slate-900 min-h-screen; +} + +/* Custom scrollbar */ +::-webkit-scrollbar { + width: 8px; +} + +::-webkit-scrollbar-track { + @apply bg-slate-800; +} + +::-webkit-scrollbar-thumb { + @apply bg-blue-600 rounded-full; +} + +::-webkit-scrollbar-thumb:hover { + @apply bg-blue-500; +} + diff --git a/faucet/frontend/tailwind.config.js b/faucet/frontend/tailwind.config.js new file mode 100644 index 000000000..5db7b7995 --- /dev/null +++ b/faucet/frontend/tailwind.config.js @@ -0,0 +1,30 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: [ + "./index.html", + "./src/**/*.{vue,js,ts,jsx,tsx}", + ], + theme: { + extend: { + colors: { + primary: { + 50: '#eff6ff', + 100: '#dbeafe', + 200: '#bfdbfe', + 300: '#93c5fd', + 400: '#60a5fa', + 500: '#3b82f6', + 600: '#2563eb', + 700: '#1d4ed8', + 800: '#1e40af', + 900: '#1e3a8a', + }, + }, + animation: { + 'pulse-slow': 'pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite', + } + }, + }, + plugins: [], +} + diff --git a/faucet/frontend/vite.config.js b/faucet/frontend/vite.config.js new file mode 100644 index 000000000..1cadd61f4 --- /dev/null +++ b/faucet/frontend/vite.config.js @@ -0,0 +1,20 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +export default defineConfig({ + plugins: [vue()], + server: { + port: 3000, + proxy: { + '/api': { + target: 'http://localhost:3001', + changeOrigin: true + } + } + }, + build: { + outDir: 'dist', + emptyOutDir: true + } +}) + diff --git a/faucet/nginx.conf.example b/faucet/nginx.conf.example new file mode 100644 index 000000000..39c953a37 --- /dev/null +++ b/faucet/nginx.conf.example @@ -0,0 +1,99 @@ +# Nginx Configuration for IPC Faucet with HTTPS +# +# This is an example configuration for serving the faucet behind +# an Nginx reverse proxy with SSL/TLS support +# +# To use: +# 1. Install nginx and certbot +# 2. Copy this file to /etc/nginx/sites-available/ipc-faucet +# 3. Update YOUR_DOMAIN with your actual domain +# 4. Get SSL certificate: sudo certbot --nginx -d your-domain.com +# 5. Enable: sudo ln -s /etc/nginx/sites-available/ipc-faucet /etc/nginx/sites-enabled/ +# 6. Test: sudo nginx -t +# 7. Reload: sudo systemctl reload nginx + +# Redirect HTTP to HTTPS +server { + listen 80; + listen [::]:80; + server_name YOUR_DOMAIN; + + location /.well-known/acme-challenge/ { + root /var/www/certbot; + } + + location / { + return 301 https://$server_name$request_uri; + } +} + +# HTTPS server +server { + listen 443 ssl http2; + listen [::]:443 ssl http2; + server_name YOUR_DOMAIN; + + # SSL certificate paths (managed by certbot) + ssl_certificate /etc/letsencrypt/live/YOUR_DOMAIN/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/YOUR_DOMAIN/privkey.pem; + + # SSL configuration + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + ssl_prefer_server_ciphers on; + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 10m; + + # Security headers + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header Referrer-Policy "no-referrer-when-downgrade" always; + + # Logging + access_log /var/log/nginx/ipc-faucet-access.log; + error_log /var/log/nginx/ipc-faucet-error.log; + + # Max body size for requests + client_max_body_size 1M; + + # Proxy to faucet application + location / { + proxy_pass http://localhost:3001; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_cache_bypass $http_upgrade; + + # Timeouts + proxy_connect_timeout 60s; + proxy_send_timeout 60s; + proxy_read_timeout 60s; + } + + # API endpoints with specific rate limiting + location /api/ { + proxy_pass http://localhost:3001; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # Additional rate limiting at nginx level (optional) + # limit_req zone=api_limit burst=5 nodelay; + } +} + +# Optional: Rate limiting zone definition +# Add this to /etc/nginx/nginx.conf in the http block: +# +# http { +# limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/m; +# ... +# } + diff --git a/faucet/package.json b/faucet/package.json new file mode 100644 index 000000000..fb46e8c00 --- /dev/null +++ b/faucet/package.json @@ -0,0 +1,19 @@ +{ + "name": "ipc-tfil-faucet", + "version": "1.0.0", + "description": "tFIL token faucet for IPC testnet", + "private": true, + "type": "module", + "scripts": { + "dev": "concurrently \"npm run dev:frontend\" \"npm run dev:backend\"", + "dev:frontend": "cd frontend && npm run dev", + "dev:backend": "cd backend && npm run dev", + "build": "cd frontend && npm run build && cd ../backend && npm run build", + "install:all": "npm install && cd frontend && npm install && cd ../backend && npm install", + "start": "cd backend && npm start" + }, + "devDependencies": { + "concurrently": "^8.2.2" + } +} + diff --git a/faucet/scripts/check-balance.js b/faucet/scripts/check-balance.js new file mode 100644 index 000000000..5430bb95a --- /dev/null +++ b/faucet/scripts/check-balance.js @@ -0,0 +1,74 @@ +#!/usr/bin/env node + +/** + * Balance Checker for IPC Faucet + * + * Checks the balance of the faucet wallet + */ + +import { ethers } from 'ethers' +import dotenv from 'dotenv' +import { fileURLToPath } from 'url' +import { dirname, join } from 'path' + +const __filename = fileURLToPath(import.meta.url) +const __dirname = dirname(__filename) + +// Load environment variables from parent directory +dotenv.config({ path: join(__dirname, '..', '.env') }) + +const RPC_URL = process.env.RPC_URL || 'http://node-1.test.ipc.space:8545' +const PRIVATE_KEY = process.env.PRIVATE_KEY +const FAUCET_AMOUNT = process.env.FAUCET_AMOUNT || '1' + +async function checkBalance() { + try { + if (!PRIVATE_KEY) { + console.error('โŒ Error: PRIVATE_KEY not found in .env file') + console.error(' Please configure your .env file first') + process.exit(1) + } + + console.log('\n๐Ÿ” Checking faucet balance...\n') + console.log(`RPC: ${RPC_URL}`) + + const provider = new ethers.JsonRpcProvider(RPC_URL) + const wallet = new ethers.Wallet(PRIVATE_KEY, provider) + + console.log(`Address: ${wallet.address}\n`) + + const balance = await provider.getBalance(wallet.address) + const balanceFIL = ethers.formatEther(balance) + const balanceNum = parseFloat(balanceFIL) + + console.log('โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”') + console.log(`๐Ÿ’ฐ Balance: ${balanceFIL} tFIL`) + console.log('โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\n') + + const amountPerRequest = parseFloat(FAUCET_AMOUNT) + const maxRequests = Math.floor(balanceNum / amountPerRequest) + + console.log(`๐Ÿ“Š Statistics:`) + console.log(` โ€ข Amount per request: ${FAUCET_AMOUNT} tFIL`) + console.log(` โ€ข Estimated requests remaining: ~${maxRequests}`) + console.log(` โ€ข Days of operation (at 100 req/day): ~${Math.floor(maxRequests / 100)}`) + console.log('') + + if (balanceNum < amountPerRequest) { + console.log('โš ๏ธ WARNING: Insufficient balance!') + console.log(' Please fund the faucet wallet with more tFIL\n') + } else if (balanceNum < amountPerRequest * 10) { + console.log('โš ๏ธ WARNING: Balance is running low!') + console.log(' Consider adding more tFIL soon\n') + } else { + console.log('โœ… Balance looks good!\n') + } + + } catch (error) { + console.error('โŒ Error:', error.message) + process.exit(1) + } +} + +checkBalance() + diff --git a/faucet/scripts/generate-wallet.js b/faucet/scripts/generate-wallet.js new file mode 100644 index 000000000..8e15791fd --- /dev/null +++ b/faucet/scripts/generate-wallet.js @@ -0,0 +1,36 @@ +#!/usr/bin/env node + +/** + * Wallet Generator for IPC Faucet + * + * Generates a new Ethereum wallet with address and private key + * Use this to create a new wallet for your faucet + */ + +import { ethers } from 'ethers' + +console.log('\n๐Ÿ” Generating new wallet for IPC Faucet...\n') + +const wallet = ethers.Wallet.createRandom() + +console.log('โœ… Wallet generated successfully!\n') +console.log('โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”') +console.log('๐Ÿ“‹ ADDRESS:') +console.log(' ' + wallet.address) +console.log('\n๐Ÿ”‘ PRIVATE KEY:') +console.log(' ' + wallet.privateKey) +console.log('โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\n') + +console.log('โš ๏ธ IMPORTANT SECURITY NOTES:') +console.log(' โ€ข Keep your private key SECRET') +console.log(' โ€ข Never share it or commit it to version control') +console.log(' โ€ข Store it securely (use a password manager)') +console.log(' โ€ข This wallet is only for testnet use\n') + +console.log('๐Ÿ“ Next steps:') +console.log(' 1. Save the private key securely') +console.log(' 2. Fund this address with tFIL tokens') +console.log(' 3. Add the private key to your .env file:') +console.log(' PRIVATE_KEY=' + wallet.privateKey) +console.log('') + diff --git a/faucet/scripts/package.json b/faucet/scripts/package.json new file mode 100644 index 000000000..52dc28ff6 --- /dev/null +++ b/faucet/scripts/package.json @@ -0,0 +1,11 @@ +{ + "name": "ipc-faucet-scripts", + "version": "1.0.0", + "type": "module", + "private": true, + "dependencies": { + "ethers": "^6.11.1", + "dotenv": "^16.4.5" + } +} + From 0beb76866c605eba41ef2d19e081a98862e46f10 Mon Sep 17 00:00:00 2001 From: philip Date: Fri, 31 Oct 2025 14:20:54 -0400 Subject: [PATCH 2/3] fix: remove unnecessary whitespace in App.vue --- faucet/frontend/src/App.vue | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/faucet/frontend/src/App.vue b/faucet/frontend/src/App.vue index eedfd8bf6..f4b7fc4bb 100644 --- a/faucet/frontend/src/App.vue +++ b/faucet/frontend/src/App.vue @@ -207,7 +207,7 @@ async function connectWallet() { connectedAddress.value = accounts[0] recipientAddress.value = accounts[0] await checkNetwork() - + if (isCorrectNetwork.value) { setStatus('Wallet connected successfully!', 'success') } else { @@ -286,7 +286,7 @@ async function switchNetwork() { await checkNetwork() setStatus('Network switched successfully!', 'success') - + // Clear success message after 3 seconds setTimeout(() => { statusMessage.value = '' @@ -311,7 +311,7 @@ async function switchNetwork() { }) await checkNetwork() setStatus('Network added and switched successfully!', 'success') - + // Clear success message after 3 seconds setTimeout(() => { statusMessage.value = '' From a9dc81de8f7c3f42022bbc8eaa8322b3ac561123 Mon Sep 17 00:00:00 2001 From: philip Date: Fri, 31 Oct 2025 15:32:01 -0400 Subject: [PATCH 3/3] fix: update faucet configuration loading state in App.vue - Added loading state for faucet amount in the button and information display. - Updated the default values in the faucetConfig to reflect loading status. --- faucet/frontend/src/App.vue | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/faucet/frontend/src/App.vue b/faucet/frontend/src/App.vue index f4b7fc4bb..22efc1758 100644 --- a/faucet/frontend/src/App.vue +++ b/faucet/frontend/src/App.vue @@ -96,6 +96,7 @@ Processing... Switch Network to Continue + Loading... Request {{ faucetConfig.amount }} tFIL @@ -124,7 +125,7 @@

Faucet Information

    -
  • โ€ข Amount per request: {{ faucetConfig.amount }} tFIL
  • +
  • โ€ข Amount per request: {{ faucetConfig.amount }} tFILLoading...
  • โ€ข Rate limit: {{ faucetConfig.rateLimit }}
  • โ€ข Network: {{ networkInfo.name }}
@@ -166,8 +167,8 @@ const networkInfo = ref({ // Faucet configuration (will be fetched from backend) const faucetConfig = ref({ - amount: '1', - rateLimit: '1 request per 24 hours per address' + amount: '', + rateLimit: 'Loading...' }) // Computed