Klaytn Baobab Faucet: Solidity Smart Contract

Klaytn Baobab Faucet: Solidity Smart Contract

Learn how to create and deploy a faucet on the Baobab Testnet and also interact with it using caver-js

·

10 min read

This is a step-by-step guide for building a faucet on Baobab testnet. In this tutorial, we're going to build a simple front-end that interacts with our faucet smart contract using the caver-js library.

This guide documents my process of building a faucet on the Baobab Testnet. At the end of this guide, you'll learn more about caver-js and solidity. You might want to check out my previous article where i explained the caver-js library

Introduction

Interacting or making transactions to any blockchain requires us to pay gas whether mainnet or testnet hence one of the importance of native cryptocurrency such as KLAY. Building and testing out bApps requires us to have sufficient amount of tokens to pay for gas hence the need for faucets.

What are Faucets?

Faucets are simply sites which distribute tokens to users upon request. They are usually called "faucets" because the rewards are small, just like small drops of water that run off a leaky tap.

leaky.jpeg

The faucet we're building would be disbursing small amounts of KLAY to users on request so as to provide them with a reasonable amount to pay for transaction fees on Baobab.

What we would be building

In this guide, we would be creating a faucet that sends small amount of KLAY to a designated address for testing. Note as a user, you can only run this faucet once every 24 hours.

The final output will look like this:

finalDemo.gif

Prerequisites

To follow along with this article, you will need:

Here is a more detailed summary of what we gonna do:

  • Build the Back-End

  • Build the Front-End

  • Test our bApp

Our project is divided into two sections: the first part, in which we will write and deploy our solidity faucet smart contract on the baobab Testnet; and the front end, where we’ll build our bApp interface with HTML and CSS, and interact with our deployed smart contract using JavaScript with Caver-js.

Building the Back-End

This is the first part as explained above. We're going to write and deploy our faucet solidity smart contract on the Boabab Testnet using Klaytn IDE and Kaikas.

Step 1 - Klaytn IDE

Klaytn IDE is an open-source web application for compiling and deploying Klaytn smart contracts. We're going to use the Klaytn IDE to write and deploy our smart contract.

Click here to navigate to Klaytn IDE on your browser:

klaytnIde.png

Step 2 - Writing the Smart Contract

Locate the contracts folder under the IDE's "File Explorers" and create a new file called faucet.sol:

ideContract.png

Copy and paste the Solidity smart contract below, inside the faucet.sol file:

      //SPDX-License-Identifier: GPL;

pragma solidity ^0.8.0;

contract faucet {
    //state variable to keep track of owner and amount of KLAY to dispense
    address public owner;
    uint public amountAllowed = 20000000000000000000;

    struct Requester {
        address beneficiary;
        uint lockTime;
        uint timeReq;
    }

    // array to keep track of requesters
    address[] requesters;

    // event to be emitted when KLAY is sent out
    event sent(address _addr, uint _amount, uint _timeReq);
    event paid(address _sender, uint _amount);

    // mapping of each address to Requester struct
    mapping (address => Requester) requesterDet;

    //constructor to set the owner
    constructor() payable {
        owner = msg.sender;
    }

    //function modifier
    modifier onlyOwner {
        require(msg.sender == owner, "Only owner can call this function.");
        _; 
    }

    //function to change the owner.  Only the owner of the contract can call this function
    function setOwner(address newOwner) public onlyOwner {
        owner = newOwner;
    }

    //function to set the amount allowable to be claimed. Only the owner can call this function
    function setAmountallowed(uint newAmountAllowed) public onlyOwner {
        amountAllowed = newAmountAllowed;
    }


    //function to send tokens from faucet to an address
    function sendKlay(address payable _beneficiary) public payable returns(address){
        Requester storage req = requesterDet[msg.sender];

        //perform a few checks to make sure function can execute
        require(block.timestamp > req.lockTime, "lock time has not expired. Please try again later");
        require(address(this).balance >= amountAllowed, "Not enough funds in the faucet. Please donate");

        // sets requester;
        req.beneficiary = _beneficiary;
        // sets time requested
        req.timeReq = block.timestamp;
        //if the balance of this contract is greater then the requested amount send funds
        _beneficiary.transfer(amountAllowed);        

        //updates locktime 1 day from now
        req.lockTime = block.timestamp + 1 days;

        requesters.push(msg.sender);
        // emits sent event
        emit sent(_beneficiary, amountAllowed, block.timestamp);
        return msg.sender;
    }


    // function to get details of Requesters
    function getAllRequesterDet(address[] memory _requesters) public view returns(Requester[] memory _r) {
        require(_requesters.length > 0, "Invalid length");
        _r = new Requester[](_requesters.length);
        for(uint i = 0; i < _requesters.length; i++) {
            _r[i] = requesterDet[_requesters[i]];
        }
    }

    // function to get list of address that already requested for KLAY
    function getAllRequesterAddr() public view returns(address[] memory _r) {
        require(requesters.length > 0, "Invalid length");
        _r = new address[](requesters.length);
        for(uint i = 0; i < requesters.length; i++) {
            _r[i] = requesters[i];
        }
    }

    function getFaucetBalance() public view returns(uint) {
        return address(this).balance;
    }

    function getRequesterLength() public view returns(uint) {
        return requesters.length;
    }

    //function to donate funds to the faucet contract
    receive() external payable {
        emit paid(msg.sender, msg.value);
    }
}

Code Walkthrough

In solidity code, the first line is usually the pragma. This declares the version of Solidity you wish to write your code in.

The next is the declaration of contract keyword, then the name of the contract. Here, our contract name is faucet.

Next, i declared an owner address and an amount allowable set to 20 Test KLAY

Next, i declared a Requester struct that contains the address of the beneficiary, lockTime and time they requested funds from the faucet.

Next, i declared an array of addresses to keep track of requesters, and also events "sent" and "paid".

Lets quickly look at the sendKlay function as the code is well commented to explain what each line does.

sendKlay Function

This takes in one parameter: the beneficiary address. Moving forward we have some require statements that checks:

a. if the lock period is upto 24hours

b. if the contract balance is greater than or equals to amount requested

After these checks, we update the state of the user and send out test KLAY.

Step 3 - Compile and Deploy

Now that we have our code ready, lets compile and deploy. To compile your contract on Klaytn IDE, then follow the following steps:

  • Save your source file with ctrl + s.
  • Then, navigate to the "Solidity Compiler" tab:

compile.png

Now, that we have successfully compiled our contract, we're going to deploy our faucet smart contract on the Baobab Test network. Familiar with deploying a contract using Klaytn IDE? Then you re good to go, if not, might be great if you check out this article before proceeding.

If the deployment was successful, you'll see our smart contract name under the "Deployed Contracts" tab as outlined below:

deploy.png

Little reminder: Copy your contract address.

Step 4 - Testing Contract Functionality

In this step, we're going to test and interact with our smart contract on the Klaytn IDE. Lets make some contract call by calling the sendKlay function as seen below:

sendKlay.png

Note that our contract must have sufficient amount to pay out. Meaning our contract has to be funded.

In the above image, we can see that our sendKlay function accepts the address of the beneficiary as an argument and the contract balance was 40 test KLAY. The contract balance should reduce after the transaction. You know the contract balance right? lets find out below:

sendklayAf.png

I guess you had the same figure in mind right? Our faucet smart contract disbursed tokens as expected. You may go ahead and call the function again knowing fully well that it should revert because you can only request once every 24hours.

Building the Front End

In this part, we're going to build the front end of our bApp, which interacts with our smart contract using caver.js. We would look at the core functional part of the bApp together and i will like to split it into two:

  • Connect wallet button

  • Request Klay button

The design of your bApp is in your hands. You own it!!

Before going into that, i would love to quickly talk about using webpack and getting your projects structured. If you are familiar with this process then lets proceed else check how i did it in one of my previous articles

Yay! Now that you have done that, lets open src/index.js and write all our bApps functionality. This would all be bundled up in dist/bundle.js which is referenced in our dist/index.html

A. Connect Wallet Button

HTML in dist/index.html

       <div class="connect">
                <button>Connect Wallet</button>
                <div class="balDisp"></div>
       </div>

JS in dist/index.js

   const connectBtn = document.querySelector('.connect button');
   connectBtn.addEventListener('click', e => {
        init();
   })

init function


let selectedAccount;
let initialized = false; 
let faucetContract;
const faucetContractAddr = "contract Address";

const init = async () => {
   // checks if kaikas is available;
    if (window.klatyn !== 'undefined') {
        // checks connected network
        if (window.klaytn.networkVersion == '8217') return console.log('Change to BaoBab')
        // connects the user to kaikas
        const accounts = await klaytn.enable();
      // gets the connected account
        const account =  accounts[0];
        selectedAccount = accounts[0];
         // gets the KLAY balance of the provided address
        const balance = await cav.klay.getBalance(account);
        setUserInfo(account, Number(caver.utils.fromPeb(balance, 'KLAY')).toFixed(2));
        // subscribe to changes in account
        klaytn.on('accountsChanged',  async (accounts) => { 
             // function that sets users address and Klay balance
            setUserInfo(accounts[0], Number(caver.utils.fromPeb(await cav.klay.getBalance(accounts[0]), 'KLAY')).toFixed(2));
            selectedAccount = accounts[0];
        })
    }

     faucetContract = new cav.klay.Contract(abi, faucetContractAddr);
     initialized = true;

}

setUserInfo

function setUserInfo(account, balance) {
       connectBtn.innerText = addressShortener(account);
       balDisp.style.display = 'block'; 
       balDisp.innerHTML = `${balance} <span>KLAY</span>`;
}

Some things to put in mind

faucetContract = new cav.klay.Contract(abi, emlContractAddress)

This helps us set the instance of the faucet contract. This gives us access to call the contract functions as such we would need its ABI and contract address as seen above. The contractAddress which houses the contract code has to be included. While the ABI which is the json representation of all contracts function can be gotten here

After we have that set, when we click our connect wallet button, we should have this result

connectW.gif

B. Request Klay Button

HTML in dist/index.html

    <form class="submitForm">
                    <label for="Wallet">
                        <input type="text" name="addr" placeholder="0x00000000000000000000" class="addr" required>
                    </label>

                    <input type="submit" value="Request Klay" class="submitBtn">
     </form>

JS in dist/index.js

  submisionForm.addEventListener('submit', async (e) => {
    e.preventDefault();
    const addr = submisionForm.addr.value;
    const balance = await cav.klay.getBalance(addr);
    const formattedBal = formatValue(balance);

    getFaucetBal();    

    if (formatValue((contractBal)).toFixed(2) >= 20 && formattedBal < 20) {
        requestKlay(addr);
    } else if(formatValue(contractBal).toFixed(2) >= 20 && formattedBal > 20 || 
     formatValue(contractBal).toFixed(2) <= 20 && formattedBal > 20 ) {
    showModal(formattedBal.toFixed(2));
    } else if(contractBal < 20 * 10 ** 18) {
    showModalForContractBal(contractBal)  

    }

})

requestKlay Function

  const requestKlay = async (addr) => {
    if (!initialized) {
        await init();
    }

    // checks if the address is a klaytn address
   const recAddr = caver.utils.isAddress(addr);

   if(recAddr != true) {
    alert("Invalid Address");
    return;
   }

    //sends transaction to the contract
    faucetContract.send({from: selectedAccount, gas: 1500000, value: 0}, 'sendKlay', addr)
    .then( async result => {
         // stores events emitted to be displayed on the front-end
        const events = result.events.sent.returnValues;
        const _addr = events._addr;
        const _amount = events._amount;
        const _timeReq = events._timeReq;

        const balance = await cav.klay.getBalance(addr);
        const formattedBal = formatValue(balance);

        balDisp.style.display = 'block'; 
        balDisp.innerHTML = `${formattedBal.toFixed(2)} <span>KLAY</span>`;
        displayMessage("00",`Yay! ${addressShortener(_addr)} just recieved ${formatValue(_amount)} Test KLAY at exactly ${formatDate(_timeReq)}`);

    })
    .catch(err => {
        displayMessage("01", `Transaction reverted, see console for details`);
        console.log(err);
    })
}

displayMessage Function

  function displayMessage(messageType, message){
    const messages = {
        "00":`
                <p class="successMessage">${message}</p>
           `,
        "01": `
                 <p class="errorMessage">${message}</p>
        `
    }
    const notification = document.querySelector(".notification");
    notification.innerHTML += messages[messageType];
}

That's all for the major functionalities and our bApp is ready!

Also, the full source code is available here on GitHub for a full grasp and also to check some other functions used.

Testing our bApp

finalDemo.gif

The live link to our bApp is here

Conclusion

Congratulations on making it until the end!

In this tutorial, we did learn about creating a faucet (bApp) on Klaytn and interacting with it using caver-js. Now that you have fully gone through the process, it’s time to experiment more! Can you think of anything that could be improved? Here is an idea:

Currently, we do not have the faucet balance displayed on the front end. How about you display that in your bApp? Sounds cool right? Here is how to call the getFaucetBalance function:

    if (!initialized) {
        await init();
    }
    faucetContract.methods.getFaucetBalance().call()
    .then( result => {
        return result;
    })

Now with the return value, you can do whatever you want to do with it. You own it!!

If you have any questions, suggestions or comments, drop them below, or reach out to me on Twitter!

Happy Buidling!!!!