EVM 체인 전반의 조용한 decimal mismatch 버그를 방지합니다. 런타임 decimal 조회, 체인 인지 캐시, 브리지드 토큰 정밀도 드리프트, 봇/대시보드/DeFi 도구를 위한 안전한 정규화를 다룹니다.
조용한 decimal mismatch는 에러 없이 잔액이나 USD 값이 자릿수 단위로 틀어지는 가장 흔한 원인 중 하나입니다.
스테이블코인이 어디서나 같은 decimals를 쓴다고 가정하지 않습니다. 런타임에 decimals()를 조회하고, (chain_id, token_address) 기준으로 캐시하며, 값 계산에는 decimal-safe 수학을 사용합니다.
from decimal import Decimal
from web3 import Web3
ERC20_ABI = [
{"name": "decimals", "type": "function", "inputs": [],
"outputs": [{"type": "uint8"}], "stateMutability": "view"},
{"name": "balanceOf", "type": "function",
"inputs": [{"name": "account", "type": "address"}],
"outputs": [{"type": "uint256"}], "stateMutability": "view"},
]
def get_token_balance(w3: Web3, token_address: str, wallet: str) -> Decimal:
contract = w3.eth.contract(
address=Web3.to_checksum_address(token_address),
abi=ERC20_ABI,
)
decimals = contract.functions.decimals().call()
raw = contract.functions.balanceOf(Web3.to_checksum_address(wallet)).call()
return Decimal(raw) / Decimal(10 ** decimals)
기호가 다른 체인에서 6 decimals인 적이 있다고 해서 1_000_000을 하드코딩하지 않습니다.
from functools import lru_cache
@lru_cache(maxsize=512)
def get_decimals(chain_id: int, token_address: str) -> int:
w3 = get_web3_for_chain(chain_id)
contract = w3.eth.contract(
address=Web3.to_checksum_address(token_address),
abi=ERC20_ABI,
)
return contract.functions.decimals().call()