Decentralized staking is one of the most powerful applications in the Ethereum ecosystem, enabling users to lock up funds and participate in network security or future protocol development. In this hands-on guide, you’ll dive into the first scaffold-eth challenge—building a functional staking dApp from scratch. Whether you're new to Solidity or brushing up on full-stack Web3 development, this step-by-step walkthrough will help you master core blockchain concepts while creating a real-world decentralized application.
By the end of this tutorial, you'll have deployed a working staking contract on a local network and tested its functionality using React. This isn’t just theory—you’ll write code, run tests, and simulate user interactions like sending ETH and tracking balances.
What Is a Staking dApp?
A staking dApp allows users to lock (or "stake") their ETH into a smart contract for a specific purpose—such as securing a network or funding future projects. In our case, we’re simulating a simplified version of Ethereum 2.0’s staking mechanism with two key rules:
- Anyone can stake ETH and have their balance tracked.
- Once a deadline is reached or a threshold (e.g., 1 ETH total) is met, withdrawals are locked—mirroring real-world conditions where staked funds become temporarily or permanently committed.
This project introduces foundational Web3 development patterns, making it an ideal starting point for aspiring blockchain developers.
Key Learning Outcomes
Through this challenge, you’ll gain practical experience in:
- Setting up the scaffold-eth development environment
- Writing and testing a Solidity smart contract
- Using payable functions, mappings, and events
- Interacting with external contracts
- Deploying and testing on a local Hardhat network
- Connecting your contract to a frontend using React
👉 Start building your first staking dApp with a powerful Web3 development toolkit.
These skills form the backbone of modern dApp development and open doors to more advanced topics like yield farming, governance, and Layer 2 scaling.
Setting Up Your Development Environment
Before writing any code, let’s set up the scaffold-eth boilerplate. This framework accelerates development by providing pre-configured tools for smart contracts, frontends, and deployment scripts.
Run the following commands in your terminal:
git clone https://github.com/austintgriffith/scaffold-eth.git challenge-1-decentralized-staking
cd challenge-1-decentralized-staking
git checkout challenge-1-decentralized-staking
yarn installThis clones the repository, checks out the correct branch for Challenge 1, and installs all required dependencies.
Essential CLI Commands
Keep these three terminal sessions open during development:
yarn chain— Starts a local Hardhat network athttp://localhost:8545yarn start— Launches the React frontend athttp://localhost:3000yarn deploy— Deploys your smart contracts and refreshes the frontend
After any code change in your Solidity contract, simply re-run yarn deploy to update the deployed instance.
Core Concepts: Payable Functions, Mappings & Events
To build our staking logic, we need to understand three critical Solidity features:
Payable Functions
A function marked payable can receive ETH when called. Without this modifier, sending ETH to a function will revert.
function stake() public payable {
// Accept ETH here
}Mappings
Mappings store key-value pairs. We’ll use one to track each user’s staked balance by their Ethereum address:
mapping(address => uint256) public balances;This creates a persistent lookup table where each address maps to a uint256 balance.
Events
Events notify off-chain applications (like your React UI) when something happens on-chain:
event Stake(address indexed sender, uint256 amount);The indexed keyword allows external tools to filter logs by the sender field—ideal for tracking individual user activity.
Implementing the Stake Function
Now it’s time to write the core logic of our dApp: allowing users to stake ETH and recording their contributions.
Step-by-Step Implementation
Here’s how we structure the contract:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "hardhat/console.sol";
import "./ExampleExternalContract.sol";
/**
* @title Staker Contract
* @author scaffold-eth
* @notice A contract that allows users to stake ETH
*/
contract Staker {
// External contract that will hold staked funds
ExampleExternalContract public exampleExternalContract;
// Balances of users' staked funds
mapping(address => uint256) public balances;
// Staking threshold (1 ETH)
uint256 public constant threshold = 1 ether;
// Event emitted when a user stakes
event Stake(address indexed sender, uint256 amount);
/**
* @notice Contract Constructor
* @param exampleExternalContractAddress Address of external contract
*/
constructor(address exampleExternalContractAddress) {
exampleExternalContract = ExampleExternalContract(exampleExternalContractAddress);
}
/**
* @notice Stake ETH into the contract
*/
function stake() public payable {
balances[msg.sender] += msg.value;
emit Stake(msg.sender, msg.value);
}
}Key Notes
msg.valuecontains the amount of ETH sent in the transaction.- Since
balances[msg.sender]defaults to0, we can safely addmsg.valuewithout prior initialization. - Public state variables like
balancesautomatically generate getter functions—so you can query them externally viabalances(address).
👉 Explore how modern dApps interact with wallets and manage user funds securely.
Testing Your Staking dApp Locally
After deploying with yarn deploy, follow these steps to verify functionality:
- Visit
http://localhost:3000to access the frontend. - Use the built-in faucet to request test ETH.
- Click “Stake” and send 0.5 ETH.
Check if:
- The Stake event appears in logs
- Your balance updates correctly
- The contract balance increases
If everything works, congratulations—you’ve built a working staking mechanism!
Frequently Asked Questions (FAQ)
Q: Why use indexed in the event parameters?
A: Indexed parameters allow external applications (like subgraphs or web interfaces) to efficiently filter and listen for specific events—for example, all stakes made by a particular user.
Q: Can I stake zero ETH?
A: Technically yes, but it won’t change the balance since balances[msg.sender] += 0. However, it would still emit an event and consume gas.
Q: What happens if someone sends ETH directly to the contract?
A: Currently, there’s no fallback or receive function, so direct transfers would fail. To accept direct deposits, you’d need to implement a receive() function.
Q: How do I make the staking threshold enforceable?
A: In Part 2, we’ll add logic to lock funds once the total staked amount reaches 1 ether, preventing further withdrawals until conditions are met.
Q: Are there security risks in this implementation?
A: While simple, this version lacks access control and withdrawal limits. Always audit contracts before mainnet deployment and consider using OpenZeppelin’s secure patterns.
Q: Can I test this on a testnet?
A: Yes! After mastering local deployment, use yarn deploy --network ropsten (or another supported testnet) and connect MetaMask for real-world testing.
Final Thoughts
You’ve now completed Part 1 of the scaffold-eth staking challenge—setting up your environment, writing a core staking function, and testing it live. You've gained hands-on experience with essential Web3 development tools and Solidity fundamentals.
With these building blocks in place, you’re ready to move on to Part 2: adding withdrawal restrictions, time locks, and integration with external protocols.
👉 Accelerate your blockchain journey with tools used by top developers worldwide.
Stay tuned for the next installment where we’ll enhance security, enforce staking thresholds, and prepare for mainnet deployment.