How Open-Source Crypto Code Creates Supply: Minting, Decimals and Circulation

By huanggs

Issuing a cryptocurrency supply requires a rule that creates units and assigns them to accounts. Publishing source code does not perform that step. For an ERC-20 token, minting updates the contract’s total supply and a recipient’s balance. For a native coin, issuance belongs to the chain’s consensus rules. Neither number automatically tells you how many units a market-data service will count as circulating.

First identify what you are building

A native coin and a token on an existing chain use different issuance mechanisms. The Bitcoin developer guide to blocks and consensus describes the coinbase transaction and block subsidy: nodes validate those transactions against consensus rules. Changing a downloaded copy of the source does not make the existing network accept a larger reward.

An ERC-20 token is instead a smart contract deployed on a compatible chain. The ethereum.org ERC-20 token standard guide defines interfaces such as totalSupply, balanceOf, transfer and allowance. ERC-20 does not require a particular maximum supply or a particular minting policy; those are implementation choices.

A concrete one-million-token example

This teaching example creates one million display tokens once, in the constructor, and assigns them to a specified non-zero recipient. It uses Solidity 0.8.20 and OpenZeppelin Contracts 5.0.2, with OpenZeppelin’s default 18 decimals. The OpenZeppelin Contracts ERC-20 guide explains both constructor minting and the difference between integer balances and display units.

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

import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract SupplyExample is ERC20 {
    constructor(address recipient) ERC20("Supply Example", "SUP") {
        _mint(recipient, 1_000_000 * 10 ** decimals());
    }
}

The example compiled with the versions stated above. It has not been deployed to a network or security-audited. Pin those dependencies when reproducing it; a later library release can have different compiler requirements.

After successful deployment, the intended state is totalSupply() = 1000000000000000000000000 base units, and the recipient holds the same balance. Divide by 10^18 to display 1,000,000 SUP. Minting the integer 1000000 without scaling would instead produce only 0.000000000001 display tokens with 18 decimals.

The constructor runs once for that deployed contract. This exact example exposes no later mint function, burn function or upgrade mechanism. It therefore creates a fixed supply for that deployment. Adding other contracts, inheritance or upgrade infrastructure later would require reviewing that conclusion again.

Minting and distributing are separate operations

The OpenZeppelin creating-supply guide explains why supply should be created through _mint: the implementation keeps balances, total supply and the required transfer event in sync. Directly editing an account balance is not a substitute for an issuance mechanism.

Once the million tokens exist, transferring 100,000 from the recipient to another account leaves 900,000 in the first account and 100,000 in the second. Total supply remains one million. An approval merely grants spending permission; it does not mint or transfer tokens by itself.

For a mintable design, identify every path that can call the internal mint operation. Check the caller’s permissions, whether a cap is enforced and whether another function can change those permissions. A contract named “CappedToken” is not evidence of a cap unless the executed logic enforces it.

Why total supply is not necessarily circulating supply

ERC-20 provides totalSupply(), but no universal circulatingSupply() method. Market-data providers may exclude vesting allocations, reserves or other balances under their own definitions. A transfer from a treasury to a time-locked vesting contract changes who holds the tokens, not necessarily their availability to the public.

Suppose a project has one million tokens, with 600,000 in a documented vesting contract and 200,000 in a treasury. A provider that excludes both categories might count 200,000 as circulating. Another provider’s policy or evidence requirements can produce a different number. That is an illustrative classification, not an on-chain fact established by totalSupply().

Similarly, sending tokens to an apparently inaccessible address does not necessarily reduce the contract’s reported supply. A burn implementation must update supply to reduce that number. Distinguish a provable contract burn, an inaccessible balance, a lockup and a website’s circulation estimate.

Verify issuance at a specific deployment

  1. Record the network and contract address. A repository name or token symbol does not uniquely identify a deployed asset.
  2. Match the implementation. Check the verified source, compiler settings, dependencies and constructor arguments. With a proxy, inspect the implementation and upgrade authority as well.
  3. Read decimals before interpreting numbers. Retain integer base units when doing accounting; avoid rounding a large supply through ordinary floating-point arithmetic.
  4. Inspect supply-changing transactions. Check mint and burn events against balances and contract logic, rather than trusting an event label alone.
  5. Document allocations separately. Identify treasury, vesting and distributed balances with a stated block number and the method used to classify them.

If an explorer shows zero supply, verify the chain and address, deployment success and whether the contract actually mints during construction. If the supply is off by many decimal places, check the base-unit calculation first. If the number grew later, trace the mint permission or upgrade that allowed the change. These checks answer how units were issued without confusing open-source availability, ownership distribution and market circulation.