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
- OpenZeppelin - Industry standard, trusted by major projects
- ConsenSys Diligence - Comprehensive smart contract audits
- Trail of Bits - Deep security analysis
- CertiK - Formal verification and auditing
- Quantstamp - Automated and manual audits
Bug Bounty Programs
Consider running a bug bounty program:
- Immunefi - Largest Web3 bug bounty platform
- HackerOne - Traditional bug bounty platform
- Code4rena - Competitive auditing
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.