Conflux Network: An Easy Technique To Deploy Smart Contracts With Hardhat And Js-conflux-sdk

Ephraim ChukwuEphraim Chukwu
8 min read

Table of contents

The Conflux Network is a newly scalable, secured, and inexpensive blockchain that accommodates users and developers alike. It allows for users with zero wallet balance to engage in blockchain activity, with the aid of sponsors who pay for some portion of the transaction fee. This engagement is easy also for developers who intends to build on the Conflux network. In this article, you will grasp how developer-friendly the Conflux network is from deploying two contracts; an ERC20 contract and a Vault contract – a token and a grant vault respectively, using hardhat and js-conflux-sdk.

SETTING UP THE PROJECT STRUCTURE

Initialize the project folder from your cli and install hardhat.

mkdir Vault

Navigate into the folder and run the following code.

yarn init -y

yarn add hardhat

This begins with installing hardhat and other dependencies that will help in writing and deploying the smart contract. This integrates hardhat into the project but there is one more step to complete this process.

yarn hardhat

hh.PNG

You can choose from the list of options provided.

This completes the whole process for setting up hardhat for your development. This brings along with it some dummy codes that explain how hardhat functions. Delete each files in the contracts, scripts, and tests folder if you don't need them as a guide.

CREATING THE ERC20 TOKEN CONTRACT

Create a TestToken.sol file directly under the contracts folder. This is where the smart contract to be deployed will be written. Ensure to install the Openzeppelin contracts in order to have quick pace in creating an ERC20 token. This token is needed to help us understand the Vault contract that allows for the creation of grants for different tokens.

To install, run:

yarn add @openzeppelin/contracts

Then write the contract,

// SPDX-License-Identifier: MIT
pragma solidity 0.8.9;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract TestToken is ERC20 {

    // At the point of deployment, 10000e18 MyToken is minted to the deployer.
    constructor() ERC20("MyToken", "MTK") {
        _mint(msg.sender, 10000e18);
    }
}

To be sure that your code works fine, compile it.

yarn hardhat compile

As a quick walkthrough, the contract above has a MIT license identifier, a fixed solidity version, and an import of the ERC20 openzeppelin standard. The name of the token is initialized at the constructor and 10000e18 worth of token was minted to the address deploying the contract.

Quite easy, right? How then do you deploy on the Conflux network?

Here comes the holy script. Before you jump on the script, install js-conflux-sdk.

yarn add js-conflux-sdk

DEPLOYING THE ERC20 TOKEN CONTRACT

The process of deploying contracts on Conflux is swift with the aid of the js-conflux-sdk. This SDK requires you to make provisions for some properties of the contract after it has been successfully compiled.

// Import the abi and bytecode from the contract artifact
import {abi, bytecode} from "../artifacts/contracts/TestToken.sol/TestToken.json";
import { Conflux } from 'js-conflux-sdk'

// Initialize constant variable
const TESTNET = "https://test.confluxrpc.com"

async function deployTestToken () {
    // Create an instance of Conflux testnet
    const conflux  = new Conflux({
        url: TESTNET,
        networkId: 1,
        logger: console,
      })

    // Establish wallet to make deployment.
    const wallet = conflux.wallet.addPrivateKey(process.env.CONFLUX_PRIVATE_KEY)

    // Create instance for the contract to be deployed
    const contractInstance = conflux.Contract({abi, bytecode})

    // Deploy contract and generate contract address
    const deploytx = await contractInstance.constructor().sendTransaction({from: wallet}).executed()

    console.log("Contractaddress is", deploytx.contractCreated)
    console.log("Deployed token tx is", deploytx.transactionHash)
}

deployTestToken().catch((error) => {
    console.error(error);
    process.exitCode = 1
})

To deploy therefore, ensure that your hardhat configuration is set up appropriately with the right Conflux testnet rpc for the network, the Solidity version, and your private key (This should be in your dotenv file and added in your gitignore to avoid pushing to the public domain). Then run the script with the following.

yarn hardhat run scripts/deployTestToken.ts --network conflux

Note:

  • process.env.CONFLUX_PRIVATE_KEY - This should be provided in your .env file to make the code run successfully.

  • scripts/deployTestToken.ts - This indicates the script to be deployed.

  • conflux - This is the name configured in the hardhatconfig file.

This is one phase of the article that explains the deployment of ERC20 token on the Conflux network. In the next phase, the Vault contract, uses the Witnet decentralized oracle to aid in the generation of random number. This helps to create a unique id for every created grant.

CREATING THE VAULT CONTRACT

Using the same project structure, create a Vault.sol file in the contracts file.

//SPDX-License-Identifier: MIT

pragma solidity 0.8.9;
import "./interfaces/IERC20.sol";
import "./interfaces/IWitnetRandomness.sol";

contract Vault {
    //---------------STATE VARIABLES-----------------//
    address owner;

    ///Randomness Sate Variable
    uint32 public randomness;
    uint256 public latestRandomizingBlock;
    IWitnetRandomness public witnet;

   //---------------STRUCT-----------------//
    struct GrantProps {
        address token;
        address receipient;
        uint64 timeline;
        uint256 amount;
    }

    //---------------MAPPING-----------------//
    mapping(uint32 => GrantProps) public grantProps;

    //---------------EVENTS -----------------//
    event createGrant(
        address token_,
        address receipient,
        uint256 amount,
        uint256 id
    );
    event grantClaimed(address receipient, uint256 amount, uint256 id);
    event grantRemoved(address token, uint256 amount, uint256 id);

    //---------------MODIFIER-----------------//
    modifier onlyOwner() {
        require(msg.sender == owner);
        _;
    }


  //--------------- CONSTRUCTOR -----------------//
    constructor(address _witnetRandomness) payable {
        owner = msg.sender;
        assert(address(_witnetRandomness) != address(0));
        witnet = IWitnetRandomness(_witnetRandomness);
        requestRandomNumber();
    }


    /// @notice create Grants for people to come and claim when the deadline is met
    /// @param _token the ERC20 tokens to pay
    /// @param _receipient The beneficiary of the ERC20 tokens
    /// @param _amount the number of tokens to claim
    /// @param timestamp the block.timestamp in which the grant is due for claim
    /// @dev will fail if transferFrom is not successful
    function createGrantFund(
        address _token,
        address _receipient,
        uint256 _amount,
        uint64 timestamp
    ) external payable onlyOwner {
        require(
            IERC20(_token).transferFrom(msg.sender, address(this), _amount),
            "transfer failed"
        );
        fetchRandomNumber();

        GrantProps storage GF = grantProps[randomness];
        GF.token = _token;
        GF.receipient = _receipient;
        GF.amount = _amount;
        GF.timeline = timestamp;
        requestRandomNumber();

        emit createGrant(_token, _receipient, _amount, randomness);
    }

    /// @notice remove Grants give access to owner to remove grants before the timestamp is met
    /// @param id the id of the grantProperties to remove

    function removeGrant(uint32 id) external onlyOwner {
        bool notlapse = hasTimelineExpired(id);
        require(!(notlapse), "timeelapse for receipient to withdraw");
        GrantProps storage GF = grantProps[id];
        address token_ = GF.token;
        uint256 amount_ = GF.amount;
        GF.amount = 0;
        bool success = IERC20(token_).transfer(msg.sender, amount_);
        require(success, "transfer failes");
        emit grantRemoved(token_, amount_, id);
    }

    /// @notice claim Grants for receipient to claim when the deadline is met
    /// @param id the unique number of the grant to claim
    /// @dev will fail if transferFrom is not successful
    function claimGrant(uint32 id) external {
        require(hasTimelineExpired(id), "Not yet time to withdraw");
        address receipient_ = grantProps[id].receipient;
        address token_ = grantProps[id].token;
        uint256 amount_ = grantProps[id].amount;
        grantProps[id].amount = 0;
        require(amount_ > 0, "NO fund for this grant");
        require(
            msg.sender == receipient_,
            "you are not the beneficiary to this grant"
        );
        bool success = IERC20(token_).transfer(msg.sender, amount_);
        require(success, "transfer fails");

        emit grantClaimed(msg.sender, amount_, id);
    }


    //--------------- VIEW FUNCTIONS --------------// 

    /// @notice This function check if time for particular grants has passed
    /// @param id the unique id of the grant to check
    function hasTimelineExpired(uint32 id) public view returns (bool) {
        GrantProps memory GF = grantProps[id];
        return (GF.timeline <= block.timestamp);
    }

    /// @notice This function returns the owner of this contract;
    /// @return returns the address of the owner.
    function getOwner() external view returns (address) {
        return owner;
    }

    /// @notice This function returns the properties of a grant;
    /// @param id the unique id of the grant to check
    /// @return returns the information of a particular grants.
    function getFundProps(uint32 id)
        external
        view
        returns (GrantProps memory)
    {
        return grantProps[id];
    }

     //--------------- INTERNAL FUNCTIONS --------------// 
    /// @notice This function initializes the random number, supposing the caller pays some token or CFX
    function requestRandomNumber() internal {
        latestRandomizingBlock = block.number;
        uint256 _usedFunds = witnet.randomize{value: msg.value}();
        if (_usedFunds < msg.value) {
            payable(msg.sender).transfer(msg.value - _usedFunds);
        }
    }

    /// @notice This function sets the randomness state variable
    function fetchRandomNumber() internal {
        assert(latestRandomizingBlock > 0);
        randomness =
            1 +
            witnet.random(type(uint32).max, 0, latestRandomizingBlock);
    }
}

The Vault contracts allows for owner of the contract to create and remove grant funds. When a new grant is to be created for a qualified recipient, the Witnet oracle helps generate a unique id, or number, for every grant. Recipients can afterwards claim grant if it is still within the timeline to claim.

import {abi, bytecode} from "../artifacts/contracts/Vault.sol/Vault.json";
import { Conflux, Drip } from 'js-conflux-sdk'


// Initialize relevant constant variables
const testnet = "https://test.confluxrpc.com"
const witnet = "cfxtest:aceh1wg5t0jyctjzsydvwktsvz596nf6ue18kkzpp3"
const tokenAddress = "cfxtest:acf00puuhggcdy2t4ywzruyr5n2f74yvs6psxamfrj"
const recipientAddr = "cfxtest:aarthy7b74x09687hxpe7fzs2kt0k3see2xcevy55r"

async function deployVault () {
    // Create an instance of Conflux testnet
    const conflux  = new Conflux({
        url: testnet,
        networkId: 1,
        logger: console,
      })

    // Establish wallet to make deployment.
    const wallet = conflux.wallet.addPrivateKey(process.env.CONFLUX_PRIVATE_KEY)

    // Create instance for the contract to be deployed using the abi and bytecode
    const contractInstance = conflux.Contract({abi, bytecode})

    // Deploy contract and generate contract address
    const deploytx = await contractInstance.constructor(witnet).sendTransaction({from: wallet, value: Drip.fromCFX(4),}).executed()

    console.log("Contractaddress is", deploytx.contractCreated)
    console.log("Deployed vault tx is", deploytx.transactionHash)

  // Create instance with abi and contract address
  const createGrant = conflux.Contract({ abi, address: deploytx.contractCreated })

  //call the createGrant function is called. Ensure that owner of the token approves the Vault contract to spend its token before the createGrantFund function can be called.
  const tx = await createGrant
    //@ts-ignore
    .createGrantFund(tokenAddress, recipientAddr, "2000", "1659954869")
    .sendTransaction({ from: wallet.toString() })
  console.log('Create grants', wallet.toString(), 'in txn ', tx)
}

deployVault().catch((error) => {
    console.error(error);
    process.exitCode = 1
})

In the deployment script for the Vault contract, the abi and bytecode was imported from the artifacts of the Vault contract and also the js-conflux-sdk. Then a series of constant variables were initialized, the testnet rpc url, witnet random address, and token whose created grant is to be created.

yarn hardhat run scripts/deployTestToken.ts --network conflux

CONCLUSION

In this article, we have extensively explored the creation of ERC20 token, integrate the Witnet decentralized oracle in a contract and deployed them all on the Conflux Network. If there are any blocker while following the tutorial, see the source code.

REFERENCES

https://hardhat.org/

https://docs.witnet.io/smart-contracts/witnet-randomness-oracle

https://github.com/Conflux-Chain/js-conflux-sdk

5
Subscribe to my newsletter

Read articles from Ephraim Chukwu directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Ephraim Chukwu
Ephraim Chukwu

Smart Contract Developer that loves to teach people about solidity and possible vulnerabilities in smart contracts.