Web3 Integration Guide Part 5: Production Deployment & Security

Complete security and deployment guide for production Web3 applications. Learn security best practices, smart contract testing, auditing, monitoring, incident response, and compliance requirements.

By GodFake Team23 min read
Web3SecurityProductionSmart ContractsTestingDeploymentAuditing
Web3 Integration Guide

Web3 Integration Guide Part 5: Production Deployment & Security

Introduction

Deploying a Web3 application to production requires careful attention to security, scalability, and reliability. Unlike traditional applications, blockchain transactions are irreversible, and smart contract bugs can lead to permanent loss of funds. This guide covers everything you need to safely deploy and maintain a production Web3 application.

In this final part, you'll learn:

  • Security best practices and common vulnerabilities
  • Input validation and sanitization
  • Smart contract testing with Hardhat/Foundry
  • Security auditing and penetration testing
  • Production RPC endpoint configuration
  • Monitoring and alerting systems
  • Incident response procedures
  • Compliance and regulatory considerations

Security Best Practices

1. Input Validation

Always validate and sanitize user inputs before using them in transactions.

import { isAddress, getAddress } from 'ethers'

/**
 * Comprehensive input validation
 */
class InputValidator {
  
  /**
   * Validate Ethereum address
   */
  static validateAddress(address) {
    if (!address) {
      throw new Error('Address is required')
    }

    if (typeof address !== 'string') {
      throw new Error('Address must be a string')
    }

    if (!isAddress(address)) {
      throw new Error('Invalid Ethereum address format')
    }

    // Check for zero address
    if (address.toLowerCase() === '0x0000000000000000000000000000000000000000') {
      throw new Error('Cannot use zero address')
    }

    // Return checksummed address
    return getAddress(address)
  }

  /**
   * Validate amount
   */
  static validateAmount(amount, options = {}) {
    const {
      min = 0,
      max = Infinity,
      decimals = 18,
      allowZero = false
    } = options

    if (amount === null || amount === undefined) {
      throw new Error('Amount is required')
    }

    const numAmount = Number(amount)

    if (isNaN(numAmount)) {
      throw new Error('Amount must be a valid number')
    }

    if (!allowZero && numAmount === 0) {
      throw new Error('Amount must be greater than zero')
    }

    if (numAmount < 0) {
      throw new Error('Amount cannot be negative')
    }

    if (numAmount < min) {
      throw new Error(`Amount must be at least ${min}`)
    }

    if (numAmount > max) {
      throw new Error(`Amount cannot exceed ${max}`)
    }

    // Validate decimal places
    const decimalPlaces = (amount.toString().split('.')[1] || '').length
    if (decimalPlaces > decimals) {
      throw new Error(`Amount cannot have more than ${decimals} decimal places`)
    }

    return numAmount
  }

  /**
   * Validate transaction hash
   */
  static validateTxHash(hash) {
    if (!hash) {
      throw new Error('Transaction hash is required')
    }

    if (typeof hash !== 'string') {
      throw new Error('Transaction hash must be a string')
    }

    if (!/^0x[a-fA-F0-9]{64}$/.test(hash)) {
      throw new Error('Invalid transaction hash format')
    }

    return hash.toLowerCase()
  }

  /**
   * Validate token address
   */
  static async validateTokenAddress(address, provider) {
    // First validate as address
    const validAddress = this.validateAddress(address)

    try {
      // Check if it's a contract
      const code = await provider.getCode(validAddress)
      
      if (code === '0x') {
        throw new Error('Address is not a contract')
      }

      // Try to call standard ERC-20 functions
      const contract = new Contract(validAddress, [
        'function totalSupply() view returns (uint256)',
        'function decimals() view returns (uint8)'
      ], provider)

      await contract.totalSupply()
      await contract.decimals()

      return validAddress

    } catch (err) {
      throw new Error('Address is not a valid ERC-20 token contract')
    }
  }

  /**
   * Sanitize user input (prevent XSS)
   */
  static sanitizeString(input) {
    if (typeof input !== 'string') {
      return input
    }

    return input
      .replace(/&/g, '&')
      .replace(//g, '>')
      .replace(/"/g, '"')
      .replace(/'/g, ''')
      .replace(/\//g, '/')
  }
}

// Usage example
try {
  const recipientAddress = InputValidator.validateAddress(userInput.address)
  const amount = InputValidator.validateAmount(userInput.amount, {
    min: 0.001,
    max: 1000,
    decimals: 6
  })

  await sendPayment(recipientAddress, amount)

} catch (err) {
  console.error('Validation error:', err.message)
  // Show error to user
}

2. Rate Limiting

Implement rate limiting to prevent abuse and DoS attacks.

/**
 * Token bucket rate limiter
 */
class RateLimiter {
  constructor(options = {}) {
    this.tokens = options.maxTokens || 10
    this.maxTokens = options.maxTokens || 10
    this.refillRate = options.refillRate || 1 // tokens per second
    this.lastRefill = Date.now()
    this.buckets = new Map()
  }

  /**
   * Check if action is allowed
   */
  async consume(identifier, cost = 1) {
    this.refill()

    const bucket = this.getBucket(identifier)

    if (bucket.tokens >= cost) {
      bucket.tokens -= cost
      bucket.lastAction = Date.now()
      return true
    }

    return false
  }

  /**
   * Get or create bucket for identifier
   */
  getBucket(identifier) {
    if (!this.buckets.has(identifier)) {
      this.buckets.set(identifier, {
        tokens: this.maxTokens,
        lastAction: Date.now()
      })
    }
    return this.buckets.get(identifier)
  }

  /**
   * Refill tokens based on time elapsed
   */
  refill() {
    const now = Date.now()
    const timePassed = (now - this.lastRefill) / 1000
    const tokensToAdd = timePassed * this.refillRate

    for (const [, bucket] of this.buckets) {
      bucket.tokens = Math.min(
        this.maxTokens,
        bucket.tokens + tokensToAdd
      )
    }

    this.lastRefill = now
  }

  /**
   * Reset bucket for identifier
   */
  reset(identifier) {
    this.buckets.delete(identifier)
  }

  /**
   * Clear old buckets
   */
  cleanup(maxAge = 3600000) { // 1 hour
    const now = Date.now()
    for (const [identifier, bucket] of this.buckets) {
      if (now - bucket.lastAction > maxAge) {
        this.buckets.delete(identifier)
      }
    }
  }
}

// Usage in API endpoint
const withdrawalLimiter = new RateLimiter({
  maxTokens: 5,     // 5 withdrawals
  refillRate: 0.5   // 1 withdrawal every 2 seconds
})

app.post('/api/withdraw', async (req, res) => {
  const userId = req.user.id

  if (!await withdrawalLimiter.consume(userId)) {
    return res.status(429).json({
      error: 'Rate limit exceeded. Please try again later.'
    })
  }

  // Process withdrawal
  // ...
})

3. Secure Private Key Storage

/**
 * Environment-based configuration
 */
// .env.production (NEVER commit this file!)
PAYMENT_WALLET_PRIVATE_KEY=0x...
INFURA_API_KEY=...
ALCHEMY_API_KEY=...
DATABASE_URL=...

// config.js
import { config } from 'dotenv'
config()

export const WALLET_CONFIG = {
  privateKey: process.env.PAYMENT_WALLET_PRIVATE_KEY,
  address: process.env.PAYMENT_WALLET_ADDRESS
}

export const RPC_CONFIG = {
  infura: process.env.INFURA_API_KEY,
  alchemy: process.env.ALCHEMY_API_KEY
}

// Validate critical env vars on startup
if (!WALLET_CONFIG.privateKey) {
  throw new Error('PAYMENT_WALLET_PRIVATE_KEY not configured')
}

/**
 * Using AWS Secrets Manager
 */
import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager'

async function getPrivateKeyFromAWS() {
  const client = new SecretsManagerClient({ region: 'us-east-1' })
  
  const command = new GetSecretValueCommand({
    SecretId: 'prod/payment-wallet/private-key'
  })

  const response = await client.send(command)
  const secret = JSON.parse(response.SecretString)
  
  return secret.privateKey
}

/**
 * Using HashiCorp Vault
 */
import vault from 'node-vault'

async function getPrivateKeyFromVault() {
  const client = vault({
    endpoint: process.env.VAULT_ADDR,
    token: process.env.VAULT_TOKEN
  })

  const { data } = await client.read('secret/data/payment-wallet')
  return data.data.privateKey
}

4. Frontend Security

/**
 * Prevent common Web3 phishing attacks
 */
class SecurityChecker {
  
  /**
   * Verify transaction before signing
   */
  static async verifyTransaction(tx) {
    const warnings = []

    // Check if sending to contract
    if (tx.to) {
      const code = await provider.getCode(tx.to)
      if (code !== '0x') {
        warnings.push('You are interacting with a smart contract')
      }
    }

    // Check for suspicious amounts
    if (tx.value) {
      const valueInEth = Number(tx.value) / 1e18
      if (valueInEth > 1) {
        warnings.push(`Large amount: ${valueInEth} ETH`)
      }
    }

    // Check gas price
    if (tx.gasPrice) {
      const gasPriceGwei = Number(tx.gasPrice) / 1e9
      if (gasPriceGwei > 100) {
        warnings.push(`High gas price: ${gasPriceGwei} gwei`)
      }
    }

    // Check for approval with unlimited amount
    if (tx.data && tx.data.startsWith('0x095ea7b3')) {
      // approve(address,uint256) function signature
      const amount = tx.data.slice(-64)
      if (amount === 'f'.repeat(64)) {
        warnings.push('⚠️ UNLIMITED TOKEN APPROVAL - This is dangerous!')
      }
    }

    return warnings
  }

  /**
   * Verify website authenticity (anti-phishing)
   */
  static verifyDomain(expectedDomain) {
    const currentDomain = window.location.hostname

    if (currentDomain !== expectedDomain) {
      throw new Error(`
        ⚠️ PHISHING WARNING ⚠️
        Expected domain: ${expectedDomain}
        Current domain: ${currentDomain}
        DO NOT PROCEED!
      `)
    }
  }

  /**
   * Check if wallet is connected to correct network
   */
  static async verifyNetwork(expectedChainId) {
    const { chainId } = await provider.getNetwork()

    if (Number(chainId) !== expectedChainId) {
      throw new Error(
        `Wrong network. Please switch to chain ID ${expectedChainId}`
      )
    }
  }
}

// Usage before transactions
async function safeSendTransaction(tx) {
  try {
    // Verify domain
    SecurityChecker.verifyDomain('app.yourwebsite.com')

    // Verify network
    await SecurityChecker.verifyNetwork(1) // Mainnet

    // Check transaction
    const warnings = await SecurityChecker.verifyTransaction(tx)

    if (warnings.length > 0) {
      const confirmed = confirm(
        `Transaction warnings:\n${warnings.join('\n')}\n\nProceed anyway?`
      )
      if (!confirmed) {
        throw new Error('Transaction cancelled by user')
      }
    }

    // Send transaction
    const signer = await provider.getSigner()
    return await signer.sendTransaction(tx)

  } catch (err) {
    console.error('Transaction security check failed:', err)
    throw err
  }
}

Smart Contract Security

Common Vulnerabilities

Vulnerability Description Prevention
Reentrancy Contract calls itself before state update Use ReentrancyGuard, checks-effects-interactions pattern
Integer Overflow Arithmetic operations exceed max value Use Solidity 0.8+ (built-in checks) or SafeMath
Access Control Unauthorized access to privileged functions Use Ownable, AccessControl modifiers
Front-Running Attacker sees pending tx and submits higher gas Use commit-reveal, batch auctions, or Flashbots
Denial of Service Contract made unusable through gas limits Avoid unbounded loops, pull over push pattern
Timestamp Dependence Relying on block.timestamp for critical logic Use block numbers or external oracles

Secure Smart Contract Example

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/security/Pausable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

/**
 * Secure payment contract with best practices
 */
contract SecurePaymentProcessor is Ownable, ReentrancyGuard, Pausable {
    using SafeERC20 for IERC20;

    // Events
    event PaymentReceived(address indexed from, uint256 amount, string orderId);
    event PaymentSent(address indexed to, uint256 amount, string withdrawalId);
    event TokenPaymentReceived(address indexed token, address indexed from, uint256 amount, string orderId);
    event TokenPaymentSent(address indexed token, address indexed to, uint256 amount, string withdrawalId);

    // State variables
    mapping(address => uint256) public balances;
    mapping(string => bool) public processedOrders;
    mapping(string => bool) public processedWithdrawals;

    // Constants
    uint256 public constant MIN_WITHDRAWAL = 0.001 ether;
    uint256 public constant MAX_WITHDRAWAL = 100 ether;

    /**
     * Receive ETH payments
     */
    receive() external payable whenNotPaused {
        require(msg.value > 0, "Amount must be greater than 0");
        balances[msg.sender] += msg.value;
        emit PaymentReceived(msg.sender, msg.value, "");
    }

    /**
     * Process order payment
     */
    function processOrderPayment(string calldata orderId) 
        external 
        payable 
        whenNotPaused 
        nonReentrant 
    {
        require(msg.value > 0, "Amount must be greater than 0");
        require(bytes(orderId).length > 0, "Order ID required");
        require(!processedOrders[orderId], "Order already processed");

        processedOrders[orderId] = true;
        balances[msg.sender] += msg.value;

        emit PaymentReceived(msg.sender, msg.value, orderId);
    }

    /**
     * Withdraw ETH
     */
    function withdraw(uint256 amount, string calldata withdrawalId) 
        external 
        whenNotPaused 
        nonReentrant 
    {
        require(amount >= MIN_WITHDRAWAL, "Amount too small");
        require(amount <= MAX_WITHDRAWAL, "Amount too large");
        require(balances[msg.sender] >= amount, "Insufficient balance");
        require(bytes(withdrawalId).length > 0, "Withdrawal ID required");
        require(!processedWithdrawals[withdrawalId], "Withdrawal already processed");

        // Checks-Effects-Interactions pattern
        processedWithdrawals[withdrawalId] = true;
        balances[msg.sender] -= amount;

        // Transfer last (after state changes)
        (bool success, ) = msg.sender.call{value: amount}("");
        require(success, "Transfer failed");

        emit PaymentSent(msg.sender, amount, withdrawalId);
    }

    /**
     * Process token payment
     */
    function processTokenPayment(
        address token,
        uint256 amount,
        string calldata orderId
    ) 
        external 
        whenNotPaused 
        nonReentrant 
    {
        require(token != address(0), "Invalid token address");
        require(amount > 0, "Amount must be greater than 0");
        require(bytes(orderId).length > 0, "Order ID required");
        require(!processedOrders[orderId], "Order already processed");

        processedOrders[orderId] = true;

        // Use SafeERC20 to handle non-standard tokens
        IERC20(token).safeTransferFrom(msg.sender, address(this), amount);

        emit TokenPaymentReceived(token, msg.sender, amount, orderId);
    }

    /**
     * Emergency pause
     */
    function pause() external onlyOwner {
        _pause();
    }

    /**
     * Unpause
     */
    function unpause() external onlyOwner {
        _unpause();
    }

    /**
     * Emergency withdraw (owner only)
     */
    function emergencyWithdraw(address payable to, uint256 amount) 
        external 
        onlyOwner 
    {
        require(to != address(0), "Invalid address");
        require(amount <= address(this).balance, "Insufficient contract balance");
        
        (bool success, ) = to.call{value: amount}("");
        require(success, "Transfer failed");
    }

    /**
     * Emergency token withdraw (owner only)
     */
    function emergencyTokenWithdraw(
        address token,
        address to,
        uint256 amount
    ) 
        external 
        onlyOwner 
    {
        require(token != address(0), "Invalid token address");
        require(to != address(0), "Invalid recipient address");

        IERC20(token).safeTransfer(to, amount);
    }

    /**
     * Get contract balance
     */
    function getBalance() external view returns (uint256) {
        return address(this).balance;
    }

    /**
     * Get user balance
     */
    function getUserBalance(address user) external view returns (uint256) {
        return balances[user];
    }
}

Testing Smart Contracts

Setup Hardhat Testing Environment

# Initialize Hardhat project
npm init -y
npm install --save-dev hardhat @nomicfoundation/hardhat-toolbox
npx hardhat init

# Install testing dependencies
npm install --save-dev @openzeppelin/contracts
npm install --save-dev chai @nomiclabs/hardhat-ethers ethers

Comprehensive Test Suite

// test/SecurePaymentProcessor.test.js
import { expect } from "chai"
import { ethers } from "hardhat"
import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"

describe("SecurePaymentProcessor", function () {
  
  async function deployContractFixture() {
    const [owner, user1, user2] = await ethers.getSigners()

    const SecurePaymentProcessor = await ethers.getContractFactory("SecurePaymentProcessor")
    const contract = await SecurePaymentProcessor.deploy()

    return { contract, owner, user1, user2 }
  }

  describe("Deployment", function () {
    it("Should set the right owner", async function () {
      const { contract, owner } = await loadFixture(deployContractFixture)
      expect(await contract.owner()).to.equal(owner.address)
    })

    it("Should start unpaused", async function () {
      const { contract } = await loadFixture(deployContractFixture)
      expect(await contract.paused()).to.equal(false)
    })
  })

  describe("Payment Processing", function () {
    it("Should accept ETH payments", async function () {
      const { contract, user1 } = await loadFixture(deployContractFixture)
      
      const amount = ethers.parseEther("1.0")
      
      await expect(
        user1.sendTransaction({
          to: contract.target,
          value: amount
        })
      ).to.changeEtherBalances(
        [user1, contract],
        [-amount, amount]
      )
    })

    it("Should process order payments", async function () {
      const { contract, user1 } = await loadFixture(deployContractFixture)
      
      const amount = ethers.parseEther("0.5")
      const orderId = "ORDER-123"

      await expect(
        contract.connect(user1).processOrderPayment(orderId, { value: amount })
      )
        .to.emit(contract, "PaymentReceived")
        .withArgs(user1.address, amount, orderId)

      expect(await contract.getUserBalance(user1.address)).to.equal(amount)
    })

    it("Should reject duplicate order IDs", async function () {
      const { contract, user1 } = await loadFixture(deployContractFixture)
      
      const amount = ethers.parseEther("0.5")
      const orderId = "ORDER-123"

      await contract.connect(user1).processOrderPayment(orderId, { value: amount })

      await expect(
        contract.connect(user1).processOrderPayment(orderId, { value: amount })
      ).to.be.revertedWith("Order already processed")
    })

    it("Should reject zero payments", async function () {
      const { contract, user1 } = await loadFixture(deployContractFixture)

      await expect(
        contract.connect(user1).processOrderPayment("ORDER-123", { value: 0 })
      ).to.be.revertedWith("Amount must be greater than 0")
    })
  })

  describe("Withdrawals", function () {
    it("Should allow withdrawal of own balance", async function () {
      const { contract, user1 } = await loadFixture(deployContractFixture)
      
      // Deposit
      const depositAmount = ethers.parseEther("1.0")
      await user1.sendTransaction({
        to: contract.target,
        value: depositAmount
      })

      // Withdraw
      const withdrawAmount = ethers.parseEther("0.5")
      const withdrawalId = "WD-123"

      await expect(
        contract.connect(user1).withdraw(withdrawAmount, withdrawalId)
      )
        .to.emit(contract, "PaymentSent")
        .withArgs(user1.address, withdrawAmount, withdrawalId)

      expect(await contract.getUserBalance(user1.address))
        .to.equal(depositAmount - withdrawAmount)
    })

    it("Should enforce minimum withdrawal", async function () {
      const { contract, user1 } = await loadFixture(deployContractFixture)
      
      const depositAmount = ethers.parseEther("1.0")
      await user1.sendTransaction({
        to: contract.target,
        value: depositAmount
      })

      const tooSmall = ethers.parseEther("0.0001")
      await expect(
        contract.connect(user1).withdraw(tooSmall, "WD-123")
      ).to.be.revertedWith("Amount too small")
    })

    it("Should enforce maximum withdrawal", async function () {
      const { contract, user1 } = await loadFixture(deployContractFixture)
      
      const largeAmount = ethers.parseEther("200")
      await user1.sendTransaction({
        to: contract.target,
        value: largeAmount
      })

      const tooLarge = ethers.parseEther("150")
      await expect(
        contract.connect(user1).withdraw(tooLarge, "WD-123")
      ).to.be.revertedWith("Amount too large")
    })

    it("Should prevent duplicate withdrawals", async function () {
      const { contract, user1 } = await loadFixture(deployContractFixture)
      
      const depositAmount = ethers.parseEther("2.0")
      await user1.sendTransaction({
        to: contract.target,
        value: depositAmount
      })

      const withdrawAmount = ethers.parseEther("0.5")
      const withdrawalId = "WD-123"

      await contract.connect(user1).withdraw(withdrawAmount, withdrawalId)

      await expect(
        contract.connect(user1).withdraw(withdrawAmount, withdrawalId)
      ).to.be.revertedWith("Withdrawal already processed")
    })

    it("Should prevent withdrawal of insufficient balance", async function () {
      const { contract, user1 } = await loadFixture(deployContractFixture)
      
      const depositAmount = ethers.parseEther("0.5")
      await user1.sendTransaction({
        to: contract.target,
        value: depositAmount
      })

      const withdrawAmount = ethers.parseEther("1.0")
      await expect(
        contract.connect(user1).withdraw(withdrawAmount, "WD-123")
      ).to.be.revertedWith("Insufficient balance")
    })
  })

  describe("Pausable", function () {
    it("Should allow owner to pause", async function () {
      const { contract, owner } = await loadFixture(deployContractFixture)
      
      await contract.connect(owner).pause()
      expect(await contract.paused()).to.equal(true)
    })

    it("Should prevent non-owner from pausing", async function () {
      const { contract, user1 } = await loadFixture(deployContractFixture)
      
      await expect(
        contract.connect(user1).pause()
      ).to.be.revertedWith("Ownable: caller is not the owner")
    })

    it("Should reject payments when paused", async function () {
      const { contract, owner, user1 } = await loadFixture(deployContractFixture)
      
      await contract.connect(owner).pause()

      await expect(
        user1.sendTransaction({
          to: contract.target,
          value: ethers.parseEther("1.0")
        })
      ).to.be.revertedWith("Pausable: paused")
    })
  })

  describe("Emergency Functions", function () {
    it("Should allow owner emergency withdraw", async function () {
      const { contract, owner, user1 } = await loadFixture(deployContractFixture)
      
      // User deposits
      const amount = ethers.parseEther("1.0")
      await user1.sendTransaction({
        to: contract.target,
        value: amount
      })

      // Owner emergency withdraw
      await expect(
        contract.connect(owner).emergencyWithdraw(owner.address, amount)
      ).to.changeEtherBalance(owner, amount)
    })

    it("Should prevent non-owner emergency withdraw", async function () {
      const { contract, user1 } = await loadFixture(deployContractFixture)
      
      await expect(
        contract.connect(user1).emergencyWithdraw(user1.address, 100)
      ).to.be.revertedWith("Ownable: caller is not the owner")
    })
  })

  describe("Reentrancy Protection", function () {
    it("Should prevent reentrancy attacks", async function () {
      // Deploy malicious contract that attempts reentrancy
      const MaliciousContract = await ethers.getContractFactory("MaliciousReentrancy")
      const { contract } = await loadFixture(deployContractFixture)
      
      const malicious = await MaliciousContract.deploy(contract.target)

      // Attempt attack
      await expect(
        malicious.attack({ value: ethers.parseEther("1.0") })
      ).to.be.revertedWith("ReentrancyGuard: reentrant call")
    })
  })
})

Run Tests with Coverage

# Run tests
npx hardhat test

# Run with gas reporting
REPORT_GAS=true npx hardhat test

# Run with coverage
npx hardhat coverage

# Test specific file
npx hardhat test test/SecurePaymentProcessor.test.js

Production Infrastructure

1. RPC Provider Configuration

/**
 * Redundant RPC provider setup
 */
import { JsonRpcProvider, FallbackProvider } from 'ethers'

function createProductionProvider() {
  const providers = [
    {
      provider: new JsonRpcProvider(
        `https://mainnet.infura.io/v3/${process.env.INFURA_API_KEY}`
      ),
      priority: 1,
      stallTimeout: 2000,
      weight: 2
    },
    {
      provider: new JsonRpcProvider(
        `https://eth-mainnet.g.alchemy.com/v2/${process.env.ALCHEMY_API_KEY}`
      ),
      priority: 1,
      stallTimeout: 2000,
      weight: 2
    },
    {
      provider: new JsonRpcProvider(
        `https://rpc.ankr.com/eth/${process.env.ANKR_API_KEY}`
      ),
      priority: 2,
      stallTimeout: 3000,
      weight: 1
    }
  ]

  return new FallbackProvider(providers, {
    quorum: 1, // Only need 1 provider to respond
    eventQuorum: 2, // Need 2 providers to agree on events
    eventWorkers: 1
  })
}

export const provider = createProductionProvider()

2. Monitoring & Alerting

/**
 * Comprehensive monitoring system
 */
import { EventEmitter } from 'events'

class Web3Monitor extends EventEmitter {
  constructor(provider, config = {}) {
    super()
    this.provider = provider
    this.config = {
      walletAddress: config.walletAddress,
      minBalance: config.minBalance || 0.1,
      checkInterval: config.checkInterval || 60000, // 1 minute
      gasPriceThreshold: config.gasPriceThreshold || 100, // gwei
      ...config
    }

    this.metrics = {
      rpcLatency: [],
      gasPrice: [],
      walletBalance: null,
      blockNumber: null,
      lastCheck: null
    }
  }

  /**
   * Start monitoring
   */
  async start() {
    console.log('🔍 Starting Web3 monitoring...')

    // Initial checks
    await this.checkAll()

    // Periodic checks
    this.interval = setInterval(() => {
      this.checkAll()
    }, this.config.checkInterval)

    // Listen to new blocks
    this.provider.on('block', async (blockNumber) => {
      this.metrics.blockNumber = blockNumber
      this.emit('newBlock', blockNumber)
    })
  }

  /**
   * Stop monitoring
   */
  stop() {
    if (this.interval) {
      clearInterval(this.interval)
    }
    this.provider.removeAllListeners('block')
    console.log('👋 Stopped monitoring')
  }

  /**
   * Run all checks
   */
  async checkAll() {
    try {
      await Promise.all([
        this.checkRPCLatency(),
        this.checkWalletBalance(),
        this.checkGasPrice(),
        this.checkNetworkStatus()
      ])

      this.metrics.lastCheck = Date.now()
      this.emit('checkComplete', this.metrics)

    } catch (err) {
      this.emit('checkError', err)
    }
  }

  /**
   * Check RPC latency
   */
  async checkRPCLatency() {
    const start = Date.now()
    await this.provider.getBlockNumber()
    const latency = Date.now() - start

    this.metrics.rpcLatency.push(latency)
    if (this.metrics.rpcLatency.length > 100) {
      this.metrics.rpcLatency.shift()
    }

    if (latency > 5000) {
      this.emit('alert', {
        severity: 'warning',
        type: 'HIGH_RPC_LATENCY',
        message: `RPC latency is ${latency}ms`,
        value: latency
      })
    }
  }

  /**
   * Check wallet balance
   */
  async checkWalletBalance() {
    if (!this.config.walletAddress) return

    const balance = await this.provider.getBalance(this.config.walletAddress)
    const balanceEth = Number(balance) / 1e18

    this.metrics.walletBalance = balanceEth

    if (balanceEth < this.config.minBalance) {
      this.emit('alert', {
        severity: 'critical',
        type: 'LOW_WALLET_BALANCE',
        message: `Wallet balance is ${balanceEth.toFixed(4)} ETH`,
        value: balanceEth
      })
    }
  }

  /**
   * Check gas price
   */
  async checkGasPrice() {
    const feeData = await this.provider.getFeeData()
    const gasPriceGwei = Number(feeData.gasPrice) / 1e9

    this.metrics.gasPrice.push(gasPriceGwei)
    if (this.metrics.gasPrice.length > 100) {
      this.metrics.gasPrice.shift()
    }

    if (gasPriceGwei > this.config.gasPriceThreshold) {
      this.emit('alert', {
        severity: 'warning',
        type: 'HIGH_GAS_PRICE',
        message: `Gas price is ${gasPriceGwei.toFixed(2)} gwei`,
        value: gasPriceGwei
      })
    }
  }

  /**
   * Check network status
   */
  async checkNetworkStatus() {
    try {
      const network = await this.provider.getNetwork()
      
      if (Number(network.chainId) !== this.config.expectedChainId) {
        this.emit('alert', {
          severity: 'critical',
          type: 'WRONG_NETWORK',
          message: `Connected to chain ${network.chainId}`,
          value: Number(network.chainId)
        })
      }
    } catch (err) {
      this.emit('alert', {
        severity: 'critical',
        type: 'NETWORK_ERROR',
        message: err.message,
        value: null
      })
    }
  }

  /**
   * Get metrics summary
   */
  getMetrics() {
    const avgLatency = this.metrics.rpcLatency.length > 0
      ? this.metrics.rpcLatency.reduce((a, b) => a + b, 0) / this.metrics.rpcLatency.length
      : 0

    const avgGasPrice = this.metrics.gasPrice.length > 0
      ? this.metrics.gasPrice.reduce((a, b) => a + b, 0) / this.metrics.gasPrice.length
      : 0

    return {
      rpcLatency: {
        current: this.metrics.rpcLatency[this.metrics.rpcLatency.length - 1] || 0,
        average: avgLatency.toFixed(2),
        samples: this.metrics.rpcLatency.length
      },
      gasPrice: {
        current: this.metrics.gasPrice[this.metrics.gasPrice.length - 1] || 0,
        average: avgGasPrice.toFixed(2),
        samples: this.metrics.gasPrice.length
      },
      walletBalance: this.metrics.walletBalance,
      blockNumber: this.metrics.blockNumber,
      lastCheck: this.metrics.lastCheck
    }
  }
}

// Usage
const monitor = new Web3Monitor(provider, {
  walletAddress: process.env.PAYMENT_WALLET_ADDRESS,
  minBalance: 0.5,
  gasPriceThreshold: 50,
  expectedChainId: 1
})

monitor.on('alert', async (alert) => {
  console.error(`🚨 ${alert.severity.toUpperCase()}: ${alert.message}`)

  // Send to monitoring service
  if (alert.severity === 'critical') {
    await sendPagerDutyAlert(alert)
    await sendSlackAlert(alert)
  } else {
    await sendSlackAlert(alert)
  }
})

monitor.on('checkComplete', (metrics) => {
  // Log metrics to monitoring service (Datadog, New Relic, etc.)
  console.log('📊 Metrics:', monitor.getMetrics())
})

monitor.start()

3. Logging & Error Tracking

/**
 * Structured logging with Winston
 */
import winston from 'winston'

const logger = winston.createLogger({
  level: process.env.LOG_LEVEL || 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.errors({ stack: true }),
    winston.format.json()
  ),
  defaultMeta: { service: 'web3-app' },
  transports: [
    new winston.transports.File({ filename: 'error.log', level: 'error' }),
    new winston.transports.File({ filename: 'combined.log' }),
    new winston.transports.Console({
      format: winston.format.combine(
        winston.format.colorize(),
        winston.format.simple()
      )
    })
  ]
})

// Log blockchain transactions
logger.info('Transaction sent', {
  txHash: '0x123...',
  from: '0xabc...',
  to: '0xdef...',
  value: '1.5',
  currency: 'ETH',
  orderId: 'ORDER-123'
})

/**
 * Error tracking with Sentry
 */
import * as Sentry from '@sentry/node'

Sentry.init({
  dsn: process.env.SENTRY_DSN,
  environment: process.env.NODE_ENV,
  tracesSampleRate: 0.1
})

// Capture blockchain errors
try {
  await contract.transfer(recipient, amount)
} catch (err) {
  Sentry.captureException(err, {
    tags: {
      type: 'blockchain_transaction',
      currency: 'ETH'
    },
    extra: {
      recipient,
      amount,
      gasPrice: feeData.gasPrice.toString()
    }
  })
  throw err
}

Incident Response Plan

Emergency Procedures

Incident Type Severity Response Steps
Smart Contract Exploit Critical 1. Pause contract
2. Notify users
3. Contact auditors
4. Assess damage
5. Deploy fix
Private Key Compromise Critical 1. Transfer all funds to new wallet
2. Rotate all keys
3. Investigate breach
4. Notify affected users
RPC Outage High 1. Failover to backup RPC
2. Queue pending transactions
3. Monitor recovery
Low Wallet Balance Medium 1. Alert on-call engineer
2. Fund wallet
3. Resume operations
High Gas Prices Low 1. Queue non-urgent transactions
2. Wait for lower gas
3. Process when favorable

Emergency Smart Contract Pause

/**
 * Emergency pause script
 */
async function emergencyPause() {
  const provider = new JsonRpcProvider(process.env.RPC_URL)
  const wallet = new Wallet(process.env.OWNER_PRIVATE_KEY, provider)
  
  const contract = new Contract(
    process.env.CONTRACT_ADDRESS,
    ['function pause()'],
    wallet
  )

  console.log('🚨 EMERGENCY: Pausing contract...')

  const tx = await contract.pause({
    gasLimit: 100000,
    maxFeePerGas: parseUnits('200', 'gwei') // High gas for fast confirmation
  })

  console.log(`Pause transaction sent: ${tx.hash}`)
  
  const receipt = await tx.wait()
  console.log(`✅ Contract paused in block ${receipt.blockNumber}`)

  // Notify team
  await sendSlackAlert({
    severity: 'critical',
    message: 'CONTRACT PAUSED - All operations halted',
    txHash: tx.hash
  })
}

// Run if needed
if (process.env.EMERGENCY_PAUSE === 'true') {
  emergencyPause().catch(console.error)
}

Deployment Checklist

Pre-Deployment

  • ☐ All tests passing (100% coverage recommended)
  • ☐ Smart contract audited by professional firm
  • ☐ Security review completed
  • ☐ Gas optimization done
  • ☐ Environment variables configured
  • ☐ RPC providers tested and redundant
  • ☐ Monitoring and alerting set up
  • ☐ Incident response plan documented
  • ☐ Team trained on emergency procedures

Deployment Steps

# 1. Deploy to testnet first
npx hardhat run scripts/deploy.js --network sepolia

# 2. Verify contract on Etherscan
npx hardhat verify --network sepolia DEPLOYED_CONTRACT_ADDRESS

# 3. Test all functions on testnet
npm run test:integration

# 4. Deploy to mainnet
npx hardhat run scripts/deploy.js --network mainnet

# 5. Verify on mainnet
npx hardhat verify --network mainnet DEPLOYED_CONTRACT_ADDRESS

# 6. Transfer ownership to multisig
npx hardhat run scripts/transfer-ownership.js --network mainnet

Post-Deployment

  • ☐ Verify contract source code on Etherscan
  • ☐ Monitor first transactions closely
  • ☐ Gradually increase transaction volume
  • ☐ Set up wallet balance alerts
  • ☐ Configure gas price monitoring
  • ☐ Document contract address and ABI
  • ☐ Update frontend with production contract
  • ☐ Announce to users

Compliance Considerations

Know Your Customer (KYC)

Depending on your jurisdiction and use case, you may need to implement KYC:

/**
 * KYC verification integration
 */
async function verifyUser(userId, kycData) {
  // Integrate with KYC provider (e.g., Jumio, Onfido)
  const response = await fetch('https://api.kycprovider.com/verify', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.KYC_API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      userId,
      firstName: kycData.firstName,
      lastName: kycData.lastName,
      dateOfBirth: kycData.dateOfBirth,
      country: kycData.country,
      documentType: kycData.documentType,
      documentNumber: kycData.documentNumber
    })
  })

  const result = await response.json()

  if (result.status === 'approved') {
    await db.users.update(userId, {
      kycStatus: 'verified',
      kycVerifiedAt: Date.now()
    })
    return true
  }

  return false
}

Anti-Money Laundering (AML)

/**
 * AML screening
 */
async function screenTransaction(address, amount) {
  // Check against OFAC sanctions list
  const response = await fetch('https://api.chainalysis.com/screening', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.CHAINALYSIS_API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      address,
      amount,
      currency: 'ETH'
    })
  })

  const result = await response.json()

  if (result.risk === 'high' || result.sanctioned) {
    // Block transaction
    await logger.warn('High-risk transaction blocked', {
      address,
      amount,
      riskScore: result.riskScore,
      reasons: result.reasons
    })

    throw new Error('Transaction blocked due to compliance check')
  }

  return true
}

Final Recommendations

Security Audit Firms

Bug Bounty Programs

Consider running a bug bounty program:

Continuous Learning

  • Follow security researchers on Twitter
  • Subscribe to Blockchain Threat Intelligence
  • Read post-mortems of hacks and exploits
  • Participate in Web3 security communities
  • Stay updated on new attack vectors

Conclusion

Congratulations! You've completed the comprehensive Web3 Integration Guide series. You now have the knowledge to:

  • ✅ Connect crypto wallets securely
  • ✅ Interact with smart contracts
  • ✅ Accept cryptocurrency payments
  • ✅ Send payments efficiently with batching
  • ✅ Deploy production-ready Web3 applications
  • ✅ Implement security best practices
  • ✅ Monitor and maintain blockchain systems

Remember: Security is not a one-time task. Continuously monitor, test, and improve your systems. Stay informed about new vulnerabilities and best practices. The Web3 ecosystem evolves rapidly—your security posture must evolve with it.

Resources

🎉 Series Complete!

You've finished all 5 parts of the Web3 Integration Guide. You're now ready to build secure, production-ready Web3 applications!

Quick Links: