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
, andChai
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
Navigate to the contract on the Sonic Blaze explorer
Go to the "Write Contract" tab
Connect your wallet
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