Transfer and approval of HRC-20 tokens via SOLIDITY smart contract
We will see how to interact with tokens using smart contracts using the Solidity language.
For this smart contract, we will create a truly virtual decentralized exchange where users can trade Hash Ahead using our newly deployed HRC-20 token.
For this tutorial, we'll use the code we wrote in the previous tutorial as a base. Our DEX will instantiate an instance of the contract in its constructor and do the following:
Swap tokens for HAH
Change HAH to token
We will start the decentralized exchange code by adding the simple HRC20 codebase:
Our new DEX smart contract will deploy HRC-20 and generate total supply:
So we now have our DEX with all its token reserves ready. The contract has two functions:
buy:Users can send HAH and get tokens in the exchange
sell:Users can decide to send tokens back to HAH
BUY function
Let's code the buy function. We first need to check the number of HAHs contained in the message and verify that the contract has enough tokens and that there are some HAHs in the message. If the contract has enough tokens, it will send the amount of tokens to the user and emit a Bought event.
Note that if we call the required function in case of error, the sent HAH will be directly reverted and returned to the user.
For simplicity, we only need to exchange 1 token for 1 Wei\
In the case of a successful purchase, we should see two events in the transaction: Token Transfer and Bought events.
SELL function
The function responsible for selling will first ask the user to approve the amount beforehand by calling the approve function. To approve a transfer, a user needs to invoke the HRC20Basic token instantiated by the decentralized exchange (DEX). To do this, you first need to call the token() function of the decentralized exchange (DEX) contract to retrieve the address where the DEX deploys the HRC20Basic contract named token. Then, we need to create an instance of this contract in the session and call its approve function. Next, we can call the DEX's sell function and exchange our tokens for HAH. For example, the process appears as follows in an interactive Brownie session:
Then when the sell function is called, we check if the transfer from the caller address to the contract address was successful, and then send the HAH back to the caller address.
If everything worked, you should see two events (Transfer and Sold) in the transaction, and the token balance and HAH balance updated.
In this tutorial, we learned how to check the balance and balance of HRC-20 tokens, and how to use the interface to call the Transfer and TransferFrom functions of the HRC20 smart contract.
contract DEX {
IHRC20 public token;
event Bought(uint256 amount);
event Sold(uint256 amount);
constructor() {
token = new HRC20Basic();
}
function buy() payable public {
// TODO
}
function sell(uint256 amount) public {
// TODO
}
}
function buy() payable public {
uint256 amountTobuy = msg.value;
uint256 dexBalance = token.balanceOf(address(this));
require(amountTobuy > 0, "You need to send some ether");
require(amountTobuy <= dexBalance, "Not enough tokens in the reserve");
token.transfer(msg.sender, amountTobuy);
emit Bought(amountTobuy);
}
Text
XPath: /pre[3]/code
#### Python in interactive brownie console...
# deploy DEX
dex = DEX.deploy({'from':account1})
# Call the buy function to exchange HAH for tokens
# 1e18 is 1 HAH denominated in wei
dex.buy({'from': account2, 1e18})
# Obtain the deployment address of the HRC20 token
# Deployed during DEX contract creation
# dex.token() return the deployment address of the token
token = HRC20Basic.at(dex.token())
# Call the approve function of the token
# Approve dex address as spender
# and how many of your tokens are allowed to spend
token.approve(dex.address, 3e18, {'from':account2})on
function sell(uint256 amount) public {
require(amount > 0, "You need to sell at least some tokens");
uint256 allowance = token.allowance(msg.sender, address(this));
require(allowance >= amount, "Check the token allowance");
token.transferFrom(msg.sender, address(this), amount);
payable(msg.sender).transfer(amount);
emit Sold(amount);
}
pragma solidity ^0.8.18;
interface IHRC20 {
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function allowance(address owner, address spender) external view returns (uint256);
function transfer(address recipient, uint256 amount) external returns (bool);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
}
contract HRC20Basic is IHRC20 {
string public constant name = "HRC20Basic";
string public constant symbol = "HRC";
uint8 public constant decimals = 18;
mapping(address => uint256) balances;
mapping(address => mapping (address => uint256)) allowed;
uint256 totalSupply_ = 10 ether;
constructor() {
balances[msg.sender] = totalSupply_;
}
function totalSupply() public override view returns (uint256) {
return totalSupply_;
}
function balanceOf(address tokenOwner) public override view returns (uint256) {
return balances[tokenOwner];
}
function transfer(address receiver, uint256 numTokens) public override returns (bool) {
require(numTokens <= balances[msg.sender]);
balances[msg.sender] = balances[msg.sender]-numTokens;
balances[receiver] = balances[receiver]+numTokens;
emit Transfer(msg.sender, receiver, numTokens);
return true;
}
function approve(address delegate, uint256 numTokens) public override returns (bool) {
allowed[msg.sender][delegate] = numTokens;
emit Approval(msg.sender, delegate, numTokens);
return true;
}
function allowance(address owner, address delegate) public override view returns (uint) {
return allowed[owner][delegate];
}
function transferFrom(address owner, address buyer, uint256 numTokens) public override returns (bool) {
require(numTokens <= balances[owner]);
require(numTokens <= allowed[owner][msg.sender]);
balances[owner] = balances[owner]-numTokens;
allowed[owner][msg.sender] = allowed[owner][msg.sender]-numTokens;
balances[buyer] = balances[buyer]+numTokens;
emit Transfer(owner, buyer, numTokens);
return true;
}
}
contract DEX {
event Bought(uint256 amount);
event Sold(uint256 amount);
IHRC20 public token;
constructor() {
token = new HRC20Basic();
}
function buy() payable public {
uint256 amountTobuy = msg.value;
uint256 dexBalance = token.balanceOf(address(this));
require(amountTobuy > 0, "You need to send some ether");
require(amountTobuy <= dexBalance, "Not enough tokens in the reserve");
token.transfer(msg.sender, amountTobuy);
emit Bought(amountTobuy);
}
function sell(uint256 amount) public {
require(amount > 0, "You need to sell at least some tokens");
uint256 allowance = token.allowance(msg.sender, address(this));
require(allowance >= amount, "Check the token allowance");
token.transferFrom(msg.sender, address(this), amount);
payable(msg.sender).transfer(amount);
emit Sold(amount);
}
}