> For the complete documentation index, see [llms.txt](https://docs.baas.sh/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.baas.sh/baas-sdk/blockchain/smart-units.md).

# Smart units

Express amounts in the token's unit or in native currency, and read them back formatted. BaaS converts them to and from base units.

Write `SmartUnits('1.5')` for **1.5 tokens**; BaaS handles the decimals. Amounts with a known unit come back with `raw` and `formatted` values for calculation and display.

```ts
import { SmartUnits } from '@baas.sh/sdk';
```

## Where decimals come from

For contracts, the decimals are called a **unit**. Units are set when you register a contract: BaaS [detects](/baas-console/contracts/units/automatic-units.md) them for ERC-20, ERC-4626, and ERC-6909 tokens, and you can [declare your own](/baas-console/contracts/units/custom-units.md) for any other function or event. Native currency always uses 18 decimals.

## Send an amount in the token's unit

Wrap the amount with `SmartUnits()` inside `.params(...)`. The examples below use a token with 6 decimals, like USDC.

```ts
const tx = await baas
  .contract('ERC20')
  .address('0x1234…')
  .function('transfer')
  .params('0xabc…', SmartUnits('1.5')) // 1.5 tokens, encoded as 1500000
  .sendTransaction();
```

Use `SmartUnits()` with `read()`, `sendTransaction()`, `simulate()` or `estimateGas()`.

Detected units cover top-level arguments. An amount nested inside a tuple or an array stays raw unless a unit you declared binds it.

## Send native currency

The same rule applies to a transaction's `value`: `SmartUnits()` means an amount in the chain's native currency, and the SDK converts it to wei with 18 decimals.

```ts
const tx = await baas.wallet.sendTransaction({
  to: '0xabc…',
  value: SmartUnits('0.001'), // 0.001 ETH, sent as 1000000000000000 wei
});
```

Use the same `value` for payable contract calls, simulations and gas estimates: `.sendTransaction({ value: SmartUnits('0.001') })`, `.simulate(...)` or `.estimateGas(...)`.

Already have a value in wei? Pass it as is; wrapping it in `SmartUnits()` would convert it again.

## Read a formatted amount

When BaaS knows an output's decimals, `read()` and `simulate()` return an object in that slot instead of a bare integer string. Type the slot as `UnitFormattedValue`.

```ts
import type { UnitFormattedValue } from '@baas.sh/sdk';

const [balance] = await baas
  .contract('ERC20')
  .address('0x1234…')
  .function('balanceOf')
  .params('0xabc…')
  .read<[UnitFormattedValue]>();
//  → [{ raw: '1500000', formatted: '1.5', metadata: { encoding: 'units', decimals: '6' } }]

console.log(`${balance.formatted} tokens`); // → "1.5 tokens"
```

`raw` is the exact on-chain integer, `formatted` the same amount in the token's unit, and `metadata.decimals` the decimals applied. On an ERC-20, `totalSupply`, `balanceOf`, and `allowance` come back this way; each standard's bound amounts are listed under [Automatic units](/baas-console/contracts/units/automatic-units.md#erc-20). Event arguments and native balances from [`baas.wallet.getBalance()`](/baas-sdk/blockchain/wallet.md#read-balances) use the same shape.

{% hint style="info" %}
**Display `formatted`, compute with `raw`.** A label shows `formatted`. `raw` is what stays exact: pass it to a later call as is, and wrap it in `BigInt()` to compare or compute. Never turn either into a JavaScript `number` for arithmetic.
{% endhint %}

When a value may arrive with or without a unit, use `UnitValue` and check. Here `tokenAddress` is the contract address and `walletAddress` is the account to read:

```ts
import type { UnitValue } from '@baas.sh/sdk';

const [amount] = await baas.contract('MyToken').address(tokenAddress)
  .function('balanceOf').params(walletAddress).read<[UnitValue]>();
const displayed = typeof amount === 'string' ? amount : amount.formatted;
```

## Filter events by amount

Use `gte('args.value', SmartUnits('1.5'))` to find transfers of at least **1.5 tokens**. See the full examples for [contract events](/baas-sdk/blockchain/events.md#filter-events) and [user history](/baas-sdk/manage-user.md#filter-the-users-history). If the argument has no unit, or its decimals depend on a token id, use a raw integer string.

## When to use raw values

Raw values are the exception. They belong in three situations:

| Situation                                                                                                                                                                                | What to do                                                                                                                                                                                                   |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Exact arithmetic.** Comparing two balances, summing amounts, checking a threshold.                                                                                                     | Use `BigInt(value.raw)`. Never `parseFloat(value.formatted)`.                                                                                                                                                |
| **An amount you already hold in base units.** The `raw` of a balance, of an output you read earlier, or of an event's amount; the plain string of a value that came back without a unit. | Pass that string as is, without `SmartUnits()`. Never pass the whole `{ raw, formatted, metadata }` object.                                                                                                  |
| **No unit for that argument or output.** A `mint` outside the ERC-20 standard, an NFT contract, an amount nested in a tuple.                                                             | Pass and read the integer string. When you register the next ABI version, [declare a custom unit](/baas-console/contracts/units/custom-units.md) for it, and the argument takes `SmartUnits()` from then on. |

Units belong to an ABI version and cannot be added to it afterwards. Using `SmartUnits()` without a unit returns `unit_binding_unavailable`; see [Unit errors](/baas-sdk/resources/error-handling.md#unit-errors).

## Precision

`SmartUnits()` takes a decimal `string`; a `number` is refused with `INVALID_ARGUMENT`. The amount must be exactly representable in the target unit; it is never rounded.

## When the amount is refused

An invalid native amount is refused with `INVALID_ARGUMENT`. This includes malformed wrappers, negative amounts, uint256 overflow and fractions smaller than one wei. A bound argument that cannot be converted comes back as a `BaasApiError`; see [Unit errors](/baas-sdk/resources/error-handling.md#unit-errors).

## Types

`UnitValue`, `UnitFormattedValue` and `UnitInput` are in the [API reference](/baas-sdk/resources/api-reference.md#types).

## Next

* [Smart contracts](/baas-sdk/blockchain/smart-contracts.md) — read balances and send tokens.
* [Error handling](/baas-sdk/resources/error-handling.md#unit-errors) — every unit rejection, with its HTTP status and what to do.
* [Units](/baas-console/contracts/units.md) — in the BaaS Console: where units come from, and when decimals are read.
