evm-decimal-validation — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited evm-decimal-validation (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Ensure token decimals are correctly configured for each blockchain deployment to prevent amount calculation errors.
| Decimals | Use Case | Examples |
|---|---|---|
| 18 | Standard ERC20 (most tokens) | WETH, DAI, USDC (on some chains) |
| 6 | Stablecoins | USDC, USDT (on Ethereum) |
| 8 | Bitcoin-wrapped | WBTC |
| 2 | Fiat-backed (cents) | IDRX, some CBDCs |
| 0 | Raw integers | Some wrapped tokens |
The same token contract deployed on different chains may use different decimals:
Token A on Ethereum: 18 decimals
Token A on Polygon: 0 decimals (different deployment)
Token A on BNB Chain: 0 decimals (different deployment)# Find hardcoded decimals
grep -rn "18)" --include="*.go" | grep -i "decimal\|wei\|amount"
# Check for decimals field in network config
grep -rn "Decimals" blockchain/networks.go// Get decimals from contract
decimals, err := contract.Decimals(nil)
if err != nil {
return fmt.Errorf("failed to get decimals: %w", err)
}type NetworkConfig struct {
// ... existing fields ...
Decimals uint8 // Token decimals for this deployment
}
// In SupportedNetworks
BaseMainnet: {
// ...
Decimals: 2, // From on-chain query
},// GetDecimals returns token decimals for a chain ID.
// Always provide a sensible fallback.
func GetDecimals(chainID uint64) uint8 {
config, _, exists := GetNetworkConfigByChainID(chainID)
if !exists {
return 2 // Fallback to majority value
}
return config.Decimals
}// Before
return FromWei(balance, 18), nil
// After
return FromWei(balance, int32(GetDecimals(chainID))), nil// WRONG: Hardcoded decimals
amount := FromWei(balance, 18)
// CORRECT: Per-chain decimals
amount := FromWei(balance, int32(GetDecimals(chainID)))// WRONG: No fallback, potential nil pointer
return config.Decimals, nil
// CORRECT: Sensible fallback
if !exists {
return 2 // Majority value or most common
}
return config.Decimals// Add startup validation
func ValidateDecimals() error {
for name, config := range SupportedNetworks {
onChainDecimals, err := queryOnChain(config)
if err != nil {
continue // Skip unreachable chains
}
if config.Decimals != onChainDecimals {
return fmt.Errorf("%s: config=%d, on-chain=%d",
name, config.Decimals, onChainDecimals)
}
}
return nil
}func TestGetDecimals(t *testing.T) {
testCases := map[uint64]uint8{
8453: 2, // Base
137: 0, // Polygon
56: 0, // BSC
}
for chainID, expected := range testCases {
got := GetDecimals(chainID)
if got != expected {
t.Errorf("Chain %d: expected %d, got %d", chainID, expected, got)
}
}
}
func TestGetDecimalsFallback(t *testing.T) {
got := GetDecimals(999999) // Unknown chain
if got != 2 {
t.Errorf("Unknown chain should return fallback, got %d", got)
}
}When adding decimal validation, check these areas:
| Area | What to Check |
|---|---|
| Balance queries | BalanceOf() uses per-chain decimals |
| Total supply | TotalSupply() uses per-chain decimals |
| Transfers | ParseTokenAmount() receives correct decimals |
| Bridge operations | Both source and dest chains considered |
| Fee calculations | Platform fees use correct decimals |
| API responses | Human-readable amounts properly converted |
See decimals-by-chain.md for a reference guide of common tokens and their decimals across different EVM chains.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.