From ecc
EVM 체인 전반의 조용한 decimal mismatch 버그를 방지합니다. 런타임 decimal 조회, 체인 인지 캐시, 브리지드 토큰 정밀도 드리프트, 봇/대시보드/DeFi 도구를 위한 안전한 정규화를 다룹니다.
npx claudepluginhub sam42-lab/everything-claude-code-krThis skill uses the workspace's default tool permissions.
조용한 decimal mismatch는 에러 없이 잔액이나 USD 값이 자릿수 단위로 틀어지는 가장 흔한 원인 중 하나입니다.
Mandates invoking relevant skills via tools before any response in coding sessions. Covers access, priorities, and adaptations for Claude Code, Copilot CLI, Gemini CLI.
Share bugs, ideas, or general feedback.
조용한 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()
try:
decimals = contract.functions.decimals().call()
except Exception:
logging.warning(
"decimals() reverted on %s (chain %s), defaulting to 18",
token_address,
chain_id,
)
decimals = 18
fallback는 로그로 남기고 눈에 보이게 유지합니다.
interface IERC20Metadata {
function decimals() external view returns (uint8);
}
function normalizeToWad(address token, uint256 amount) internal view returns (uint256) {
uint8 d = IERC20Metadata(token).decimals();
if (d == 18) return amount;
if (d < 18) return amount * 10 ** (18 - d);
return amount / 10 ** (d - 18);
}
import { Contract, formatUnits } from 'ethers';
const ERC20_ABI = [
'function decimals() view returns (uint8)',
'function balanceOf(address) view returns (uint256)',
];
async function getBalance(provider: any, tokenAddress: string, wallet: string): Promise<string> {
const token = new Contract(tokenAddress, ERC20_ABI, provider);
const [decimals, raw] = await Promise.all([
token.decimals(),
token.balanceOf(wallet),
]);
return formatUnits(raw, decimals);
}
cast call <token_address> "decimals()(uint8)" --rpc-url <rpc>
decimals()를 조회합니다Decimal, BigInt, 동등한 정확한 수학을 사용합니다