ERC-20 token
ERC-20 tokens are blockchain-based assets, issued on all EVM compatible blockchain networks such as Hedera, that have value and can be sent and received. These tokens are fungible, meaning they can be exchanged with another token of the same type because they have identical properties and there is an equal value. For example, the ERC-20 token of Alice is exactly the same as the ERC-20 token of Bob. They can exchange their token without consequences.
Examples of fungible assets are currencies, stocks of a company, bonds, gold and other precious metals.
The ERC-20 smart contract is the perfect standard to organize a crowdsale and let companies raise funds to launch their project.
ERC-20 smart contract features
An ERC-20 smart contract is used to create fungible tokens and bring them to the blockchain. This process is called minting. It also keeps track of the total supply as well as the balances of users as they exchange their tokens.
The ERC-20 smart contract on the SettleMint platform has the following features:
- Custom name, symbol and initial supply that can be chosen by the user.
- Minting capabilities that let the admin of the smart contract mint (i.e create) new tokens.
- Pausable capabilities that let the admin pause the contract in case of emergency.
- Burnable capabilities that let users burn (i.e destroy) their token.
By default, the account that deploys the ERC-20 smart contract gets 1,000,000 tokens. You can change this behaviour by modifying the “constructor” in “GenericToken.sol”. If you do not mint tokens in the constructor, make sure to mint some after the deployment.
contract GenericToken is ERC20, ERC20Burnable, Pausable, AccessControl {
constructor(string memory name_, string memory symbol_) ERC20(name_, symbol_) {
_setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
_mint(msg.sender, 1000000 * 10**decimals());
}
Deploying an ERC-20 smart contract
To set the name and symbol for your token, go to the “deploy” folder and in “00_Deploy_GenericToken.ts”, change the values in “args” in the “deploy” function.
await deploy('GenericToken', {
from: deployer,
args: ['GenericToken', 'GT'],
log: true,
});
As soon as you are happy with the changes you made, just click on “deploy” in the “task runner” of the IDE and after a few seconds, your ERC-20 smart contract should be deployed on the network of your choice.
The “GenericToken.ts” script in the “test” folder showcases all the functionalities of the ERC-20 standard. It shows you how to use the smart contract in your dapp.
Note: On the Hedera Mainnet time to time you can get timeouts errors(Service Unavailable
in the IDE UI). JSON RPC relay in Hedera is a complex software which relies on multiple components (the consensus nodes, and the mirror node). One Ethereum TX can generate more than ten Hedera txs and if one of the tx fails due to the connection timeout, it can cause the problem. However, it's likely to have the problem with the smart contract deployment only. If you can keep trying a couple of times and then once your contract is deployed successfully. The subsequent contract calls should not have this kind of problem.
ERC-20 with meta transactions
The SettleMint platform also provides an ERC-20 template set with meta transaction capabilities. Meta transactions are used to fill the need for EVM-based contracts to accept transactions from externally owned accounts that do not have Matic to pay for gas. In short, implementing such an interface omits the need for the end user to pay for gas. With meta transactions, gas is paid by a gas relayer
and a smart contract, known as a trusted forwarder
, forwards the transactions to the recipient contract, i.e the one that the end user wants to interact with in the first place.
Setting up meta transactions
When looking at the GenericTokenMeta.sol smart contract in the IDE we can see the main differences with the basic ERC20 being the following:
...
constructor(
...
address trustedForwarder
) ERC20(name_, symbol_) ERC2771Context(trustedForwarder_)
...
function _msgSender() internal view override(Context, ERC2771Context) returns (address sender) {
sender = ERC2771Context._msgSender();
}
function _msgData() internal view override(Context, ERC2771Context) returns (bytes calldata) {
return ERC2771Context._msgData();
}
Let’s unpack this:
- We pass in the constructor an Ethereum address, the
trustedForwarder
, to theERC2771Context
constructor. This enables the smart contract to accept transactions coming from theTrusted Forwarder
. _msgSender()
function is kind of an alias formsg.sender
. When called it returnsmsg.sender
for regular transactions, but for meta transactions it returns the end user (rather than therelayer
or thetrusted forwarder
)._msgData()
function is again an alias of some sort for msg.data. For meta transactions it will return the raw transaction data from the perspective of the end user rather than therelayer
.
Sending a meta transaction to the ERC-20 contract
Sending a meta transaction is slightly different than sending a regular transaction, but the template set comes with an example in which 10 tokens are transferred between two wallets without Matic.
First, to send meta transactions using the forwarder
, we have to define three objects called EIP712Domain
, domain
and types
as follows:
const EIP712Domain = [
{ name: 'name', type: 'string' },
{ name: 'version', type: 'string' },
{ name: 'chainId', type: 'uint256' },
{ name: 'verifyingContract', type: 'address' },
];
const domain = {
name: 'MinimalForwarder',
version: '0.0.1',
chainId: parseInt(await getChainId()),
verifyingContract: forwarderAddress,
};
const types = {
EIP712Domain,
ForwardRequest: [
{ name: 'from', type: 'address' },
{ name: 'to', type: 'address' },
{ name: 'value', type: 'uint256' },
{ name: 'gas', type: 'uint256' },
{ name: 'nonce', type: 'uint256' },
{ name: 'data', type: 'bytes' },
],
};
The name and version of domain have to match those of the forwarder (see the contract Forwarder.sol
).
Then, we need to generate the function data as follows:
const functionData = token.interface.encodeFunctionData('transfer', [walletTwoAddress, ethers.utils.parseUnits('10')]);
In that expression, transfer
is the ERC-20 function we want to execute, walletTwoAddress
is the account that will receive the tokens and the last parameter is the amount of tokens to be transferred.
The last step before sending the meta transaction is to create and sign the message containing the underlying transaction as follows:
const walletOneNonce = Number(await read('Forwarder', 'getNonce', walletOneAddress));
const req = {
from: walletOneAddress,
to: token.address,
value: '0',
gas: '100000',
nonce: walletOneNonce,
data: functionData,
};
const signedData = ethSigUtil.signTypedData({
privateKey: walletOne.getPrivateKey(),
data: {
types: types,
domain: domain,
primaryType: 'ForwardRequest',
message: req,
},
version: ethSigUtil.SignTypedDataVersion.V4,
});
Finally, once the transaction is signed, we can send it to the forwarder
:
await forwarder.execute(req, signedData, { gasLimit: '100000' });
ERC-20 Crowdsale
Crowdsales allow the participants of a network to purchase tokens, usually in exchange for ether. A crowdsale can take many different forms, and our powerful templateset allows you the flexibility to shape and deploy the crowdsale according to your needs.
Stage 1: Creating a supply of the tokens being sold
This is done in the templateset by deploying an ERC-20 contract.
Stage 2: Configuring and deploying the crowdsale
When deploying a crowdsale, there are different specifications to be considered:
- Crowdsale rates - price of the token being sold
- Validation - Who can actually purchase the tokens?
- Distribution - When does the distribution of the tokens actually take place?
- Token emission - Who actually transfers the tokens to the beneficiaries?
- Phases of the crowdsale - Are all the tokens going to be distributed in one go or are they going to be distributed in different phases?
The ERC-20 crowdsale templateset we provide is designed to give you full flexibility to modify these different parameters according to your requirements.
Price of the token being sold
In our Crowdsale templateset, you can fix the price of the token in the _usdRate
field on the CrowdSale
contract.
Its value is the number of tokens for one USD.
Validation
Validation refers to ensuring that the buyers meet certain conditions before they can purchase tokens.
Our templateset provides KYC / AML whitelisting capabilities out of the box. The buyers must be whitelisted before they can purchase tokens.
This is implemented using openzepellin’s AccessControl
. The address with the DEFAULT_ADMIN_ROLE
grants WHITELISTED_ROLE
to buyers before they can purchase tokens.
Purchase of tokens
The buyers can be allocated the tokens in two ways.
The first method is where the whitelisted buyers can send Matic to the contract directly. The equivalent amount of tokens will be calculated automatically. The calculation is done by first converting the Matic to USD leveraging Chainlink Oracle. Then, the USD is converted to the equivalent amount of tokens using the _usdRate
set in the crowdsale.
Alternatively, the admin of the crowdsale can directly allocate tokens to certain buyers by calling the externalBuyTokens
function. This function can only be called by addresses which have been granted DEFAULT_ADMIN_ROLE
. Here the tokens get transferred from the sender of the transaction to the beneficiary address listed as a parameter. The reason for providing such a function is to support allocation of tokens to buyers:
- Who do not know how to send Matic to a contract
- Support payments in other forms other than Matic. For example, fiat payments can be supported. The buyer would send fiat to the crowdsale admin. The crowdsale admin would then call this function to allocate the equivalent amount of tokens to the buyer.
Distribution
Our templateset gives you flexibility over when you want to actually credit the tokens to the beneficiary.
This can be done immediately after the tokens have been purchased or a certain amount of time after the purchase, called vesting period.
To transfer the tokens immediately, set the _vestingEndDate
field on the CrowdSale
contract to 0
while deploying the contract.
When the vesting end date is not set, the tokens purchased get transferred immediately to the beneficiary’s address.
Transfering the tokens a certain amount of time after the purchase is achieved using two pieces:
Setting the _vestingEndDate
on the contract to the timestamp at the end of the vesting period
Deploying a VestingVault
contract and initializing _vestingVault
field on the CrowdSale
contract to it’s address
The beneficiary in this case is the VestingVault
contract. All the tokens purchased by buyers get stored in the VestingVault
contract.
A point to note here is that to store tokens in the VestingVault
contract, the sender of the transaction must have a VAULT_CONTROLLER_ROLE
of the VestingVault
enabled. This can be seen in the deploy steps, explained below.
The buyers withdraw the tokens they bought after the vesting period has ended by calling the release
method on the VestingVault
of the crowdsale contract.
Token emission
Token emission refers to the actual transaction of transfer of tokens to the beneficiary. In our templateset, the tokens are transferred from the crowdsale contract itself to the beneficiary.
The actual transfer from the contract to the beneficiary happens in the deliverTokens
function on the CrowdSale
contract.
This is the standard method for token emission.
There are 2 other patterns of token emission - you can read more about them here: https://docs.openzeppelin.com/contracts/2.x/crowdsales#token-emission
Phases of the crowdsale
Crowdsales are divided into two broad categories. Crowdsales where the tokens are distributed all in one sale. Or, crowdsales with different phases, where in each phase of the crowdsale, the token being sold usually has a different price. There is also a limit to the number of tokens that can be sold in a particular phase.
To run a crowdsale where there is only one phase, you need to deploy only one CrowdSale
contract.
To orchestrate a crowdsale with multiple phases, you need to deploy multiple CrowdSale
contracts. Here, you deploy a CrowdSale
contract for each phase.
Deploy steps:
Now that we know the different specifications needed for a crowdsale, we will walk you through how we have configured them and deployed the templateset example.
We are deploying an ExampleCrowdSale
which is going to sell ExampleToken
s at the rate of 250 tokens for 1 USD. The tokens are not going to be transferred to the beneficiary directly, they are going to be vested for 30 months in the ExampleVestingVault
.
Here we are going to deploy:
-
An ERC20 token, the token to be sold In step
00_deploy_token
we deploy theExampleToken
which is the actual token to be sold. -
A
VestingVault
contract to store the tokens for the vesting period In step02_deploy_vestingvault
we deploy theExampleVestingVault
to store the tokens for the vesting period. -
An
CrowdSale
contract which is the actual crowdsale In step03_deploy_crowdsale
we deploy theExampleCrowdSale
contract which is the actual crowdsale. We pass the address of the token to be sold, the address of the vesting vault, along with other parameters like the USD rate. -
Enable the
ExampleCrowdSale
to store tokens in theExampleVestingVault
In step04_enable_crowdsale
we grant theExampleCrowdSale
crowdsale aVAULT_CONTROLLER_ROLE
which allows the crowdsale to store tokens in the vesting vault. -
Transfer tokens to the
ExampleCrowdSale
to start the crowdsale process We transfer 100 million Example Tokens to the crowdsale to be sold.
Integration with the Middleware
Working with complex or large data in your dApp can be a challenge. In the SettleMint platform we provide you with a middleware solution that allows you to index and query this data easily and efficiently.