Web3/Blockchain MVP Development Guide: Build Decentralized Applications
Master Web3 and blockchain MVP development. Learn smart contract design, DeFi protocols, NFT platforms, security best practices, and how to navigate the decentralized ecosystem.

Web3/Blockchain MVP Development Guide: Build Decentralized Applications
Web3 represents a fundamental shift in how we build applications. This guide provides practical frameworks for developing blockchain-based MVPs, from simple NFT platforms to complex DeFi protocols, while navigating the unique challenges of decentralized systems.
Web3 & Blockchain Fundamentals
Understanding Web3 Architecture
Web2 vs Web3 Paradigm:
// Architectural differences
const web2VsWeb3 = {
web2: {
control: 'Centralized servers',
data: 'Company-owned databases',
identity: 'Email/password accounts',
payments: 'Credit cards, PayPal',
trust: 'Trust the company',
censorship: 'Platform can ban users',
availability: 'Server dependent'
},
web3: {
control: 'Decentralized network',
data: 'User-owned, on-chain/IPFS',
identity: 'Wallet addresses',
payments: 'Cryptocurrency native',
trust: 'Trustless protocols',
censorship: 'Censorship resistant',
availability: 'Always online'
},
hybrid: {
approach: 'Progressive decentralization',
benefits: 'Best of both worlds',
examples: [
'Centralized frontend, decentralized backend',
'Optional Web3 features',
'Custodial + non-custodial options'
]
}
};
Web3 Technology Stack
Modern Web3 Stack:
// Web3 development stack
const web3Stack = {
blockchain: {
layer1: ['Ethereum', 'Solana', 'Avalanche', 'BSC'],
layer2: ['Polygon', 'Arbitrum', 'Optimism', 'zkSync'],
appChains: ['Cosmos SDK', 'Substrate', 'Avalanche Subnets']
},
smartContracts: {
languages: {
ethereum: ['Solidity', 'Vyper', 'Yul'],
solana: ['Rust', 'Anchor'],
near: ['Rust', 'AssemblyScript'],
cosmos: ['CosmWasm (Rust)']
},
frameworks: {
development: ['Hardhat', 'Foundry', 'Truffle'],
testing: ['Waffle', 'Forge', 'Brownie'],
deployment: ['Hardhat Deploy', 'Truffle Migrate']
}
},
frontend: {
libraries: ['Web3.js', 'Ethers.js', 'Wagmi', 'Web3Modal'],
wallets: ['MetaMask', 'WalletConnect', 'Coinbase Wallet'],
frameworks: ['Next.js', 'React', 'Vue'],
rpc: ['Alchemy', 'Infura', 'QuickNode']
},
storage: {
decentralized: ['IPFS', 'Arweave', 'Filecoin'],
indexed: ['The Graph', 'Covalent', 'Moralis'],
hybrid: ['Ceramic', 'Textile', 'OrbitDB']
},
infrastructure: {
nodes: ['Alchemy', 'Infura', 'Ankr'],
indexing: ['The Graph', 'SubQuery'],
oracles: ['Chainlink', 'Band Protocol'],
messaging: ['LayerZero', 'Axelar', 'Wormhole']
}
};
Web3 Use Cases
Common Web3 MVP Types:
// Web3 application categories
const web3UseCases = {
defi: {
description: 'Decentralized Finance',
examples: [
'Lending/Borrowing protocols',
'Decentralized exchanges (DEX)',
'Yield farming platforms',
'Stablecoin systems',
'Derivatives protocols'
],
complexity: 'High',
requirements: [
'Economic modeling',
'Security audits',
'Liquidity bootstrapping',
'Oracle integration'
]
},
nfts: {
description: 'Non-Fungible Tokens',
examples: [
'Art/collectible platforms',
'Gaming assets',
'Music/media rights',
'Real estate tokenization',
'Identity/credentials'
],
complexity: 'Medium',
requirements: [
'Metadata standards',
'IPFS integration',
'Marketplace mechanics',
'Royalty systems'
]
},
dao: {
description: 'Decentralized Autonomous Organizations',
examples: [
'Protocol governance',
'Investment DAOs',
'Social DAOs',
'Service DAOs',
'Media DAOs'
],
complexity: 'High',
requirements: [
'Voting mechanisms',
'Treasury management',
'Proposal systems',
'Delegation logic'
]
},
gaming: {
description: 'Blockchain Gaming',
examples: [
'Play-to-earn games',
'On-chain games',
'Asset marketplaces',
'Gaming guilds',
'Metaverse platforms'
],
complexity: 'High',
requirements: [
'High performance',
'Low transaction costs',
'Smooth UX',
'Anti-bot measures'
]
},
infrastructure: {
description: 'Web3 Infrastructure',
examples: [
'Cross-chain bridges',
'Wallet solutions',
'Identity protocols',
'Data indexing',
'Developer tools'
],
complexity: 'Very High',
requirements: [
'Deep protocol knowledge',
'Security expertise',
'Multi-chain support',
'High reliability'
]
}
};
Choosing the Right Blockchain
Blockchain Comparison
Major Blockchain Platforms:
// Blockchain selection criteria
const blockchainComparison = {
ethereum: {
pros: [
'Largest ecosystem',
'Most developers',
'Best tooling',
'Maximum composability',
'Proven security'
],
cons: [
'High gas fees',
'Slow transactions (15 TPS)',
'MEV issues',
'Scaling challenges'
],
bestFor: [
'DeFi protocols',
'High-value NFTs',
'DAOs',
'Maximum decentralization'
],
costs: {
deployment: '$500-5000',
transaction: '$5-100',
storage: 'Very expensive'
}
},
polygon: {
pros: [
'Low fees (<$0.01)',
'Ethereum compatible',
'Fast transactions',
'Growing ecosystem'
],
cons: [
'Less decentralized',
'Occasional congestion',
'Bridge risks',
'Reorg possibility'
],
bestFor: [
'Gaming',
'High-frequency trading',
'NFT marketplaces',
'Consumer apps'
]
},
solana: {
pros: [
'Very fast (65k TPS)',
'Low fees (<$0.001)',
'Growing ecosystem',
'Parallel processing'
],
cons: [
'Frequent outages',
'Less mature',
'Rust complexity',
'Centralization concerns'
],
bestFor: [
'High-frequency apps',
'Gaming',
'DeFi with speed needs',
'Consumer applications'
]
},
arbitrum: {
pros: [
'Ethereum security',
'Low fees',
'EVM compatible',
'Fast finality'
],
cons: [
'Centralized sequencer',
'7-day withdrawal',
'Less ecosystem',
'Bridge dependency'
],
bestFor: [
'DeFi protocols',
'Complex smart contracts',
'Ethereum scaling',
'Enterprise apps'
]
},
avalanche: {
pros: [
'Sub-second finality',
'Subnet capability',
'EVM compatible',
'Good performance'
],
cons: [
'Smaller ecosystem',
'Complex architecture',
'Higher hardware requirements'
],
bestFor: [
'Custom blockchains',
'Enterprise solutions',
'Gaming',
'DeFi'
]
}
};
// Decision framework
function selectBlockchain(requirements) {
const factors = {
decentralization: requirements.trustlessness,
performance: requirements.tps,
cost: requirements.transactionCost,
ecosystem: requirements.composability,
security: requirements.securityNeeds
};
// Scoring logic
const scores = {};
for (const [chain, config] of Object.entries(blockchainComparison)) {
scores[chain] = calculateScore(config, factors);
}
return Object.entries(scores)
.sort(([,a], [,b]) => b - a)
.map(([chain, score]) => ({ chain, score }));
}
Multi-Chain Strategy
Cross-Chain Architecture:
// Multi-chain deployment strategy
const multiChainStrategy = {
approaches: {
singleChain: {
when: 'Starting out, focused use case',
benefits: 'Simplicity, lower costs',
drawbacks: 'Limited reach, chain risk'
},
multiDeploy: {
when: 'Mature product, user demand',
benefits: 'Wider reach, redundancy',
drawbacks: 'Complexity, maintenance',
implementation: {
sharedCode: 'Same contracts, different chains',
bridging: 'Asset/message passing',
indexing: 'Unified data layer'
}
},
crossChain: {
when: 'Interoperability core feature',
benefits: 'Seamless UX, liquidity',
drawbacks: 'Security complexity',
protocols: ['LayerZero', 'Axelar', 'Wormhole']
},
appChain: {
when: 'Specific requirements, scale',
benefits: 'Full control, customization',
drawbacks: 'Infrastructure overhead',
options: ['Cosmos SDK', 'Polygon Edge', 'Avalanche Subnet']
}
},
implementation: `
// Multi-chain contract deployment
const deployMultiChain = async (chains) => {
const deployments = {};
for (const chain of chains) {
const provider = getProvider(chain);
const signer = getSigner(chain);
// Deploy contracts
const contracts = await deployContracts(signer, chain.config);
// Configure cross-chain messaging
if (chain.messaging) {
await configureBridge(contracts, chain.messaging);
}
deployments[chain.name] = {
contracts,
explorer: chain.explorer,
rpc: chain.rpc
};
}
return deployments;
};
`
};
Smart Contract Development
Solidity Best Practices
Modern Solidity Patterns:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
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";
/**
* @title SecureMVPProtocol
* @notice Example of secure smart contract patterns for MVPs
* @dev Implements common security patterns and best practices
*/
contract SecureMVPProtocol is Ownable, ReentrancyGuard, Pausable {
// State variables
mapping(address => uint256) public balances;
mapping(address => mapping(address => uint256)) public allowances;
// Events
event Deposit(address indexed user, uint256 amount);
event Withdrawal(address indexed user, uint256 amount);
// Custom errors (gas efficient)
error InsufficientBalance(uint256 requested, uint256 available);
error ZeroAmount();
error InvalidAddress();
// Modifiers
modifier validAmount(uint256 amount) {
if (amount == 0) revert ZeroAmount();
_;
}
modifier validAddress(address addr) {
if (addr == address(0)) revert InvalidAddress();
_;
}
/**
* @notice Deposit funds into the protocol
* @param amount Amount to deposit
*/
function deposit(uint256 amount)
external
nonReentrant
whenNotPaused
validAmount(amount)
{
// Effects
balances[msg.sender] += amount;
// Interactions
IERC20(token).transferFrom(msg.sender, address(this), amount);
emit Deposit(msg.sender, amount);
}
/**
* @notice Withdraw funds from the protocol
* @param amount Amount to withdraw
*/
function withdraw(uint256 amount)
external
nonReentrant
whenNotPaused
validAmount(amount)
{
uint256 balance = balances[msg.sender];
if (amount > balance) {
revert InsufficientBalance(amount, balance);
}
// Effects
balances[msg.sender] -= amount;
// Interactions
IERC20(token).transfer(msg.sender, amount);
emit Withdrawal(msg.sender, amount);
}
/**
* @notice Emergency withdrawal function
* @dev Only callable by owner when paused
*/
function emergencyWithdraw(address token, uint256 amount)
external
onlyOwner
whenPaused
{
IERC20(token).transfer(owner(), amount);
}
}
/**
* @title GasOptimizedStorage
* @notice Demonstrates gas optimization techniques
*/
contract GasOptimizedStorage {
// Pack struct variables
struct User {
uint128 balance; // 16 bytes
uint64 lastUpdate; // 8 bytes
uint64 rewards; // 8 bytes
bool isActive; // 1 byte
// Total: 32 bytes (1 storage slot)
}
// Use mappings instead of arrays when possible
mapping(address => User) public users;
// Batch operations to save gas
function batchUpdate(
address[] calldata addresses,
uint128[] calldata balances
) external {
uint256 length = addresses.length;
require(length == balances.length, "Length mismatch");
for (uint256 i; i < length;) {
users[addresses[i]].balance = balances[i];
users[addresses[i]].lastUpdate = uint64(block.timestamp);
unchecked { ++i; }
}
}
}
Smart Contract Testing
Comprehensive Testing Strategy:
// Hardhat testing with TypeScript
import { expect } from "chai";
import { ethers } from "hardhat";
import { Contract, Signer } from "ethers";
import { time } from "@nomicfoundation/hardhat-network-helpers";
describe("SecureMVPProtocol", function () {
let protocol: Contract;
let token: Contract;
let owner: Signer;
let user1: Signer;
let user2: Signer;
beforeEach(async function () {
[owner, user1, user2] = await ethers.getSigners();
// Deploy mock token
const Token = await ethers.getContractFactory("MockERC20");
token = await Token.deploy("Test Token", "TEST", 18);
// Deploy protocol
const Protocol = await ethers.getContractFactory("SecureMVPProtocol");
protocol = await Protocol.deploy(token.address);
// Setup: Give users some tokens
await token.mint(user1.address, ethers.utils.parseEther("1000"));
await token.mint(user2.address, ethers.utils.parseEther("1000"));
});
describe("Deposits", function () {
it("Should allow deposits", async function () {
const amount = ethers.utils.parseEther("100");
// Approve protocol
await token.connect(user1).approve(protocol.address, amount);
// Deposit
await expect(protocol.connect(user1).deposit(amount))
.to.emit(protocol, "Deposit")
.withArgs(user1.address, amount);
// Check balance
expect(await protocol.balances(user1.address)).to.equal(amount);
});
it("Should prevent zero deposits", async function () {
await expect(protocol.connect(user1).deposit(0))
.to.be.revertedWithCustomError(protocol, "ZeroAmount");
});
it("Should prevent deposits when paused", async function () {
await protocol.connect(owner).pause();
const amount = ethers.utils.parseEther("100");
await token.connect(user1).approve(protocol.address, amount);
await expect(protocol.connect(user1).deposit(amount))
.to.be.revertedWith("Pausable: paused");
});
});
describe("Withdrawals", function () {
beforeEach(async function () {
// Setup: User1 deposits 100 tokens
const amount = ethers.utils.parseEther("100");
await token.connect(user1).approve(protocol.address, amount);
await protocol.connect(user1).deposit(amount);
});
it("Should allow withdrawals", async function () {
const amount = ethers.utils.parseEther("50");
await expect(protocol.connect(user1).withdraw(amount))
.to.emit(protocol, "Withdrawal")
.withArgs(user1.address, amount);
expect(await protocol.balances(user1.address))
.to.equal(ethers.utils.parseEther("50"));
});
it("Should prevent overdraft", async function () {
const amount = ethers.utils.parseEther("200");
await expect(protocol.connect(user1).withdraw(amount))
.to.be.revertedWithCustomError(protocol, "InsufficientBalance")
.withArgs(amount, ethers.utils.parseEther("100"));
});
});
describe("Security", function () {
it("Should prevent reentrancy attacks", async function () {
// Deploy malicious contract
const Attacker = await ethers.getContractFactory("ReentrancyAttacker");
const attacker = await Attacker.deploy(protocol.address);
// Fund attacker
await token.mint(attacker.address, ethers.utils.parseEther("100"));
await attacker.approveProtocol(token.address);
// Attempt attack
await expect(attacker.attack()).to.be.reverted;
});
});
});
// Fuzzing with Foundry
contract SecureMVPProtocolTest is Test {
SecureMVPProtocol protocol;
MockERC20 token;
function setUp() public {
token = new MockERC20("TEST", "TEST", 18);
protocol = new SecureMVPProtocol(address(token));
}
function testFuzz_Deposit(uint256 amount) public {
// Bound amount to reasonable range
amount = bound(amount, 1, 1e24);
// Setup
token.mint(address(this), amount);
token.approve(address(protocol), amount);
// Test
protocol.deposit(amount);
assertEq(protocol.balances(address(this)), amount);
}
function invariant_TotalSupplyMatchesBalances() public {
uint256 totalInProtocol = token.balanceOf(address(protocol));
uint256 totalUserBalances = protocol.getTotalUserBalances();
assertEq(totalInProtocol, totalUserBalances);
}
}
Gas Optimization
Gas-Efficient Patterns:
// Gas optimization techniques
contract GasOptimized {
// 1. Use custom errors instead of strings
error Unauthorized();
error InvalidInput(uint256 provided, uint256 expected);
// 2. Pack struct variables
struct OptimizedStruct {
uint128 amount; // Slot 1
uint64 timestamp; // Slot 1
uint32 nonce; // Slot 1
address user; // Slot 2
bool isActive; // Slot 2
}
// 3. Use unchecked for safe operations
function safeMath(uint256 a, uint256 b) external pure returns (uint256) {
unchecked {
return a + b; // Only if overflow impossible
}
}
// 4. Cache array length
function processArray(uint256[] calldata data) external {
uint256 length = data.length;
for (uint256 i; i < length;) {
// Process data[i]
unchecked { ++i; }
}
}
// 5. Use bytes32 for strings when possible
mapping(bytes32 => uint256) public values;
function setValue(string calldata key, uint256 value) external {
values[keccak256(bytes(key))] = value;
}
// 6. Minimize storage operations
uint256 private _totalSupply;
function efficientUpdate(uint256[] calldata amounts) external {
uint256 tempTotal = _totalSupply;
for (uint256 i; i < amounts.length;) {
tempTotal += amounts[i];
unchecked { ++i; }
}
_totalSupply = tempTotal; // Single SSTORE
}
}
DeFi & Protocol Design
DeFi Building Blocks
Core DeFi Components:
// Automated Market Maker (AMM) Example
contract SimpleAMM {
IERC20 public immutable token0;
IERC20 public immutable token1;
uint256 public reserve0;
uint256 public reserve1;
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
uint256 private constant MINIMUM_LIQUIDITY = 1000;
event Mint(address indexed to, uint256 amount);
event Burn(address indexed from, uint256 amount);
event Swap(
address indexed user,
uint256 amount0In,
uint256 amount1In,
uint256 amount0Out,
uint256 amount1Out
);
constructor(address _token0, address _token1) {
token0 = IERC20(_token0);
token1 = IERC20(_token1);
}
function mint(address to) external returns (uint256 liquidity) {
uint256 balance0 = token0.balanceOf(address(this));
uint256 balance1 = token1.balanceOf(address(this));
uint256 amount0 = balance0 - reserve0;
uint256 amount1 = balance1 - reserve1;
if (totalSupply == 0) {
liquidity = Math.sqrt(amount0 * amount1) - MINIMUM_LIQUIDITY;
_mint(address(0), MINIMUM_LIQUIDITY); // Lock minimum
} else {
liquidity = Math.min(
amount0 * totalSupply / reserve0,
amount1 * totalSupply / reserve1
);
}
require(liquidity > 0, "Insufficient liquidity minted");
_mint(to, liquidity);
_update(balance0, balance1);
emit Mint(to, liquidity);
}
function swap(
uint256 amount0Out,
uint256 amount1Out,
address to
) external {
require(amount0Out > 0 || amount1Out > 0, "Invalid outputs");
require(amount0Out < reserve0 && amount1Out < reserve1, "Insufficient liquidity");
if (amount0Out > 0) token0.transfer(to, amount0Out);
if (amount1Out > 0) token1.transfer(to, amount1Out);
uint256 balance0 = token0.balanceOf(address(this));
uint256 balance1 = token1.balanceOf(address(this));
uint256 amount0In = balance0 > reserve0 - amount0Out ?
balance0 - (reserve0 - amount0Out) : 0;
uint256 amount1In = balance1 > reserve1 - amount1Out ?
balance1 - (reserve1 - amount1Out) : 0;
require(amount0In > 0 || amount1In > 0, "Invalid inputs");
// Check constant product formula (with 0.3% fee)
uint256 balance0Adjusted = balance0 * 1000 - amount0In * 3;
uint256 balance1Adjusted = balance1 * 1000 - amount1In * 3;
require(
balance0Adjusted * balance1Adjusted >= reserve0 * reserve1 * 1000**2,
"Invalid K"
);
_update(balance0, balance1);
emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out);
}
function _update(uint256 balance0, uint256 balance1) private {
reserve0 = balance0;
reserve1 = balance1;
}
function _mint(address to, uint256 amount) private {
balanceOf[to] += amount;
totalSupply += amount;
}
}
// Lending Protocol Example
contract SimpleLending {
using SafeMath for uint256;
struct Market {
uint256 totalSupply;
uint256 totalBorrows;
uint256 borrowIndex;
uint256 lastAccrualBlock;
uint256 reserveFactor;
}
struct UserInfo {
uint256 suppliedAmount;
uint256 borrowedAmount;
uint256 borrowIndex;
}
mapping(address => Market) public markets;
mapping(address => mapping(address => UserInfo)) public userInfo;
uint256 constant BASE_RATE = 2e16; // 2% base rate
uint256 constant MULTIPLIER = 1e17; // 10% slope
function supply(address asset, uint256 amount) external {
Market storage market = markets[asset];
UserInfo storage user = userInfo[asset][msg.sender];
_accrueInterest(asset);
IERC20(asset).transferFrom(msg.sender, address(this), amount);
user.suppliedAmount = user.suppliedAmount.add(amount);
market.totalSupply = market.totalSupply.add(amount);
}
function borrow(address asset, uint256 amount) external {
Market storage market = markets[asset];
UserInfo storage user = userInfo[asset][msg.sender];
_accrueInterest(asset);
uint256 borrowCapacity = _getBorrowCapacity(msg.sender, asset);
require(amount <= borrowCapacity, "Insufficient collateral");
user.borrowedAmount = user.borrowedAmount.add(amount);
user.borrowIndex = market.borrowIndex;
market.totalBorrows = market.totalBorrows.add(amount);
IERC20(asset).transfer(msg.sender, amount);
}
function _accrueInterest(address asset) internal {
Market storage market = markets[asset];
uint256 currentBlock = block.number;
uint256 accrualBlocks = currentBlock.sub(market.lastAccrualBlock);
if (accrualBlocks == 0) return;
uint256 borrowRate = _getBorrowRate(market);
uint256 interestAccumulated = borrowRate.mul(accrualBlocks).mul(market.totalBorrows).div(1e18);
market.totalBorrows = market.totalBorrows.add(interestAccumulated);
market.borrowIndex = market.borrowIndex.add(
interestAccumulated.mul(1e18).div(market.totalBorrows)
);
market.lastAccrualBlock = currentBlock;
}
function _getBorrowRate(Market memory market) internal pure returns (uint256) {
if (market.totalSupply == 0) return 0;
uint256 utilization = market.totalBorrows.mul(1e18).div(market.totalSupply);
return BASE_RATE.add(utilization.mul(MULTIPLIER).div(1e18));
}
}
Tokenomics Design
Token Economic Models:
// Tokenomics design framework
const tokenomicsDesign = {
tokenTypes: {
utility: {
purpose: 'Access to services',
examples: ['BNB', 'FIL', 'LINK'],
characteristics: [
'Clear use case',
'Demand driven by usage',
'Often deflationary'
]
},
governance: {
purpose: 'Protocol decisions',
examples: ['UNI', 'AAVE', 'MKR'],
characteristics: [
'Voting rights',
'Protocol ownership',
'Often includes revenue share'
]
},
security: {
purpose: 'Network security',
examples: ['ETH', 'DOT', 'ATOM'],
characteristics: [
'Staking requirements',
'Slashing mechanisms',
'Block rewards'
]
},
hybrid: {
purpose: 'Multiple functions',
examples: ['CRV', 'SNX', 'SUSHI'],
characteristics: [
'Utility + governance',
'Complex mechanisms',
'Multiple revenue streams'
]
}
},
distribution: {
fair_launch: {
allocation: {
community: '100%',
team: '0%',
investors: '0%'
},
examples: ['YFI', 'Bitcoin'],
pros: 'Maximum decentralization',
cons: 'No funding for development'
},
traditional: {
allocation: {
community: '40-60%',
team: '15-20%',
investors: '20-30%',
treasury: '10-20%'
},
vesting: '4 years with cliff',
pros: 'Aligned incentives',
cons: 'Potential dump risk'
},
liquidity_mining: {
mechanism: 'Rewards for liquidity',
duration: '2-4 years',
curve: 'Decreasing emissions',
examples: ['COMP', 'UNI', 'SUSHI']
}
},
mechanisms: {
burn: {
purpose: 'Reduce supply',
trigger: 'Transaction fees',
example: 'EIP-1559 ETH burn'
},
stake: {
purpose: 'Lock supply, earn rewards',
rewards: 'Inflation or fees',
example: 'ETH 2.0 staking'
},
vote_escrow: {
purpose: 'Time-locked governance',
mechanism: 'Longer lock = more power',
example: 'veCRV model'
},
rebasing: {
purpose: 'Algorithmic supply',
mechanism: 'Supply adjusts to price',
example: 'OHM, AMPL'
}
}
};
// Token contract with modern patterns
contract GovernanceToken is ERC20, ERC20Permit, Ownable {
uint256 public constant MAX_SUPPLY = 1_000_000_000 * 10**18; // 1 billion
mapping(address => uint256) public vestingSchedule;
mapping(address => uint256) public claimedAmount;
constructor() ERC20("Governance Token", "GOV") ERC20Permit("Governance Token") {
// Initial distribution
_mint(msg.sender, MAX_SUPPLY * 10 / 100); // 10% to deployer/treasury
}
function setupVesting(address beneficiary, uint256 amount, uint256 duration) external onlyOwner {
require(vestingSchedule[beneficiary] == 0, "Already set");
vestingSchedule[beneficiary] = block.timestamp + duration;
_mint(address(this), amount);
}
function claimVested() external {
uint256 vested = getVestedAmount(msg.sender);
uint256 claimable = vested - claimedAmount[msg.sender];
require(claimable > 0, "Nothing to claim");
claimedAmount[msg.sender] += claimable;
_transfer(address(this), msg.sender, claimable);
}
}
Web3 Architecture & Infrastructure
Frontend Integration
Web3 Frontend Setup:
// Web3 frontend with wagmi and React
import { WagmiConfig, createClient, configureChains, mainnet, polygon } from 'wagmi';
import { alchemyProvider } from 'wagmi/providers/alchemy';
import { publicProvider } from 'wagmi/providers/public';
import { MetaMaskConnector } from 'wagmi/connectors/metaMask';
import { WalletConnectConnector } from 'wagmi/connectors/walletConnect';
import { InjectedConnector } from 'wagmi/connectors/injected';
// Configure chains & providers
const { chains, provider, webSocketProvider } = configureChains(
[mainnet, polygon],
[
alchemyProvider({ apiKey: process.env.NEXT_PUBLIC_ALCHEMY_KEY }),
publicProvider(),
],
);
// Set up client
const client = createClient({
autoConnect: true,
connectors: [
new MetaMaskConnector({ chains }),
new WalletConnectConnector({
chains,
options: {
qrcode: true,
},
}),
new InjectedConnector({
chains,
options: {
name: 'Injected',
shimDisconnect: true,
},
}),
],
provider,
webSocketProvider,
});
// React hooks for Web3 interaction
import { useAccount, useConnect, useContractRead, useContractWrite } from 'wagmi';
import { useState, useEffect } from 'react';
function DeFiInterface() {
const { address, isConnected } = useAccount();
const { connect, connectors } = useConnect();
// Read contract state
const { data: balance } = useContractRead({
address: '0x...',
abi: protocolABI,
functionName: 'balanceOf',
args: [address],
watch: true,
});
// Write to contract
const { write: deposit, isLoading } = useContractWrite({
address: '0x...',
abi: protocolABI,
functionName: 'deposit',
onSuccess(data) {
console.log('Success', data);
},
onError(error) {
console.error('Error', error);
},
});
// Handle transactions
const handleDeposit = async (amount: string) => {
try {
// Approve token first
const approveTx = await approveToken(amount);
await approveTx.wait();
// Then deposit
await deposit({
args: [ethers.utils.parseEther(amount)],
});
} catch (error) {
console.error('Transaction failed:', error);
}
};
return (
<div>
{!isConnected ? (
<div>
{connectors.map((connector) => (
<button key={connector.id} onClick={() => connect({ connector })}>
Connect with {connector.name}
</button>
))}
</div>
) : (
<div>
<p>Connected: {address}</p>
<p>Balance: {balance ? ethers.utils.formatEther(balance) : '0'}</p>
<input
type="number"
placeholder="Amount to deposit"
onChange={(e) => setAmount(e.target.value)}
/>
<button onClick={() => handleDeposit(amount)} disabled={isLoading}>
{isLoading ? 'Depositing...' : 'Deposit'}
</button>
</div>
)}
</div>
);
}
// Web3 utilities
export const web3Utils = {
// Format addresses
formatAddress: (address: string) => {
return `${address.slice(0, 6)}...${address.slice(-4)}`;
},
// Handle errors
parseError: (error: any) => {
if (error?.reason) return error.reason;
if (error?.data?.message) return error.data.message;
if (error?.message) return error.message;
return 'Unknown error occurred';
},
// Transaction helpers
waitForTransaction: async (tx: any) => {
const receipt = await tx.wait();
return {
success: receipt.status === 1,
gasUsed: receipt.gasUsed.toString(),
blockNumber: receipt.blockNumber,
};
},
};
Backend Infrastructure
Web3 Backend Services:
// Node.js backend for Web3
import { ethers } from 'ethers';
import { Queue } from 'bull';
import Redis from 'redis';
import { PrismaClient } from '@prisma/client';
class Web3Backend {
constructor() {
this.provider = new ethers.providers.AlchemyProvider(
'homestead',
process.env.ALCHEMY_KEY
);
this.prisma = new PrismaClient();
this.redis = Redis.createClient();
this.queue = new Queue('transactions');
this.setupEventListeners();
this.setupQueueProcessors();
}
// Event monitoring
setupEventListeners() {
const contract = new ethers.Contract(
process.env.CONTRACT_ADDRESS,
abi,
this.provider
);
// Listen for events
contract.on('Deposit', async (user, amount, event) => {
await this.handleDeposit(user, amount, event);
});
contract.on('Withdrawal', async (user, amount, event) => {
await this.handleWithdrawal(user, amount, event);
});
// Handle reorgs
this.provider.on('block', async (blockNumber) => {
await this.checkForReorgs(blockNumber);
});
}
// Transaction queue
setupQueueProcessors() {
this.queue.process('sendTransaction', async (job) => {
const { to, data, value } = job.data;
try {
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, this.provider);
// Gas estimation
const gasPrice = await this.getOptimalGasPrice();
const gasLimit = await wallet.estimateGas({ to, data, value });
// Send transaction
const tx = await wallet.sendTransaction({
to,
data,
value,
gasPrice,
gasLimit: gasLimit.mul(110).div(100), // 10% buffer
});
// Store in database
await this.prisma.transaction.create({
data: {
hash: tx.hash,
status: 'pending',
from: wallet.address,
to,
value: value.toString(),
gasPrice: gasPrice.toString(),
},
});
// Wait for confirmation
const receipt = await tx.wait(2); // 2 confirmations
await this.prisma.transaction.update({
where: { hash: tx.hash },
data: {
status: 'confirmed',
blockNumber: receipt.blockNumber,
gasUsed: receipt.gasUsed.toString(),
},
});
return receipt;
} catch (error) {
await this.handleTransactionError(error, job);
throw error;
}
});
}
// Gas optimization
async getOptimalGasPrice() {
// Get gas prices from multiple sources
const [ethGasStation, etherscan, provider] = await Promise.all([
this.fetchEthGasStation(),
this.fetchEtherscan(),
this.provider.getGasPrice(),
]);
// Use median price
const prices = [ethGasStation, etherscan, provider].filter(Boolean);
prices.sort((a, b) => a.sub(b).toNumber());
return prices[Math.floor(prices.length / 2)];
}
// Indexing and caching
async getTokenBalance(address, tokenAddress) {
const cacheKey = `balance:${address}:${tokenAddress}`;
// Check cache
const cached = await this.redis.get(cacheKey);
if (cached) return cached;
// Fetch from blockchain
const token = new ethers.Contract(tokenAddress, erc20ABI, this.provider);
const balance = await token.balanceOf(address);
// Cache for 30 seconds
await this.redis.setex(cacheKey, 30, balance.toString());
return balance;
}
}
// GraphQL API for frontend
import { ApolloServer, gql } from 'apollo-server-express';
const typeDefs = gql`
type User {
address: String!
balance: String!
transactions: [Transaction!]!
}
type Transaction {
hash: String!
from: String!
to: String!
value: String!
status: String!
timestamp: Int!
}
type Query {
user(address: String!): User
transaction(hash: String!): Transaction
transactions(limit: Int, offset: Int): [Transaction!]!
}
type Mutation {
queueTransaction(to: String!, data: String!, value: String!): String!
}
`;
const resolvers = {
Query: {
user: async (_, { address }, { dataSources }) => {
return dataSources.userAPI.getUser(address);
},
transaction: async (_, { hash }, { dataSources }) => {
return dataSources.transactionAPI.getTransaction(hash);
},
},
Mutation: {
queueTransaction: async (_, args, { dataSources }) => {
return dataSources.transactionAPI.queueTransaction(args);
},
},
};
Decentralized Storage
IPFS and Arweave Integration:
// IPFS integration
import { create } from 'ipfs-http-client';
import { Web3Storage } from 'web3.storage';
class DecentralizedStorage {
constructor() {
// IPFS client
this.ipfs = create({
host: 'ipfs.infura.io',
port: 5001,
protocol: 'https',
});
// Web3.storage client
this.web3storage = new Web3Storage({
token: process.env.WEB3STORAGE_TOKEN,
});
}
// Upload to IPFS
async uploadToIPFS(data) {
try {
// For JSON data
if (typeof data === 'object') {
const json = JSON.stringify(data);
const added = await this.ipfs.add(json);
return {
cid: added.path,
url: `https://ipfs.io/ipfs/${added.path}`,
};
}
// For files
const added = await this.ipfs.add(data);
return {
cid: added.path,
url: `https://ipfs.io/ipfs/${added.path}`,
};
} catch (error) {
console.error('IPFS upload error:', error);
throw error;
}
}
// Upload NFT metadata
async uploadNFTMetadata(metadata, image) {
// Upload image first
const imageResult = await this.uploadToIPFS(image);
// Create metadata with image IPFS URL
const nftMetadata = {
...metadata,
image: imageResult.url,
};
// Upload metadata
const metadataResult = await this.uploadToIPFS(nftMetadata);
return {
metadataURI: metadataResult.url,
imageURI: imageResult.url,
};
}
// Arweave permanent storage
async uploadToArweave(data) {
const arweave = Arweave.init({
host: 'arweave.net',
port: 443,
protocol: 'https',
});
// Create transaction
const transaction = await arweave.createTransaction({
data: JSON.stringify(data),
});
// Add tags
transaction.addTag('Content-Type', 'application/json');
transaction.addTag('App-Name', 'MyDApp');
// Sign and submit
await arweave.transactions.sign(transaction, wallet);
await arweave.transactions.post(transaction);
return {
id: transaction.id,
url: `https://arweave.net/${transaction.id}`,
};
}
}
// On-chain storage pointer
contract StoragePointer {
mapping(uint256 => string) public tokenURIs;
mapping(address => string) public userProfiles;
event MetadataUpdate(uint256 indexed tokenId, string uri);
event ProfileUpdate(address indexed user, string uri);
function setTokenURI(uint256 tokenId, string calldata uri) external {
require(ownerOf(tokenId) == msg.sender, "Not owner");
tokenURIs[tokenId] = uri;
emit MetadataUpdate(tokenId, uri);
}
function setProfile(string calldata ipfsHash) external {
userProfiles[msg.sender] = ipfsHash;
emit ProfileUpdate(msg.sender, ipfsHash);
}
}
Security & Auditing
Common Vulnerabilities
Web3 Security Checklist:
// Common vulnerability patterns and fixes
contract SecurityPatterns {
// 1. Reentrancy Protection
uint256 private _status = 1;
modifier nonReentrant() {
require(_status != 2, "ReentrancyGuard: reentrant call");
_status = 2;
_;
_status = 1;
}
// 2. Integer Overflow (Solidity 0.8+ has built-in protection)
function safeAdd(uint256 a, uint256 b) public pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow");
return c;
}
// 3. Access Control
mapping(bytes32 => mapping(address => bool)) private _roles;
modifier onlyRole(bytes32 role) {
require(hasRole(role, msg.sender), "Access denied");
_;
}
// 4. Front-running Protection
mapping(bytes32 => bool) private _commitments;
function commit(bytes32 commitment) external {
_commitments[commitment] = true;
}
function reveal(uint256 value, uint256 nonce) external {
bytes32 commitment = keccak256(abi.encodePacked(value, nonce, msg.sender));
require(_commitments[commitment], "Invalid commitment");
// Process revealed value
}
// 5. Flash Loan Attack Prevention
modifier flashLoanProtection() {
require(msg.sender == tx.origin, "No contracts");
_;
}
// 6. Timestamp Manipulation
uint256 private constant BLOCK_TIME_TOLERANCE = 900; // 15 minutes
function timeBasedFunction() external {
require(
block.timestamp >= someTimestamp - BLOCK_TIME_TOLERANCE &&
block.timestamp <= someTimestamp + BLOCK_TIME_TOLERANCE,
"Invalid timestamp"
);
}
// 7. Delegate Call Vulnerabilities
address private implementation;
function upgrade(address newImplementation) external onlyOwner {
require(isContract(newImplementation), "Not a contract");
implementation = newImplementation;
}
// 8. Oracle Manipulation
uint256 private constant PRICE_TOLERANCE = 500; // 5%
function validatePrice(uint256 oraclePrice, uint256 expectedPrice) internal pure {
uint256 difference = oraclePrice > expectedPrice ?
oraclePrice - expectedPrice : expectedPrice - oraclePrice;
require(
difference * 10000 / expectedPrice <= PRICE_TOLERANCE,
"Price deviation too high"
);
}
}
Security Testing
Comprehensive Security Testing:
// Security testing with Hardhat
const { expect } = require("chai");
const { ethers } = require("hardhat");
describe("Security Tests", function () {
let protocol, attacker, owner;
beforeEach(async function () {
[owner, attacker] = await ethers.getSigners();
const Protocol = await ethers.getContractFactory("Protocol");
protocol = await Protocol.deploy();
});
describe("Reentrancy", function () {
it("Should prevent reentrancy attacks", async function () {
const Attacker = await ethers.getContractFactory("ReentrancyAttacker");
const attackContract = await Attacker.deploy(protocol.address);
await expect(
attackContract.attack({ value: ethers.utils.parseEther("1") })
).to.be.revertedWith("ReentrancyGuard: reentrant call");
});
});
describe("Access Control", function () {
it("Should restrict admin functions", async function () {
await expect(
protocol.connect(attacker).pause()
).to.be.revertedWith("Ownable: caller is not the owner");
});
});
describe("Integer Overflow", function () {
it("Should handle large numbers safely", async function () {
const maxUint = ethers.constants.MaxUint256;
await expect(
protocol.add(maxUint, 1)
).to.be.reverted;
});
});
});
// Fuzzing with Echidna
contract TestProtocol is Protocol {
address echidna_sender = msg.sender;
// Invariant: Total supply should never exceed max
function echidna_test_max_supply() public view returns (bool) {
return totalSupply() <= MAX_SUPPLY;
}
// Invariant: User balance should never exceed total supply
function echidna_test_balance_invariant() public view returns (bool) {
return balanceOf(echidna_sender) <= totalSupply();
}
}
// Formal verification with Certora
rule preserveTotalSupply(method f) {
uint256 totalBefore = totalSupply();
env e;
calldataarg args;
f(e, args);
uint256 totalAfter = totalSupply();
assert totalAfter == totalBefore ||
(f.selector == mint.selector && totalAfter == totalBefore + args[1]) ||
(f.selector == burn.selector && totalAfter == totalBefore - args[1]);
}
Audit Preparation
Audit Checklist:
// Audit preparation framework
const auditPreparation = {
documentation: {
required: [
'System architecture diagram',
'Threat model',
'Function specifications',
'State machine diagrams',
'External dependencies',
'Deployment procedures'
],
codeDocumentation: `
/**
* @title ProtocolName
* @author TeamName
* @notice Main protocol contract for...
* @dev Implements EIP-XXX standard
* Security contact: security@protocol.com
*/
`
},
testing: {
coverage: {
target: '100%',
types: ['Unit', 'Integration', 'Fuzzing', 'Formal']
},
scenarios: [
'Happy path',
'Edge cases',
'Failure modes',
'Attack vectors',
'Gas optimization'
]
},
deployment: {
scripts: 'Automated deployment',
verification: 'Etherscan verification',
multisig: 'Gnosis Safe for admin',
timelock: 'Compound Timelock',
monitoring: 'Forta agents'
},
postAudit: {
fixes: 'Address all findings',
reaudit: 'Critical changes only',
bounty: 'Immunefi program',
insurance: 'Nexus Mutual coverage'
}
};
Your Web3 MVP Action Plan
Week 1: Planning & Design
- [ ] Define use case and tokenomics
- [ ] Choose blockchain platform
- [ ] Design smart contract architecture
- [ ] Plan security measures
Week 2-3: Development
- [ ] Write smart contracts
- [ ] Implement test suite
- [ ] Build frontend interface
- [ ] Integrate Web3 features
Week 4-6: Testing & Security
- [ ] Unit and integration tests
- [ ] Fuzzing and invariant testing
- [ ] Internal security review
- [ ] Testnet deployment
Week 7-8: Launch Preparation
- [ ] External audit
- [ ] Fix audit findings
- [ ] Mainnet deployment
- [ ] Monitor and iterate
Web3 Development Resources
Tools & Frameworks
- Development: Hardhat, Foundry, Remix
- Testing: Waffle, Forge, Echidna
- Frontend: Web3.js, Ethers.js, Wagmi
- Infrastructure: Alchemy, Infura, The Graph
Templates & Downloads
Key Takeaways
Web3 MVP Success Principles
- Security First - One bug can kill your protocol
- Start Simple - Complex tokenomics can wait
- Test Everything - 100% coverage is minimum
- User Experience - Web3 UX is still hard
- Progressive Decentralization - Okay to start centralized
The future is decentralized, but the path there is iterative. Build, test, audit, and ship.
About the Author

Dimitri Tarasowski
AI Software Developer & Technical Co-Founder
I'm the technical co-founder you hire when you need your AI-powered MVP built right the first time. My story: I started as a data consultant, became a product leader at Libertex ($80M+ revenue), then discovered my real passion in Silicon Valley—after visiting 500 Startups, Y Combinator, and Plug and Play. That's where I saw firsthand how fast, focused execution turns bold ideas into real products. Now, I help founders do exactly that: turn breakthrough ideas into breakthrough products. Building the future, one MVP at a time.
Credentials:
- HEC Paris Master of Science in Innovation
- MIT Executive Education in Artificial Intelligence
- 3x AWS Certified Expert
- Former Head of Product at Libertex (5x growth, $80M+ revenue)
Want to build your MVP with expert guidance?
Book a Strategy SessionMore from Dimitri Tarasowski
EdTech MVP Development Guide: Build Learning Solutions That Scale
Master EdTech MVP development with proven strategies for learning management systems, assessment platforms, and educational content delivery. Learn compliance, engagement tactics, and scaling strategies.
AI Chatbot MVP Development Guide: Build ChatGPT-like Applications
Create powerful AI chatbots using LLMs like GPT-4, Claude, and open-source models. Learn prompt engineering, conversation design, deployment strategies, and how to build production-ready conversational AI.
AI/ML MVP Implementation Guide: Build Intelligent Products Fast
Master AI/ML MVP development with practical strategies for model selection, data pipelines, deployment, and iteration. Learn to build intelligent products that deliver real value.
Related Resources
AI Chatbot MVP Development Guide: Build ChatGPT-like Applications
Create powerful AI chatbots using LLMs like GPT-4, Claude, and open-source models. Learn prompt engineering, conversation design, deployment strategies, and how to build production-ready conversational AI.
Read moreAI/ML MVP Implementation Guide: Build Intelligent Products Fast
Master AI/ML MVP development with practical strategies for model selection, data pipelines, deployment, and iteration. Learn to build intelligent products that deliver real value.
Read moreMVP API Strategy & Developer Experience: Build APIs Developers Love
Design and build APIs that accelerate your MVP growth. Learn API strategy, developer experience best practices, documentation, and how to create an ecosystem around your product.
Read more