GM! I've been interacting with the crypto space for a few months as a consumer, and with this project, I wanted to experience the creator side of things. This post follows the development of a simple web 3.0 application that allows users to send ETH to a smart contract to read a fun fact. Of course, no one should have to pay for it, so this would operate on the Rinkeby network, one of the many testnets supported by the Ethereum blockchain.
I used React to build the web application. I kept it minimal in terms of styling to focus more on the contract/blockchain side. A prominent button in the middle of the landing screen signals the user to connect their wallet to the dApp. Generally, users connect to the Mainnet, but since this is a demo app, it would automatically switch over the network to Rinkeby so that no real ETH is involved. Once the wallet is connected, the dApp receives the wallet address.
The user can click the 'Get Fact' button to trigger a wallet action to transfer 0.01 ETH to the smart contract. After the user confirms the transaction, it runs the generateFact
function on the smart contract. This function will withdraw 0.01 ETH from the user's wallet and emit an event on success. The dApp listens for the event and fetches a fact from a node server.
Here is a quick demo of the dApp:
The smart contract also includes additional checks and balances to provide additional controls to the owner. Initially, the wallet that deploys the contract msg.sender
is set as the owner in the constructor. The isOwner
modifier sets up a check for privileged functions in the contract, like pausing the contract, withdrawing ETH to a wallet, and setting another wallet as the owner.
The primary function is generateFact()
, which will only work if the contract is not paused and the ETH sent to the contract is greater than or equal to 0.01. If the user sends more than 0.01 ETH, the difference is refunded, and a transaction confirmation event is emitted.
1pragma solidity ^0.8.0;23contract Facts {4 address owner;5 bool paused = false;6 uint minAmount = 0.01 ether;78 constructor() {9 owner = msg.sender;10 }1112 modifier isOwner() {13 require(owner == msg.sender, "You are not the owner");14 _;15 }1617 modifier isUser() {18 require(tx.origin == msg.sender, "Contracts aren't allowed to interact with this method");19 _;20 }2122 event MintFact(address from, uint amount, uint256 timestamp);2324 function generateFact() payable public isUser {25 require(!paused, "This contract is currently paused");26 require (msg.value >= minAmount, "Not enough ETH sent for the transaction");2728 uint refund = msg.value - minAmount;2930 if(refund > 0) {31 payable(msg.sender).transfer(refund);32 }3334 emit MintFact(msg.sender, msg.value, block.timestamp);35 }3637 function setOwner(address newOwner) public isOwner {38 owner = newOwner;39 }4041 function setPaused(bool value) public isOwner {42 paused = value;43 }4445 function withdraw(address payable wallet, uint amount) public isOwner {46 require(amount >= address(this).balance, "Withdraw amount is greater that contract balance");47 wallet.transfer(amount);48 }4950 function isWalletOwner() public view returns (bool) {51 if(msg.sender == owner) {52 return true;53 }54 return false;55 }5657 function getOwner() public view returns (address) {58 return owner;59 }6061 function isPaused() public view returns (bool) {62 return paused;63 }64}
This is a partially decentralized application as it still uses a node server to receive the 'fact'. Nevertheless, it was a fun exercise to build an end-to-end application based on the blockchain.