XCN Decimal Handling
Critical information about XCN token decimal handling differences from Ethereum
⚠️ Critical: 8 vs 18 Decimals Discrepancy
This is a fundamental difference from Ethereum that will cause bugs if not handled correctly.
Inside EVM: XCN uses 8 decimals (tinyxcns)
JSON-RPC: Returns 18 decimals (weixcns)
Conversion: 1 tinyxcn = 10^10 weixcns
Overview
On Goliath, the native XCN token has a unique decimal system that differs significantly from Ethereum's standard 18 decimals everywhere approach. This discrepancy exists between the internal EVM representation and the external JSON-RPC interface.
Quick Reference
EVM Internal
8
10^8 tinyxcns
msg.value, address.balance, Solidity
JSON-RPC
18
10^18 weixcns
eth_getBalance, MetaMask, web3.js
Conversion
-
1 tinyxcn = 10^10 weixcns
Between systems
Understanding the Two Systems
EVM Internal (8 decimals)
Inside smart contracts, all native token operations use tinyxcns with 8 decimal places:
// 1 XCN = 100,000,000 tinyxcns = 10^8 tinyxcns
uint256 constant ONE_XCN = 1e8;This affects:
msg.valuein payable functionsaddress.balancequeriestransfer()andcall{value: }()operationsAll arithmetic with native token amounts
JSON-RPC Interface (18 decimals)
External tools and libraries see balances in weixcns with 18 decimal places:
// 1 XCN = 1,000,000,000,000,000,000 weixcns = 10^18 weixcns
const oneXCN = ethers.parseEther("1"); // Returns 1e18This affects:
eth_getBalanceRPC callsMetaMask balance displays
Ethers.js/Web3.js balance queries
Foundry's
cast balancecommand
Conversion Formula
Balance_RPC = Balance_EVM × 10^10
Where:
- Balance_RPC is in weixcns (18 decimals)
- Balance_EVM is in tinyxcns (8 decimals)Practical Example
Consider an address with 9,000,099.81460509 XCN:
Inside Smart Contract
// Solidity view
uint256 balance = address(this).balance;
// Returns: 900,009,981,460,509 (tinyxcns)
// This is 9,000,099.81460509 × 10^8Via JSON-RPC
// JavaScript view
const balance = await provider.getBalance(address);
// Returns: "9000099814605090000000000" (weixcns)
// This is 9,000,099.81460509 × 10^18Verification
9,000,099,814,605,090,000,000,000 ÷ 900,009,981,460,509 = 10,000,000,000 = 10^10 ✓Smart Contract Development
❌ Common Mistakes
// ❌ WRONG - This is 10 billion XCN!
uint256 public constant ONE_XCN = 1e18;
// ❌ WRONG - Using Ethereum's ether keyword
require(msg.value >= 1 ether, "Send 1 XCN");// ✅ CORRECT - This is 1 XCN
uint256 public constant ONE_XCN = 1e8;
// ✅ CORRECT - Using proper decimals
require(msg.value >= 1e8, "Send 1 XCN");Best Practices
1. Define Clear Constants
contract XCNPayment {
uint256 constant XCN_DECIMALS = 8;
uint256 constant ONE_XCN = 10 ** XCN_DECIMALS; // 1e8
uint256 constant HALF_XCN = ONE_XCN / 2; // 5e7
uint256 constant MIN_PAYMENT = ONE_XCN / 100; // 0.01 XCN
// For display/logging purposes
uint256 constant DISPLAY_DECIMALS = 2;
}2. Handle Payments Correctly
function deposit() external payable {
// msg.value is in tinyxcns (8 decimals)
require(msg.value >= ONE_XCN, "Minimum 1 XCN required");
// Calculate amount in XCN for events/display
uint256 amountInXCN = msg.value / ONE_XCN;
uint256 remainder = msg.value % ONE_XCN;
emit Deposit(msg.sender, amountInXCN, remainder);
}3. Balance Management
function getBalanceInXCN(address account) public view returns (
uint256 whole,
uint256 fractional
) {
uint256 balanceTinybars = account.balance;
whole = balanceTinybars / ONE_XCN;
fractional = balanceTinybars % ONE_XCN;
}
function withdraw(uint256 amountXCN) external {
uint256 amountTinybars = amountXCN * ONE_XCN;
require(address(this).balance >= amountTinybars, "Insufficient balance");
(bool success, ) = msg.sender.call{value: amountTinybars}("");
require(success, "Transfer failed");
}Frontend Integration
Sending Transactions
// User wants to send 1.5 XCN
const amountInXCN = 1.5;
// Convert to weixcns for RPC (18 decimals)
const amountInWeibars = ethers.parseEther(amountInXCN.toString());
// Sends: 1,500,000,000,000,000,000 weixcns
// The EVM receives this as tinyxcns (8 decimals)
// EVM sees: 150,000,000 tinyxcns (correct: 1.5 × 10^8)
await contract.deposit({ value: amountInWeibars });Reading Balances
// Get balance via RPC (returns 18 decimals)
const balanceWei = await provider.getBalance(address);
// Example: "9000099814605090000000000"
// Convert to human-readable XCN
const balanceXCN = ethers.formatEther(balanceWei);
// Result: "9000099.81460509"
// If you need tinyxcns (to match contract's view)
const balanceTinybars = balanceWei / 10n**10n;
// Result: 900009981460509nUtility Functions
// Conversion utilities for your dApp
const XCNUtils = {
// Convert between representations
weiToTinybars: (wei) => {
return BigInt(wei) / 10_000_000_000n; // ÷ 10^10
},
tinyxcnsToWei: (tinyxcns) => {
return BigInt(tinyxcns) * 10_000_000_000n; // × 10^10
},
tinyxcnsToXCN: (tinyxcns) => {
return Number(tinyxcns) / 1e8;
},
weiToXCN: (wei) => {
return Number(wei) / 1e18;
},
// For user input
parseXCN: (xcnString) => {
return ethers.parseEther(xcnString); // Returns weixcns
},
// For display
formatXCN: (weixcns) => {
return ethers.formatEther(weixcns); // Returns XCN string
}
};Testing Considerations
Hardhat Configuration
// hardhat.config.js
module.exports = {
networks: {
goliathTestnet: {
url: "https://testnet-rpc.goliath.net",
accounts: [process.env.PRIVATE_KEY],
chainId: 8901,
// Note: Gas prices are in weixcns (18 decimals)
}
}
};Test Examples
describe("XCN Payment Contract", function() {
it("Should handle 1 XCN deposit correctly", async function() {
// Send 1 XCN using ethers (18 decimals for RPC)
await contract.deposit({
value: ethers.parseEther("1") // 1e18 weixcns
});
// Contract sees this as 1e8 tinyxcns
const contractBalance = await contract.getBalance();
expect(contractBalance).to.equal(100_000_000); // tinyxcns
// RPC returns 18 decimals
const rpcBalance = await ethers.provider.getBalance(contract.address);
expect(rpcBalance).to.equal(ethers.parseEther("1")); // 1e18 weixcns
});
it("Should calculate fees correctly", async function() {
const depositAmount = ethers.parseEther("100"); // 100 XCN
// Contract takes 1% fee in tinyxcns
await contract.depositWithFee({ value: depositAmount });
// Check fee calculation (contract works in tinyxcns)
const fee = await contract.getFeeForAmount(10_000_000_000); // 100 XCN in tinyxcns
expect(fee).to.equal(100_000_000); // 1 XCN fee (1% of 100)
});
});Common Pitfalls
1. Porting Ethereum Contracts
When porting Ethereum contracts, search for:
1 etheror1e18constantsUses of
etherkeywordHardcoded 18 decimal assumptions
// Ethereum contract (DON'T copy directly)
contract EthereumVault {
uint256 public constant MIN_DEPOSIT = 0.1 ether; // 1e17 wei
function deposit() external payable {
require(msg.value >= MIN_DEPOSIT, "Too small");
}
}
// Goliath adaptation (DO this instead)
contract GoliathVault {
uint256 public constant MIN_DEPOSIT = 1e7; // 0.1 XCN = 1e7 tinyxcns
function deposit() external payable {
require(msg.value >= MIN_DEPOSIT, "Too small");
}
}2. Event Emissions
// Be clear about units in events
event Deposit(
address indexed user,
uint256 amountTinybars, // Raw msg.value
uint256 amountXCN // Human-readable whole XCN
);
function deposit() external payable {
emit Deposit(
msg.sender,
msg.value, // tinyxcns (8 decimals)
msg.value / ONE_XCN // whole XCN units
);
}3. Price Calculations
// Price feeds and calculations
contract PriceCalculator {
uint256 constant XCN_DECIMALS = 8;
uint256 constant USD_DECIMALS = 8; // Example: Chainlink price feeds
function calculateXCNAmount(
uint256 usdAmount,
uint256 xcnPriceInUSD // Price with USD_DECIMALS
) public pure returns (uint256) {
// Convert USD to XCN tinyxcns
return (usdAmount * 10**XCN_DECIMALS) / xcnPriceInUSD;
}
}Migration Checklist
When migrating from Ethereum:
Summary
Key Rules for Success:
Smart contracts: Always use 8 decimals (tinyxcns)
Frontend/RPC: Expect 18 decimals (weixcns)
Conversion: Remember the 10^10 factor
Testing: Verify both representations
Documentation: Always specify units clearly
The decimal difference between EVM internal (8) and JSON-RPC (18) is a fundamental characteristic of Goliath's XCN token. Understanding and properly handling this difference is crucial for building secure and functional applications on Goliath.
Related Resources
Last updated