# XCN Decimal Handling

## Critical Difference

{% hint style="danger" %}
Native XCN does **not** use the same decimal scale everywhere.

* Inside Solidity and the EVM, native value uses **8 decimals** in `tinyxcn`
* Across Ethereum-compatible JSON-RPC tooling, native value is exposed with **18-decimal scaling**
* `1 tinyxcn = 10^10` RPC-scaled units
  {% endhint %}

If you port code from Ethereum and keep `1 ether`, `1e18`, or "wei everywhere" assumptions for the native currency, you will introduce incorrect pricing, payment checks, and balance math.

## At a Glance

| Context                  | Unit / Scale             | Decimals                       | Examples                                                                                 |
| ------------------------ | ------------------------ | ------------------------------ | ---------------------------------------------------------------------------------------- |
| Solidity / EVM execution | `tinyxcn`                | 8                              | `msg.value`, `address(this).balance`, `transfer`, `send`, `call{value: ...}`             |
| JSON-RPC and wallets     | 18-decimal native amount | 18                             | `eth_getBalance`, `provider.getBalance`, transaction `value`, `gasPrice`, `maxFeePerGas` |
| ERC-20 token balances    | Token-defined            | Usually 18, but not guaranteed | `balanceOf`, `transfer`, `decimals()`                                                    |

The 8-vs-18 rule applies to the **native currency only**. Do not apply the `10^10` conversion factor to ERC-20 tokens.

## Conversion Rule

For native XCN:

```
1 XCN      = 100,000,000 tinyxcn = 10^8 tinyxcn
1 XCN      = 1,000,000,000,000,000,000 RPC units = 10^18
1 tinyxcn  = 10,000,000,000 RPC units = 10^10
```

That gives you the two conversions you need:

```
rpcAmount   = tinyxcnAmount * 10^10
tinyxcn     = rpcAmount / 10^10
```

## What This Means in Practice

### Inside contracts: always think in `tinyxcn`

When Solidity code handles native value, the amount is always in `tinyxcn`:

* `msg.value`
* `address(this).balance`
* `payable(addr).transfer(amount)`
* `payable(addr).send(amount)`
* `payable(addr).call{value: amount}("")`

`1 XCN` inside a contract is:

```solidity
uint256 constant ONE_XCN = 1e8;
```

Not:

```solidity
uint256 constant ONE_XCN = 1e18; // Wrong for native XCN in Solidity
```

### Outside contracts: JSON-RPC uses 18-decimal scaling

Wallets, libraries, and RPC endpoints expose native balances and transaction value in the familiar 18-decimal Ethereum format:

```javascript
const oneXcnForRpc = ethers.parseEther("1");
// 1000000000000000000n
```

That value arrives inside the contract as:

```solidity
msg.value == 100000000; // 1 XCN in tinyxcn
```

### Your own function parameters are application-defined

If you write a function like `deposit(uint256 amount)` or `quote(uint256 amount)`, the chain does **not** reinterpret that integer for you. You must choose the unit and document it.

For native-value APIs, the safest convention is:

* Accept native amounts in `tinyxcn`
* Name them clearly, for example `amountTinyxcn`
* Emit events with unit names in the field names

## Solidity Patterns

### Use explicit constants

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

contract XcnConstants {
    uint256 internal constant XCN_DECIMALS = 8;
    uint256 internal constant ONE_XCN = 10 ** XCN_DECIMALS; // 1e8
    uint256 internal constant HALF_XCN = ONE_XCN / 2;       // 0.5 XCN
    uint256 internal constant MIN_FEE = ONE_XCN / 100;      // 0.01 XCN
}
```

Do not use:

* `1 ether`
* `0.1 ether`
* `1e18`
* any hardcoded "wei-style" native constants copied from Ethereum contracts

### Payable checks

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

contract Vault {
    uint256 internal constant ONE_XCN = 1e8;
    uint256 internal constant MIN_DEPOSIT = ONE_XCN / 10; // 0.1 XCN

    mapping(address => uint256) public deposits;

    function deposit() external payable {
        require(msg.value >= MIN_DEPOSIT, "Minimum 0.1 XCN");
        deposits[msg.sender] += msg.value; // msg.value is tinyxcn
    }
}
```

### Event naming should include units

```solidity
event DepositRecorded(address indexed account, uint256 amountTinyxcn);
event WithdrawalRecorded(address indexed account, uint256 amountTinyxcn);
```

Avoid ambiguous fields like `amount` if the surrounding code also deals with 18-decimal RPC values off-chain.

## Receiving XCN in Contracts

Solidity receive and fallback behavior works as EVM developers expect **when the value transfer goes through the EVM path**.

If native XCN is sent to a contract through an EVM contract call:

* `receive()` runs when calldata is empty
* `fallback()` runs when calldata does not match a function selector and the fallback is payable

Example:

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

contract XcnReceiver {
    event XcnReceived(address indexed sender, uint256 amountTinyxcn);

    receive() external payable {
        emit XcnReceived(msg.sender, msg.value);
    }

    fallback() external payable {
        emit XcnReceived(msg.sender, msg.value);
    }
}
```

{% hint style="warning" %}
Directly crediting a contract account outside the EVM does **not** execute contract logic. If your application depends on `receive()`, `fallback()`, accounting updates, or access checks, users must send XCN through an EVM transaction that targets the contract.
{% endhint %}

This distinction matters for:

* deposit contracts that credit balances on receipt
* escrow contracts that must validate senders
* accounting contracts that emit events when funds arrive
* upgradeable systems that depend on payable entrypoints

If the business logic must run, expose an explicit payable function such as `deposit()` and route users there.

## Sending XCN From Contracts

The usual Solidity mechanisms are supported:

* `transfer()`
* `send()`
* `call{value: ...}("")`

Example:

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

contract XcnPayout {
    event Paid(address indexed to, uint256 amountTinyxcn);

    function payWithTransfer(address payable to, uint256 amountTinyxcn) external {
        to.transfer(amountTinyxcn);
        emit Paid(to, amountTinyxcn);
    }

    function payWithSend(address payable to, uint256 amountTinyxcn) external {
        bool ok = to.send(amountTinyxcn);
        require(ok, "XCN send failed");
        emit Paid(to, amountTinyxcn);
    }

    function payWithCall(address payable to, uint256 amountTinyxcn) external {
        (bool ok, ) = to.call{value: amountTinyxcn}("");
        require(ok, "XCN call failed");
        emit Paid(to, amountTinyxcn);
    }
}
```

For modern Solidity code, `call` is generally the most flexible choice. Whatever transfer mechanism you use, the `amountTinyxcn` parameter is still an 8-decimal native amount.

## Frontend and RPC Guidance

### Sending 1.5 XCN from a dApp

```javascript
const value = ethers.parseEther("1.5");

await contract.deposit({
  value,
});
```

The transaction is submitted through RPC using 18-decimal scaling, but the contract receives:

```solidity
msg.value == 150000000; // 1.5 XCN in tinyxcn
```

### Reading balances

```javascript
const rpcBalance = await provider.getBalance(contractAddress);
const xcn = ethers.formatEther(rpcBalance);
const tinyxcn = rpcBalance / 10n ** 10n;
```

* `rpcBalance` is the 18-decimal RPC value
* `xcn` is the human-readable XCN string
* `tinyxcn` matches what Solidity would see from `address(contractAddress).balance`

### Recommended utility helpers

```javascript
import { ethers } from "ethers";

const RPC_NATIVE_FACTOR = 10n ** 10n;

export function tinyxcnToRpcAmount(amountTinyxcn) {
  return BigInt(amountTinyxcn) * RPC_NATIVE_FACTOR;
}

export function rpcAmountToTinyxcn(rpcAmount) {
  return BigInt(rpcAmount) / RPC_NATIVE_FACTOR;
}

export function formatTinyxcn(amountTinyxcn) {
  return ethers.formatUnits(amountTinyxcn, 8);
}

export function parseXcnForRpc(xcn) {
  return ethers.parseEther(xcn);
}
```

Use `BigInt`, not JavaScript `Number`, for raw amounts.

## Example: Same Balance, Two Representations

Assume an account holds `9,000,099.81460509 XCN`.

Inside Solidity:

```solidity
uint256 balanceTinyxcn = address(this).balance;
// 900009981460509
```

Via JSON-RPC:

```javascript
const balanceRpc = await provider.getBalance(address);
// 9000099814605090000000000n
```

The ratio is exactly `10^10`.

## ERC-20 and Other Token Amounts

Do not generalize native XCN rules to token contracts.

* ERC-20 amounts use the token's own `decimals()` setting
* Many ERC-20 tokens use 18 decimals, but not all do
* NFT standards do not use this native-value conversion model
* The `10^10` factor is only for converting **native XCN** between Solidity/EVM and JSON-RPC representations

## Testing Guidance

A good test should assert both views of the same transfer:

```javascript
it("keeps RPC and contract views aligned", async function () {
  await vault.deposit({ value: ethers.parseEther("1") });

  const onChainBalanceTinyxcn = await vault.totalHeld();
  expect(onChainBalanceTinyxcn).to.equal(100_000_000n);

  const rpcBalance = await ethers.provider.getBalance(await vault.getAddress());
  expect(rpcBalance).to.equal(ethers.parseEther("1"));
});
```

Also search migrated contracts for:

* `ether`
* `1e18`
* `parseEther` values compared directly against contract-side amounts
* event names that hide units
* comments claiming `msg.value` is 18 decimals

## Migration Checklist

* Replace native `ether` constants with `tinyxcn` constants
* Audit every `msg.value` check
* Audit every `address.balance` comparison
* Standardize off-chain conversion helpers
* Name native-amount variables with `Tinyxcn` suffixes when possible
* Ensure payable flows go through an EVM contract call if contract logic must execute
* Add tests that compare RPC values and contract-side values for the same transfer

## Summary

{% hint style="success" %}
Use these rules and the decimal mismatch becomes manageable:

* Solidity native value is always `tinyxcn` with 8 decimals
* JSON-RPC native value is exposed with 18-decimal scaling
* `1 tinyxcn = 10^10` RPC units
* `receive()` and `fallback()` only run when value enters through the EVM path
* ERC-20 token decimals are separate from native XCN handling
  {% endhint %}

## Related Pages

* [Deploying Contracts](/developer-guide/deploying-contracts.md)
* [Lazy Account Creation (Gas Requirements)](/developer-guide/lazy-create-gas.md)
* [Getting Started](/developer-guide/getting-started.md)
* [EVM JSON-RPC API](/apis/evm-json-rpc.md)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.goliath.net/developer-guide/decimal-handling.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
