The x/precisebank module from cosmos/evm extends the standard x/bank module from 6 to 18 decimal precision for EVM compatibility.
For conceptual understanding of precision handling and mathematical proofs, see Precision Handling.

Overview

The module acts as a wrapper around x/bank, providing:
  • 18 decimal precision for EVM (10^18 sub-atomic units)
  • Backward compatibility with 6 decimal Cosmos operations
  • Transparent conversion between precision levels
  • Fractional balance tracking for sub-uatom amounts
Developed with contributions from the Kava team.

State

The module maintains fractional balances and remainder (source):
ObjectKeyValueDescription
FractionalBalance0x01 + addressmath.IntAccount fractional balance (0 to 10^12-1)
Remainder0x02math.IntUncirculated fractional amount

Balance Representation

Full balance calculation:
aatom_balance = uatom_balance × 10^12 + fractional_balance
Where:
  • uatom_balance: Stored in x/bank (6 decimals)
  • fractional_balance: Stored in x/precisebank (0 to 10^12-1)
  • aatom_balance: Full 18-decimal precision

Keeper Interface

The module provides a bank-compatible keeper (source):
type Keeper interface {
    // Query methods
    GetBalance(ctx, addr, denom) sdk.Coin
    SpendableCoins(ctx, addr) sdk.Coins
    
    // Transfer methods
    SendCoins(ctx, from, to, amt) error
    SendCoinsFromModuleToAccount(ctx, module, to, amt) error
    SendCoinsFromAccountToModule(ctx, from, module, amt) error
    
    // Mint/Burn methods
    MintCoins(ctx, module, amt) error
    BurnCoins(ctx, module, amt) error
}

Extended Coin Support

Automatic handling of “aatom” denomination:
  • Converts between uatom and aatom transparently
  • Maintains fractional balances for sub-uatom amounts
  • Ensures consistency between x/bank and x/precisebank

Operations

Transfer

Handles both integer and fractional components:
// SendCoins automatically handles precision
keeper.SendCoins(ctx, from, to, sdk.NewCoins(
    sdk.NewCoin("aatom", sdk.NewInt(1500000000000)), // 0.0015 uatom
))
Algorithm:
  1. Subtract from sender (update b(sender) and f(sender))
  2. Add to receiver (update b(receiver) and f(receiver))
  3. Update reserve based on carry/borrow
  4. Remainder unchanged (mathematical guarantee)

Mint

Creates new tokens with proper backing:
// MintCoins with extended precision
keeper.MintCoins(ctx, moduleName, sdk.NewCoins(
    sdk.NewCoin("aatom", sdk.NewInt(1000000000000000000)), // 1 uatom
))
Algorithm:
  1. Add to account (update b(account) and f(account))
  2. Decrease remainder (tokens enter circulation)
  3. Update reserve for consistency

Burn

Removes tokens from circulation:
// BurnCoins with extended precision
keeper.BurnCoins(ctx, moduleName, sdk.NewCoins(
    sdk.NewCoin("aatom", sdk.NewInt(500000000000)),  // 0.0005 uatom
))
Algorithm:
  1. Subtract from account (update b(account) and f(account))
  2. Increase remainder (tokens leave circulation)
  3. Update reserve for consistency

Events

Standard bank events with extended precision amounts:

Transfer Events

EventAttributesDescription
transfersender, recipient, amountFull aatom amount
coin_spentspender, amountExtended precision
coin_receivedreceiver, amountExtended precision

Mint/Burn Events

EventAttributesDescription
coinbaseminter, amountMinted with 18 decimals
burnburner, amountBurned with 18 decimals

Queries

gRPC

service Query {
    // Get total of all fractional balances
    rpc TotalFractionalBalances(QueryTotalFractionalBalancesRequest)
        returns (QueryTotalFractionalBalancesResponse);
    
    // Get current remainder amount
    rpc Remainder(QueryRemainderRequest)
        returns (QueryRemainderResponse);
    
    // Get fractional balance for an account
    rpc FractionalBalance(QueryFractionalBalanceRequest)
        returns (QueryFractionalBalanceResponse);
}

CLI

# Query total fractional balances
evmd query precisebank total-fractional-balances

# Example output:
# total: "2000000000000aatom"

Integration

For EVM Module

Replace bank keeper with precisebank keeper in app.go:
app.EvmKeeper = evmkeeper.NewKeeper(
    app.PrecisebankKeeper, // Instead of app.BankKeeper
    // ... other parameters
)

For Other Modules

Query extended balances through standard interface:
// Automatically handles aatom denomination
balance := keeper.GetBalance(ctx, addr, "aatom")

// Transfer with 18 decimal precision
err := keeper.SendCoins(ctx, from, to, 
    sdk.NewCoins(sdk.NewCoin("aatom", amount)))

Reserve Account

The reserve account maintains backing for fractional balances:
Reserve_uatom × 10^12 = Σ(all fractional balances) + remainder

Monitoring Reserve

# Check reserve account balance
evmd query bank balances cosmos1m3h30wlvsf8llruxtpukdvsy0km2kum8g38c8q

# Verify invariant
evmd query precisebank total-fractional-balances
evmd query precisebank remainder

Invariants

Critical invariants maintained by the module:
InvariantFormulaDescription
SupplyTotal_aatom = Total_uatom × 10^12 - remainderTotal supply consistency
Fractional Range0 ≤ f(n) < 10^12Valid fractional bounds
Reserve BackingReserve × 10^12 = Σf(n) + rFull backing guarantee
ConservationΔ(Total_aatom) = Δ(Total_uatom × 10^12)No creation/destruction

Best Practices

Chain Integration

  1. Reserve Monitoring
    • Track reserve balance for validation
    • Set up alerts for invariant violations
    • Regular audits of fractional sums
  2. Migration Path
    // Deploy in passive mode
    app.PrecisebankKeeper = precisebankkeeper.NewKeeper(
        app.BankKeeper,
        // Fractional balances start at zero
    )
    
  3. Testing
    // Verify fractional operations
    suite.Require().Equal(
        expectedFractional,
        keeper.GetFractionalBalance(ctx, addr),
    )
    

dApp Development

  1. Balance Queries
    // Query in aatom (18 decimals)
    const balance = await queryClient.precisebank.fractionalBalance({
        address: "cosmos1..."
    });
    
  2. Precision Handling
    // Convert between precisions
    const uatomAmount = aatomAmount / BigInt(10**12);
    const aatomAmount = uatomAmount * BigInt(10**12);
    

Security Considerations

Overflow Protection

  • All arithmetic uses checked math
  • Fractional values bounded to [0, 10^12)
  • Integer overflow impossible by design

Atomicity

  • Balance updates are atomic
  • Reserve adjustments in same transaction
  • No intermediate states visible

Precision Guarantees

  • No precision loss during operations
  • All fractional amounts preserved
  • Rounding only at display layer

Performance

Storage Impact

  • Additional O(n) storage for accounts with fractional balances
  • Most accounts have zero fractional balance (no storage)
  • Reserve account: single additional balance

Computation

  • Constant time operations for all transfers
  • Single addition/multiplication for balance queries
  • ~10% gas overhead for fractional updates

Optimization

  • Lazy initialization (fractional balances start at zero)
  • Sparse storage (only non-zero fractions stored)
  • Batch operations maintain efficiency

Troubleshooting

Common Issues

IssueCauseSolution
”fractional overflow”Fractional > 10^12Check calculation logic
”insufficient balance”Including fractionalVerify full aatom balance
”invariant violation”Supply mismatchAudit reserve and remainder

Validation Commands

# Verify module invariants
evmd query precisebank total-fractional-balances
evmd query precisebank remainder

# Check specific account
evmd query bank balances cosmos1... --denom uatom
evmd query precisebank fractional-balance cosmos1...

References

Source Code