Developing a decentralized application (dApp) is an exciting entry point into the world of Web3, combining blockchain technology with real-world functionality. In this comprehensive guide, you'll learn how to build, deploy, and interact with a fully functional dApp that retrieves and stores the current ETH/USD price using smart contracts and Chainlink data feeds.
By the end of this tutorial, you’ll have a working end-to-end dApp built with Solidity, Hardhat, React, and ethers.js—ideal for beginners and developers looking to expand into decentralized development.
What Is a dApp?
A decentralized application, or dApp, operates on a blockchain network instead of relying on centralized servers. Unlike traditional Web2 apps, dApps run their backend logic through tamper-proof smart contracts, offering enhanced security, transparency, and resistance to censorship.
The core components of any dApp include:
- Smart Contracts – Handle business logic and state on-chain.
- Frontend (UI) – Built with standard web tools like HTML, CSS, and JavaScript.
- Data Storage – Often combines on-chain data with off-chain solutions like IPFS for efficiency.
👉 Discover how blockchain integration powers next-gen dApps.
Advantages of dApps
- No downtime: Runs on a distributed network.
- Trustless execution: Code executes exactly as written.
- Censorship-resistant: No single entity controls the app.
- Enhanced privacy: Users control their data and identity.
Challenges to Consider
- Immutable code: Once deployed, updates are complex.
- Lower performance: Due to consensus mechanisms.
- User experience friction: Requires Web3 wallets and gas fees.
Despite these hurdles, dApps represent the future of digital applications—transparent, secure, and user-owned.
Step 1: Create the Smart Contract
Our dApp will use a smart contract to fetch and store the current ETH/USD price from a Chainlink oracle. We'll write this in Solidity using Hardhat for development.
Set Up Your Development Environment
First, ensure you have:
- Node.js installed
- MetaMask wallet configured
Create your project directory:
mkdir chainlink-dapp-example
cd chainlink-dapp-example
mkdir backend
cd backendInitialize Hardhat:
npm init -y
npm install --save-dev hardhat
npx hardhatSelect "Create a JavaScript project" and accept default settings.
Replace the default Lock.sol in the contracts folder with a new file named PriceConsumerV3.sol. Paste the following code:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
contract PriceConsumerV3 {
AggregatorV3Interface internal priceFeed;
int public storedPrice;
constructor() {
priceFeed = AggregatorV3Interface(0x8A753747A1Fa494EC906cE90E9f37563A8AF630e); // Rinkeby ETH/USD feed
}
function getLatestPrice() public view returns (int) {
(
/*uint80 roundID*/,
int price,
/*uint startedAt*/,
/*uint timeStamp*/,
/*uint80 answeredInRound*/
) = priceFeed.latestRoundData();
return price;
}
function storeLatestPrice() external {
storedPrice = getLatestPrice();
}
}This contract uses Chainlink’s decentralized oracle network to pull live price data and stores it permanently on-chain.
Step 2: Deploy the Smart Contract
To deploy your contract to the Rinkeby testnet, you need:
- Test ETH (get from a Chainlink faucet)
- An RPC endpoint (via Alchemy or Infura)
- Your wallet’s private key (use a test-only account)
Install required packages:
npm install --save-dev @nomicfoundation/hardhat-toolbox
npm install @chainlink/contracts dotenv --saveUpdate hardhat.config.js:
require("@nomicfoundation/hardhat-toolbox");
require('dotenv').config();
const RINKEBY_RPC_URL = process.env.RINKEBY_RPC_URL || "";
const PRIVATE_KEY = process.env.PRIVATE_KEY || "";
module.exports = {
defaultNetwork: "rinkeby",
networks: {
rinkeby: {
url: RINKEBY_RPC_URL,
accounts: [PRIVATE_KEY],
saveDeployments: true,
},
},
solidity: "0.8.9",
};Create a .env file in /backend:
RINKEBY_RPC_URL=your_rpc_url_here
PRIVATE_KEY=your_private_key_hereModify scripts/deploy.js:
const hre = require("hardhat");
async function main() {
const PriceConsumer = await hre.ethers.getContractFactory("PriceConsumerV3");
const priceConsumer = await PriceConsumer.deploy();
await priceConsumer.deployed();
console.log("Contract deployed to:", priceConsumer.address);
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});Compile and deploy:
npx hardhat compile
npx hardhat run --network rinkeby scripts/deploy.jsSave the deployed contract address—you’ll need it for the frontend.
👉 Learn how secure smart contract deployment boosts dApp reliability.
Step 3: Build the Frontend with React and ethers.js
Now let’s create a user interface so users can interact with your deployed contract.
Initialize the React App
From the project root:
cd ..
npx create-react-app frontend
cd frontend
npm install bootstrap ethersClean up unnecessary files (logo.svg, App.test.js, etc.) and open /src/App.js.
Replace its content with:
import React, { useEffect, useState } from 'react';
import { ethers } from "ethers";
import 'bootstrap/dist/css/bootstrap.min.css';
function App() {
const [storedPrice, setStoredPrice] = useState('');
const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();
const contractAddress = 'REPLACE_WITH_DEPLOYED_CONTRACT_ADDRESS';
const ABI = [
{
"inputs": [],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "getLatestPrice",
"outputs": [{ "internalType": "int256", "name": "", "type": "int256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "storeLatestPrice",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "storedPrice",
"outputs": [{ "internalType": "int256", "name": "", "type": "int256" }],
"stateMutability": "view",
"type": "function"
}
];
const contract = new ethers.Contract(contractAddress, ABI, signer);
const getStoredPrice = async () => {
try {
const contractPrice = await contract.storedPrice();
setStoredPrice(parseInt(contractPrice) / 100000000);
} catch (error) {
console.log("getStoredPrice Error: ", error);
}
};
const updateNewPrice = async () => {
try {
const transaction = await contract.storeLatestPrice();
await transaction.wait();
await getStoredPrice();
} catch (error) {
console.log("updateNewPrice Error: ", error);
}
};
useEffect(() => {
getStoredPrice();
}, []);
return (
<div className="container mt-5">
<h1>ETH/USD Price dApp</h1>
<div className="row mt-4">
<div className="col-md-6">
<div className="card">
<div className="card-body">
<h5 className="card-title">Stored Price</h5>
<p className="card-text">ETH/USD: {storedPrice}</p>
</div>
</div>
</div>
<div className="col-md-6">
<div className="card">
<div className="card-body">
<h5 className="card-title">Update Price</h5>
<button className="btn btn-primary" onClick={updateNewPrice}>
Update
</button>
</div>
</div>
</div>
</div>
</div>
);
}
export default App;Run the app:
npm run startConnect your MetaMask wallet when prompted, click Update, confirm the transaction, and watch the price refresh automatically.
Frequently Asked Questions (FAQ)
What is a dApp in blockchain?
A dApp (decentralized application) is an application that runs on a blockchain network using smart contracts instead of centralized servers. It offers trustless, transparent, and censorship-resistant functionality.
How do I connect a frontend to a smart contract?
Use libraries like ethers.js or web3.js to interact with deployed contracts. The frontend connects via a Web3 provider (like MetaMask), using the contract address and ABI to call functions.
Can I update a smart contract after deployment?
Generally, no—smart contracts are immutable. However, developers use proxy patterns or upgradeable contracts to allow limited updates while maintaining security.
Why use Chainlink in a dApp?
Chainlink provides secure, decentralized oracle services that connect smart contracts to real-world data (like price feeds), enabling them to respond to external events reliably.
Is MetaMask necessary for dApp interaction?
Yes—for most Ethereum-based dApps, MetaMask acts as the Web3 wallet interface, allowing users to sign transactions and manage identities securely.
Where can I host a dApp frontend?
You can host the frontend on traditional cloud platforms (e.g., AWS, Vercel) or use decentralized storage like IPFS for full decentralization.
Final Thoughts
Building a dApp involves three core steps: writing a smart contract, deploying it to a blockchain, and connecting it to a user-friendly frontend. This tutorial walked you through creating a functional ETH price tracker using Solidity, Hardhat, React, and Chainlink oracles.
As Web3 adoption grows, mastering dApp development opens doors to innovation in DeFi, NFTs, DAOs, and more.
👉 Start building your own dApp today with secure tools and resources.