Skip to main content

The Contract

pragma language_version >= 0.17.0;

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 currentBalance = getBalance(account);

    // Check for overflow
    const MAX_UINT128 = 340282366920938463463374607431768211455 as Uint<128>;
    assert(currentBalance <= MAX_UINT128 - amount, "Balance overflow");

    const newBalance = currentBalance + amount;
    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 currentBalance = getBalance(account);

// Check for overflow before adding
const MAX_UINT128 = 340282366920938463463374607431768211455 as Uint<128>;
assert(currentBalance <= MAX_UINT128 - amount, "Balance overflow");

const newBalance = currentBalance + amount;
balances.insert(disclose(account), disclose(newBalance));
Always validate before modifying balances to prevent overflow and underflow.

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