Prerequisites
- Basic understanding of JavaScript ๐
- TypeScript installation โก
- VS Code or preferred IDE ๐ป
What you'll learn
- Understand the concept fundamentals ๐ฏ
- Apply the concept in real projects ๐๏ธ
- Debug common issues ๐
- Write type-safe code โจ
๐ฏ Introduction
Welcome to this exciting tutorial on building blockchain applications with Web3 integration in TypeScript! ๐ In this guide, weโll explore how to connect your TypeScript applications to the blockchain, interact with smart contracts, and build decentralized applications (dApps).
Youโll discover how Web3 and TypeScript work together to create powerful, type-safe blockchain applications. Whether youโre building DeFi platforms ๐ฐ, NFT marketplaces ๐จ, or decentralized games ๐ฎ, understanding Web3 integration is essential for modern blockchain development.
By the end of this tutorial, youโll feel confident building your own blockchain applications with TypeScript! Letโs dive in! ๐โโ๏ธ
๐ Understanding Web3 and Blockchain
๐ค What is Web3?
Web3 is like the internetโs evolution ๐ - instead of companies controlling your data, you control it! Think of it as moving from renting an apartment (Web2) to owning your own house (Web3) ๐ .
In TypeScript terms, Web3 provides libraries and tools to interact with blockchain networks like Ethereum. This means you can:
- โจ Connect to blockchain networks
- ๐ Interact with smart contracts
- ๐ก๏ธ Manage cryptocurrency wallets
- ๐ Read blockchain data
๐ก Why Use TypeScript for Blockchain Development?
Hereโs why developers love TypeScript for Web3:
- Type Safety ๐: Catch errors before deploying expensive transactions
- Better IDE Support ๐ป: Autocomplete for contract methods
- Code Documentation ๐: Types make contract interfaces clear
- Refactoring Confidence ๐ง: Change code without breaking integrations
Real-world example: Imagine building a decentralized exchange ๐ฑ. With TypeScript, you can ensure all token amounts, addresses, and transaction parameters are correctly typed before sending them to the blockchain!
๐ง Basic Syntax and Usage
๐ Setting Up Web3 with TypeScript
Letโs start with a friendly example:
// ๐ Hello, Web3!
import { ethers } from 'ethers';
// ๐ Connect to Ethereum network
const provider = new ethers.JsonRpcProvider('https://eth-mainnet.g.alchemy.com/v2/your-api-key');
// ๐จ Define types for our blockchain data
interface TokenBalance {
address: string; // ๐ Wallet address
balance: bigint; // ๐ฐ Token balance
symbol: string; // ๐ช Token symbol
decimals: number; // ๐ข Token decimals
}
// ๐ Check ETH balance
async function getEthBalance(address: string): Promise<string> {
const balance = await provider.getBalance(address);
return ethers.formatEther(balance) + ' ETH ๐';
}
๐ก Explanation: Notice how we use TypeScript interfaces to define our blockchain data structures. The bigint
type is perfect for handling large cryptocurrency values!
๐ฏ Common Web3 Patterns
Here are patterns youโll use daily:
// ๐๏ธ Pattern 1: Contract Interface
interface ERC20Contract {
name(): Promise<string>;
symbol(): Promise<string>;
balanceOf(address: string): Promise<bigint>;
transfer(to: string, amount: bigint): Promise<ethers.TransactionResponse>;
}
// ๐จ Pattern 2: Transaction Types
type TransactionStatus = "pending" | "confirmed" | "failed";
type NetworkType = "mainnet" | "testnet" | "localhost";
// ๐ Pattern 3: Event Listening with Types
interface TransferEvent {
from: string;
to: string;
value: bigint;
blockNumber: number;
}
๐ก Practical Examples
๐ Example 1: NFT Marketplace
Letโs build something real:
// ๐จ Define our NFT types
interface NFTMetadata {
id: string;
name: string;
description: string;
image: string;
price: bigint;
owner: string;
rarity: 'โญ' | '๐' | '๐' | 'โจ';
}
// ๐ช NFT Marketplace class
class NFTMarketplace {
private provider: ethers.Provider;
private contract: ethers.Contract;
constructor(providerUrl: string, contractAddress: string) {
this.provider = new ethers.JsonRpcProvider(providerUrl);
this.contract = new ethers.Contract(contractAddress, NFT_ABI, this.provider);
}
// ๐ผ๏ธ List NFT for sale
async listNFT(nft: NFTMetadata, signer: ethers.Signer): Promise<void> {
const contractWithSigner = this.contract.connect(signer);
const tx = await contractWithSigner.listItem(nft.id, nft.price);
console.log(`๐จ Listing ${nft.rarity} ${nft.name} for sale!`);
await tx.wait();
console.log(`โ
NFT listed successfully!`);
}
// ๐ฐ Buy NFT
async buyNFT(nftId: string, buyer: ethers.Signer): Promise<void> {
const nft = await this.getNFTDetails(nftId);
const contractWithSigner = this.contract.connect(buyer);
console.log(`๐ Purchasing ${nft.name}...`);
const tx = await contractWithSigner.purchase(nftId, { value: nft.price });
await tx.wait();
console.log(`๐ You now own ${nft.name}!`);
}
// ๐ Get NFT details
async getNFTDetails(nftId: string): Promise<NFTMetadata> {
const details = await this.contract.getNFT(nftId);
return {
id: nftId,
name: details.name,
description: details.description,
image: details.image,
price: details.price,
owner: details.owner,
rarity: this.calculateRarity(details.attributes)
};
}
// โจ Calculate rarity
private calculateRarity(attributes: any[]): NFTMetadata['rarity'] {
const score = attributes.reduce((sum, attr) => sum + attr.value, 0);
if (score > 90) return 'โจ';
if (score > 70) return '๐';
if (score > 50) return '๐';
return 'โญ';
}
}
// ๐ฎ Let's use it!
const marketplace = new NFTMarketplace(
'https://eth-mainnet.g.alchemy.com/v2/your-key',
'0x123...'
);
๐ฏ Try it yourself: Add a searchNFTs
method that filters by rarity and price range!
๐ฎ Example 2: DeFi Yield Tracker
Letโs make it fun:
// ๐ฐ DeFi yield tracking system
interface YieldPool {
name: string;
address: string;
apy: number;
tvl: bigint;
rewardToken: string;
emoji: string;
}
interface UserPosition {
pool: YieldPool;
stakedAmount: bigint;
pendingRewards: bigint;
startTime: Date;
}
class DeFiYieldTracker {
private positions: Map<string, UserPosition[]> = new Map();
private provider: ethers.Provider;
constructor(provider: ethers.Provider) {
this.provider = provider;
}
// ๐พ Track new farming position
async trackPosition(
userAddress: string,
pool: YieldPool,
amount: bigint
): Promise<void> {
const position: UserPosition = {
pool,
stakedAmount: amount,
pendingRewards: 0n,
startTime: new Date()
};
const userPositions = this.positions.get(userAddress) || [];
userPositions.push(position);
this.positions.set(userAddress, userPositions);
console.log(`๐พ Started farming in ${pool.emoji} ${pool.name}!`);
console.log(`๐ฐ Staked: ${ethers.formatEther(amount)} tokens`);
console.log(`๐ Current APY: ${pool.apy}%`);
}
// ๐ Calculate total yields
async calculateTotalYield(userAddress: string): Promise<string> {
const positions = this.positions.get(userAddress) || [];
let totalYield = 0n;
for (const position of positions) {
const hoursStaked = (Date.now() - position.startTime.getTime()) / (1000 * 60 * 60);
const hourlyRate = position.pool.apy / 365 / 24 / 100;
const earned = position.stakedAmount * BigInt(Math.floor(hourlyRate * hoursStaked * 1e18)) / 1000000000000000000n;
totalYield += earned;
console.log(`${position.pool.emoji} ${position.pool.name}: +${ethers.formatEther(earned)} tokens`);
}
return ethers.formatEther(totalYield);
}
// ๐ Get best performing pool
getBestPool(pools: YieldPool[]): YieldPool {
return pools.reduce((best, current) =>
current.apy > best.apy ? current : best
);
}
// ๐ Portfolio summary
async getPortfolioSummary(userAddress: string): Promise<void> {
const positions = this.positions.get(userAddress) || [];
console.log(`\n๐ DeFi Portfolio Summary for ${userAddress.slice(0, 6)}...${userAddress.slice(-4)}`);
console.log(`${'='.repeat(50)}`);
let totalStaked = 0n;
let totalPending = 0n;
for (const position of positions) {
totalStaked += position.stakedAmount;
const yield_ = await this.calculateTotalYield(userAddress);
console.log(`\n${position.pool.emoji} ${position.pool.name}`);
console.log(` ๐ฐ Staked: ${ethers.formatEther(position.stakedAmount)}`);
console.log(` ๐ Earned: ${yield_}`);
console.log(` ๐ APY: ${position.pool.apy}%`);
}
console.log(`\n๐ฏ Total Portfolio Value: ${ethers.formatEther(totalStaked)} ๐`);
}
}
// ๐ฎ Example usage
const yieldTracker = new DeFiYieldTracker(provider);
const sushiPool: YieldPool = {
name: "SUSHI-ETH LP",
address: "0xabc...",
apy: 45.5,
tvl: ethers.parseEther("1000000"),
rewardToken: "SUSHI",
emoji: "๐ฃ"
};
await yieldTracker.trackPosition(
"0x123...",
sushiPool,
ethers.parseEther("100")
);
๐ Advanced Concepts
๐งโโ๏ธ Advanced Topic 1: Type-Safe Smart Contract Interactions
When youโre ready to level up, try this advanced pattern:
// ๐ฏ Advanced generic contract factory
type ContractMethod<T extends any[], R> = (...args: T) => Promise<R>;
interface TypedContract<T> {
[K in keyof T]: T[K] extends (...args: infer A) => infer R
? ContractMethod<A, R>
: never;
}
// ๐ช Create type-safe contract wrapper
function createTypedContract<T>(
address: string,
abi: any[],
provider: ethers.Provider
): TypedContract<T> {
const contract = new ethers.Contract(address, abi, provider);
return new Proxy(contract, {
get(target, prop) {
if (typeof prop === 'string' && typeof target[prop] === 'function') {
return async (...args: any[]) => {
console.log(`โจ Calling ${prop} with args:`, args);
return target[prop](...args);
};
}
return target[prop];
}
}) as TypedContract<T>;
}
// ๐จ Usage with full type safety
interface MyDeFiProtocol {
stake(amount: bigint): ethers.TransactionResponse;
unstake(amount: bigint): ethers.TransactionResponse;
getRewards(user: string): bigint;
compound(): ethers.TransactionResponse;
}
const defiContract = createTypedContract<MyDeFiProtocol>(
'0x123...',
DEFI_ABI,
provider
);
// โจ Full IntelliSense and type checking!
const rewards = await defiContract.getRewards('0xuser...');
๐๏ธ Advanced Topic 2: Multi-Chain Support
For the brave developers:
// ๐ Multi-chain configuration system
type ChainId = 1 | 56 | 137 | 43114; // ETH, BSC, Polygon, Avalanche
type ChainEmoji = "๐ท" | "๐ก" | "๐ฃ" | "๐บ";
interface ChainConfig {
id: ChainId;
name: string;
rpcUrl: string;
emoji: ChainEmoji;
nativeCurrency: {
name: string;
symbol: string;
decimals: number;
};
}
class MultiChainManager {
private chains: Map<ChainId, ChainConfig> = new Map([
[1, {
id: 1,
name: "Ethereum",
rpcUrl: "https://eth-mainnet.g.alchemy.com/v2/",
emoji: "๐ท",
nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 }
}],
[56, {
id: 56,
name: "BSC",
rpcUrl: "https://bsc-dataseed.binance.org/",
emoji: "๐ก",
nativeCurrency: { name: "BNB", symbol: "BNB", decimals: 18 }
}]
]);
// ๐ Get provider for specific chain
getProvider(chainId: ChainId): ethers.Provider {
const config = this.chains.get(chainId);
if (!config) throw new Error(`Chain ${chainId} not supported! ๐ข`);
console.log(`${config.emoji} Connecting to ${config.name}...`);
return new ethers.JsonRpcProvider(config.rpcUrl);
}
// ๐ Bridge tokens between chains
async bridgeTokens(
from: ChainId,
to: ChainId,
amount: bigint,
token: string
): Promise<void> {
const fromChain = this.chains.get(from)!;
const toChain = this.chains.get(to)!;
console.log(`๐ Bridging tokens:`);
console.log(` ${fromChain.emoji} From: ${fromChain.name}`);
console.log(` ${toChain.emoji} To: ${toChain.name}`);
console.log(` ๐ฐ Amount: ${ethers.formatEther(amount)}`);
// Bridge implementation here...
}
}
โ ๏ธ Common Pitfalls and Solutions
๐ฑ Pitfall 1: Not Handling Gas Properly
// โ Wrong way - might fail with out of gas!
const tx = await contract.complexOperation(data);
// โ
Correct way - estimate and add buffer!
const estimatedGas = await contract.complexOperation.estimateGas(data);
const gasLimit = estimatedGas * 120n / 100n; // Add 20% buffer
const tx = await contract.complexOperation(data, {
gasLimit: gasLimit
});
console.log(`โฝ Using ${gasLimit} gas for safety!`);
๐คฏ Pitfall 2: Forgetting to Handle Reverts
// โ Dangerous - no error handling!
const result = await contract.riskyOperation();
// โ
Safe - catch and handle reverts!
try {
const result = await contract.riskyOperation();
console.log(`โ
Operation successful!`);
} catch (error: any) {
if (error.reason) {
console.log(`โ ๏ธ Contract reverted: ${error.reason}`);
} else {
console.log(`๐ฅ Unexpected error: ${error.message}`);
}
}
๐ ๏ธ Best Practices
- ๐ฏ Always Use Types: Define interfaces for all contract interactions
- ๐ Test on Testnet First: Never deploy directly to mainnet
- ๐ก๏ธ Validate Addresses: Use ethers.isAddress() before transactions
- ๐จ Handle BigNumbers Carefully: Use bigint for all token amounts
- โจ Monitor Gas Prices: Implement gas price strategies
๐งช Hands-On Exercise
๐ฏ Challenge: Build a Token Swap DApp
Create a type-safe token swapping application:
๐ Requirements:
- โ Connect to userโs wallet (MetaMask)
- ๐ท๏ธ Display token balances with logos
- ๐ค Implement token approval flow
- ๐ Add price impact warnings
- ๐จ Show transaction history with emojis!
๐ Bonus Points:
- Add slippage tolerance settings
- Implement multi-hop swaps
- Create a favorites list for token pairs
๐ก Solution
๐ Click to see solution
// ๐ฏ Our type-safe token swap system!
interface Token {
address: string;
symbol: string;
name: string;
decimals: number;
logo: string;
emoji: string;
}
interface SwapQuote {
inputToken: Token;
outputToken: Token;
inputAmount: bigint;
outputAmount: bigint;
priceImpact: number;
route: string[];
}
class TokenSwapDApp {
private provider: ethers.Provider;
private signer: ethers.Signer | null = null;
private tokens: Map<string, Token> = new Map();
constructor() {
// ๐ Connect to user's wallet
if (typeof window !== 'undefined' && window.ethereum) {
this.provider = new ethers.BrowserProvider(window.ethereum);
} else {
throw new Error('๐ซ Please install MetaMask!');
}
}
// ๐ Connect wallet
async connectWallet(): Promise<string> {
const signer = await this.provider.getSigner();
this.signer = signer;
const address = await signer.getAddress();
console.log(`๐ Connected to ${address.slice(0, 6)}...${address.slice(-4)}`);
return address;
}
// ๐ฐ Get token balance
async getTokenBalance(token: Token, address: string): Promise<string> {
const contract = new ethers.Contract(
token.address,
['function balanceOf(address) view returns (uint256)'],
this.provider
);
const balance = await contract.balanceOf(address);
const formatted = ethers.formatUnits(balance, token.decimals);
return `${token.emoji} ${formatted} ${token.symbol}`;
}
// ๐ Get swap quote
async getSwapQuote(
inputToken: Token,
outputToken: Token,
inputAmount: bigint
): Promise<SwapQuote> {
// Simulate getting quote from DEX
const outputAmount = inputAmount * 95n / 100n; // 5% slippage for demo
const priceImpact = 2.5; // 2.5% impact
return {
inputToken,
outputToken,
inputAmount,
outputAmount,
priceImpact,
route: [inputToken.symbol, 'WETH', outputToken.symbol]
};
}
// โ
Approve token
async approveToken(token: Token, spender: string, amount: bigint): Promise<void> {
if (!this.signer) throw new Error('๐ซ Wallet not connected!');
const contract = new ethers.Contract(
token.address,
['function approve(address, uint256) returns (bool)'],
this.signer
);
console.log(`๐ Approving ${token.emoji} ${token.symbol}...`);
const tx = await contract.approve(spender, amount);
await tx.wait();
console.log(`โ
Approved!`);
}
// ๐ Execute swap
async executeSwap(quote: SwapQuote): Promise<void> {
if (!this.signer) throw new Error('๐ซ Wallet not connected!');
// Check price impact
if (quote.priceImpact > 5) {
console.log(`โ ๏ธ High price impact: ${quote.priceImpact}%`);
const confirm = await this.confirmHighImpact();
if (!confirm) return;
}
console.log(`๐ Swapping ${quote.inputToken.emoji} โ ${quote.outputToken.emoji}`);
console.log(`๐ Route: ${quote.route.join(' โ ')}`);
// Execute swap transaction...
console.log(`โ
Swap successful!`);
console.log(`๐ Received ${ethers.formatUnits(quote.outputAmount, quote.outputToken.decimals)} ${quote.outputToken.symbol}`);
}
// โ ๏ธ Confirm high impact
private async confirmHighImpact(): Promise<boolean> {
console.log(`๐จ Price impact is high! Continue anyway?`);
return true; // In real app, show user confirmation dialog
}
// ๐ Get transaction history
async getSwapHistory(address: string): Promise<void> {
console.log(`๐ Recent swaps for ${address.slice(0, 6)}...`);
console.log(` ๐ ETH โ USDC: 0.5 ETH for 950 USDC`);
console.log(` ๐ USDC โ DAI: 500 USDC for 499.5 DAI`);
console.log(` ๐ DAI โ ETH: 1000 DAI for 0.52 ETH`);
}
}
// ๐ฎ Test it out!
const swapApp = new TokenSwapDApp();
const eth: Token = {
address: '0x0',
symbol: 'ETH',
name: 'Ethereum',
decimals: 18,
logo: 'eth.png',
emoji: '๐ท'
};
const usdc: Token = {
address: '0xa0b8....',
symbol: 'USDC',
name: 'USD Coin',
decimals: 6,
logo: 'usdc.png',
emoji: '๐ต'
};
// Connect and swap!
await swapApp.connectWallet();
const quote = await swapApp.getSwapQuote(eth, usdc, ethers.parseEther('1'));
await swapApp.executeSwap(quote);
๐ Key Takeaways
Youโve learned so much! Hereโs what you can now do:
- โ Connect to blockchain networks with confidence ๐ช
- โ Interact with smart contracts type-safely ๐ก๏ธ
- โ Build DeFi applications that handle real value ๐ฏ
- โ Handle Web3 errors gracefully ๐
- โ Create multi-chain dApps with TypeScript! ๐
Remember: Building on the blockchain requires extra care - real money is at stake! Always test thoroughly and think about security. ๐
๐ค Next Steps
Congratulations! ๐ Youโve mastered Web3 integration with TypeScript!
Hereโs what to do next:
- ๐ป Build a simple dApp using the patterns above
- ๐๏ธ Deploy a smart contract and create TypeScript bindings
- ๐ Explore advanced topics like MEV protection
- ๐ Join a Web3 hackathon and build something amazing!
Remember: The blockchain space is evolving rapidly. Keep learning, keep building, and most importantly, have fun! ๐
Happy coding! ๐๐โจ