Demo Contract

(Beta)

Smart Contract Development Tutorial

This hands-on tutorial will guide you through the process of creating, testing, and deploying a customized ERC20 token using Axicov.

Prerequisites

Before starting, ensure you have:

  • Node.js v14.0.0 or later

  • npm v6.0.0 or later

  • Axicov CLI installed globally (npm install -g axicov-sdk)

  • A wallet with testnet ETH (for deployment)

Project Setup

Step 1: Initialize a New Project

# Create and navigate to project directory
mkdir rewards-token
cd rewards-token

# Initialize Axicov project
axicov init

Follow the interactive prompts:

  • Project Name: RewardsToken

  • Dependencies: Select OpenZeppelin Contracts, Ethers.js, and Chai

  • Network: Select Educhain

  • AI Recommendations: Select Yes

Step 2: Project Structure Overview

After initialization, you'll have this structure:

RewardsToken/
โ”œโ”€โ”€ contracts/           # We'll add our token contract here
โ”œโ”€โ”€ scripts/             # Deployment scripts will go here
โ”œโ”€โ”€ test/                # We'll add tests here
โ”œโ”€โ”€ .env                 # Configure with your private key
โ”œโ”€โ”€ axicov.config.ts     # Axicov configuration
โ”œโ”€โ”€ hardhat.config.ts    # Generated Hardhat configuration
โ””โ”€โ”€ package.json         # Project dependencies

Step 3: Configure Environment Variables

Create a .env file with your private key:

PRIVATE_KEY=your_wallet_private_key_here
ETHERSCAN_API_KEY=your_etherscan_api_key_here

โš ๏ธ Never commit your .env file to version control

Token Contract Development

Step 1: Generate a Token Contract

axicov generate erc20 --name RewardsToken

Answer the prompts:

  • Token Name: Rewards Token

  • Token Symbol: RWRD

  • Initial Supply: 10000000

  • Should the token be mintable? Yes

  • Should the token be burnable? Yes

Step 2: Review the Generated Contract

Examine the generated contract at contracts/RewardsToken.sol:

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

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import "@openzeppelin/contracts/security/Pausable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract RewardsToken is ERC20, ERC20Burnable, Pausable, Ownable {
    constructor() ERC20("Rewards Token", "RWRD") {
        _mint(msg.sender, 10000000 * 10 ** decimals());
    }

    function pause() public onlyOwner {
        _pause();
    }

    function unpause() public onlyOwner {
        _unpause();
    }

    function mint(address to, uint256 amount) public onlyOwner {
        _mint(to, amount);
    }

    function _beforeTokenTransfer(address from, address to, uint256 amount)
        internal
        whenNotPaused
        override
    {
        super._beforeTokenTransfer(from, to, amount);
    }
}

Step 3: Customize the Token Contract

Let's add a rewards distribution feature. Edit contracts/RewardsToken.sol:

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

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import "@openzeppelin/contracts/security/Pausable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract RewardsToken is ERC20, ERC20Burnable, Pausable, Ownable {
    // Rewards configuration
    uint256 public rewardRate = 100; // 100 tokens per distribution
    mapping(address => uint256) public lastRewardTimestamp;
    uint256 public rewardInterval = 1 days;
    
    event RewardClaimed(address indexed user, uint256 amount);
    
    constructor() ERC20("Rewards Token", "RWRD") {
        _mint(msg.sender, 10000000 * 10 ** decimals());
    }

    function pause() public onlyOwner {
        _pause();
    }

    function unpause() public onlyOwner {
        _unpause();
    }

    function mint(address to, uint256 amount) public onlyOwner {
        _mint(to, amount);
    }
    
    /**
     * @dev Claims available rewards for the caller
     * @return Amount of rewards claimed
     */
    function claimRewards() public whenNotPaused returns (uint256) {
        require(
            block.timestamp >= lastRewardTimestamp[msg.sender] + rewardInterval,
            "RewardsToken: too early to claim rewards"
        );
        
        lastRewardTimestamp[msg.sender] = block.timestamp;
        uint256 rewardAmount = rewardRate * 10 ** decimals();
        
        _mint(msg.sender, rewardAmount);
        
        emit RewardClaimed(msg.sender, rewardAmount);
        return rewardAmount;
    }
    
    /**
     * @dev Updates the reward rate
     * @param newRate New tokens per distribution
     */
    function setRewardRate(uint256 newRate) public onlyOwner {
        rewardRate = newRate;
    }
    
    /**
     * @dev Updates the reward interval
     * @param newInterval New interval in seconds
     */
    function setRewardInterval(uint256 newInterval) public onlyOwner {
        rewardInterval = newInterval;
    }

    function _beforeTokenTransfer(address from, address to, uint256 amount)
        internal
        whenNotPaused
        override
    {
        super._beforeTokenTransfer(from, to, amount);
    }
}

Testing the Contract

Step 1: Generate and Customize Tests

Axicov generated basic tests, but let's enhance them for our reward functionality. Edit test/RewardsToken-test.ts:

import { expect } from "chai";
import { ethers } from "hardhat";
import { time } from "@nomicfoundation/hardhat-network-helpers";

describe("RewardsToken", function () {
  it("Should deploy with correct name and symbol", async function () {
    const RewardsToken = await ethers.getContractFactory("RewardsToken");
    const token = await RewardsToken.deploy();
    await token.deployed();

    expect(await token.name()).to.equal("Rewards Token");
    expect(await token.symbol()).to.equal("RWRD");
  });

  it("Should have the correct initial supply", async function () {
    const RewardsToken = await ethers.getContractFactory("RewardsToken");
    const token = await RewardsToken.deploy();
    await token.deployed();

    const totalSupply = await token.totalSupply();
    expect(totalSupply).to.equal(ethers.utils.parseEther("10000000"));
  });

  it("Should allow minting new tokens", async function () {
    const [owner, addr1] = await ethers.getSigners();
    const RewardsToken = await ethers.getContractFactory("RewardsToken");
    const token = await RewardsToken.deploy();
    await token.deployed();

    const initialBalance = await token.balanceOf(addr1.address);
    const mintAmount = ethers.utils.parseEther("1000");
    
    await token.mint(addr1.address, mintAmount);
    
    const newBalance = await token.balanceOf(addr1.address);
    expect(newBalance).to.equal(initialBalance.add(mintAmount));
  });

  it("Should allow burning tokens", async function () {
    const [owner] = await ethers.getSigners();
    const RewardsToken = await ethers.getContractFactory("RewardsToken");
    const token = await RewardsToken.deploy();
    await token.deployed();

    const initialBalance = await token.balanceOf(owner.address);
    const burnAmount = ethers.utils.parseEther("100");
    
    await token.burn(burnAmount);
    
    const newBalance = await token.balanceOf(owner.address);
    expect(newBalance).to.equal(initialBalance.sub(burnAmount));
  });
  
  // New tests for reward functionality
  it("Should not allow claiming rewards before interval passes", async function() {
    const [owner, user] = await ethers.getSigners();
    const RewardsToken = await ethers.getContractFactory("RewardsToken");
    const token = await RewardsToken.deploy();
    await token.deployed();
    
    // Try to claim rewards immediately
    await expect(token.connect(user).claimRewards())
      .to.be.revertedWith("RewardsToken: too early to claim rewards");
  });
  
  it("Should allow claiming rewards after interval passes", async function() {
    const [owner, user] = await ethers.getSigners();
    const RewardsToken = await ethers.getContractFactory("RewardsToken");
    const token = await RewardsToken.deploy();
    await token.deployed();
    
    // Fast forward time by 1 day + 1 second
    await time.increase(86401);
    
    // Now claim should work
    await expect(token.connect(user).claimRewards())
      .to.emit(token, "RewardClaimed")
      .withArgs(user.address, ethers.utils.parseEther("100"));
      
    // User should have received tokens
    expect(await token.balanceOf(user.address))
      .to.equal(ethers.utils.parseEther("100"));
  });
  
  it("Should allow owner to change reward rate", async function() {
    const [owner] = await ethers.getSigners();
    const RewardsToken = await ethers.getContractFactory("RewardsToken");
    const token = await RewardsToken.deploy();
    await token.deployed();
    
    // Change reward rate to 200
    await token.setRewardRate(200);
    expect(await token.rewardRate()).to.equal(200);
  });
});

Step 2: Run the Tests

axicov test

You should see output similar to:

๐Ÿงช Running tests...
โœ… Contracts compiled successfully!
โœ… Tests completed!

  RewardsToken
    โœ“ Should deploy with correct name and symbol
    โœ“ Should have the correct initial supply
    โœ“ Should allow minting new tokens
    โœ“ Should allow burning tokens
    โœ“ Should not allow claiming rewards before interval passes
    โœ“ Should allow claiming rewards after interval passes
    โœ“ Should allow owner to change reward rate

  7 passing (3.45s)

โœ… All tests passed successfully!

Deployment

Step 1: Deploy to Testnet

Ensure your .env file contains your private key, then run:

axicov deploy RewardsToken --network sonicblaze

You'll see output like:

๐Ÿš€ Deploying RewardsToken to sonicblaze...
โœ… Contract compiled successfully!
โœ… Contract deployed successfully!
๐Ÿ“ Contract Address: 0x8B45D5A8617E980C9cDAb21c16eDC4C6134AC8D0
๐Ÿ”— Explorer: https://sonicblaze.explorer/address/0x8B45D5A8617E980C9cDAb21c16eDC4C6134AC8D0
โœ… Deployment info saved to deployments/RewardsToken-sonicblaze.json

Step 2: Verify the Contract

axicov verify RewardsToken --network sonicblaze

Output:

๐Ÿ” Verifying RewardsToken on sonicblaze...
   Contract address: 0x8B45D5A8617E980C9cDAb21c16eDC4C6134AC8D0
โœ… Contract verified successfully!
๐Ÿ”— Explorer: https://sonicblaze.explorer/address/0x8B45D5A8617E980C9cDAb21c16eDC4C6134AC8D0

Interacting with the Deployed Contract

Using Hardhat Console

# Start Hardhat console connected to Sonic Blaze
npx hardhat console --network sonicblaze
// Get contract instance
const RewardsToken = await ethers.getContractFactory("RewardsToken");
const token = await RewardsToken.attach("0x8B45D5A8617E980C9cDAb21c16eDC4C6134AC8D0");

// Check total supply
const totalSupply = await token.totalSupply();
console.log(`Total Supply: ${ethers.utils.formatEther(totalSupply)} RWRD`);

// Get signer
const [signer] = await ethers.getSigners();

// Check your balance
const balance = await token.balanceOf(signer.address);
console.log(`Your Balance: ${ethers.utils.formatEther(balance)} RWRD`);

// Transfer tokens
await token.transfer("0xRecipientAddress", ethers.utils.parseEther("1000"));

Using Block Explorer

  1. Navigate to the contract on the Sonic Blaze explorer

  2. Go to the "Write Contract" tab

  3. Connect your wallet

  4. Use the UI to interact with functions like claimRewards, transfer, etc.

Next Steps

Congratulations! You've built, tested, and deployed a custom ERC20 token with a rewards mechanism. Here are some ways to extend your project:

Enhance Token Functionality

  • Add time-locked vesting schedules

  • Implement token staking for enhanced rewards

  • Create a whitelist system for certain operations

Build a Frontend

# Generate a React frontend for your token
axicov generate frontend --contract RewardsToken --framework react

Create Additional Contracts

# Add a staking contract that works with your token
axicov generate defi --type staking --name RewardsStaking

Deploy to Production

When you're ready for mainnet:

axicov deploy RewardsToken --network mainnet

Troubleshooting

Common Issues

  • Transaction Failures: Ensure you have enough testnet ETH for gas

  • Verification Errors: Try waiting a few minutes after deployment

  • Testing Timeouts: Reduce the time interval in tests for faster execution

Getting Help

If you encounter issues:

# Check Axicov version
axicov --version

# Enable verbose logging
axicov deploy RewardsToken --network sonicblaze --verbose

Last updated