Skip to main content

The Contract

pragma language_version >= 0.17.0;

import CompactStandardLibrary;

// Account balances
export ledger balances: Map<Either<ZswapCoinPublicKey, ContractAddress>, Uint<128>>;

// Total token supply
export ledger totalSupply: Uint<128>;

// Get total circulating supply
export circuit getTotalSupply(): Uint<128> {
    return totalSupply;
}

// Mint new tokens
export circuit mint(
    account: Either<ZswapCoinPublicKey, ContractAddress>,
    amount: Uint<128>
): [] {
    _mint(account, amount);
}

// Internal: Mint tokens
circuit _mint(
    account: Either<ZswapCoinPublicKey, ContractAddress>,
    amount: Uint<128>
): [] {
    // Check total supply won't overflow
    const MAX_UINT128 = 340282366920938463463374607431768211455 as Uint<128>;
    assert(totalSupply <= MAX_UINT128 - amount, "Total supply overflow");

    // Update total supply
    totalSupply = disclose(totalSupply + amount);

    // Get current balance
    const currentBalance = getBalance(account);

    // Check account balance won't overflow
    assert(currentBalance <= MAX_UINT128 - amount, "Balance overflow");

    // Increase account balance
    balances.insert(disclose(account), disclose(currentBalance + amount));
}

// Helper: Get balance
circuit getBalance(account: Either<ZswapCoinPublicKey, ContractAddress>): Uint<128> {
    if (!balances.member(disclose(account))) {
        return 0;
    }
    return balances.lookup(disclose(account));
}

How It Works

Total Supply Tracking

export ledger totalSupply: Uint<128>;

export circuit getTotalSupply(): Uint<128> {
    return totalSupply;
}
Total supply represents all tokens in circulation:
  • Mint: Increases total supply
  • Burn: Decreases total supply (see Burning tutorial)
  • Transfer: Doesn’t change total supply (moves between accounts)
Invariant: sum(all balances) == totalSupply should always be true. Minting and burning are the only operations that change this.

Minting Process

circuit _mint(
    account: Either<ZswapCoinPublicKey, ContractAddress>,
    amount: Uint<128>
): [] {
    // Check total supply won't overflow
    const MAX_UINT128 = 340282366920938463463374607431768211455 as Uint<128>;
    assert(totalSupply <= MAX_UINT128 - amount, "Total supply overflow");

    // Update total supply
    totalSupply = disclose(totalSupply + amount);

    // Get current balance
    const currentBalance = getBalance(account);

    // Check account balance won't overflow
    assert(currentBalance <= MAX_UINT128 - amount, "Balance overflow");

    // Increase account balance
    balances.insert(disclose(account), disclose(currentBalance + amount));
}
Minting flow:
  1. Check total supply overflow: Prevent exceeding Uint<128> maximum
  2. Increase total supply: Add new tokens to circulation
  3. Check account overflow: Prevent recipient balance overflow
  4. Credit account: Add tokens to recipient’s balance
See Overflow Protection for detailed arithmetic safety patterns.

Access Control

// Public: Anyone can call (should add access control!)
export circuit mint(
    account: Either<ZswapCoinPublicKey, ContractAddress>,
    amount: Uint<128>
): [] {
    _mint(account, amount);
}
The example mint() function has NO access control - anyone can mint unlimited tokens! See Access Control for owner-only and role-based patterns.

What’s Next