Skip to main content

The Contract

pragma language_version >= 0.23;

import CompactStandardLibrary;

// Simple balance tracker
export ledger balances: Map<Either<ZswapCoinPublicKey, ContractAddress>, Uint<128>>;

// Get balance for an account
export circuit getBalance(account: Either<ZswapCoinPublicKey, ContractAddress>): Uint<128> {
    // Check if account exists in map
    if (!balances.member(disclose(account))) {
        return 0; // Return 0 for accounts with no balance
    }
    return balances.lookup(disclose(account));
}

// Set balance for an account
export circuit setBalance(
    account: Either<ZswapCoinPublicKey, ContractAddress>,
    amount: Uint<128>
): [] {
    balances.insert(disclose(account), disclose(amount));
}

// Add to existing balance
export circuit addBalance(
    account: Either<ZswapCoinPublicKey, ContractAddress>,
    amount: Uint<128>
): [] {
    const MAX_UINT128: Uint<128> = 340282366920938463463374607431768211455 as Uint<128>;
    const currentBalance = getBalance(account);

    // Check for overflow before casting back
    assert(currentBalance <= MAX_UINT128 - amount, "Balance overflow");

    const newBalance: Uint<128> = (currentBalance + amount) as Uint<128>;
    balances.insert(disclose(account), disclose(newBalance));
}

// Subtract from balance
export circuit subtractBalance(
    account: Either<ZswapCoinPublicKey, ContractAddress>,
    amount: Uint<128>
): [] {
    const currentBalance = getBalance(account);

    // Check sufficient balance
    assert(currentBalance >= amount, "Insufficient balance");

    const newBalance = currentBalance - amount;
    balances.insert(disclose(account), disclose(newBalance));
}

How It Works

Map Declaration

export ledger balances: Map<Either<ZswapCoinPublicKey, ContractAddress>, Uint<128>>;
Maps have key and value type parameters. This one maps accounts to balance amounts.

Checking Membership

if (!balances.member(disclose(account))) {
    return 0;
}
Use member(key) to check if a key exists before lookup.

Looking Up Values

return balances.lookup(disclose(account));
Use lookup(key) to get the value for a key.

Inserting or Updating

balances.insert(disclose(account), disclose(amount));
Use insert(key, value) to add or update an entry.

Safe Arithmetic

const MAX_UINT128: Uint<128> = 340282366920938463463374607431768211455 as Uint<128>;
const currentBalance = getBalance(account);

// Check for overflow before casting the widened sum back to Uint<128>
assert(currentBalance <= MAX_UINT128 - amount, "Balance overflow");

const newBalance: Uint<128> = (currentBalance + amount) as Uint<128>;
balances.insert(disclose(account), disclose(newBalance));
Compact arithmetic widens the result type, so the sum must be asserted to fit and then explicitly cast back to Uint<128> before storing. See Overflow Protection.
setBalance and addBalance have no authentication. Anyone can overwrite any account’s balance. The pattern here is for illustration; production token contracts must gate writes with the Access Control keypair pattern.

Common Patterns

Initialize with Default

export circuit ensureAccount(account: Either<ZswapCoinPublicKey, ContractAddress>): [] {
    if (!balances.member(disclose(account))) {
        balances.insert(disclose(account), 0);
    }
}
Create an entry if it doesn’t exist.

Nested Maps

// Allowances: owner -> (spender -> amount)
export ledger allowances: Map<
    Either<ZswapCoinPublicKey, ContractAddress>,
    Map<Either<ZswapCoinPublicKey, ContractAddress>, Uint<128>>
>;

export circuit getAllowance(
    owner: Either<ZswapCoinPublicKey, ContractAddress>,
    spender: Either<ZswapCoinPublicKey, ContractAddress>
): Uint<128> {
    if (!allowances.member(disclose(owner))) {
        return 0;
    }

    const spenderAllowances = allowances.lookup(disclose(owner));
    if (!spenderAllowances.member(disclose(spender))) {
        return 0;
    }

    return spenderAllowances.lookup(disclose(spender));
}
Maps can contain other Maps for complex relationships (used in approval systems).

Iterating Over Maps

Compact doesn’t support iterating over Map keys directly. Track keys separately if needed.

What’s Next

Transfer Function

Build a token transfer system

ERC20 Token

See Maps in a complete token