In this article, we will take a deep dive into the world of Solidity by creating a Simple Tax Token. This tutorial will help you understand the basics of Solidity programming and how to build smart contracts. By the end of this tutorial, you will have a solid understanding of Solidity and be able to apply your knowledge to create your own smart contracts.
So if you’re ready to start your journey into the exciting world of Solidity, let’s dive in!
Setting up the environment
To set up your development environment follow the Hardhat getting started guide. We will be using Typescript for this project.
Once you are set up, you can install the @openzeppelin/contracts library with the following command.
yarn add --dev @openzeppelin/contracts
Creating the Simple Tax Token contract
We will be creating a smart contract that implements the ERC-20 standard. Our contract includes a tax function, which automatically deducts a percentage of the token value from each transaction that takes place on the blockchain.
The tax is collected automatically and is then distributed to a specified address (fund), such as a charity or a development fund.
We will inherit contracts from the @openzeppelin/contracts library in order to improve the safety and speed of the development process.
We will only be creating two tests for this tutorial.
- It should set the fund address at deployment.
- It should transfer 5% to the fund address.
import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"
import { expect } from "chai"
import { ethers } from "hardhat"
describe("SimpleVoting", function () {
async function deploy() {
const [deployer, fund, target] = await ethers.getSigners()
const Contract = await ethers.getContractFactory("SimpleTax")
const contract = await Contract.deploy(fund.address)
await contract.deployed()
return { contract, deployer, fund, target }
}
describe("deployment", function () {
it("should set the fund address", async function () {
const { contract, fund } = await loadFixture(deploy)
expect(await contract.fund()).to.eq(fund.address)
})
})
describe("transfer", function () {
it("should transfer 5% to the fund's address", async function () {
const { contract, deployer, fund, target } = await loadFixture(deploy)
const amount = ethers.utils.parseEther("100")
await expect(contract.transfer(target.address, amount)).to.changeTokenBalances(contract,
[deployer, fund, target],
[amount.mul(-1), amount.mul(5).div(100), amount.mul(95).div(100)]
)
})
})
})
In our contract, we will have to do two things.
fund
In our constructor, we will add an argument for the fund. You can see in the deploy function of our test case that we call Contract.deploy
with the fund address as an argument. We will then set the fund in the contract.
If you look at the declaration of the fund in the contract, you will see that it is immutable
. This simply means that it can not be changed once initialized.
_transfer
_transfer
is an internal function. The external transfer
function calls it. It is responsible for changing the balances. It is in this _transfer
function that we calculate the amount that should be transferred to the recipient and the fund.
In solidity, there are no such things as decimals, so we can not use a fixed percentage i.e. 5/100 = 0.05, this number doesn’t exist in solidity.
In our test case, we are transferring 100 tokens. In solidity, 100 is represented by 100000000000000000000. Given the size of that number, we can divide it by 100 and multiply it by 10.
Last, but not least, we call the super._transfer
function (twice). This is calling the _transfer
function declared in the OpenZeppelin ERC20.sol contract. We call it once to transfer funds to the recipient, and once to transfer funds to the fund.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract SimpleTax is ERC20 {
address public immutable fund;
constructor(address fund_) ERC20("SimpleTax", "STX") {
_mint(msg.sender, 1000 * 10 ** decimals());
fund = fund_;
}
function _transfer(
address sender,
address recipient,
uint256 amount
) internal virtual override {
uint tax = (amount / 100) * 5; // 5% tax
super._transfer(sender, recipient, amount - tax);
super._transfer(sender, fund, tax);
}
}
Congratulations! We wrote a simple tax token in 25 lines of code. Not bad. The code is available on GitHub.
Production
It is important to note that in order to make this contract work correctly on a decentralized exchange, we would need to add a whitelist, in order to not tax certain transfers involving the liquidity pair. More on this in a future article.
If this article was helpful, join our Telegram group! It is a web3 space to discuss ideas, build projects and help troubleshoot code.
I’m a multidisciplinary founder, project manager, solidity + react developer, husband, and family+friends cook.
Got a project idea? Reach out to me on Twitter or join the Telegram group.
New to trading? Try crypto trading bots or copy trading on best crypto exchanges
Join Coinmonks Telegram Channel and Youtube Channel get daily Crypto News