Goal: create a simple ERC‑20 token using OpenZeppelin contracts, deploy it with MetaMask on Ganache, then test transfer
, approve
, and allowance
in Remix.
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);
}
}
100 * 10^18
.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.
Gas estimation failed? Ensure your selected account has Ganache ETH and the network is your Localhost.
100 * 10^18
).100000000000000000000
.Decimals are 18 by default. To display human‑readable amounts, divide by 10^18.
balanceOf
are free.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.
mint
+ balanceOf
.transfer
and approve/transferFrom
working (with amounts shown).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.