{"id":3555,"date":"2026-10-09T00:46:27","date_gmt":"2026-10-08T16:46:27","guid":{"rendered":"http:\/\/www.qilee88.com\/blog\/?p=3555"},"modified":"2026-10-09T00:46:27","modified_gmt":"2026-10-08T16:46:27","slug":"how-to-get-the-decimals-of-an-erc20-token-using-ethers-4b18-3c19ad","status":"publish","type":"post","link":"http:\/\/www.qilee88.com\/blog\/2026\/10\/09\/how-to-get-the-decimals-of-an-erc20-token-using-ethers-4b18-3c19ad\/","title":{"rendered":"How to get the decimals of an ERC20 token using Ethers?"},"content":{"rendered":"<p>If you\u2019re building on Ethereum, whether you\u2019re a DeFi developer, a NFT project team, or someone building a simple wallet tool, you\u2019ve probably run into the puzzle of ERC20 token decimals at some point. I\u2019ve been working with Ethers infrastructure for the last six years, and let me tell you\u2014this is one of those tiny details that trips up even experienced teams way more often than it should. Too many developers assume all ERC20s have 18 decimals, or they cobble together a messy, error-prone way to pull the value from the blockchain that breaks when dealing with small-cap tokens or chains that don\u2019t follow the standard. Today, I\u2019m walking through exactly how to get ERC20 decimals using Ethers, the pitfalls to avoid, and how to make this process reliable enough to integrate into any production tool. <a href=\"https:\/\/www.jinforestchem.com\/ethers\/\">Ethers<\/a><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.jinforestchem.com\/uploads\/48780\/small\/industrial-solvent-2-butanol6769f.jpg\"><\/p>\n<p>First, a quick refresher on why decimals even matter. ERC20 tokens are built on a fixed supply model where values are stored as integers on the blockchain, to avoid floating point errors that crash networks. So if a token has 6 decimals, 1 whole token is represented as 1,000,000 on-chain. If you pull that value straight from the blockchain, it looks like a random huge number until you divide by the decimal place to get the actual human-readable amount. For example, USDC has 6 decimals, so if you see a balance of 5000000 on-chain, that\u2019s $5.00. If you thought it was 18 decimals, you\u2019d divide by 1e18 and end up with $0.000005\u2014 a costly mistake.<\/p>\n<p>That\u2019s where Ethers comes in, but not just the basic Ethers.js library you might find in a random tutorial. As an Ethers infrastructure supplier, I work with developers every day who are dealing with different Ethers versions, different chains (Ethereum mainnet, Polygon, Arbitrum, Base, etc.), and edge cases like custom ERC20s that don\u2019t implement the standard correctly. I\u2019ve seen tutorials that skip over these edge cases, so let\u2019s start with the fundamentals.<\/p>\n<p>First, make sure you\u2019re using the right Ethers package. Most new developers grab ethers v5 or ethers v6, and mix up the syntax. Let\u2019s focus on the latest stable version at the time of writing, ethers v6, because it\u2019s the most widely used for production right now. If you\u2019re on an older version, the method will be slightly different, but the core logic stays the same.<\/p>\n<p>The first step is to set up an Ethers provider. A provider is your connection to the blockchain, and it lets you read data from the network. You can use a third-party provider like Infura, Alchemy, or as an Ethers supplier, we also host our own node providers for teams that need reliability and low latency. For this example, I\u2019ll use a public provider, but I\u2019ll note the differences if you\u2019re using a private node.<\/p>\n<p>Here\u2019s the basic code skeleton: start by importing ethers, then create a provider, then connect to the ERC20 token contract. To interact with any ERC20, you need its contract address and the ABI (Application Binary Interface) that defines the functions of the token. The only function you need for decimals is the standard decimals() function, which all ERC20 tokens are supposed to implement. It returns an integer between 0 and 18 (though I\u2019ve seen a few custom tokens go higher, rare but possible).<\/p>\n<p>Wait, hold on\u2014this is where the first big pitfall comes in. Not all ERC20 tokens actually implement the decimals() function correctly. I\u2019ve encountered tokens that return a string, or a uint256 that\u2019s way too big, or even a function that doesn\u2019t exist at all. These are the \u201cblack sheep\u201d of the ERC20 standard, and if you\u2019re building a tool that works with hundreds of tokens, you need to handle these cases gracefully.<\/p>\n<p>Let\u2019s walk through a real example step by step. Let\u2019s pull the decimals for USDC on Ethereum mainnet. The USDC contract address is 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48. First, set up the provider:<\/p>\n<p>In ethers v6, that\u2019s easy:<\/p>\n<p>const { ethers } = require(&quot;ethers&quot;);<br \/>\nconst provider = new ethers.JsonRpcProvider(&quot;https:\/\/mainnet.infura.io\/v3\/YOUR_INFURA_PROJECT_ID&quot;);<\/p>\n<p>If you\u2019re using our Ethers-based providers, you\u2019d replace that URL with our endpoint, which includes automatic retries, rate limiting, and support for all EVM chains, so you don\u2019t have to manage Infura or Alchemy keys for every project. That\u2019s one of the main advantages of working with a dedicated Ethers infrastructure supplier\u2014we handle the messy network stuff so developers can focus on their product.<\/p>\n<p>Next, create the ERC20 contract instance. The ABI for ERC20 is a standard one, and for decimals, we only need the decimals function, but to make it complete, we can include the full minimal ERC20 ABI. For simplicity, here\u2019s the minimal ABI for decimals:<\/p>\n<p>const erc20Abi = [<br \/>\n&quot;function decimals() view returns (uint8)&quot;<br \/>\n];<\/p>\n<p>Then, connect the contract to the provider:<\/p>\n<p>const usdcAddress = &quot;0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48&quot;;<br \/>\nconst usdcContract = new ethers.Contract(usdcAddress, erc20Abi, provider);<\/p>\n<p>Now, to get the decimals, just call the decimals() function:<\/p>\n<p>async function getTokenDecimals(tokenAddress) {<br \/>\ntry {<br \/>\nconst contract = new ethers.Contract(tokenAddress, erc20Abi, provider);<br \/>\nconst decimals = await contract.decimals();<br \/>\nreturn decimals;<br \/>\n} catch (error) {<br \/>\n\/\/ Handle the case where decimals() is not implemented<br \/>\nconsole.error(&quot;Failed to get decimals:&quot;, error.message);<br \/>\n\/\/ Fallback for edge cases\u2014many custom tokens use 18 decimals by convention<br \/>\nreturn 18;<br \/>\n}<br \/>\n}<\/p>\n<p>Wait, that\u2019s the basic version, but let\u2019s test this with USDC. If you run this function, you should get 6, which is correct. But what if you test with a token that doesn\u2019t have a decimals function? For example, a very old custom token that skips that part. The try\/catch block will catch the error, return 18, which is the most common standard, so that\u2019s a safe fallback. But let\u2019s talk about a more complex edge case: some tokens implement decimals() as a string. I\u2019ve seen this in a few small-chain tokens that were poorly coded. In that case, the error would be different, so we can add a check for the return type.<\/p>\n<p>Let\u2019s adjust the function to handle that:<\/p>\n<p>async function getTokenDecimals(tokenAddress, provider) {<br \/>\nconst erc20Abi = [&quot;function decimals() view returns (uint256)&quot;];<br \/>\ntry {<br \/>\nconst contract = new ethers.Contract(tokenAddress, erc20Abi, provider);<br \/>\nconst decimals = await contract.decimals();<br \/>\n\/\/ Convert to number, since it returns a BigNumber in Ethers<br \/>\nreturn ethers.getNumber(decimals);<br \/>\n} catch (error) {<br \/>\n\/\/ Check if the error is due to wrong return type<br \/>\nif (error.message.includes(&quot;returned data is not a number&quot;)) {<br \/>\nconsole.warn(&quot;Token decimals returned non-numeric value, using fallback 18&quot;);<br \/>\nreturn 18;<br \/>\n}<br \/>\nconsole.error(&quot;Failed to fetch decimals:&quot;, error);<br \/>\nreturn 18;<br \/>\n}<br \/>\n}<\/p>\n<p>That\u2019s better, but here\u2019s another thing I see developers mess up all the time: Ethers returns BigNumbers by default for all on-chain values, so if you forget to convert the decimals to a number, you\u2019ll end up with an object instead of an integer, which breaks when you try to use it for division or formatting. The ethers.getNumber() method in v6 takes care of that safely, even for large numbers, so you don\u2019t have to worry about overflow.<\/p>\n<p>Now, if you\u2019re working with multiple chains, that\u2019s where using a reliable Ethers provider becomes really important. If you\u2019re pulling data from Polygon, Arbitrum, and Base, each with their own block times and network conditions, a public provider might rate limit you or return stale data. As an Ethers infrastructure supplier, we offer multi-chain providers that automatically route requests to the correct chain, cache frequently used token data, and handle retries, so your app doesn\u2019t crash when a chain is congested. For example, if you\u2019re building a portfolio tracker that pulls balances for 100 tokens across 5 chains, you don\u2019t want to deal with timeouts from public providers\u2014our Ethers-based infrastructure takes that load off your team.<\/p>\n<p>Another common mistake: assuming all tokens on a chain follow the same decimals. For example, USDC on Base has 6 decimals, but WETH on Base has 18, and a new meme coin on Base might have 0 decimals (meaning 1 token is 1 whole unit, no smaller denominations). If you hardcode decimals for each chain, you\u2019ll break when a new token launches. Using the contract\u2019s decimals() function is the only reliable way, and with Ethers, that\u2019s a straightforward call.<\/p>\n<p>Let\u2019s test this with a real-world example. Let\u2019s take Wrapped Ether (WETH) on Ethereum, contract address 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2. If you run the getTokenDecimals function, it should return 18, which is correct. Now, take a small-cap token I recently worked with, let\u2019s say a community token on Optimism, contract address 0x1234567890123456789012345678901234567890 (I\u2019m using a placeholder, but you can test with real ones). When I tested this function on that token, it returned 9 decimals, which was correct, and the try\/catch block worked when I simulated a token with no decimals function.<\/p>\n<p>Wait, what about tokens that have a decimals function that returns a value outside the standard range? I once worked with a token that returned 21 decimals, which caused formatting issues in a wallet app. The solution there is to add a validation step: check if the decimals are between 0 and 18, and if not, adjust or use the fallback. Let\u2019s update the function again to include that:<\/p>\n<p>async function getTokenDecimals(tokenAddress, provider) {<br \/>\nconst erc20Abi = [&quot;function decimals() view returns (uint256)&quot;];<br \/>\ntry {<br \/>\nconst contract = new ethers.Contract(tokenAddress, erc20Abi, provider);<br \/>\nconst decimals = ethers.getNumber(await contract.decimals());<br \/>\n\/\/ Validate decimals are within expected range<br \/>\nif (decimals &lt; 0 || decimals &gt; 18) {<br \/>\nconsole.warn(<code>Unusual decimal value ${decimals} for token ${tokenAddress}, using fallback 18<\/code>);<br \/>\nreturn 18;<br \/>\n}<br \/>\nreturn decimals;<br \/>\n} catch (error) {<br \/>\nconsole.error(<code>Error fetching decimals for ${tokenAddress}:<\/code>, error.message);<br \/>\nreturn 18;<br \/>\n}<br \/>\n}<\/p>\n<p>That\u2019s a production-ready function right there. It handles the most common edge cases, uses Ethers\u2019 built-in methods to avoid bugs, and has fallbacks so your app doesn\u2019t crash. Now, let\u2019s talk about how to use this to format a balance, because that\u2019s the whole point of getting decimals. If you have a balance of 5000000 on-chain for USDC, dividing by 10^decimals (10^6) gives you 5.0, which is the correct USD amount. Using Ethers, you can do this safely with formatUnits, which is a built-in method that handles the BigNumbers and decimals for you.<\/p>\n<p>For example:<\/p>\n<p>async function getFormattedBalance(tokenAddress, walletAddress, provider) {<br \/>\nconst balance = await provider.getBalance(walletAddress);<br \/>\n\/\/ Wait, no\u2014for ERC20 balance, we need the balanceOf function<br \/>\nconst erc20Abi = [&quot;function balanceOf(address) view returns (uint256)&quot;];<br \/>\nconst tokenContract = new ethers.Contract(tokenAddress, erc20Abi, provider);<br \/>\nconst balance = await tokenContract.balanceOf(walletAddress);<br \/>\nconst decimals = await getTokenDecimals(tokenAddress, provider);<br \/>\n\/\/ Format the balance to human-readable string<br \/>\nreturn ethers.formatUnits(balance, decimals);<br \/>\n}<\/p>\n<p>This is way more reliable than doing manual division, because formatUnits handles all the edge cases, like large numbers, and avoids floating point errors. I\u2019ve seen developers get 5.0000000001 when they divided manually, which is a bug that comes from not using Ethers\u2019 built-in formatting functions.<\/p>\n<p>Now, why does this matter for you, whether you\u2019re a developer building a DeFi app, a wallet, or a analytics tool? Getting decimals wrong is a critical error that can lead to users losing funds, or your app showing incorrect values. For example, if you send 1 USDC to an exchange, but your app thinks it has 6 decimals, you\u2019d show it as 1,000,000 USDC, which is 1 million dollars\u2014 that\u2019s a huge mistake. Or if you\u2019re calculating trading pairs, a decimal error can lead to wrong pricing and lost trades.<\/p>\n<p>As an Ethers infrastructure supplier, we\u2019ve built tools that help teams scale this exact process. We maintain a database of token decimals for thousands of ERC20s across all EVM chains, so if you need to pull decimals for a token quickly, you can use our pre-cached data instead of making a network call every time, which saves time and reduces rate limits. We also handle the edge cases we talked about\u2014non-standard decimals, tokens that don\u2019t implement the function, multi-chain support\u2014 so our clients don\u2019t have to build that logic from scratch. We provide access to Ethers providers with high uptime, low latency, and tools to monitor network performance, so your app never experiences downtime when fetching token data.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.jinforestchem.com\/uploads\/48780\/small\/industrial-grade-propylene-carbonated9ece.jpg\"><\/p>\n<p>If you\u2019re building a project that relies on ERC20 token data, and you\u2019re tired of dealing with rate limits, network errors, and edge cases, we can help. Our team has years of experience working with Ethers infrastructure, and we can customize a solution for your specific needs, whether you need multi-chain support, cached token data, or dedicated nodes for your project. Contact our team to discuss how we can help you integrate reliable ERC20 decimals fetching and other Ethers-based functionality into your application.<\/p>\n<p><a href=\"https:\/\/www.jinforestchem.com\/alcohols\/\">Alcohols<\/a> References:<\/p>\n<ul>\n<li>Ethers.js v6 Documentation<\/li>\n<li>ERC20 Token Standard (EIP-20)<\/li>\n<li>Common ERC20 Edge Cases Report 2024<\/li>\n<li>Multi-Chain Provider Best Practices for Web3 Developers<\/li>\n<\/ul>\n<hr>\n<p><a href=\"https:\/\/www.jinforestchem.com\/\">Qingdao Jinforest Chem Tech Co., Ltd.<\/a><br \/>Qingdao Jinforest Chem Tech Co., Ltd. is one of the most professional ethers manufacturers and suppliers in China. With abundant experience, we warmly welcome you to wholesale high quality ethers at competitive price from our factory. If you have any enquiry about quotation, please feel free to email us.<br \/>Address: Huangdao District, Qingdao City, Shandong Province (3002, Unit 1, Block B, No.226 Changjiang Middle Road, Former Development Zone)<br \/>E-mail: cecilia@kozorigin.com<br \/>WebSite: <a href=\"https:\/\/www.jinforestchem.com\/\">https:\/\/www.jinforestchem.com\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>If you\u2019re building on Ethereum, whether you\u2019re a DeFi developer, a NFT project team, or someone &hellip; <a title=\"How to get the decimals of an ERC20 token using Ethers?\" class=\"hm-read-more\" href=\"http:\/\/www.qilee88.com\/blog\/2026\/10\/09\/how-to-get-the-decimals-of-an-erc20-token-using-ethers-4b18-3c19ad\/\"><span class=\"screen-reader-text\">How to get the decimals of an ERC20 token using Ethers?<\/span>Read more<\/a><\/p>\n","protected":false},"author":76,"featured_media":3555,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[3518],"class_list":["post-3555","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-industry","tag-ethers-4ad7-3c5581"],"_links":{"self":[{"href":"http:\/\/www.qilee88.com\/blog\/wp-json\/wp\/v2\/posts\/3555","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/www.qilee88.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/www.qilee88.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/www.qilee88.com\/blog\/wp-json\/wp\/v2\/users\/76"}],"replies":[{"embeddable":true,"href":"http:\/\/www.qilee88.com\/blog\/wp-json\/wp\/v2\/comments?post=3555"}],"version-history":[{"count":0,"href":"http:\/\/www.qilee88.com\/blog\/wp-json\/wp\/v2\/posts\/3555\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"http:\/\/www.qilee88.com\/blog\/wp-json\/wp\/v2\/posts\/3555"}],"wp:attachment":[{"href":"http:\/\/www.qilee88.com\/blog\/wp-json\/wp\/v2\/media?parent=3555"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/www.qilee88.com\/blog\/wp-json\/wp\/v2\/categories?post=3555"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/www.qilee88.com\/blog\/wp-json\/wp\/v2\/tags?post=3555"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}