# Token Vendor | Web3.0 dApp Dev 0x08

By [Web3dAppDevCamp](https://paragraph.com/@apecoder) · 2022-03-21

---

> Authors: [ken](https://github.com/kenspirit), [msfew](https://github.com/fewwwww), [Snowyaya](https://github.com/Snowyaya)

Token Vendor is a vending machine that's combined of scaffold-eth and BuidlGuidl. This tutorial will walk you through how the project is implemented. To do that, we split the entire project to five smaller parts and it's verrifiable whether each part functions successfully after it's implemented.

0x01 Installation and Local Configuration
-----------------------------------------

**Step1. Open the terminal window and clone scaffold-eth base code**

    git clone https://github.com/scaffold-eth/scaffold-eth-typescript-challenges.git challenge-2-token-vendor
    
    cd challenge-2-token-vendor
    
    git checkout challenge-2-token-vendor
    

**Step2. Install dependencies**

**Step3. Prepare the enviroment** We need three terminal windows for this step, and each will execute their own command consecutively. The three commands will be used to:

    yarn chain (start the `hardhat` backend and local nodes)
    
    yarn deploy (compile, deploy the smart contract and push to the front-ended project reference)
    
    yarn start (React front-ended App)
    

Terminal

After commands executed

Command Completed

App UI

0x02 Prepare MetaMask Account
-----------------------------

Generate Account

View Account

Change the `DEBUG` statement in `packages/hardhat-ts/hardhat.config.ts` to `const DEBUG = true;`; When viewing the account information, the private key of the wallet will aso be displayed, which can be imported to MetaMask.

**Configure MetaMask:** If you do not have MetaMask local network, configure by doing so:

*   network name: `Localhost 8545`
    
*   new RPC URL: `http://localhost:8545`
    
*   chain ID: `31337`
    
*   currency symbol： `ETH`
    

Remember to check the value of chain ID. It should be `1337` normally, but for `hardhat`, it is `31337`. If it's not changed, you might end up having [this issue](https://hardhat.org/metamask-issue.html), which couldn't send transactions.

0x03 Launch your own token
--------------------------

### 3.1 Token smart contract

The Token in Ethereum is actually smart contract. The location of the contract file we need to customize is: `packages/hardhat-ts/contracts/YourToken.sol`.

    pragma solidity >=0.8.0 <0.9.0;
    // SPDX-License-Identifier: MIT
    
    // inherited from OpenZeppelin's ERC20 Token standard
    import '@openzeppelin/contracts/token/ERC20/ERC20.sol';
    
    // learn more: https://docs.openzeppelin.com/contracts/3.x/erc20
    
    contract YourToken is ERC20 {
      // ToDo: add constructor and mint tokens for deployer,
      //       you can use the above import for ERC20.sol. Read the docs ^^^
    
      // constructor
      constructor() public ERC20('Gold', 'GLD') {
        // _mint() 1000 * 10 ** 18 to msg.sender
        _mint(msg.sender, 1000 * 10**18);
      }
    }
    

By inheriting the ERC20 standard (`is ERC20`), this Token has basic functions such as basic transfer, querying the balance of the Token held by the account. We only need to name the Token and specify its initial total amount to use.

*   token symbol: `GLD`
    
*   token name: `Gold`
    
*   initial amount: `1000 * 10**18` (1000 Token)
    

`10**18` means 10 to the 18th degree, where there are 18 zeroes. Why use such a large number, `1000000000000000000`, to represent a Token? The reason is the same as we use **penny** in calculating **dollar**. The language EVM and Solidity can only handle integers, so in order to facilitate the Token to be cut into small units for circulation, we need a smaller unit, and the inherited ERC20 defines the length of this decimal place to be 18.

### 3.2 Deploy and transfer Token

After the contract is written, change the statement in `packages/hardhat-ts/deploy/00_deploy_your_token.ts`, fill in the prepared account, and test whether the transfer can be done successfully:

      // Todo: transfer tokens to frontend address
      const result = await yourToken.transfer(
        "0xC0802222dB0F31d63dc85d9C8CAa00485715A13c", ethers.utils.parseEther("1000"));
    

Note that if we want to transfer 1000 Token, we do not directly pass 1000 to the `transfer` function, but need to go through `ethers.utils.parseEther` to convert it into the number that is handleable by the contract.

After modification, deploy by command `yarn deploy --reset`.

Deploy Token

If deployed successfully, call the `balanceOf` function through the `Debug` page in the browser to check whether the address after the transfer has the corresponding number of Tokens.

Account Balance

You can also try to transfer tokens from the current account to another account. However, before that, the wallet of the current account needs to have few ETH.

Press the button **Grab funds from the faucet** on the current page to get some.

Grab ETH

Another way to get more ETH is through the [faucet.paradigm.xyz](https://faucet.paradigm.xyz/) page to authorize Twitter to log in and fill in the address to request.

Through the `transfer` function on the Debug page, we can transfer Token. The amount to be filled in needs to be converted. In the meantime, you can also click the **Send** button and modify it again in the confirmation box that pops up in MetaMask.

**Note**: Due to the contract changes or the time constraints, you may need to deploy multiple times and cannot complete the test at one time. Changes to the local network will cause the number of transactions in the account to be different from the number of transactions on MetaMask. When initiating a transaction, MetaMask is likely to alert you to the errors like **Nonce too high. Expected nonce to be 0 but got x.**. If so, you need to prepare a new account, or delete the account from MetaMask and try importing it again.

Transfer Another

Confirm Transfer

0x04 Build a vending machine Vendor
-----------------------------------

Now we start to implement the smart contract of the vending machine Vendor. It's framework is in the file `packages/hardhat-ts/contracts/Vendor.sol`.

### 4.1 Define the Token price

Token trading requires first determining the exchange rate between Token and ETH.

In the contract, we can define how many tokens an ETH can buy through the constant `tokensPerEth`. Remember that 1 Token means 1 \* 10\*\*18. Since the free ETH that the test account can get may be very few, this number may be set larger.

    uint256 public constant tokensPerEth = 10000;
    

### 4.2 buyTokens function

The logic of buying Token is very simple. It is to calculate how many Tokens the sender of the transaction can get from the vending machine according to the amount of ETH in the transaction. The complete contract after implementing the `buyTokens` function is as follows:

    pragma solidity >=0.8.0 <0.9.0;
    // SPDX-License-Identifier: MIT
    
    import '@openzeppelin/contracts/access/Ownable.sol';
    import './YourToken.sol';
    
    contract Vendor is Ownable {
      YourToken yourToken;
    
      uint256 public constant tokensPerEth = 10000;
    
      event BuyTokens(address buyer, uint256 amountOfEth, uint256 amountOfTokens);
    
      constructor(address tokenAddress) public {
        yourToken = YourToken(tokenAddress);
      }
    
      // ToDo: create a payable buyTokens() function:
      function buyTokens() public payable returns (uint256 tokenAmount) {
        require(msg.value > 0, 'ETH used to buy token must be greater than 0');
    
        uint256 tokenToBuy = msg.value * tokensPerEth;
    
        // check if the Vendor Contract has enough amount of tokens for the transaction
        uint256 vendorBalance = yourToken.balanceOf(address(this));
        require(vendorBalance >= tokenToBuy, 'Vendor has not enough tokens to sell');
    
        // Transfer token to the msg.sender
        bool success = yourToken.transfer(msg.sender, tokenToBuy);
        require(success, 'Failed to transfer token to user');
    
        emit BuyTokens(msg.sender, msg.value, tokenToBuy);
    
        return tokenToBuy;
      }
    }
    

Key parts of the contract:

1.  `payable` modifier:
    
    > indicates that this function can receive ETH. It is necessary to mark this function as payable because buying Token requires transferring ETH to Vendor.
    
2.  the statement `require(msg.value > 0, 'ETH used to buy token must be greater than 0');`
    
    > constraint checking. Obviously, to buy Token, the amount of incoming ETH must be greater than 0. The value of `msg.value` is the amount of ETH.
    
3.  `address(this)` function call:
    
    > get the address of this contract
    
4.  `emit BuyTokens(msg.sender, msg.value, tokenToBuy)`：
    
    > trigger the `BuyTokens` event to record the address, cost, and purchase quantity of the purchased Token. Evens are available as EVM logging.
    

### 4.3 withdraw function

After the vending machine Vendor sells the Token, the buyer's ETH slowly accumulates into the Vendor account. So how do you get the ETH out of the contract? At this point we need to implement the `withdraw` function:

    // ToDo: create a withdraw() function that lets the owner withdraw ETH
    function withdraw() public onlyOwner {
      uint256 ethToWithdraw = address(this).balance;
      require(ethToWithdraw > 0, 'No ETH to withdraw');
    
      payable(msg.sender).transfer(ethToWithdraw);
    }
    

Key parts of the contract:

1.  `onlyOwner` midifier:
    
    > Indicates that this function can only be called by the contract owner. This modifier is inherited from the `Ownable.sol` contract.
    
2.  statement `payable(msg.sender).transfer(ethToWithdraw)`:
    
    > When calling the `transfer` function, the payable address must be used, not the ordinary address. So, you need to use `payable()` to convert instead of `address()` as in the previous sentence.
    

### 4.4 Deploy Vendor contract

`withdraw` function is ready. Who are allowed to withdraw funds from the contract? Of course the owner of the vendor, which is the owner of the vendor contract. The initial owner is the address that deploys the contract. If the address you connected with MetaMask is not the address for deployment, then you need to transfer the ownership to the address you use to log in the App.

Uncomment `packages/hardhat-ts/deploy/01_deploy_vendor.ts`'s commented lines, then modify the Token amount transferring from YourToken to Vendor. If needed, assign new owner to Vendor contract.

    // Todo: deploy the vendor
    
    await deploy('Vendor', {
      // Learn more about args here: https://www.npmjs.com/package/hardhat-deploy#deploymentsdeploy
      from: deployer,
      args: [yourToken.address],
      log: true,
    });
    
    const vendor = await ethers.getContract("Vendor", deployer);
    
    // Todo: transfer the tokens to the vendor
    console.log("\n 🏵  Sending all 1000 tokens to the vendor...\n");
    
    await yourToken.transfer(
      vendor.address,
      ethers.utils.parseEther("500") // Specify the amount of Token transferred to the Vendor.
    );
    
    // Assign new owner of Vendor contract
    await vendor.transferOwnership("0xC0802222dB0F31d63dc85d9C8CAa00485715A13c");
    

**Note:** You also need to uncomment the lines that transfers Token to your address in `packages/hardhat-ts/deploy/00_deploy_your_token.ts` or make the value smaller, like modifying it into 500.

After the change, run `yarn deploy --reset` to deploy the contract again.

Below is the output for successful deployment:

Vendor will initially have 500 tokens, and I am going to buy 10 tokens.

Vendor Balance

It will need about 0.001 ETH.

Buy Token

After the purchase, Vendor will have 490 tokens left, and ETH balanced is increased by 0.001. We can also see the event for Buy Token:

New Balance

Withdraw ETH (Now the account has 0.0089 ETH)

Withdraw

Withdraw successfully (Now the account has 0.0098 ETH)

Withdraw Success

0x05 Vendor buy back
--------------------

Sometimes, we need to sell our Token and get ETH back. It will be great if Vendor can have this kind of operation.

Suppose here is the buy back steps:

1.  Token owner takes out some of the Token, call YourToken's `approve` function with Vendor's contract address and Token amount, means user allows Vendor to sell this amount of Token.
    
2.  Then, user can call Vendor contract's `sellTokens` function based on the approved amount, and get back ETH to user's address.
    

### 5.1 YourToken's `approve` function

This funciton does not need to be implemented in YourToken's contract since it is inherited from `ERC20.sol`. (Codes below do not need to be copied to YourToken contract)

*   `_approve` is the main part of the logic. When user calls the method, `spender` will be the Vendor contract's address.
    
*   核心部分 `_allowances[owner][spender] = amount;` 负责在 YourToken 合约地址里面记录下用户允许 Vendor 获取的 Token 数量。
    
*   The core is `_allowances[owner][spender] = amount;` that records in the YourToken's address the amount of users' Token amount that allows Vendor to get.
    
         function approve(address spender, uint256 amount) public virtual override returns (bool) {
            address owner = _msgSender();
            _approve(owner, spender, amount);
            return true;
        }
        
        function _approve(
            address owner,
            address spender,
            uint256 amount
        ) internal virtual {
            require(owner != address(0), "ERC20: approve from the zero address");
            require(spender != address(0), "ERC20: approve to the zero address");
        
            _allowances[owner][spender] = amount;
            emit Approval(owner, spender, amount);
        }
        
    

### 5.2 Vendor's `sellTokens` function:

Copy the `SoldTokens` event and `sellTokens` function into Vendor.sol:

    ```solidity
    // ToDo: create a sellTokens() function:
    event SoldTokens(uint256 amountOfEth, uint256 amountOfTokens);
    
    function sellTokens(uint256 tokenToSell) public {
      require(tokenToSell > 0, 'You need to sell at least some tokens');
    
      // Calculate the needed ETH amount
      uint256 ethSold = tokenToSell / tokensPerEth;
      require(address(this).balance > ethSold, 'Not enough ETH to buy from Vendor');
    
      // Transfer Token from user to Vendor contract
      yourToken.transferFrom(msg.sender, address(this), tokenToSell);
    
      payable(msg.sender).transfer(ethSold);
    
      emit SoldTokens(ethSold, tokenToSell);
    }
    

    
    With the previous experience about `buyTokens`, we can easily understand `sellToken`. But the function of `yourToken.transferFrom` called within it needs to be looked at. `transferFrom`'s implementation in `ERC20.sol` is like that (you do not need to copy them into YourToken.sol or Vendor.sol):
    

    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) public virtual override returns (bool) {
        address spender = _msgSender();
        _spendAllowance(from, spender, amount);
        _transfer(from, to, amount);
        return true;
    }
    
    // check whether owner (Vendor contract) can get `msg.sender`'s needed amount of Token.
    function _spendAllowance(
        address owner,
        address spender,
        uint256 amount
    ) internal virtual {
        uint256 currentAllowance = allowance(owner, spender);
        if (currentAllowance != type(uint256).max) {
            require(currentAllowance >= amount, "ERC20: insufficient allowance");
            unchecked {
                _approve(owner, spender, currentAllowance - amount);
            }
        }
    }
    
    function _transfer(
        address from,
        address to,
        uint256 amount
    ) internal virtual {
        require(from != address(0), "ERC20: transfer from the zero address");
        require(to != address(0), "ERC20: transfer to the zero address");
    
        _beforeTokenTransfer(from, to, amount);
    
        uint256 fromBalance = _balances[from];
        require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
        unchecked {
            _balances[from] = fromBalance - amount;
        }
        _balances[to] += amount;
    
        emit Transfer(from, to, amount);
    
        _afterTokenTransfer(from, to, amount);
    }
    

    
    Some key parts:
    
    1. `internal` functions can only be called inside the contract or the child.
    2. `virtual` functions means the method can be overwritten by the child.
    3. ERC20 contract is to use a `_balances` mapping variable to store the amount of Token a address has. Transfer Token from one address to another, is to modify the value of the field in `_balances`.
    
       Let's see what sellTokens page works.
    
       Approve Vendor can sell this amount of Token:
    
       Sell Token to Vendor:
    
       Successfully with ETH (Now we have 0.0098 ETH)
    
    ## 0x06 Deploy contract to test networks
    
    Now, `YourToken` and `Vendor`'s contracts are all done, and tested in the local test network. Now we can deploy the contract to the public testnet or mainnet.
    
    File `packages/hardhat-ts/hardhat.config.ts`
    
    ```typescript
    // const defaultNetwork = 'localhost';
    const defaultNetwork = 'ropsten';
    

File `packages/vite-app-ts/src/config/providersConfig.ts`

    // export const targetNetworkInfo: TNetworkInfo = NETWORKS.local;
    export const targetNetworkInfo: TNetworkInfo = NETWORKS.ropsten;
    

After modifying the previous files, you can deploy the contract to Ropsten with `yarn deploy` (you need to have ETH on Ropsten network). The following error may be threwed when deploying:

    deploying "Vendor"replacement fee too low (error={"name":"ProviderError","code":-32000,"_isProviderError":true}
    

This means the deployment operation is too fast, so that the transaction is sent too early and blocked. To solve this, you can seperate two deploy config files under `packages/hardhat-ts/deploy` into different folders.

Finally, run `yarn build` and `yarn surge` to build the front-end page, and deploy to Surge static file. Now we are all set!

thinkingincrowd

msfew

---

*Originally published on [Web3dAppDevCamp](https://paragraph.com/@apecoder/token-vendor-web3-0-dapp-dev-0x08)*
