During my Blockchain Internship at EtherAuthority, I built a fully functional DeFi staking platform from scratch β a smart contract deployed on SCAI Mainnet, a React frontend connected via MetaMask, and real-time blockchain event tracking. Here's everything I learned.
π Live Demo & Code
Live App: https://defi-staking-platform-wc8n.vercel.app
GitHub: https://github.com/Tanisha-aggarwal1085/Defi-Staking-Platform
What is a DeFi Staking Platform?
A DeFi (Decentralized Finance) staking platform allows users to lock up cryptocurrency tokens in a smart contract and earn rewards over time β similar to a savings account, but fully on-chain with no central authority.
In this project, users can:
Stake SCAI tokens and earn 12% APY
Withdraw their stake at any time (no lock-up period)
Claim accrued rewards
View full transaction history from blockchain events
Tech Stack
LayerTechnologySmart ContractSolidity ^0.8.20DevelopmentHardhatFrontendReact + ViteBlockchain Libraryethers.js v6WalletMetaMaskNetworkSCAI Mainnet (Chain ID: 34)HostingVercel
Smart Contract Design
The core of the platform is Staking.sol. Here's the key logic:
solidity// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract Staking {
uint256 public constant APY = 12;
uint256 public constant YEAR = 365 days;
mapping(address => uint256) public stakes;
mapping(address => uint256) public rewards;
mapping(address => uint256) public lastUpdated;
event Staked(address indexed user, uint256 amount, uint256 newTotal);
event Withdrawn(address indexed user, uint256 amount, uint256 newTotal);
event RewardClaimed(address indexed user, uint256 amount);
function _updateReward(address user) internal {
if (stakes[user] > 0) {
uint256 timeElapsed = block.timestamp - lastUpdated[user];
uint256 earned = (stakes[user] * APY * timeElapsed) / (100 * YEAR);
rewards[user] += earned;
}
lastUpdated[user] = block.timestamp;
}
function stake() public payable {
require(msg.value > 0, "Amount must be greater than 0");
_updateReward(msg.sender);
stakes[msg.sender] += msg.value;
emit Staked(msg.sender, msg.value, stakes[msg.sender]);
}
function withdraw(uint256 amount) public {
require(stakes[msg.sender] >= amount, "Not enough stake");
_updateReward(msg.sender);
stakes[msg.sender] -= amount;
(bool success, ) = payable(msg.sender).call{value: amount}("");
require(success, "Transfer failed");
emit Withdrawn(msg.sender, amount, stakes[msg.sender]);
}
function claimReward() public {
_updateReward(msg.sender);
uint256 reward = rewards[msg.sender];
require(reward > 0, "No rewards");
rewards[msg.sender] = 0;
(bool success, ) = payable(msg.sender).call{value: reward}("");
require(success, "Transfer failed");
emit RewardClaimed(msg.sender, reward);
}
}
Key Design Decisions:
Time-based APY β Rewards accrue continuously using block.timestamp
Checks-Effects-Interactions β State changes happen before external calls (reentrancy prevention)
Events β Every action emits an event for transparent on-chain history
Reward Calculation Formula
earned = (stakes[user] Γ APY Γ timeElapsed) / (100 Γ YEAR)
This calculates rewards proportionally β the longer you stake, the more you earn.
Frontend Architecture
The React app uses a Context-based architecture to share blockchain state across all pages:
App.jsx (Router)
βββ Web3Context.jsx β wallet + contract logic
βββ Dashboard.jsx β stake/withdraw/claim UI
βββ MyStakes.jsx β balance overview
βββ Transactions.jsx β blockchain event history
βββ About.jsx β platform info
Connecting to MetaMask with ethers.js v6:
javascriptconst connectWallet = async () => {
const provider = new ethers.BrowserProvider(window.ethereum);
await window.ethereum.request({ method: "eth_requestAccounts" });
const signer = await provider.getSigner();
const walletAddress = await signer.getAddress();
const contract = new ethers.Contract(CONTRACT_ADDRESS, ABI, signer);
// read stake and reward...
};
Network Auto-Switch:
javascriptawait window.ethereum.request({
method: "wallet_switchEthereumChain",
params: [{ chainId: "0x22" }], // SCAI Mainnet = 34
});
Deployment to SCAI Mainnet
javascript// hardhat.config.js
module.exports = {
solidity: "0.8.20",
networks: {
scai: {
url: "https://mainnet-rpc.scai.network",
accounts: [process.env.PRIVATE_KEY],
chainId: 34
}
}
};
bashnpx hardhat run scripts/deploy.js --network scai
Contract deployed to: 0xCd279499974Ac556a7DA538e5F1f1B501E46c14c
Challenges I Faced & How I Solved Them
- Invalid Contract Address My first deployment had a 41-character address (should be 40). MetaMask flagged every transaction as suspicious. Fix: Always verify address length after deployment.
- Shared Loading State All three buttons (Stake, Withdraw, Claim) showed "Processing..." simultaneously because they shared one loading state. Fix: Created separate stakeLoading, withdrawLoading, claimLoading states.
- Wrong Network The app was initially deployed on local Hardhat β mentors couldn't test it. Fix: Deployed to SCAI Mainnet and added automatic network switching.
- Transaction History Not Persisting Blockchain events (queryFilter) sometimes failed on SCAI RPC. Fix: Dual approach β immediate in-memory update + async blockchain event sync.
What I Learned
β
How DeFi staking protocols work under the hood
β
Writing production-safe Solidity with CEI pattern
β
Deploying to real mainnets (not just testnets)
β
Debugging MetaMask transaction issues
β
Building multi-page Web3 dApps with React
Conclusion
Building this DeFi staking platform gave me hands-on experience with the full blockchain development stack β from smart contract design to frontend deployment. The biggest takeaway: real-world blockchain development is 20% writing code and 80% debugging transactions, network issues, and wallet quirks.
If you want to explore the code or try the live app:
π Live: https://defi-staking-platform-wc8n.vercel.app
π GitHub: https://github.com/Tanisha-aggarwal1085/Defi-Staking-Platform
Built during Blockchain Internship at EtherAuthority
Top comments (0)