Skip to main content
This guide explains how to work with balance values returned by the Dfns API, including decimal conversion and precision handling.

Understanding balance fields

When you call Get Wallet Assets, you receive balances in their raw (smallest unit) form:
{
  "assets": [
    {
      "symbol": "ETH",
      "balance": "1500000000000000000",
      "decimals": 18,
      "verified": true
    },
    {
      "symbol": "USDC",
      "balance": "1000000",
      "decimals": 6,
      "verified": true
    }
  ]
}
FieldDescription
balanceRaw balance in the smallest unit (wei, satoshi, etc.)
decimalsNumber of decimal places for this token
symbolToken symbol

Converting for display

Formula: displayBalance = rawBalance / 10^decimals

JavaScript/TypeScript

function formatBalance(balance: string, decimals: number): string {
  // Use BigInt to avoid floating point issues
  const raw = BigInt(balance)
  const divisor = BigInt(10 ** decimals)

  const wholePart = raw / divisor
  const fractionalPart = raw % divisor

  // Pad fractional part with leading zeros
  const fractionalStr = fractionalPart.toString().padStart(decimals, '0')

  // Trim trailing zeros for cleaner display
  const trimmedFractional = fractionalStr.replace(/0+$/, '')

  if (trimmedFractional === '') {
    return wholePart.toString()
  }

  return `${wholePart}.${trimmedFractional}`
}

// Examples
formatBalance('1500000000000000000', 18) // "1.5" (ETH)
formatBalance('1000000', 6)              // "1" (USDC)
formatBalance('123456789', 6)            // "123.456789" (USDC)

Using ethers.js

import { formatUnits } from 'ethers'

const balance = '1500000000000000000'
const decimals = 18

const formatted = formatUnits(balance, decimals)
console.log(formatted) // "1.5"

Using viem

import { formatUnits } from 'viem'

const balance = 1500000000000000000n
const decimals = 18

const formatted = formatUnits(balance, decimals)
console.log(formatted) // "1.5"

Precision best practices

Never use JavaScript floating point numbers for balance calculations. They lose precision with large numbers.
// BAD - loses precision
const balance = 1500000000000000000
const display = balance / 1e18 // Floating point errors!

// GOOD - use BigInt
const balance = BigInt('1500000000000000000')
const divisor = BigInt(10 ** 18)
  1. Store and calculate using raw values (strings or BigInt)
  2. Convert to display only for UI rendering
  3. Convert back to raw before sending transactions
// Display to user
const displayBalance = formatBalance(asset.balance, asset.decimals)
console.log(`You have ${displayBalance} ${asset.symbol}`)

// User enters amount to send
const userInput = '0.5' // User wants to send 0.5 ETH

// Convert back to raw for the API
function parseBalance(amount: string, decimals: number): string {
  const [whole, fraction = ''] = amount.split('.')
  const paddedFraction = fraction.padEnd(decimals, '0').slice(0, decimals)
  return BigInt(whole + paddedFraction).toString()
}

const rawAmount = parseBalance('0.5', 18) // "500000000000000000"

// Send via API
await dfnsClient.wallets.transferAsset({
  walletId,
  body: {
    kind: 'Native',
    to: recipient,
    amount: rawAmount, // Always use raw values
  },
})

”Send Max” implementation

To send the entire balance, use the raw value directly:
async function sendMax(walletId: string, recipient: string) {
  // Get current balance
  const { assets } = await dfnsClient.wallets.getWalletAssets({ walletId })
  const nativeAsset = assets.find((a) => a.symbol === 'ETH')

  if (!nativeAsset || nativeAsset.balance === '0') {
    throw new Error('No balance to send')
  }

  // For native tokens, gas is deducted automatically
  // For ERC-20 tokens, you can send the full balance
  await dfnsClient.wallets.transferAsset({
    walletId,
    body: {
      kind: 'Native',
      to: recipient,
      amount: nativeAsset.balance, // Send full raw balance
    },
  })
}
For native token transfers (ETH, MATIC, etc.), Dfns automatically reserves enough for gas fees. For token transfers, you can send the exact balance shown.

Common mistakes

Double conversion

// WRONG - converting twice
const display = formatBalance(balance, decimals)
const toSend = formatBalance(display, decimals) // Already converted!

// RIGHT - convert raw value once
const display = formatBalance(balance, decimals)

Wrong decimals

Different tokens have different decimal places:
TokenDecimals
ETH, MATIC, most ERC-2018
USDC, USDT6
WBTC8
Bitcoin (satoshis)8
Always use the decimals field from the API response, don’t hardcode.

Displaying too many decimals

For user-friendly display, limit decimal places:
function formatForDisplay(
  balance: string,
  decimals: number,
  maxDisplayDecimals = 6
): string {
  const full = formatBalance(balance, decimals)
  const [whole, fraction = ''] = full.split('.')

  if (fraction.length <= maxDisplayDecimals) {
    return full
  }

  return `${whole}.${fraction.slice(0, maxDisplayDecimals)}`
}

// "1.234567890123456789" becomes "1.234567"