JavaScript Blockchain Development: Web3, Smart Contracts, and DApp Development
Build blockchain applications with JavaScript. Learn Web3.js, Ethereum development, smart contract interaction, and decentralized app creation.
Blockchain development with JavaScript enables building decentralized applications (DApps) that interact with blockchain networks like Ethereum. Using libraries like Web3.js and Ethers.js, developers can create applications that interact with smart contracts, handle cryptocurrency transactions, and build complete Web3 experiences. This comprehensive guide covers blockchain fundamentals, smart contract interaction, and DApp development.
Blockchain Fundamentals and Web3 Setup
Web3.js Integration
// Web3 Connection Manager
class Web3Manager {
constructor() {
this.web3 = null;
this.account = null;
this.networkId = null;
this.isConnected = false;
this.provider = null;
this.contracts = new Map();
this.eventListeners = new Map();
}
// Initialize Web3 connection
async initialize(providerUrl = null) {
try {
// Check if MetaMask is available
if (typeof window !== 'undefined' && window.ethereum) {
this.provider = window.ethereum;
this.web3 = new Web3(window.ethereum);
// Request account access
await this.connectWallet();
} else if (providerUrl) {
// Use provided RPC URL
this.provider = new Web3.providers.HttpProvider(providerUrl);
this.web3 = new Web3(this.provider);
} else {
throw new Error('No Web3 provider available');
}
// Get network information
this.networkId = await this.web3.eth.net.getId();
console.log('Connected to network:', this.networkId);
this.isConnected = true;
this.setupEventListeners();
return this;
} catch (error) {
console.error('Web3 initialization failed:', error);
throw error;
}
}
// Connect to MetaMask wallet
async connectWallet() {
try {
const accounts = await window.ethereum.request({
method: 'eth_requestAccounts',
});
this.account = accounts[0];
console.log('Connected account:', this.account);
return this.account;
} catch (error) {
console.error('Wallet connection failed:', error);
throw error;
}
}
// Switch network
async switchNetwork(chainId) {
try {
await window.ethereum.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: this.web3.utils.toHex(chainId) }],
});
this.networkId = chainId;
} catch (error) {
// Network doesn't exist, add it
if (error.code === 4902) {
await this.addNetwork(chainId);
} else {
throw error;
}
}
}
// Add custom network
async addNetwork(chainId) {
const networks = {
5: {
chainId: '0x5',
chainName: 'Goerli Test Network',
nativeCurrency: {
name: 'ETH',
symbol: 'ETH',
decimals: 18,
},
rpcUrls: ['https://goerli.infura.io/v3/YOUR_PROJECT_ID'],
blockExplorerUrls: ['https://goerli.etherscan.io'],
},
137: {
chainId: '0x89',
chainName: 'Polygon Mainnet',
nativeCurrency: {
name: 'MATIC',
symbol: 'MATIC',
decimals: 18,
},
rpcUrls: ['https://polygon-rpc.com/'],
blockExplorerUrls: ['https://polygonscan.com/'],
},
};
const networkConfig = networks[chainId];
if (!networkConfig) {
throw new Error('Unsupported network');
}
await window.ethereum.request({
method: 'wallet_addEthereumChain',
params: [networkConfig],
});
}
// Setup event listeners
setupEventListeners() {
if (window.ethereum) {
// Account change listener
window.ethereum.on('accountsChanged', (accounts) => {
this.account = accounts[0] || null;
this.onAccountChanged(this.account);
});
// Network change listener
window.ethereum.on('chainChanged', (chainId) => {
this.networkId = parseInt(chainId, 16);
this.onNetworkChanged(this.networkId);
});
// Connection listener
window.ethereum.on('connect', (connectInfo) => {
this.isConnected = true;
this.onConnected(connectInfo);
});
// Disconnection listener
window.ethereum.on('disconnect', (error) => {
this.isConnected = false;
this.onDisconnected(error);
});
}
}
// Event handlers (override in subclasses)
onAccountChanged(newAccount) {
console.log('Account changed:', newAccount);
}
onNetworkChanged(newNetworkId) {
console.log('Network changed:', newNetworkId);
}
onConnected(connectInfo) {
console.log('Connected:', connectInfo);
}
onDisconnected(error) {
console.log('Disconnected:', error);
}
// Utility methods
toWei(amount, unit = 'ether') {
return this.web3.utils.toWei(amount.toString(), unit);
}
fromWei(amount, unit = 'ether') {
return this.web3.utils.fromWei(amount.toString(), unit);
}
isValidAddress(address) {
return this.web3.utils.isAddress(address);
}
async getBalance(address = null) {
const targetAddress = address || this.account;
if (!targetAddress) throw new Error('No address provided');
const balance = await this.web3.eth.getBalance(targetAddress);
return this.fromWei(balance);
}
async getGasPrice() {
return await this.web3.eth.getGasPrice();
}
async estimateGas(transaction) {
return await this.web3.eth.estimateGas(transaction);
}
// Transaction methods
async sendTransaction(to, value, data = '0x', gasLimit = null) {
const transaction = {
from: this.account,
to,
value: this.toWei(value),
data,
};
if (gasLimit) {
transaction.gas = gasLimit;
} else {
transaction.gas = await this.estimateGas(transaction);
}
transaction.gasPrice = await this.getGasPrice();
return await this.web3.eth.sendTransaction(transaction);
}
async getTransactionReceipt(txHash) {
return await this.web3.eth.getTransactionReceipt(txHash);
}
async waitForTransaction(txHash, confirmations = 1) {
return new Promise((resolve, reject) => {
const checkTransaction = async () => {
try {
const receipt = await this.getTransactionReceipt(txHash);
if (receipt) {
const currentBlock = await this.web3.eth.getBlockNumber();
const confirmedBlocks = currentBlock - receipt.blockNumber;
if (confirmedBlocks >= confirmations) {
resolve(receipt);
} else {
setTimeout(checkTransaction, 5000); // Check every 5 seconds
}
} else {
setTimeout(checkTransaction, 5000);
}
} catch (error) {
reject(error);
}
};
checkTransaction();
});
}
}
// Smart Contract Manager
class SmartContractManager {
constructor(web3Manager) {
this.web3Manager = web3Manager;
this.web3 = web3Manager.web3;
this.contracts = new Map();
this.abis = new Map();
}
// Load contract ABI
loadABI(contractName, abi) {
this.abis.set(contractName, abi);
}
// Create contract instance
createContract(contractName, address, abi = null) {
const contractABI = abi || this.abis.get(contractName);
if (!contractABI) {
throw new Error(`ABI not found for contract: ${contractName}`);
}
const contract = new this.web3.eth.Contract(contractABI, address);
this.contracts.set(contractName, contract);
return contract;
}
// Get contract instance
getContract(contractName) {
return this.contracts.get(contractName);
}
// Call contract method (read-only)
async callMethod(contractName, methodName, ...args) {
const contract = this.getContract(contractName);
if (!contract) {
throw new Error(`Contract not found: ${contractName}`);
}
return await contract.methods[methodName](...args).call();
}
// Send contract transaction (write)
async sendMethod(contractName, methodName, options = {}, ...args) {
const contract = this.getContract(contractName);
if (!contract) {
throw new Error(`Contract not found: ${contractName}`);
}
const method = contract.methods[methodName](...args);
const transaction = {
from: this.web3Manager.account,
to: contract.options.address,
data: method.encodeABI(),
value: options.value || 0,
gas:
options.gas ||
(await method.estimateGas({ from: this.web3Manager.account })),
gasPrice: options.gasPrice || (await this.web3.eth.getGasPrice()),
};
return await this.web3.eth.sendTransaction(transaction);
}
// Listen to contract events
subscribeToEvent(contractName, eventName, options = {}) {
const contract = this.getContract(contractName);
if (!contract) {
throw new Error(`Contract not found: ${contractName}`);
}
const eventSubscription = contract.events[eventName](options);
return eventSubscription;
}
// Get past events
async getPastEvents(contractName, eventName, options = {}) {
const contract = this.getContract(contractName);
if (!contract) {
throw new Error(`Contract not found: ${contractName}`);
}
return await contract.getPastEvents(eventName, options);
}
// Deploy contract
async deployContract(
contractName,
bytecode,
constructorArgs = [],
options = {}
) {
const abi = this.abis.get(contractName);
if (!abi) {
throw new Error(`ABI not found for contract: ${contractName}`);
}
const contract = new this.web3.eth.Contract(abi);
const deployment = contract.deploy({
data: bytecode,
arguments: constructorArgs,
});
const gas = options.gas || (await deployment.estimateGas());
const gasPrice = options.gasPrice || (await this.web3.eth.getGasPrice());
const deployedContract = await deployment.send({
from: this.web3Manager.account,
gas,
gasPrice,
});
this.contracts.set(contractName, deployedContract);
return deployedContract;
}
}
// ERC-20 Token Manager
class ERC20TokenManager {
constructor(contractManager) {
this.contractManager = contractManager;
this.web3 = contractManager.web3;
// Standard ERC-20 ABI
this.erc20ABI = [
{
constant: true,
inputs: [],
name: 'name',
outputs: [{ name: '', type: 'string' }],
type: 'function',
},
{
constant: true,
inputs: [],
name: 'symbol',
outputs: [{ name: '', type: 'string' }],
type: 'function',
},
{
constant: true,
inputs: [],
name: 'decimals',
outputs: [{ name: '', type: 'uint8' }],
type: 'function',
},
{
constant: true,
inputs: [],
name: 'totalSupply',
outputs: [{ name: '', type: 'uint256' }],
type: 'function',
},
{
constant: true,
inputs: [{ name: 'owner', type: 'address' }],
name: 'balanceOf',
outputs: [{ name: '', type: 'uint256' }],
type: 'function',
},
{
constant: false,
inputs: [
{ name: 'to', type: 'address' },
{ name: 'value', type: 'uint256' },
],
name: 'transfer',
outputs: [{ name: '', type: 'bool' }],
type: 'function',
},
{
constant: false,
inputs: [
{ name: 'spender', type: 'address' },
{ name: 'value', type: 'uint256' },
],
name: 'approve',
outputs: [{ name: '', type: 'bool' }],
type: 'function',
},
{
constant: true,
inputs: [
{ name: 'owner', type: 'address' },
{ name: 'spender', type: 'address' },
],
name: 'allowance',
outputs: [{ name: '', type: 'uint256' }],
type: 'function',
},
{
anonymous: false,
inputs: [
{ indexed: true, name: 'from', type: 'address' },
{ indexed: true, name: 'to', type: 'address' },
{ indexed: false, name: 'value', type: 'uint256' },
],
name: 'Transfer',
type: 'event',
},
{
anonymous: false,
inputs: [
{ indexed: true, name: 'owner', type: 'address' },
{ indexed: true, name: 'spender', type: 'address' },
{ indexed: false, name: 'value', type: 'uint256' },
],
name: 'Approval',
type: 'event',
},
];
}
// Load ERC-20 token
loadToken(tokenName, tokenAddress) {
this.contractManager.loadABI(tokenName, this.erc20ABI);
return this.contractManager.createContract(tokenName, tokenAddress);
}
// Get token information
async getTokenInfo(tokenName) {
const name = await this.contractManager.callMethod(tokenName, 'name');
const symbol = await this.contractManager.callMethod(tokenName, 'symbol');
const decimals = await this.contractManager.callMethod(
tokenName,
'decimals'
);
const totalSupply = await this.contractManager.callMethod(
tokenName,
'totalSupply'
);
return {
name,
symbol,
decimals: parseInt(decimals),
totalSupply: totalSupply.toString(),
};
}
// Get token balance
async getBalance(tokenName, address) {
const balance = await this.contractManager.callMethod(
tokenName,
'balanceOf',
address
);
const tokenInfo = await this.getTokenInfo(tokenName);
return {
raw: balance.toString(),
formatted: (balance / Math.pow(10, tokenInfo.decimals)).toString(),
};
}
// Transfer tokens
async transfer(tokenName, to, amount, decimals = 18) {
const value = (amount * Math.pow(10, decimals)).toString();
return await this.contractManager.sendMethod(
tokenName,
'transfer',
{},
to,
value
);
}
// Approve token spending
async approve(tokenName, spender, amount, decimals = 18) {
const value = (amount * Math.pow(10, decimals)).toString();
return await this.contractManager.sendMethod(
tokenName,
'approve',
{},
spender,
value
);
}
// Get allowance
async getAllowance(tokenName, owner, spender) {
const allowance = await this.contractManager.callMethod(
tokenName,
'allowance',
owner,
spender
);
return allowance.toString();
}
// Monitor token transfers
monitorTransfers(tokenName, options = {}) {
return this.contractManager.subscribeToEvent(
tokenName,
'Transfer',
options
);
}
}
// Usage example
async function initializeWeb3App() {
try {
// Initialize Web3
const web3Manager = new Web3Manager();
await web3Manager.initialize();
console.log('Web3 initialized');
console.log('Account:', web3Manager.account);
console.log('Network ID:', web3Manager.networkId);
// Get account balance
const balance = await web3Manager.getBalance();
console.log('Account balance:', balance, 'ETH');
// Initialize contract manager
const contractManager = new SmartContractManager(web3Manager);
// Initialize ERC-20 token manager
const tokenManager = new ERC20TokenManager(contractManager);
// Example: Load USDC token (mainnet address)
const usdcAddress = '0xA0b86a33E6441B8A4B8A45C0C26e9DFF1c66ec59'; // Example address
tokenManager.loadToken('USDC', usdcAddress);
// Get token info
try {
const tokenInfo = await tokenManager.getTokenInfo('USDC');
console.log('Token info:', tokenInfo);
// Get token balance
const tokenBalance = await tokenManager.getBalance(
'USDC',
web3Manager.account
);
console.log('Token balance:', tokenBalance);
} catch (error) {
console.log(
'Token operations failed (expected if not on mainnet):',
error.message
);
}
return { web3Manager, contractManager, tokenManager };
} catch (error) {
console.error('Web3 app initialization failed:', error);
throw error;
}
}
// Initialize the app
initializeWeb3App()
.then((result) => {
console.log('Web3 app initialized successfully');
})
.catch((error) => {
console.error('Failed to initialize Web3 app:', error);
});
DeFi Integration and DEX Development
Decentralized Exchange (DEX) Implementation
// DEX Manager for Uniswap-like functionality
class DEXManager {
constructor(contractManager, tokenManager) {
this.contractManager = contractManager;
this.tokenManager = tokenManager;
this.web3 = contractManager.web3;
this.pairs = new Map();
this.reserves = new Map();
// Uniswap V2 Router ABI (simplified)
this.routerABI = [
{
inputs: [
{ internalType: 'uint256', name: 'amountIn', type: 'uint256' },
{ internalType: 'address[]', name: 'path', type: 'address[]' },
],
name: 'getAmountsOut',
outputs: [
{ internalType: 'uint256[]', name: 'amounts', type: 'uint256[]' },
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{ internalType: 'uint256', name: 'amountOutMin', type: 'uint256' },
{ internalType: 'address[]', name: 'path', type: 'address[]' },
{ internalType: 'address', name: 'to', type: 'address' },
{ internalType: 'uint256', name: 'deadline', type: 'uint256' },
],
name: 'swapExactETHForTokens',
outputs: [
{ internalType: 'uint256[]', name: 'amounts', type: 'uint256[]' },
],
stateMutability: 'payable',
type: 'function',
},
{
inputs: [
{ internalType: 'uint256', name: 'amountIn', type: 'uint256' },
{ internalType: 'uint256', name: 'amountOutMin', type: 'uint256' },
{ internalType: 'address[]', name: 'path', type: 'address[]' },
{ internalType: 'address', name: 'to', type: 'address' },
{ internalType: 'uint256', name: 'deadline', type: 'uint256' },
],
name: 'swapExactTokensForTokens',
outputs: [
{ internalType: 'uint256[]', name: 'amounts', type: 'uint256[]' },
],
stateMutability: 'nonpayable',
type: 'function',
},
];
// Uniswap V2 Pair ABI (simplified)
this.pairABI = [
{
inputs: [],
name: 'getReserves',
outputs: [
{ internalType: 'uint112', name: '_reserve0', type: 'uint112' },
{ internalType: 'uint112', name: '_reserve1', type: 'uint112' },
{
internalType: 'uint32',
name: '_blockTimestampLast',
type: 'uint32',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'token0',
outputs: [{ internalType: 'address', name: '', type: 'address' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'token1',
outputs: [{ internalType: 'address', name: '', type: 'address' }],
stateMutability: 'view',
type: 'function',
},
];
}
// Initialize DEX with router address
initializeDEX(routerAddress) {
this.contractManager.loadABI('UniswapRouter', this.routerABI);
this.contractManager.createContract('UniswapRouter', routerAddress);
}
// Load trading pair
async loadPair(pairAddress, token0Address, token1Address) {
this.contractManager.loadABI('UniswapPair', this.pairABI);
const pairContract = this.contractManager.createContract(
`Pair_${pairAddress}`,
pairAddress
);
// Verify token addresses
const contractToken0 = await this.contractManager.callMethod(
`Pair_${pairAddress}`,
'token0'
);
const contractToken1 = await this.contractManager.callMethod(
`Pair_${pairAddress}`,
'token1'
);
const pairInfo = {
address: pairAddress,
token0: contractToken0.toLowerCase(),
token1: contractToken1.toLowerCase(),
contract: pairContract,
};
this.pairs.set(
`${token0Address.toLowerCase()}_${token1Address.toLowerCase()}`,
pairInfo
);
this.pairs.set(
`${token1Address.toLowerCase()}_${token0Address.toLowerCase()}`,
pairInfo
);
return pairInfo;
}
// Get pair reserves
async getPairReserves(token0Address, token1Address) {
const pairKey = `${token0Address.toLowerCase()}_${token1Address.toLowerCase()}`;
const pairInfo = this.pairs.get(pairKey);
if (!pairInfo) {
throw new Error('Pair not loaded');
}
const reserves = await this.contractManager.callMethod(
`Pair_${pairInfo.address}`,
'getReserves'
);
// Determine correct order
const isToken0First = token0Address.toLowerCase() === pairInfo.token0;
return {
reserve0: isToken0First ? reserves._reserve0 : reserves._reserve1,
reserve1: isToken0First ? reserves._reserve1 : reserves._reserve0,
blockTimestampLast: reserves._blockTimestampLast,
};
}
// Calculate swap amounts
async getAmountsOut(amountIn, path) {
const amounts = await this.contractManager.callMethod(
'UniswapRouter',
'getAmountsOut',
amountIn,
path
);
return amounts;
}
// Calculate price impact
async calculatePriceImpact(amountIn, token0Address, token1Address) {
const reserves = await this.getPairReserves(token0Address, token1Address);
const reserve0 = parseFloat(reserves.reserve0);
const reserve1 = parseFloat(reserves.reserve1);
// Current price
const currentPrice = reserve1 / reserve0;
// Price after swap (simplified calculation)
const newReserve0 = reserve0 + parseFloat(amountIn);
const newReserve1 = (reserve0 * reserve1) / newReserve0; // Constant product
const newPrice = newReserve1 / newReserve0;
const priceImpact =
Math.abs((newPrice - currentPrice) / currentPrice) * 100;
return {
currentPrice,
newPrice,
priceImpact: priceImpact.toFixed(4),
};
}
// Swap ETH for tokens
async swapETHForTokens(amountETH, tokenAddress, slippageTolerance = 0.5) {
const path = [this.getWETHAddress(), tokenAddress];
const deadline = Math.floor(Date.now() / 1000) + 300; // 5 minutes
// Get expected output
const amountInWei = this.web3.utils.toWei(amountETH.toString(), 'ether');
const amountsOut = await this.getAmountsOut(amountInWei, path);
// Calculate minimum output with slippage
const expectedOutput = amountsOut[1];
const minOutput = expectedOutput * (1 - slippageTolerance / 100);
return await this.contractManager.sendMethod(
'UniswapRouter',
'swapExactETHForTokens',
{ value: amountInWei },
minOutput.toString(),
path,
this.contractManager.web3Manager.account,
deadline
);
}
// Swap tokens for tokens
async swapTokensForTokens(
amountIn,
tokenInAddress,
tokenOutAddress,
slippageTolerance = 0.5
) {
const path = [tokenInAddress, tokenOutAddress];
const deadline = Math.floor(Date.now() / 1000) + 300; // 5 minutes
// Get expected output
const amountsOut = await this.getAmountsOut(amountIn, path);
const expectedOutput = amountsOut[1];
const minOutput = expectedOutput * (1 - slippageTolerance / 100);
// Approve token spending first
await this.approveTokenSpending(tokenInAddress, amountIn);
return await this.contractManager.sendMethod(
'UniswapRouter',
'swapExactTokensForTokens',
{},
amountIn,
minOutput.toString(),
path,
this.contractManager.web3Manager.account,
deadline
);
}
// Approve token spending for router
async approveTokenSpending(tokenAddress, amount) {
const routerContract = this.contractManager.getContract('UniswapRouter');
const routerAddress = routerContract.options.address;
// Load token if not already loaded
const tokenName = `Token_${tokenAddress}`;
if (!this.contractManager.getContract(tokenName)) {
this.tokenManager.loadToken(tokenName, tokenAddress);
}
return await this.tokenManager.approve(tokenName, routerAddress, amount);
}
// Get WETH address (varies by network)
getWETHAddress() {
const networkWETH = {
1: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', // Mainnet
5: '0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6', // Goerli
137: '0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270', // Polygon
};
return (
networkWETH[this.contractManager.web3Manager.networkId] || networkWETH[1]
);
}
// Monitor swap events
monitorSwaps(callback) {
// This would require listening to DEX events
// Implementation depends on specific DEX contract events
}
}
// Liquidity Pool Manager
class LiquidityPoolManager {
constructor(dexManager) {
this.dexManager = dexManager;
this.contractManager = dexManager.contractManager;
this.web3 = dexManager.web3;
this.positions = new Map();
}
// Add liquidity to pool
async addLiquidity(
tokenAAddress,
tokenBAddress,
amountA,
amountB,
slippageTolerance = 0.5
) {
const deadline = Math.floor(Date.now() / 1000) + 300; // 5 minutes
// Calculate minimum amounts with slippage
const minAmountA = amountA * (1 - slippageTolerance / 100);
const minAmountB = amountB * (1 - slippageTolerance / 100);
// Approve token spending
await this.dexManager.approveTokenSpending(tokenAAddress, amountA);
await this.dexManager.approveTokenSpending(tokenBAddress, amountB);
// Add liquidity (this would require the addLiquidity method in router ABI)
return await this.contractManager.sendMethod(
'UniswapRouter',
'addLiquidity',
{},
tokenAAddress,
tokenBAddress,
amountA.toString(),
amountB.toString(),
minAmountA.toString(),
minAmountB.toString(),
this.contractManager.web3Manager.account,
deadline
);
}
// Remove liquidity from pool
async removeLiquidity(
tokenAAddress,
tokenBAddress,
liquidity,
slippageTolerance = 0.5
) {
const deadline = Math.floor(Date.now() / 1000) + 300; // 5 minutes
// Calculate minimum amounts
const pairInfo = await this.dexManager.getPairReserves(
tokenAAddress,
tokenBAddress
);
const totalSupply = await this.getTotalSupply(pairInfo.address);
const amountA = (liquidity * pairInfo.reserve0) / totalSupply;
const amountB = (liquidity * pairInfo.reserve1) / totalSupply;
const minAmountA = amountA * (1 - slippageTolerance / 100);
const minAmountB = amountB * (1 - slippageTolerance / 100);
return await this.contractManager.sendMethod(
'UniswapRouter',
'removeLiquidity',
{},
tokenAAddress,
tokenBAddress,
liquidity.toString(),
minAmountA.toString(),
minAmountB.toString(),
this.contractManager.web3Manager.account,
deadline
);
}
// Get LP token balance
async getLPTokenBalance(pairAddress, userAddress) {
const pairContract = this.contractManager.getContract(
`Pair_${pairAddress}`
);
if (!pairContract) {
throw new Error('Pair contract not loaded');
}
return await this.contractManager.callMethod(
`Pair_${pairAddress}`,
'balanceOf',
userAddress
);
}
// Calculate LP token value
async calculateLPValue(pairAddress, lpAmount) {
const reserves = await this.dexManager.getPairReserves(
this.pairs.get(pairAddress).token0,
this.pairs.get(pairAddress).token1
);
const totalSupply = await this.getTotalSupply(pairAddress);
const share = lpAmount / totalSupply;
const value0 = reserves.reserve0 * share;
const value1 = reserves.reserve1 * share;
return { value0, value1, share };
}
// Get total supply of LP tokens
async getTotalSupply(pairAddress) {
return await this.contractManager.callMethod(
`Pair_${pairAddress}`,
'totalSupply'
);
}
// Calculate impermanent loss
calculateImpermanentLoss(initialPrice, currentPrice) {
const priceRatio = currentPrice / initialPrice;
const impermanentLoss = (2 * Math.sqrt(priceRatio)) / (1 + priceRatio) - 1;
return {
percentage: (impermanentLoss * 100).toFixed(4),
multiplier: (1 + impermanentLoss).toFixed(4),
};
}
}
// Yield Farming Manager
class YieldFarmingManager {
constructor(contractManager) {
this.contractManager = contractManager;
this.web3 = contractManager.web3;
this.farms = new Map();
// Masterchef-style farming contract ABI (simplified)
this.farmABI = [
{
inputs: [
{ internalType: 'uint256', name: '_pid', type: 'uint256' },
{ internalType: 'uint256', name: '_amount', type: 'uint256' },
],
name: 'deposit',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{ internalType: 'uint256', name: '_pid', type: 'uint256' },
{ internalType: 'uint256', name: '_amount', type: 'uint256' },
],
name: 'withdraw',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{ internalType: 'uint256', name: '_pid', type: 'uint256' },
{ internalType: 'address', name: '_user', type: 'address' },
],
name: 'pendingReward',
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{ internalType: 'uint256', name: '_pid', type: 'uint256' },
{ internalType: 'address', name: '_user', type: 'address' },
],
name: 'userInfo',
outputs: [
{ internalType: 'uint256', name: 'amount', type: 'uint256' },
{ internalType: 'uint256', name: 'rewardDebt', type: 'uint256' },
],
stateMutability: 'view',
type: 'function',
},
];
}
// Initialize farm contract
initializeFarm(farmName, farmAddress) {
this.contractManager.loadABI(farmName, this.farmABI);
this.contractManager.createContract(farmName, farmAddress);
this.farms.set(farmName, {
address: farmAddress,
pools: new Map(),
});
}
// Add pool information
addPool(farmName, poolId, stakingToken, rewardToken, apr = 0) {
const farm = this.farms.get(farmName);
if (!farm) {
throw new Error('Farm not initialized');
}
farm.pools.set(poolId, {
stakingToken,
rewardToken,
apr,
});
}
// Stake tokens in farm
async stake(farmName, poolId, amount) {
const farm = this.farms.get(farmName);
const pool = farm.pools.get(poolId);
if (!pool) {
throw new Error('Pool not found');
}
// Approve staking token
const stakingTokenName = `Token_${pool.stakingToken}`;
if (!this.contractManager.getContract(stakingTokenName)) {
this.contractManager.tokenManager.loadToken(
stakingTokenName,
pool.stakingToken
);
}
await this.contractManager.tokenManager.approve(
stakingTokenName,
farm.address,
amount
);
// Deposit to farm
return await this.contractManager.sendMethod(
farmName,
'deposit',
{},
poolId,
amount.toString()
);
}
// Unstake tokens from farm
async unstake(farmName, poolId, amount) {
return await this.contractManager.sendMethod(
farmName,
'withdraw',
{},
poolId,
amount.toString()
);
}
// Get pending rewards
async getPendingRewards(farmName, poolId, userAddress) {
return await this.contractManager.callMethod(
farmName,
'pendingReward',
poolId,
userAddress
);
}
// Get user staking info
async getUserInfo(farmName, poolId, userAddress) {
const userInfo = await this.contractManager.callMethod(
farmName,
'userInfo',
poolId,
userAddress
);
return {
stakedAmount: userInfo.amount.toString(),
rewardDebt: userInfo.rewardDebt.toString(),
};
}
// Calculate estimated rewards
calculateEstimatedRewards(stakedAmount, apr, days) {
const dailyRate = apr / 365 / 100;
const estimatedRewards = stakedAmount * dailyRate * days;
return estimatedRewards;
}
// Monitor farming positions
async monitorFarms(userAddress) {
const positions = [];
for (const [farmName, farm] of this.farms) {
for (const [poolId, pool] of farm.pools) {
try {
const userInfo = await this.getUserInfo(
farmName,
poolId,
userAddress
);
const pendingRewards = await this.getPendingRewards(
farmName,
poolId,
userAddress
);
if (parseFloat(userInfo.stakedAmount) > 0) {
positions.push({
farmName,
poolId,
stakingToken: pool.stakingToken,
rewardToken: pool.rewardToken,
stakedAmount: userInfo.stakedAmount,
pendingRewards: pendingRewards.toString(),
apr: pool.apr,
});
}
} catch (error) {
console.error(
`Error monitoring farm ${farmName} pool ${poolId}:`,
error
);
}
}
}
return positions;
}
}
// Usage example
async function initializeDeFiApp() {
try {
// Initialize Web3
const web3Manager = new Web3Manager();
await web3Manager.initialize();
// Initialize managers
const contractManager = new SmartContractManager(web3Manager);
const tokenManager = new ERC20TokenManager(contractManager);
const dexManager = new DEXManager(contractManager, tokenManager);
const liquidityManager = new LiquidityPoolManager(dexManager);
const farmingManager = new YieldFarmingManager(contractManager);
// Initialize DEX (Uniswap V2 Router address - varies by network)
const routerAddress = '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D'; // Mainnet
dexManager.initializeDEX(routerAddress);
console.log('DeFi app initialized');
// Example: Get token price
try {
const wethAddress = dexManager.getWETHAddress();
const usdcAddress = '0xA0b86a33E6441B8A4B8A45C0C26e9DFF1c66ec59'; // Example
const amountIn = web3Manager.toWei('1', 'ether'); // 1 ETH
const path = [wethAddress, usdcAddress];
const amountsOut = await dexManager.getAmountsOut(amountIn, path);
console.log('1 ETH =', web3Manager.fromWei(amountsOut[1]), 'USDC');
} catch (error) {
console.log(
'Price query failed (expected if not on mainnet):',
error.message
);
}
return {
web3Manager,
contractManager,
tokenManager,
dexManager,
liquidityManager,
farmingManager,
};
} catch (error) {
console.error('DeFi app initialization failed:', error);
throw error;
}
}
// Initialize the DeFi app
initializeDeFiApp()
.then((result) => {
console.log('DeFi app initialized successfully');
})
.catch((error) => {
console.error('Failed to initialize DeFi app:', error);
});
NFT Marketplace Development
NFT Contract Integration
// NFT Manager for ERC-721 tokens
class NFTManager {
constructor(contractManager) {
this.contractManager = contractManager;
this.web3 = contractManager.web3;
this.collections = new Map();
// ERC-721 ABI (simplified)
this.erc721ABI = [
{
inputs: [{ name: 'owner', type: 'address' }],
name: 'balanceOf',
outputs: [{ name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [{ name: 'tokenId', type: 'uint256' }],
name: 'ownerOf',
outputs: [{ name: '', type: 'address' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [{ name: 'tokenId', type: 'uint256' }],
name: 'tokenURI',
outputs: [{ name: '', type: 'string' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{ name: 'from', type: 'address' },
{ name: 'to', type: 'address' },
{ name: 'tokenId', type: 'uint256' },
],
name: 'transferFrom',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{ name: 'to', type: 'address' },
{ name: 'approved', type: 'address' },
],
name: 'setApprovalForAll',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{ name: 'owner', type: 'address' },
{ name: 'operator', type: 'address' },
],
name: 'isApprovedForAll',
outputs: [{ name: '', type: 'bool' }],
stateMutability: 'view',
type: 'function',
},
{
anonymous: false,
inputs: [
{ indexed: true, name: 'from', type: 'address' },
{ indexed: true, name: 'to', type: 'address' },
{ indexed: true, name: 'tokenId', type: 'uint256' },
],
name: 'Transfer',
type: 'event',
},
];
}
// Load NFT collection
loadCollection(collectionName, contractAddress) {
this.contractManager.loadABI(collectionName, this.erc721ABI);
const contract = this.contractManager.createContract(
collectionName,
contractAddress
);
this.collections.set(collectionName, {
address: contractAddress,
contract,
});
return contract;
}
// Get NFT balance
async getBalance(collectionName, ownerAddress) {
return await this.contractManager.callMethod(
collectionName,
'balanceOf',
ownerAddress
);
}
// Get NFT owner
async getOwner(collectionName, tokenId) {
return await this.contractManager.callMethod(
collectionName,
'ownerOf',
tokenId
);
}
// Get NFT metadata URI
async getTokenURI(collectionName, tokenId) {
return await this.contractManager.callMethod(
collectionName,
'tokenURI',
tokenId
);
}
// Get NFT metadata from URI
async getTokenMetadata(uri) {
try {
// Handle IPFS URIs
if (uri.startsWith('ipfs://')) {
uri = uri.replace('ipfs://', 'https://ipfs.io/ipfs/');
}
const response = await fetch(uri);
return await response.json();
} catch (error) {
console.error('Failed to fetch metadata:', error);
return null;
}
}
// Transfer NFT
async transferNFT(collectionName, fromAddress, toAddress, tokenId) {
return await this.contractManager.sendMethod(
collectionName,
'transferFrom',
{},
fromAddress,
toAddress,
tokenId
);
}
// Approve marketplace for all NFTs
async setApprovalForAll(collectionName, operatorAddress, approved) {
return await this.contractManager.sendMethod(
collectionName,
'setApprovalForAll',
{},
operatorAddress,
approved
);
}
// Check approval status
async isApprovedForAll(collectionName, ownerAddress, operatorAddress) {
return await this.contractManager.callMethod(
collectionName,
'isApprovedForAll',
ownerAddress,
operatorAddress
);
}
// Get owned NFTs
async getOwnedNFTs(collectionName, ownerAddress) {
const balance = await this.getBalance(collectionName, ownerAddress);
const ownedNFTs = [];
// This is a simplified approach - in practice, you'd need to track token IDs
// or use events to find owned tokens efficiently
const totalSupply = 10000; // Example max supply
for (let tokenId = 1; tokenId <= Math.min(totalSupply, 1000); tokenId++) {
try {
const owner = await this.getOwner(collectionName, tokenId);
if (owner.toLowerCase() === ownerAddress.toLowerCase()) {
const uri = await this.getTokenURI(collectionName, tokenId);
const metadata = await this.getTokenMetadata(uri);
ownedNFTs.push({
tokenId,
uri,
metadata,
});
}
} catch (error) {
// Token doesn't exist or other error
continue;
}
}
return ownedNFTs;
}
// Monitor NFT transfers
monitorTransfers(collectionName, options = {}) {
return this.contractManager.subscribeToEvent(
collectionName,
'Transfer',
options
);
}
}
// NFT Marketplace Manager
class NFTMarketplaceManager {
constructor(contractManager, nftManager) {
this.contractManager = contractManager;
this.nftManager = nftManager;
this.web3 = contractManager.web3;
this.marketplace = null;
// Marketplace ABI (simplified)
this.marketplaceABI = [
{
inputs: [
{ name: 'nftContract', type: 'address' },
{ name: 'tokenId', type: 'uint256' },
{ name: 'price', type: 'uint256' },
],
name: 'listItem',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{ name: 'nftContract', type: 'address' },
{ name: 'tokenId', type: 'uint256' },
],
name: 'buyItem',
outputs: [],
stateMutability: 'payable',
type: 'function',
},
{
inputs: [
{ name: 'nftContract', type: 'address' },
{ name: 'tokenId', type: 'uint256' },
],
name: 'cancelListing',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{ name: 'nftContract', type: 'address' },
{ name: 'tokenId', type: 'uint256' },
],
name: 'getListing',
outputs: [
{ name: 'seller', type: 'address' },
{ name: 'price', type: 'uint256' },
{ name: 'isActive', type: 'bool' },
],
stateMutability: 'view',
type: 'function',
},
{
anonymous: false,
inputs: [
{ indexed: true, name: 'seller', type: 'address' },
{ indexed: true, name: 'nftContract', type: 'address' },
{ indexed: true, name: 'tokenId', type: 'uint256' },
{ indexed: false, name: 'price', type: 'uint256' },
],
name: 'ItemListed',
type: 'event',
},
{
anonymous: false,
inputs: [
{ indexed: true, name: 'buyer', type: 'address' },
{ indexed: true, name: 'nftContract', type: 'address' },
{ indexed: true, name: 'tokenId', type: 'uint256' },
{ indexed: false, name: 'price', type: 'uint256' },
],
name: 'ItemSold',
type: 'event',
},
];
}
// Initialize marketplace
initializeMarketplace(marketplaceAddress) {
this.contractManager.loadABI('Marketplace', this.marketplaceABI);
this.marketplace = this.contractManager.createContract(
'Marketplace',
marketplaceAddress
);
return this.marketplace;
}
// List NFT for sale
async listNFT(nftContract, tokenId, priceInETH) {
// First, approve marketplace to transfer the NFT
const collectionName = this.getCollectionName(nftContract);
await this.nftManager.setApprovalForAll(
collectionName,
this.marketplace.options.address,
true
);
const priceInWei = this.web3.utils.toWei(priceInETH.toString(), 'ether');
return await this.contractManager.sendMethod(
'Marketplace',
'listItem',
{},
nftContract,
tokenId,
priceInWei
);
}
// Buy NFT from marketplace
async buyNFT(nftContract, tokenId) {
const listing = await this.getListing(nftContract, tokenId);
if (!listing.isActive) {
throw new Error('Item not listed for sale');
}
return await this.contractManager.sendMethod(
'Marketplace',
'buyItem',
{ value: listing.price },
nftContract,
tokenId
);
}
// Cancel NFT listing
async cancelListing(nftContract, tokenId) {
return await this.contractManager.sendMethod(
'Marketplace',
'cancelListing',
{},
nftContract,
tokenId
);
}
// Get listing information
async getListing(nftContract, tokenId) {
const listing = await this.contractManager.callMethod(
'Marketplace',
'getListing',
nftContract,
tokenId
);
return {
seller: listing.seller,
price: listing.price,
priceInETH: this.web3.utils.fromWei(listing.price, 'ether'),
isActive: listing.isActive,
};
}
// Get all listings for a collection
async getCollectionListings(nftContract, maxTokens = 1000) {
const listings = [];
for (let tokenId = 1; tokenId <= maxTokens; tokenId++) {
try {
const listing = await this.getListing(nftContract, tokenId);
if (listing.isActive) {
const collectionName = this.getCollectionName(nftContract);
const uri = await this.nftManager.getTokenURI(
collectionName,
tokenId
);
const metadata = await this.nftManager.getTokenMetadata(uri);
listings.push({
nftContract,
tokenId,
...listing,
uri,
metadata,
});
}
} catch (error) {
// Token doesn't exist or not listed
continue;
}
}
return listings;
}
// Get user's listings
async getUserListings(userAddress) {
// This would typically be done by querying events
// For now, we'll check known collections
const userListings = [];
for (const [collectionName, collection] of this.nftManager.collections) {
try {
const listings = await this.getCollectionListings(collection.address);
const userCollectionListings = listings.filter(
(listing) =>
listing.seller.toLowerCase() === userAddress.toLowerCase()
);
userListings.push(...userCollectionListings);
} catch (error) {
console.error(`Error getting listings for ${collectionName}:`, error);
}
}
return userListings;
}
// Monitor marketplace events
monitorItemListed(callback) {
return this.contractManager
.subscribeToEvent('Marketplace', 'ItemListed', {})
.on('data', callback);
}
monitorItemSold(callback) {
return this.contractManager
.subscribeToEvent('Marketplace', 'ItemSold', {})
.on('data', callback);
}
// Calculate marketplace fees
calculateFees(price, feePercentage = 2.5) {
const fee = price * (feePercentage / 100);
const sellerReceives = price - fee;
return {
totalPrice: price,
marketplaceFee: fee,
sellerReceives,
feePercentage,
};
}
// Helper method to get collection name from address
getCollectionName(address) {
for (const [name, collection] of this.nftManager.collections) {
if (collection.address.toLowerCase() === address.toLowerCase()) {
return name;
}
}
return `Collection_${address}`;
}
}
// NFT Analytics Manager
class NFTAnalyticsManager {
constructor(nftManager, marketplaceManager) {
this.nftManager = nftManager;
this.marketplaceManager = marketplaceManager;
this.web3 = nftManager.web3;
this.priceHistory = new Map();
this.floorPrices = new Map();
}
// Calculate collection floor price
async calculateFloorPrice(nftContract) {
const listings =
await this.marketplaceManager.getCollectionListings(nftContract);
if (listings.length === 0) {
return null;
}
const prices = listings.map((listing) => parseFloat(listing.priceInETH));
const floorPrice = Math.min(...prices);
this.floorPrices.set(nftContract, {
price: floorPrice,
timestamp: Date.now(),
listingCount: listings.length,
});
return floorPrice;
}
// Calculate collection volume
async calculateVolume(nftContract, timeRange = 86400000) {
// 24 hours
// This would typically query past sale events
// For demonstration, we'll use a simplified approach
const currentTime = Date.now();
const fromTime = currentTime - timeRange;
try {
const soldEvents =
await this.marketplaceManager.contractManager.getPastEvents(
'Marketplace',
'ItemSold',
{
filter: { nftContract },
fromBlock: 'earliest',
toBlock: 'latest',
}
);
let volume = 0;
let salesCount = 0;
soldEvents.forEach((event) => {
const eventTime = event.blockNumber * 15000; // Rough timestamp estimate
if (eventTime >= fromTime) {
volume += parseFloat(
this.web3.utils.fromWei(event.returnValues.price, 'ether')
);
salesCount++;
}
});
return {
volume,
salesCount,
averagePrice: salesCount > 0 ? volume / salesCount : 0,
};
} catch (error) {
console.error('Error calculating volume:', error);
return { volume: 0, salesCount: 0, averagePrice: 0 };
}
}
// Get collection statistics
async getCollectionStats(nftContract) {
const floorPrice = await this.calculateFloorPrice(nftContract);
const volume24h = await this.calculateVolume(nftContract);
const listings =
await this.marketplaceManager.getCollectionListings(nftContract);
return {
floorPrice,
volume24h: volume24h.volume,
sales24h: volume24h.salesCount,
averagePrice24h: volume24h.averagePrice,
totalListings: listings.length,
uniqueOwners: await this.calculateUniqueOwners(nftContract),
totalSupply: await this.getTotalSupply(nftContract),
};
}
// Calculate unique owners (simplified)
async calculateUniqueOwners(nftContract) {
// This would typically be done by analyzing transfer events
// For demonstration, return a placeholder
return Math.floor(Math.random() * 1000) + 100;
}
// Get total supply (if supported)
async getTotalSupply(nftContract) {
try {
const collectionName =
this.marketplaceManager.getCollectionName(nftContract);
return await this.nftManager.contractManager.callMethod(
collectionName,
'totalSupply'
);
} catch (error) {
// Method not supported
return null;
}
}
// Track price changes
trackPriceChange(nftContract, tokenId, oldPrice, newPrice) {
const key = `${nftContract}_${tokenId}`;
if (!this.priceHistory.has(key)) {
this.priceHistory.set(key, []);
}
this.priceHistory.get(key).push({
timestamp: Date.now(),
oldPrice,
newPrice,
change: newPrice - oldPrice,
changePercent: ((newPrice - oldPrice) / oldPrice) * 100,
});
}
// Get trending collections
async getTrendingCollections() {
const collections = Array.from(this.nftManager.collections.keys());
const trending = [];
for (const collection of collections) {
const stats = await this.getCollectionStats(
this.nftManager.collections.get(collection).address
);
trending.push({
collection,
...stats,
});
}
// Sort by 24h volume
return trending.sort((a, b) => b.volume24h - a.volume24h);
}
}
// Usage example
async function initializeNFTApp() {
try {
// Initialize Web3
const web3Manager = new Web3Manager();
await web3Manager.initialize();
// Initialize managers
const contractManager = new SmartContractManager(web3Manager);
const nftManager = new NFTManager(contractManager);
const marketplaceManager = new NFTMarketplaceManager(
contractManager,
nftManager
);
const analyticsManager = new NFTAnalyticsManager(
nftManager,
marketplaceManager
);
console.log('NFT app initialized');
// Example: Load a popular NFT collection
const collectionAddress = '0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D'; // BAYC example
nftManager.loadCollection('BoredApes', collectionAddress);
// Example: Initialize marketplace
const marketplaceAddress = '0x495f947276749Ce646f68AC8c248420045cb7b5e'; // Example address
marketplaceManager.initializeMarketplace(marketplaceAddress);
console.log('NFT contracts loaded');
return {
web3Manager,
contractManager,
nftManager,
marketplaceManager,
analyticsManager,
};
} catch (error) {
console.error('NFT app initialization failed:', error);
throw error;
}
}
// Initialize the NFT app
initializeNFTApp()
.then((result) => {
console.log('NFT app initialized successfully');
})
.catch((error) => {
console.error('Failed to initialize NFT app:', error);
});
Conclusion
JavaScript blockchain development opens up powerful possibilities for building decentralized applications that interact with blockchain networks. Through Web3.js and smart contract integration, developers can create applications for DeFi trading, NFT marketplaces, yield farming, and more. The key to successful blockchain development is understanding smart contract interactions, handling asynchronous operations properly, and implementing robust error handling for network issues.
When building blockchain applications with JavaScript, focus on security best practices, efficient transaction handling, proper event monitoring, and user-friendly wallet integration. Consider implementing proper caching strategies for blockchain data, monitoring gas prices for optimal transaction timing, and providing clear feedback to users about transaction states and confirmations.