Update: Rinkeby testnet has been deprecated. I have not ported this application to the new testnet, so it no longer functions.

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.

pragma solidity ^0.8.0;

contract Facts {
    address owner;
    bool paused = false;
    uint minAmount = 0.01 ether;

    constructor() {
        owner = msg.sender;
    }

    modifier isOwner() {
        require(owner == msg.sender, "You are not the owner");
        _;
    }

    modifier isUser() {
        require(tx.origin == msg.sender, "Contracts aren't allowed to interact with this method");
        _;
    }

    event MintFact(address from, uint amount, uint256 timestamp);

    function generateFact() payable public isUser {
        require(!paused, "This contract is currently paused");
        require (msg.value >= minAmount, "Not enough ETH sent for the transaction");
        
        uint refund = msg.value - minAmount; 

        if(refund > 0) {
            payable(msg.sender).transfer(refund);
        }

        emit MintFact(msg.sender, msg.value, block.timestamp);
    }

    function setOwner(address newOwner) public isOwner {
        owner = newOwner;
    }

    function setPaused(bool value) public isOwner {
        paused = value;
    }

    function withdraw(address payable wallet, uint amount) public isOwner {
        require(amount >= address(this).balance, "Withdraw amount is greater that contract balance");
        wallet.transfer(amount);
    }

    function isWalletOwner() public view returns (bool) {
        if(msg.sender == owner) {
            return true;
        }
        return false;
    }

    function getOwner() public view returns (address) {
        return owner;
    }

    function isPaused() public view returns (bool) {
        return paused;
    }
}

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.

Try it out

dApp
Contract