How I Implemented Speed Run Ethereum Challenge 2: Token Vendor
Explaining my accepted solution to Speed Run Ethereum Challenge 2
What I'm Building
In this post, I'll show you how I implemented Challenge 2 of Speed Run Ethereum
๐ฉ Challenge 2: ๐ต Token Vendor ๐ค
๐ต Create
YourToken.sol
smart contract that inherits the ERC20 token standard from OpenZeppelin. Set your token to_mint()
1000 ( 10 * 18) tokens to themsg.sender
. Then create aVendor.sol
contract that sells your token using a payablebuyTokens()
function.
The Code
Deployed token contract: rinkeby.etherscan.io/address/0x2Bfd3d12bABf..
contract YourToken is ERC20 {
constructor() ERC20("Gold", "GLD") {
_mint(msg.sender, 1000 * 10 ** 18);
}
}
Deployed vendor contract:
rinkeby.etherscan.io/address/0x759c77f4f268..
contract Vendor is Ownable {
/// Reference to our ERC20 token contract
YourToken public yourToken;
/// Our token price
uint256 public constant tokensPerEth = 100;
// Events
event BuyTokens(address buyer, uint256 amountOfETH, uint256 amountOfTokens);
event SellTokens(address buyer, uint256 amountOfETH, uint256 amountOfTokens);
constructor(address tokenAddress) {
yourToken = YourToken(tokenAddress);
}
/// Allow users to buy tokens
function buyTokens() public payable {
// Validate the user sent eth
uint256 amountOfEth = msg.value;
require(amountOfEth > 0, "Send some ETH to buy tokens");
// Validate the vendor has enough tokens
uint256 amountOfTokens = amountOfEth * tokensPerEth;
uint256 vendorBalance = yourToken.balanceOf(address(this));
require(vendorBalance >= amountOfTokens, "Vendor does not have enough tokens");
// Send the tokens
address buyer = msg.sender;
(bool sent) = yourToken.transfer(buyer, amountOfTokens);
require(sent, "Failed to transfer token");
// Emit buy event
emit BuyTokens(buyer, amountOfEth, amountOfTokens);
}
/// Allow the owner to withdraw ETH
function withdraw() public onlyOwner {
// Validate the vendor has ETH to withdraw
uint256 vendorBalance = address(this).balance;
require(vendorBalance > 0, "Vendor does not have any ETH to withdraw");
// Send ETH
address owner = msg.sender;
(bool sent, ) = owner.call{value: vendorBalance}("");
require(sent, "Failed to withdraw");
}
/// Allow users to sell tokens back to the vendor
function sellTokens(uint256 amount) public {
// Validate token amount
require(amount > 0, "Must sell a token amount greater than 0");
// Validate the user has the tokens to sell
address user = msg.sender;
uint256 userBalance = yourToken.balanceOf(user);
require(userBalance >= amount, "User does not have enough tokens");
// Validate the vendor has enough ETH
uint256 amountOfEth = amount / tokensPerEth;
uint256 vendorEthBalance = address(this).balance;
require(vendorEthBalance > amountOfEth, "Vendor does not have enough ETH");
// Transfer tokens
(bool sent) = yourToken.transferFrom(user, address(this), amount);
require(sent, "Failed to transfer tokens");
// Transfer ETH
(bool ethSent, ) = user.call{value: amountOfEth }("");
require(ethSent, "Failed to send back eth");
// Emit sell event
emit SellTokens(user, amountOfEth, amount);
}
}
Deployed UI: https://thundering-process.surge.sh/
Checkpoint 2: ๐ตYour Token ๐ต
The first thing we need to do is create our ERC20 token. We're using the popular OpenZeppelin library to create the token.
contract YourToken is ERC20 {
constructor() ERC20("Gold", "GLD") {
_mint(msg.sender, 1000 * 10 ** 18);
}
}
What we need to do is update the address we're minting too. Otherwise this is boilerplate for minting ERC20 tokens.
Checkpoint 3: โ๏ธ Vendor ๐ค
For this checkpoint, I need to implement the ability to buy these new tokens
// Events
event BuyTokens(address buyer, uint256 amountOfETH, uint256 amountOfTokens);
/// Allow users to buy tokens
function buyTokens() public payable {
// Validate the user sent eth
uint256 amountOfEth = msg.value;
require(amountOfEth > 0, "Send some ETH to buy tokens");
// Validate the vendor has enough tokens
uint256 amountOfTokens = amountOfEth * tokensPerEth;
uint256 vendorBalance = yourToken.balanceOf(address(this));
require(vendorBalance >= amountOfTokens, "Vendor does not have enough tokens");
// Send the tokens
address buyer = msg.sender;
(bool sent) = yourToken.transfer(buyer, amountOfTokens);
require(sent, "Failed to transfer token");
// Emit buy event
emit BuyTokens(buyer, amountOfEth, amountOfTokens);
}
After all the validation, we transfer tokens with
address buyer = msg.sender;
(bool sent) = yourToken.transfer(buyer, amountOfTokens);
and then emit the buy event so the UI can update
emit BuyTokens(buyer, amountOfEth, amountOfTokens);
Then we add a withdraw function so the owner can withdraw ETH from the contract
Checkpoint 4: ๐ค Vendor Buyback ๐คฏ
In order to buy tokens back, we need to implement the "Approve" pattern for ERC20s.
The Approve Pattern
In order for a contract to move tokens in your wallet, you need to approve the contract to do so. This means that there needs to be 2 transactions in order for the vendor to buy your tokens back.
Your wallet approves the vendor contract and an associated number of tokens.
Your wallet sends the transaction to sell tokens back.
This is why using sites like Uniswap is a 2 step process. You first need to approve Uniswap to move the tokens for you and then send another transaction to actually swap the tokens.
The ERC20 token contract provided by OpenZeppelin comes with an Approve function. Update the UI to expose the button to approve. After the user approves, we can move the tokens out of their wallet back to the vendor. Then the vendor sends ETH to the caller.
/// Allow users to sell tokens back to the vendor
function sellTokens(uint256 amount) public {
// Validate token amount
require(amount > 0, "Must sell a token amount greater than 0");
// Validate the user has the tokens to sell
address user = msg.sender;
uint256 userBalance = yourToken.balanceOf(user);
require(userBalance >= amount, "User does not have enough tokens");
// Validate the vendor has enough ETH
uint256 amountOfEth = amount / tokensPerEth;
uint256 vendorEthBalance = address(this).balance;
require(vendorEthBalance > amountOfEth, "Vendor does not have enough ETH");
// Transfer tokens
(bool sent) = yourToken.transferFrom(user, address(this), amount);
require(sent, "Failed to transfer tokens");
// Transfer ETH
(bool ethSent, ) = user.call{value: amountOfEth }("");
require(ethSent, "Failed to send back eth");
// Emit sell event
emit SellTokens(user, amountOfEth, amount);
}
So What Did I Do?
I've now implemented a token vendor which can buy and sell the token that I created.
At this point, I updated the code to deploy to Rinkeby and deployed the UI to surge.
On to the next challenge.
See you then!