π© Challenge: π Decentralized Staking App

Design and implement a decentralized application (dApp) with a state machine
Handle and send ETH in a smart contract using payable functions
Interact with external smart contracts
Use events to track and display onchain activity on the frontend
π¦Έ A superpower of Ethereum is allowing you, the builder, to create a simple set of rules that an adversarial group of players can use to work together. In this challenge, you create a decentralized application where users can coordinate a group funding effort. If the users cooperate, the money is collected in a second smart contract. If they defect, the worst that can happen is everyone gets their money back. The users only have to trust the code.
π¦ Build a Staker.sol contract that collects ETH from numerous addresses using a payable stake() function and keeps track of balances. After some deadline if it has at least some threshold of ETH, it sends it to an ExampleExternalContract and triggers the complete() action sending the full balance. If not enough ETH is collected, allow users to withdraw().
π Building the frontend to display the information and UI is just as important as writing the contract. The goal is to deploy the contract and the app to allow anyone to stake using your app. Use a Stake(address, uint256) event to list all stakes.
π Note: If you use named arguments in your event (e.g.
event Stake(address indexed staker, uint256 amount)), you'll need to update/packages/nextjs/app/stakings/page.tsxto reference event parameters by their names instead of numeric indices.
π The final deliverable is deploying a Dapp that lets users send ether to a contract and stake if the conditions are met, then yarn vercel your app to a public webserver. Submit the url on SpeedRunEthereum.com!
π¬ Meet other builders working on this challenge and get help in the challenge Telegram!
Checkpoint 0: π¦ Environment π
Before you begin, you need to install the following tools:
- Node (>= v20.18.3)
- Yarn (v1 or v2+)
- Git
Then download the challenge to your computer and install dependencies by running:
npx create-eth@1.0.2 -e challenge-decentralized-staking challenge-decentralized-staking
cd challenge-decentralized-staking
in the same terminal, start your local network (a blockchain emulator in your computer):
yarn chain
in a second terminal window, π° deploy your contract (locally):
cd challenge-decentralized-staking
yarn deploy
in a third terminal window, start your π± frontend:
cd challenge-decentralized-staking
yarn start
π± Open http://localhost:3000 to see the app.
π©βπ» Rerun
yarn deploywhenever you want to deploy new contracts to the frontend. If you haven't made any contract changes, you can runyarn deploy --resetfor a completely fresh deploy.
π Now you are ready to edit your smart contract Staker.sol in packages/hardhat/contracts
βοΈ At this point you will need to know basic Solidity syntax. If not, you can pick it up quickly by tinkering with concepts from π Solidity By Example using ποΈ Scaffold-ETH-2. (In particular: global units,Β primitive data types,Β mappings, sending ether, and payable functions.)
β οΈ We have disabled AI in Cursor and VSCode and highly suggest that you do not enable it so you can focus on the challenge, do everything by yourself, and hence better understand and remember things. If you are using another IDE, please disable AI yourself.
π§ If you are a vibe-coder and don't care about understanding the syntax of the code used and just want to understand the general takeaways, you can re-enable AI by:
- Cursor: remove
*from.cursorignorefile - VSCode: set
chat.disableAIFeaturestofalsein.vscode/settings.jsonfile
Checkpoint 1: π Staking π΅
You'll need to track individual balances using a mapping:
mapping ( address => uint256 ) public balances;
And also track a constant threshold at 1 ether
uint256 public constant threshold = 1 ether;
π©βπ» Write your
stake()function and test it with theDebug Contractstab in the frontend.
πΈ Need more funds from the faucet? Click on "Grab funds from faucet", or use the Faucet feature at the bottom left of the page to get as much as you need!
β Need to troubleshoot your code? If you import
hardhat/console.solto your contract, you can callconsole.log()right in your Solidity code. The output will appear in youryarn chainterminal.
π₯ Goals
-
Do you see the balance of the
Stakercontract go up when youstake()? -
Is your
balancecorrectly tracked? -
Do you see the events in the
Stake Eventstab?
Checkpoint 2: π¬ State Machine / Timing β±
State Machine
βοΈ Think of your smart contract like a state machine. First, there is a stake period. Then, if you have gathered the
thresholdworth of ETH, there is a success state. Or, we go into a withdraw state to let users withdraw their funds.
Set a deadline of block.timestamp + 30 seconds
uint256 public deadline = block.timestamp + 30 seconds;
π¨βπ« Smart contracts can't execute automatically, you always need to have a transaction execute to change state. Because of this, you will need to have an execute() function that anyone can call, just once, after the deadline has expired.
π©βπ» Write your
execute()function and test it with theDebug Contractstab
Check the
ExampleExternalContract.solfor the bool you can use to test if it has been completed or not. But do not edit theExampleExternalContract.solas it can slow the auto grading.
If the address(this).balance of the contract is over the threshold by the deadline, you will want to call: exampleExternalContract.complete{value: address(this).balance}()
If the balance is less than the threshold, you want to set a openForWithdraw bool to true which will allow users to withdraw() their funds.
Timing
You'll have 30 seconds after deploying until the deadline is reached, you can adjust this in the contract.
π©βπ» Create a
timeLeft()function includingpublic view returns (uint256)that returns how much time is left.
β οΈ Be careful! If block.timestamp >= deadline you want to return 0;
β³ "Time Left" will only update if a transaction occurs. You can see the time update by getting funds from the faucet button in navbar just to trigger a new block.
π©βπ» You can call
yarn deploy --resetany time you want a fresh contract, it will get re-deployed even if there are no changes on it. You may need it when you want to reload the "Time Left" of your tests.
Your Staker UI tab should be almost done and working at this point.
π₯ Goals
- Can you see
timeLeftcounting down in theStaker UItab when you trigger a transaction with the faucet button? - If enough ETH is staked by the deadline, does your
execute()function correctly callcomplete()and stake the ETH? - If the threshold isn't met by the deadline, are you able to
withdraw()your funds?
Checkpoint 3: π΅ Receive Function / UX π
π To improve the user experience, set your contract up so it accepts ETH sent to it and calls stake(). You will use what is called the receive() function.
Use the receive() function in solidity to "catch" ETH sent to the contract and call
stake()to updatebalances.
π₯ Goals
- If you send ETH directly to the contract address does it update your
balanceand thebalanceof the contract?
βοΈ Side Quests
- Can
execute()get called more than once, and is that okay? - Can you stake and withdraw freely after the
deadline, and is that okay? - What are other implications of anyone being able to withdraw for someone?
πΈ It's a trap!
- Make sure funds can't get trapped in the contract! Try sending funds after you have executed! What happens?
- Try to create a modifier called
notCompleted. It will check thatExampleExternalContractis not completed yet. Use it to protect yourexecuteandwithdrawfunctions.
β οΈ Test it!
- Now is a good time to run
yarn testto run the automated testing function. It will test that you hit the core checkpoints. You are looking for all green checkmarks and passing tests!
Checkpoint 4: πΎ Deploy your contract! π°
π‘ Edit the defaultNetwork to your choice of public EVM networks in packages/hardhat/hardhat.config.ts
π You will need to generate a deployer address using yarn generate This creates a mnemonic and saves it locally.
π©βπ Use yarn account to view your deployer account balances.
β½οΈ You will need to send ETH to your deployer address with your wallet, or get it from a public faucet of your chosen network.
π If you plan on submitting this challenge, be sure to set your
deadlineto at leastblock.timestamp + 72 hours
π Run yarn deploy to deploy your smart contract to a public network (selected in hardhat.config.ts)
π¬ Hint: You can set the
defaultNetworkinhardhat.config.tstosepoliaoroptimismSepoliaOR you canyarn deploy --network sepoliaoryarn deploy --network optimismSepolia.
Checkpoint 5: π’ Ship your frontend! π
βοΈ Edit your frontend config in packages/nextjs/scaffold.config.ts to change the targetNetwork to chains.sepolia (or chains.optimismSepolia if you deployed to OP Sepolia)
π» View your frontend at http://localhost:3000/staker-ui and verify you see the correct network.
π‘ When you are ready to ship the frontend app...
π¦ Run yarn vercel to package up your frontend and deploy.
You might need to log in to Vercel first by running
yarn vercel:login. Once you log in (email, GitHub, etc), the default options should work.
If you want to redeploy to the same production URL you can run
yarn vercel --prod. If you omit the--prodflag it will deploy it to a preview/test URL.
Follow the steps to deploy to Vercel. It'll give you a public URL.
π¦ Since we have deployed to a public testnet, you will now need to connect using a wallet you own or use a burner wallet. By default π₯
burner walletsare only available onhardhat. You can enable them on every chain by settingonlyLocalBurnerWallet: falsein your frontend config (scaffold.config.tsinpackages/nextjs/)
Configuration of Third-Party Services for Production-Grade Apps.
By default, π Scaffold-ETH 2 provides predefined API keys for popular services such as Alchemy and Etherscan. This allows you to begin developing and testing your applications more easily, avoiding the need to register for these services. This is great to complete your SpeedRunEthereum.
For production-grade applications, it's recommended to obtain your own API keys (to prevent rate limiting issues). You can configure these at:
-
π·
ALCHEMY_API_KEYvariable inpackages/hardhat/.envandpackages/nextjs/.env.local. You can create API keys from the Alchemy dashboard. -
π
ETHERSCAN_API_KEYvariable inpackages/hardhat/.envwith your generated API key. You can get your key here.
π¬ Hint: It's recommended to store env's for nextjs in Vercel/system env config for live apps and use .env.local for local testing.
Checkpoint 6: π Contract Verification
Run the yarn verify --network your_network command to verify your contracts on etherscan π°
π Search this address on Sepolia Etherscan (or Optimism Sepolia Etherscan if you deployed to OP Sepolia) to get the URL you submit to πββοΈSpeedRunEthereum.com.
π Head to your next challenge here.
π¬ Problems, questions, comments on the stack? Post them to the π scaffold-eth developers chat
Note: The staking page is in /packages/nextjs/stakings/page.tsx.tsx
You're viewing this challenge as a guest. Want to start building your onchain portfolio?
Connect your wallet and register to unlock the full Speedrun Ethereum experience.