ICE 5 – ERC‑20 Token (OpenZeppelin + Remix)

Goal: create a simple ERC‑20 token using OpenZeppelin contracts, deploy it with MetaMask on Ganache, then test transfer, approve, and allowance in Remix.

0) Prereqs

1) Make a token contract

Create MyToken.sol in Remix and paste this (uses OpenZeppelin via import URLs):

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.2/contracts/token/ERC20/ERC20.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.2/contracts/access/Ownable.sol";

contract MyToken is ERC20, Ownable {
constructor(string memory name_, string memory symbol_) ERC20(name_, symbol_) Ownable(msg.sender) {}

function mint(address to, uint256 amount) public onlyOwner {
_mint(to, amount);
}
}

If import fails due to network, change the version to any stable tag (e.g., v5.0.x) or use Remix Libraries plugin to pin OZ.

2) Compile

  1. Solidity Compiler → set version to 0.8.20 (or a compatible 0.8.x).
  2. Click Compile MyToken.sol and wait for the green check.

3) Deploy with MetaMask → Ganache

  1. Deploy & Run → Environment: Injected Provider – MetaMask (should say Localhost/Ganache).
  2. Contract: MyToken.
  3. Constructor inputs:
    • name_: e.g., FIN451 Token
    • symbol_: e.g., FIN
  4. Click Deploy → confirm in MetaMask.

Gas estimation failed? Ensure your selected account has Ganache ETH and the network is your Localhost.

4) Mint tokens

  1. Under Deployed Contracts → expand your MyToken instance.
  2. Use mint(to, amount):
    • to: your own address (paste from MetaMask).
    • amount: e.g., 100000000000000000000 (that’s 100 * 10^18).
  3. Confirm the transaction in MetaMask.
  4. Call balanceOf(yourAddress) → it should show 100000000000000000000.

Decimals are 18 by default. To display human‑readable amounts, divide by 10^18.

5) Transfer & Allowance

  1. transfer(recipient, amount) → send some tokens to a second address (import another Ganache account into MetaMask to copy its address).
  2. approve(spender, amount) → grant an allowance to another address.
  3. From the spender account, call transferFrom(owner, recipient, amount) to use the allowance.
  4. Use allowance(owner, spender) to check remaining allowance.

Example values

  • amount: 500000000000000000 (0.5 token)
  • amount: 1000000000000000000 (1 token)

Notes

  • Only the owner (deployer) can mint in this template.
  • Transfers/approvals cost gas; reads like balanceOf are free.

6) Optional: Minimal HTML/JS to read balances (ethers.js)

Save as read_balance.html and replace ADDR and ABI from Remix (Compilation Details → ABI).

<!DOCTYPE html>
<html><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><title>Read ERC‑20</title></head>
<body>
<button id="connect">Connect</button>
<div>Token address: <input id="addr" value="PASTE_CONTRACT_ADDR" style="width:360px"/></div>
<textarea id="abi" rows="6" style="width:100%">PASTE_ABI_JSON_ARRAY</textarea>
<div>Account: <input id="acct" placeholder="0x..." style="width:360px"/>
<button id="bal">balanceOf()</button> <span id="out"></span></div>
<script src="https://cdn.jsdelivr.net/npm/ethers@6.13.2/dist/ethers.min.js"></script>
<script>
let provider, signer, c;
document.getElementById('connect').onclick = async () => {
if(!window.ethereum) return alert('MetaMask not found');
provider = new ethers.BrowserProvider(window.ethereum);
await provider.send('eth_requestAccounts', []);
signer = await provider.getSigner();
const abi = JSON.parse(document.getElementById('abi').value);
const addr = document.getElementById('addr').value.trim();
c = new ethers.Contract(addr, abi, provider); // read‑only
};
document.getElementById('bal').onclick = async () => {
const a = document.getElementById('acct').value.trim();
const v = await c.balanceOf(a);
document.getElementById('out').textContent = v.toString();
};
</script>
</body></html>

Keep MetaMask on your Ganache network so RPC calls hit localhost.

Submission

  • Screenshot: Deployed MyToken with address on Ganache.
  • Screenshot: Successful mint + balanceOf.
  • Screenshot: transfer and approve/transferFrom working (with amounts shown).
  • Short reflection: What are decimals? Why does 1 token equal 10^18 units? Who can mint and why?

Troubleshooting

  • Import URL failed: Try another OZ version (v5.0.x), or install OpenZeppelin via Remix Libraries.
  • Gas estimation failed: Ensure funded account & correct network (Injected Provider → Ganache).
  • Wrong decimals: Default is 18; multiply human value by 10^18.
  • Not owner: Only the deployer can call mint in this template.

Next: ICE 6 – ERC‑721 NFT (metadata & minting) or deploy the ERC‑20 to a public testnet (Sepolia) with a faucet.