PreciseBank Module Reference
The PreciseBank module (x/precisebank
) extends the precision of the standard Cosmos SDK bank module from 6 decimals to 18 decimals, enabling full EVM compatibility while maintaining Cosmos coin integrity. This module is required for chains using non-18-decimal native tokens.
Big thanks to the Kava team for their valuable contributions to this module.
Module Overview
Purpose: Bridge the decimal precision gap between Cosmos (typically 6 decimals) and EVM (18 decimals) Key Functionality:- Extends token precision without changing the base denomination
- Tracks fractional balances (sub-atomic units) separate from integer balances
- Maintains 1:1 backing between fractional and integer units
- Transparent to users - balances appear as expected in both environments
- Wraps
x/bank
to provide 18-decimal precision forx/vm
When Do You Need PreciseBank?
✅ You NEED PreciseBank if:
Your native token has 6 decimals (or any non-18 decimal count):- Base denom:
ustake
,utoken
,uatom
(micro prefix = 10^6) - Display denom:
stake
,token
,atom
- Example: 1 STAKE = 1,000,000 ustake = 10^6 smallest units
❌ You DON’T NEED PreciseBank if:
Your native token has 18 decimals:- Base denom:
atest
,atoken
(atto prefix = 10^18) - Display denom:
test
,token
- Example: 1 TEST = 1,000,000,000,000,000,000 atest = 10^18 smallest units
Mathematical Foundation
The Precision Problem
Cosmos Standard: 6 decimal placesPreciseBank Solution
PreciseBank subdivides eachuatom
into 10^12 sub-atomic units called aatom
:
aatom
is fully backed by uatom
in x/bank. You cannot have fractional aatom
without corresponding integer uatom
reserves.
Balance Representation
For any accountn
, the total balance in sub-atomic units a(n)
is:
Where:
a(n)
= Total aatom balance (18-decimal representation)b(n)
= Integer uatom balance (stored in x/bank)f(n)
= Fractional balance (stored in x/precisebank)C
= Conversion factor = 10^12
Module Integration
Adding to Your Chain
PreciseBank requires integration inapp/app.go
:
1. Import the module:
Configuration
Genesis Configuration
PreciseBank has minimal genesis configuration - it primarily tracks state, not parameters. File Location:~/.evmd/config/genesis.json
under app_state.precisebank
Structure:
Required VM Module Configuration
When using PreciseBank, you MUST configureextended_denom_options
in the VM module:
native_denom
: 6-decimal Cosmos denom (ustake
)extended_denom
: 18-decimal EVM denom (astake
)- Conversion: 1 ustake = 10^12 astake
u
prefix (micro, 10^6) →a
prefix (atto, 10^18):ustake
→astake
- Other prefixes → add
evm
prefix:stake
→evmstake
State
fractional_balances
What It Stores: The fractional (sub-atomic) portion of each account’s balance that cannot be represented as whole integer units. Type: Array ofFractionalBalance
objects
Structure (fractional_balance.go:43-48):
- Amount must be positive (
amount > 0
) - Amount must be less than conversion factor (
amount < 10^12
) - Address must be valid Bech32
remainder
What It Stores: A module-level reserve balance that backs all fractional units in circulation. Type: Integer (sdkmath.Int) Purpose: Maintains invariant that total fractional balances equal the module reserve Invariant: Where:remainder
= Module reserve in fractional units- = Sum of all account fractional balances
Operations
Transfer
When transferring fractional amounts, PreciseBank handles the complexity automatically: Example Transfer: Alice sends 1.5 ustake + 500 billion aatom to BobMint
Operation: Create new fractional units- Split amount into integer and fractional parts
- Mint integer part via x/bank
- Update fractional balance in x/precisebank
- Update remainder to maintain backing invariant
Burn
Operation: Destroy fractional units- Split amount into integer and fractional parts
- Burn integer part via x/bank
- Update fractional balance in x/precisebank
- Update remainder to maintain backing invariant
Keeper Interface
PreciseBank implements the fullBankKeeper
interface, making it a drop-in replacement:
Source: keeper.go:16
SendCoins(ctx, from, to, coins)
- Transfer with fractional precisionMintCoins(ctx, module, coins)
- Create new fractional unitsBurnCoins(ctx, module, coins)
- Destroy fractional unitsGetBalance(ctx, addr, denom)
- Get extended balance (integer + fractional)SpendableCoins(ctx, addr)
- Get spendable balances with fractional precision
GetSupply()
- Total supplyIterateTotalSupply()
- Supply iteration
Queries
gRPC Queries
Query Fractional Balance:EVM Integration
In Solidity Contracts
From the EVM perspective, users interact with the extended denomination:transfer(recipient, 1.5e18 astake)
- PreciseBank: Transfers 1 ustake via x/bank + 500,000,000,000 aatom fractional
- User sees seamless 18-decimal precision
Events
PreciseBank emits events for fractional balance changes:SendCoins Event
MintCoins Event
BurnCoins Event
Common Issues and Solutions
Issue: “Fractional amount exceeds conversion factor”
Symptom: Transaction fails with fractional validation error Cause: Fractional balance >= 10^12 (should have been converted to integer unit) Solution: This indicates a bug in the keeper logic. Report to Cosmos EVM team.Issue: Balances Don’t Match Between Cosmos/EVM
Symptom: User sees different balance in Cosmos vs MetaMask Cause:- PreciseBank not integrated correctly in app.go
- VM module not using PreciseBankKeeper
- Missing
extended_denom_options
configuration
Issue: Chain Won’t Start After Adding PreciseBank
Symptom: Genesis validation fails Cause: Missingextended_denom_options
in VM params
Solution: Add to genesis.json:
Issue: Total Supply Mismatch
Symptom: Sum of balances doesn’t equal total supply Cause: Remainder not properly maintained Solution: Query remainder and verify:Testing and Verification
Verify Integration
1. Check module is loaded:Performance Considerations
Storage: Fractional balances add one storage entry per account with non-zero fractional amount Gas Cost: Fractional operations add minimal gas overhead (~5-10% more than standard bank operations) Scaling: Module has been tested with millions of accounts, no performance degradation Optimization: Fractional balances are only created when needed. Transfers of exact integer amounts don’t create fractional entries.Related Documentation
- Building Your Chain Guide - Main configuration walkthrough
- Token Configuration Guide - Decimal configuration
- VM Module - extended_denom_options setup
- ERC20 Module - Token pair configuration
Source Code References
- Module Implementation: x/precisebank
- README (Math Background): x/precisebank/README.md
- Keeper: keeper/keeper.go
- Fractional Balance Logic: types/fractional_balance.go
- Send Operations: keeper/send.go
- Mint Operations: keeper/mint.go
- Burn Operations: keeper/burn.go
- Remainder Management: keeper/remainder_amount.go
- Storage Keys: types/keys.go