Web3 dApp Development 2025: Ethereum, React ve Modern Frontend Stack ile Blockchain Uygulamaları
Web3 ve blockchain development 2025'te mainstream oluyor. React, ethers.js, wagmi ve RainbowKit ile decentralized applications (dApps) nasıl geliştirilir? Smart contract entegrasyonu, wallet connection ve modern dApp architecture rehberi.
Web3 dApp Development 2025: Ethereum, React ve Modern Frontend Stack ile Blockchain Uygulamaları
Web3 development 2025'te maturity'e ulaştı. Market $4.43B'dan (2024) $6.15B'a (2025) büyüdü, %38.9 CAGR gösteriyor. Ethereum merge sonrası energy-efficient PoS, Layer 2 solutions (Arbitrum, Optimism, Base) ile transaction fees düştü, mainstream adoption başladı.
Frontend developer olarak Web3'e giriş şaşırtıcı derecede kolay: React biliyorsanız, ethers.js/viem öğrenip dApp geliştirebilirsiniz. Wallet connection için RainbowKit, blockchain interaction için wagmi, smart contracts için Hardhat/Foundry. Ecosystem mature, documentation comprehensive.
Bu kapsamlı rehberde, modern dApp architecture'ı, React ile Ethereum entegrasyonunu, wallet management'ı, smart contract interaction'ı, ve production best practices'i detaylı inceleyeceğiz.
İçindekiler
- Web3 ve dApp Nedir?
- Modern Web3 Stack
- Wallet Connection ve Authentication
- Smart Contract Interaction
- Ethereum ve Layer 2 Networks
- State Management ve Data Fetching
- Transaction Handling
- Production Best Practices
- Sık Sorulan Sorular
Web3 ve dApp Nedir?
Web3: Internet'in Decentralized Hali
Web1 (1990-2005): Read-only, static pages Web2 (2005-2020): Read-write, centralized platforms (Facebook, Google) Web3 (2020+): Read-write-own, decentralized, blockchain-based
dApp (Decentralized Application)
dApp, backend logic'i blockchain üzerinde smart contracts olarak çalışan uygulamadır.
Traditional App:
Frontend → API Server → Database
(Centralized)
dApp:
Frontend → Smart Contract → Blockchain
(Decentralized)
Key Differences
| Feature | Traditional App | dApp |
|---|---|---|
| Backend | Server | Smart Contract |
| Database | PostgreSQL/MongoDB | Blockchain |
| Auth | Email/Password | Wallet Signature |
| Payments | Stripe/PayPal | Cryptocurrency |
| Ownership | Platform owns data | User owns data |
| Censorship | Possible | Resistant |
Modern Web3 Stack
Frontend Framework: React/Next.js
# Create Next.js app with TypeScript
npx create-next-app@latest my-dapp --typescript
cd my-dappBlockchain Interaction Libraries
1. ethers.js (Traditional)
npm install ethersimport { ethers } from 'ethers'
// Connect to Ethereum
const provider = new ethers.JsonRpcProvider('https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY')
// Read contract
const contract = new ethers.Contract(contractAddress, abi, provider)
const balance = await contract.balanceOf(userAddress)2. viem (Modern, Type-Safe)
npm install viemimport { createPublicClient, http } from 'viem'
import { mainnet } from 'viem/chains'
const client = createPublicClient({
chain: mainnet,
transport: http()
})
const balance = await client.readContract({
address: contractAddress,
abi: contractAbi,
functionName: 'balanceOf',
args: [userAddress]
})React Hooks: wagmi
npm install wagmi viem @tanstack/react-query// wagmi.ts - Configuration
import { http, createConfig } from 'wagmi'
import { mainnet, sepolia } from 'wagmi/chains'
export const config = createConfig({
chains: [mainnet, sepolia],
transports: {
[mainnet.id]: http(),
[sepolia.id]: http(),
},
})
// App.tsx
import { WagmiProvider } from 'wagmi'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
const queryClient = new QueryClient()
function App() {
return (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<MyDApp />
</QueryClientProvider>
</WagmiProvider>
)
}Wallet Connection: RainbowKit
npm install @rainbow-me/rainbowkitimport '@rainbow-me/rainbowkit/styles.css'
import { RainbowKitProvider, getDefaultConfig } from '@rainbow-me/rainbowkit'
const config = getDefaultConfig({
appName: 'My dApp',
projectId: 'YOUR_WALLETCONNECT_PROJECT_ID',
chains: [mainnet, sepolia],
})
function App() {
return (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<RainbowKitProvider>
<MyDApp />
</RainbowKitProvider>
</QueryClientProvider>
</WagmiProvider>
)
}Wallet Connection ve Authentication
Connect Wallet Button
import { ConnectButton } from '@rainbow-me/rainbowkit'
export function Header() {
return (
<header>
<h1>My dApp</h1>
<ConnectButton />
</header>
)
}ConnectButton otomatik handle eder:
- Wallet selection (MetaMask, WalletConnect, Coinbase Wallet, etc.)
- Connection state
- Account display
- Network switching
- Disconnect
Custom Wallet Connection
'use client'
import { useAccount, useConnect, useDisconnect } from 'wagmi'
export function WalletConnection() {
const { address, isConnected } = useAccount()
const { connect, connectors } = useConnect()
const { disconnect } = useDisconnect()
if (isConnected) {
return (
<div>
<p>Connected: {address}</p>
<button onClick={() => disconnect()}>
Disconnect
</button>
</div>
)
}
return (
<div>
{connectors.map((connector) => (
<button
key={connector.id}
onClick={() => connect({ connector })}
>
Connect with {connector.name}
</button>
))}
</div>
)
}Sign Message (Authentication)
import { useSignMessage } from 'wagmi'
export function SignInButton() {
const { signMessageAsync } = useSignMessage()
async function handleSignIn() {
try {
const message = `Sign this message to authenticate.\nNonce: ${Date.now()}`
const signature = await signMessageAsync({ message })
// Send signature to backend for verification
const response = await fetch('/api/auth/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message, signature })
})
const { token } = await response.json()
localStorage.setItem('authToken', token)
} catch (error) {
console.error('Sign in failed:', error)
}
}
return (
<button onClick={handleSignIn}>
Sign In with Wallet
</button>
)
}Smart Contract Interaction
Reading Contract Data
import { useReadContract } from 'wagmi'
const ERC20_ABI = [
{
name: 'balanceOf',
type: 'function',
stateMutability: 'view',
inputs: [{ name: 'account', type: 'address' }],
outputs: [{ name: 'balance', type: 'uint256' }]
},
{
name: 'totalSupply',
type: 'function',
stateMutability: 'view',
inputs: [],
outputs: [{ type: 'uint256' }]
}
] as const
export function TokenBalance({ address }: { address: `0x${string}` }) {
const { data: balance, isLoading } = useReadContract({
address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC
abi: ERC20_ABI,
functionName: 'balanceOf',
args: [address]
})
if (isLoading) return <div>Loading...</div>
return (
<div>
Balance: {balance?.toString()} USDC
</div>
)
}Writing to Contract (Transactions)
import { useWriteContract, useWaitForTransactionReceipt } from 'wagmi'
import { parseEther } from 'viem'
const NFT_ABI = [
{
name: 'mint',
type: 'function',
stateMutability: 'payable',
inputs: [{ name: 'quantity', type: 'uint256' }],
outputs: []
}
] as const
export function MintNFT() {
const { data: hash, writeContract, isPending } = useWriteContract()
const { isLoading: isConfirming, isSuccess } = useWaitForTransactionReceipt({
hash
})
async function handleMint() {
writeContract({
address: '0xYourNFTContract',
abi: NFT_ABI,
functionName: 'mint',
args: [1n], // Mint 1 NFT
value: parseEther('0.08') // 0.08 ETH
})
}
return (
<div>
<button onClick={handleMint} disabled={isPending || isConfirming}>
{isPending ? 'Confirming...' :
isConfirming ? 'Minting...' :
'Mint NFT'}
</button>
{hash && <div>Transaction: {hash}</div>}
{isSuccess && <div>Successfully minted!</div>}
</div>
)
}Contract Events (Real-time Updates)
import { useWatchContractEvent } from 'wagmi'
export function TransferWatcher() {
const [transfers, setTransfers] = useState<any[]>([])
useWatchContractEvent({
address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
abi: ERC20_ABI,
eventName: 'Transfer',
onLogs(logs) {
setTransfers(prev => [...prev, ...logs])
}
})
return (
<div>
<h2>Recent Transfers</h2>
{transfers.map((transfer, i) => (
<div key={i}>
{transfer.args.from} → {transfer.args.to}
: {transfer.args.value.toString()}
</div>
))}
</div>
)
}Ethereum ve Layer 2 Networks
Network Configuration
import { mainnet, sepolia, arbitrum, optimism, base, polygon } from 'wagmi/chains'
export const config = createConfig({
chains: [
mainnet, // Ethereum Mainnet
sepolia, // Ethereum Testnet
arbitrum, // Arbitrum (L2)
optimism, // Optimism (L2)
base, // Base (Coinbase L2)
polygon // Polygon
],
transports: {
[mainnet.id]: http(),
[sepolia.id]: http(),
[arbitrum.id]: http(),
[optimism.id]: http(),
[base.id]: http(),
[polygon.id]: http()
}
})Network Switching
import { useSwitchChain } from 'wagmi'
export function NetworkSwitcher() {
const { chains, switchChain } = useSwitchChain()
return (
<select onChange={(e) => switchChain({ chainId: Number(e.target.value) })}>
{chains.map((chain) => (
<option key={chain.id} value={chain.id}>
{chain.name}
</option>
))}
</select>
)
}Gas Price Estimation
import { useEstimateGas, useGasPrice } from 'wagmi'
import { formatGwei } from 'viem'
export function GasEstimator() {
const { data: gasPrice } = useGasPrice()
const { data: gasEstimate } = useEstimateGas({
to: '0xRecipientAddress',
value: parseEther('0.01')
})
const totalCost = gasPrice && gasEstimate
? (gasPrice * gasEstimate)
: 0n
return (
<div>
<p>Gas Price: {gasPrice ? formatGwei(gasPrice) : '...'} gwei</p>
<p>Estimated Gas: {gasEstimate?.toString()}</p>
<p>Total Cost: {formatEther(totalCost)} ETH</p>
</div>
)
}State Management ve Data Fetching
React Query Integration
import { useQuery } from '@tanstack/react-query'
export function useTokenPrice(tokenAddress: string) {
return useQuery({
queryKey: ['tokenPrice', tokenAddress],
queryFn: async () => {
const response = await fetch(
`https://api.coingecko.com/api/v3/simple/token_price/ethereum?contract_addresses=${tokenAddress}&vs_currencies=usd`
)
return response.json()
},
refetchInterval: 30000 // Refetch every 30s
})
}
// Usage
function TokenPrice() {
const { data, isLoading } = useTokenPrice('0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48')
if (isLoading) return <div>Loading price...</div>
return <div>USDC: ${data?.['0xA0b86991...'].usd}</div>
}The Graph Integration
import { gql, request } from 'graphql-request'
const UNISWAP_V3_SUBGRAPH = 'https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3'
const POOL_QUERY = gql`
query GetPool($poolAddress: String!) {
pool(id: $poolAddress) {
token0 {
symbol
name
}
token1 {
symbol
name
}
volumeUSD
totalValueLockedUSD
}
}
`
export function usePoolData(poolAddress: string) {
return useQuery({
queryKey: ['pool', poolAddress],
queryFn: () => request(UNISWAP_V3_SUBGRAPH, POOL_QUERY, { poolAddress })
})
}Transaction Handling
Transaction Flow with UX
import { useWriteContract, useWaitForTransactionReceipt } from 'wagmi'
import { toast } from 'sonner'
export function SwapTokens() {
const { data: hash, writeContract, isPending, error } = useWriteContract()
const { isLoading: isConfirming, isSuccess } = useWaitForTransactionReceipt({
hash
})
async function handleSwap() {
try {
writeContract({
address: '0xSwapRouterAddress',
abi: SWAP_ABI,
functionName: 'swap',
args: [tokenIn, tokenOut, amount]
})
toast.loading('Confirming transaction...')
} catch (err) {
toast.error('Transaction failed')
}
}
useEffect(() => {
if (isConfirming) {
toast.loading('Processing on blockchain...', { id: hash })
}
if (isSuccess) {
toast.success('Swap successful!', { id: hash })
}
}, [isConfirming, isSuccess, hash])
return (
<button onClick={handleSwap} disabled={isPending || isConfirming}>
{isPending ? 'Confirm in wallet...' :
isConfirming ? 'Swapping...' :
'Swap Tokens'}
</button>
)
}Error Handling
import { BaseError, ContractFunctionRevertedError } from 'viem'
try {
await writeContract({...})
} catch (err) {
if (err instanceof BaseError) {
const revertError = err.walk(err => err instanceof ContractFunctionRevertedError)
if (revertError instanceof ContractFunctionRevertedError) {
const errorName = revertError.data?.errorName ?? ''
if (errorName === 'InsufficientBalance') {
toast.error('Insufficient balance')
} else if (errorName === 'InsufficientAllowance') {
toast.error('Please approve tokens first')
}
} else if (err.message.includes('User rejected')) {
toast.error('Transaction cancelled')
}
}
}Production Best Practices
Environment Variables
# .env.local
NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID=your_project_id
NEXT_PUBLIC_ALCHEMY_API_KEY=your_alchemy_key
NEXT_PUBLIC_ETHERSCAN_API_KEY=your_etherscan_keyconst config = createConfig({
chains: [mainnet],
transports: {
[mainnet.id]: http(`https://eth-mainnet.g.alchemy.com/v2/${process.env.NEXT_PUBLIC_ALCHEMY_API_KEY}`)
}
})RPC Provider Best Practices
// Fallback RPC providers
import { fallback } from 'wagmi'
const config = createConfig({
chains: [mainnet],
transports: {
[mainnet.id]: fallback([
http(`https://eth-mainnet.g.alchemy.com/v2/${ALCHEMY_KEY}`),
http(`https://mainnet.infura.io/v3/${INFURA_KEY}`),
http() // Public RPC as fallback
])
}
})Contract ABI Management
// abis/ERC20.ts
export const ERC20_ABI = [
{
name: 'balanceOf',
type: 'function',
stateMutability: 'view',
inputs: [{ name: 'account', type: 'address' }],
outputs: [{ type: 'uint256' }]
},
// ... more methods
] as const
// Type-safe contract addresses
export const CONTRACTS = {
USDC: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
DAI: '0x6B175474E89094C44Da98b954EedeAC495271d0F'
} as constTesting dApps
// __tests__/wallet.test.tsx
import { render, screen } from '@testing-library/react'
import { WagmiProvider } from 'wagmi'
import { renderHook } from '@testing-library/react-hooks'
describe('Wallet Connection', () => {
it('should connect to MetaMask', async () => {
const { result } = renderHook(() => useConnect())
await result.current.connect({ connector: result.current.connectors[0] })
expect(result.current.isConnected).toBe(true)
})
})Security Checklist
✅ Input Validation: Validate all user inputs before sending to contract ✅ Amount Limits: Set reasonable max transaction amounts ✅ Allowance Checks: Check token allowances before swap ✅ Slippage Protection: Implement slippage tolerance ✅ Front-Running Protection: Use deadline parameters ✅ Contract Verification: Only interact with verified contracts ✅ Phishing Protection: Display contract addresses to users
// Example: Slippage protection
const amountOutMin = calculateMinimumOutput(amountIn, slippageTolerance)
writeContract({
functionName: 'swap',
args: [amountIn, amountOutMin] // Minimum output enforced
})Sık Sorulan Sorular
Web3 development için Solidity bilmem gerekir mi?
Frontend developer olarak hayır. Smart contracts zaten deployed, siz sadece interact ediyorsunuz. Ancak Solidity basics bilmek yararlı.
Gas fees çok yüksek, ne yapmalıyım?
Layer 2 solutions kullanın (Arbitrum, Optimism, Base). Mainnet'te 50-100x daha ucuz transactions.
Wallet connection güvenli mi?
Evet, kullanıcı private key'i paylaşmaz, sadece public address ve signature. Ancak phishing'e dikkat!
dApp'ımı nasıl deploy ederim?
Frontend normal Next.js app gibi deploy edilir (Vercel, Netlify). Smart contracts blockchain'de zaten deployed.
Test için gerçek ETH gerekir mi?
Hayır, testnets (Sepolia, Goerli) kullanın. Faucet'lerden ücretsiz test ETH alın.
Mobile support var mı?
Evet, WalletConnect ile mobile wallets (MetaMask Mobile, Rainbow, Coinbase Wallet) support edilir.
Sonuç
Web3 dApp development 2025'te mature ecosystem. React biliyorsanız, wagmi + RainbowKit ile production-ready dApp 1-2 haftada geliştirebilirsiniz.
Key Takeaways:
- wagmi + viem modern, type-safe stack
- RainbowKit wallet connection'ı trivial yapıyor
- Layer 2 solutions gas fees'i affordable yapıyor
- React Query ile data fetching familiar
Aksiyon Öğeleri:
- WalletConnect project ID alın
- wagmi + RainbowKit ile basit dApp yapın
- Sepolia testnet'te test edin
- The Graph ile data querying öğrenin
- Layer 2'de deploy edin (gas savings)
Web3 market büyüyor, demand yüksek. Early adopter olun, competitive advantage kazanın!
Projenizi Hayata Geçirelim
Web sitesi, mobil uygulama veya yapay zeka çözümü mü arıyorsunuz? Fikirlerinizi birlikte değerlendirelim.
Ücretsiz Danışmanlık Alın