Building dApp UX

Back

Loading concept...

🏗️ Building dApp UX: Making Blockchain Feel Like Magic

The Story of Your Digital Restaurant

Imagine you’re running a magical restaurant where orders go to a kitchen you can’t see, and everything takes a little time to cook. Your job? Make customers feel happy and confident while they wait!

That’s exactly what building dApp UX is about. You’re creating the front door, menu, and service for blockchain applications. Let’s learn how to make it delightful!


🎭 The Cast of Characters

Before we dive in, meet our heroes:

Character Real World In Our dApp
🏪 Restaurant Smart Contract The backend logic
📋 Menu Contract Instance How we talk to it
🔔 Kitchen Bell Event Listener Notifications when things happen
🧾 Receipt Transaction Receipt Proof something was done
😅 Oops Moments Error Handling When things go wrong
⏳ Waiting Screen Transaction Status UI Keeping users informed
💰 Price Estimate Gas Estimation How much it costs
🗺️ Different Kitchens Network Switching Changing blockchains

📋 Contract Instances: Your Menu to the Blockchain

What Is It?

A contract instance is like having a direct phone line to a specific smart contract. Instead of shouting into the void, you have a clear way to communicate.

Simple Example

Think of it this way:

  • The smart contract ADDRESS is like a phone number
  • The ABI (Application Binary Interface) is like knowing what language they speak
  • The contract instance is the actual phone call you make
// 1. Import your tools
import { ethers } from 'ethers';

// 2. Get connected to blockchain
const provider = new ethers.
  BrowserProvider(window.ethereum);

// 3. Get the person who can sign
const signer = await provider.
  getSigner();

// 4. Create your phone line!
const contract = new ethers.Contract(
  contractAddress,  // Phone number
  contractABI,      // Language guide
  signer            // You!
);

Why It Matters

Without a contract instance, you’re like someone trying to order food by yelling at the building. With one, you have a clear, reliable connection.

graph TD A["Your dApp"] --> B["Contract Instance"] B --> C["Smart Contract"] C --> D["Blockchain"] style B fill:#4ECDC4,color:#fff

🔔 Event Listeners: The Kitchen Bell

What Is It?

Event listeners are like notification bells that ring when something happens on the blockchain. Instead of constantly asking “Is my order ready?”, the kitchen tells YOU when it’s done!

Simple Example

Imagine a real bell:

  • 🛎️ DING - “Your NFT was minted!”
  • 🛎️ DING - “Someone sent you tokens!”
  • 🛎️ DING - “Your swap is complete!”
// Listen for Transfer events
contract.on("Transfer",
  (from, to, amount) => {
    console.log("🎉 Transfer happened!");
    console.log(`From: ${from}`);
    console.log(`To: ${to}`);
    console.log(`Amount: ${amount}`);

    // Update your UI!
    showNotification("Transfer complete!");
});

Real Life Use

// Stop listening when done
const cleanup = () => {
  contract.removeAllListeners("Transfer");
};

// Always clean up when user
// leaves the page!
window.addEventListener(
  'beforeunload',
  cleanup
);

Why It Matters

Without event listeners, your app would need to constantly check the blockchain (expensive and slow!). With them, you get instant updates like magic!


🧾 Transaction Receipt Handling: Your Proof of Purchase

What Is It?

When you buy something, you get a receipt. Same with blockchain! A transaction receipt proves your action happened and gives you all the details.

The Journey of a Transaction

graph TD A["📤 Send Transaction"] --> B["⏳ Pending..."] B --> C["⛏️ Mining..."] C --> D["✅ Confirmed!"] D --> E["🧾 Receipt Ready"] style E fill:#4CAF50,color:#fff

Simple Example

// Send a transaction
const tx = await contract.transfer(
  toAddress,
  amount
);

// Wait for receipt (like waiting
// for your food to cook)
const receipt = await tx.wait();

// Now you have proof!
console.log("✅ Success!");
console.log(`Block: ${receipt.blockNumber}`);
console.log(`Gas used: ${receipt.gasUsed}`);
console.log(`Status: ${receipt.status}`);

What’s Inside a Receipt?

Field What It Means
status 1 = success, 0 = failed
blockNumber Which block included your tx
gasUsed Actual cost paid
logs Events that happened
transactionHash Unique ID of your tx

😅 Error Handling in dApps: When Things Go Oops

What Is It?

Things go wrong sometimes! Maybe the user doesn’t have enough tokens, or the network is busy. Good error handling turns a confusing crash into a helpful message.

Common Blockchain Errors

Error What Happened User-Friendly Message
INSUFFICIENT_FUNDS Not enough ETH for gas “You need more ETH!”
USER_REJECTED User clicked “Reject” “You cancelled the transaction”
NETWORK_ERROR Connection problem “Check your internet”
UNPREDICTABLE_GAS Contract will fail “This transaction would fail”

Simple Example

try {
  const tx = await contract.transfer(
    toAddress,
    amount
  );
  await tx.wait();
  showSuccess("Transfer complete! 🎉");

} catch (error) {
  // Decode the error
  if (error.code === 'ACTION_REJECTED') {
    showMessage("You cancelled it!");
  }
  else if (error.code === 'INSUFFICIENT_FUNDS') {
    showError("Need more ETH for gas!");
  }
  else if (error.message.includes('revert')) {
    showError("Contract said no!");
  }
  else {
    showError("Something went wrong");
    console.error(error);
  }
}

The Golden Rule

Never show raw errors to users! Always translate technical messages into friendly language.

graph TD A["❌ Raw Error"] --> B["🔍 Decode It"] B --> C["💬 Friendly Message"] C --> D["😊 Happy User"] style D fill:#4CAF50,color:#fff

⏳ Transaction Status UI: The Waiting Room Experience

What Is It?

When users send a transaction, they’re anxious! A good Transaction Status UI keeps them informed and calm at every step.

The Emotional Journey

graph TD A["😬 Waiting to Sign"] --> B["😰 Pending..."] B --> C["😅 Confirming..."] C --> D["😊 Success!"] style A fill:#FFA500,color:#fff style B fill:#FFD700,color:#000 style C fill:#87CEEB,color:#000 style D fill:#4CAF50,color:#fff

Simple Example

function updateStatus(stage) {
  const messages = {
    'signing': '✍️ Please sign in wallet...',
    'pending': '⏳ Transaction sent! Waiting...',
    'confirming': '⛏️ Being confirmed...',
    'success': '✅ All done!',
    'error': '❌ Something went wrong'
  };

  statusDisplay.textContent = messages[stage];
}

// Use it!
async function sendTransaction() {
  try {
    updateStatus('signing');
    const tx = await contract.doSomething();

    updateStatus('pending');

    // Listen for confirmations
    tx.wait().then(() => {
      updateStatus('success');
    });

  } catch (error) {
    updateStatus('error');
  }
}

Best Practices

DO:

  • Show progress indicators
  • Tell users what’s happening
  • Provide estimated wait times
  • Allow users to see transaction on explorer

DON’T:

  • Leave screen blank while waiting
  • Show technical hashes without context
  • Let users wonder if it worked

💰 Gas Estimation: The Price Tag Before You Buy

What Is It?

Before you buy something, you want to know the price! Gas estimation tells users approximately how much a transaction will cost BEFORE they confirm.

Why Estimate?

Without Estimation With Estimation
“Sign this transaction” “This will cost ~0.002 ETH ($4)”
User: 😰 “How much??” User: 😊 “OK that’s fine!”

Simple Example

async function estimateGas() {
  // Get gas estimate from contract
  const gasEstimate = await contract
    .transfer
    .estimateGas(toAddress, amount);

  // Get current gas price
  const feeData = await provider.getFeeData();
  const gasPrice = feeData.gasPrice;

  // Calculate total cost
  const totalCost = gasEstimate * gasPrice;

  // Convert to ETH for display
  const costInEth = ethers.formatEther(totalCost);

  return {
    gasUnits: gasEstimate.toString(),
    pricePerUnit: gasPrice.toString(),
    totalCostEth: costInEth
  };
}

// Show user before they confirm
const estimate = await estimateGas();
showCostPreview(`
  Estimated cost: ${estimate.totalCostEth} ETH
`);

Add a Buffer!

Gas estimates aren’t perfect. Add a small buffer to prevent failures:

// Add 20% buffer for safety
const safeGasLimit = gasEstimate * 120n / 100n;

🗺️ Network Switching in dApps: Changing Kitchens

What Is It?

Your dApp might work on multiple blockchains (Ethereum, Polygon, Arbitrum). Network switching lets users change between them smoothly.

The Challenge

Imagine if restaurants had different menus in different locations. You need to:

  1. Detect which “location” the user is at
  2. Help them switch if needed
  3. Update everything when they do

Simple Example

// Check current network
const network = await provider.getNetwork();
const currentChainId = network.chainId;

// Define supported networks
const NETWORKS = {
  1: { name: 'Ethereum', symbol: 'ETH' },
  137: { name: 'Polygon', symbol: 'MATIC' },
  42161: { name: 'Arbitrum', symbol: 'ETH' }
};

// Request network switch
async function switchNetwork(chainId) {
  try {
    await window.ethereum.request({
      method: 'wallet_switchEthereumChain',
      params: [{
        chainId: '0x' + chainId.toString(16)
      }]
    });
  } catch (error) {
    // Network not added yet?
    if (error.code === 4902) {
      await addNetwork(chainId);
    }
  }
}

Listen for Network Changes

// When user switches network manually
window.ethereum.on('chainChanged',
  (newChainId) => {
    // Reload everything!
    // Contract addresses might be different
    // User balances will change
    window.location.reload();
});

Network Switch Flow

graph TD A["User Clicks Switch"] --> B{Wallet Has Network?} B -->|Yes| C["Switch Request"] B -->|No| D["Add Network First"] D --> C C --> E["Update dApp State"] E --> F["Refresh Contract Instances"] style F fill:#4CAF50,color:#fff

🎯 Putting It All Together

Here’s how all these pieces work in a real dApp:

graph TD A["🔗 Connect Wallet"] --> B["📋 Create Contract Instance"] B --> C["🔔 Set Up Event Listeners"] C --> D["💰 Show Gas Estimates"] D --> E["📤 User Sends Transaction"] E --> F["⏳ Show Status UI"] F --> G["🧾 Handle Receipt"] G --> H{Success?} H -->|Yes| I["🎉 Update UI"] H -->|No| J["😅 Handle Error"] style I fill:#4CAF50,color:#fff style J fill:#FF6B6B,color:#fff

🌟 Quick Tips for Amazing dApp UX

  1. Always show loading states - Users hate blank screens
  2. Explain errors in plain English - No one knows what 0x1234... means
  3. Estimate costs upfront - No surprises!
  4. Clean up listeners - Prevent memory leaks
  5. Handle network changes - Users switch chains often
  6. Provide transaction links - Let users verify on explorers
  7. Cache wisely - Don’t spam the blockchain

🎉 You Did It!

You now understand the 7 pillars of great dApp UX:

Pillar What You Learned
Contract Instances How to connect to smart contracts
Event Listeners How to get real-time updates
Transaction Receipts How to confirm actions
Error Handling How to handle problems gracefully
Status UI How to keep users informed
Gas Estimation How to show costs upfront
Network Switching How to support multiple chains

Your users will thank you! Now go build something amazing! 🚀

Loading story...

Story - Premium Content

Please sign in to view this story and start learning.

Upgrade to Premium to unlock full access to all stories.

Stay Tuned!

Story is coming soon.

Story Preview

Story - Premium Content

Please sign in to view this concept and start learning.

Upgrade to Premium to unlock full access to all content.