Core Module

The Ton.Core module provides fundamental TON blockchain data structures: Cells, Addresses, Types, and utilities.

Features

  • Cell manipulation - Build and parse TON cells
  • BOC serialization - Convert cells to/from BOC format
  • Address parsing - Handle friendly and raw formats
  • Type definitions - Account, Transaction, Message, StateInit
  • Dictionary support - Work with TON dictionaries (hash maps)
  • Tuple operations - For contract get methods

Cells and BOC

Building Cells

Cells are built using the Builder class:

using Ton.Core.Boc;

Builder builder = Builder.BeginCell();

// Store integers
builder.StoreUint(123, 32);          // 32-bit unsigned
builder.StoreInt(-456, 64);          // 64-bit signed
builder.StoreVarUint(1000, 4);       // Variable-length uint

// Store booleans
builder.StoreBit(true);
builder.StoreBit(false);

// Store addresses
Address addr = Address.Parse("EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N");
builder.StoreAddress(addr);

// Store strings
builder.StoreString("Hello TON");    // UTF-8 string
builder.StoreBuffer(bytes);          // Raw bytes

// Store coins (nanoTON)
builder.StoreCoins(1_000_000_000);   // 1 TON in nanotons

// Store references to other cells
Cell ref1 = Builder.BeginCell().StoreUint(1, 8).EndCell();
Cell ref2 = Builder.BeginCell().StoreUint(2, 8).EndCell();
builder.StoreRef(ref1);
builder.StoreRef(ref2);

// Build the cell
Cell cell = builder.EndCell();

Reading Cells

Cells are read using the Slice class:

Slice slice = cell.BeginParse();

// Load integers
uint value1 = slice.LoadUint(32);
long value2 = slice.LoadInt(64);
BigInteger value3 = slice.LoadVarUint(4);

// Load booleans
bool flag1 = slice.LoadBit();
bool flag2 = slice.LoadBit();

// Load address
Address addr = slice.LoadAddress();

// Load strings and bytes
string text = slice.LoadString(slice.RemainingBits / 8);
byte[] bytes = slice.LoadBuffer(32);

// Load coins
BigInteger coins = slice.LoadCoins();

// Load references
Cell ref1 = slice.LoadRef();
Cell ref2 = slice.LoadRef();

// Check remaining
Console.WriteLine($"Remaining bits: {slice.RemainingBits}");
Console.WriteLine($"Remaining refs: {slice.RemainingRefs}");

Optional Values

// Store optional
Builder builder = Builder.BeginCell();
builder.StoreMaybe(address, (b, a) => b.StoreAddress(a));

// Load optional
Slice slice = cell.BeginParse();
Address? addr = slice.LoadMaybe(s => s.LoadAddress());

Either Type

// Store either (left or right)
builder.StoreEither(
    isLeft: true,
    left: 123,
    right: "text",
    storeLeft: (b, val) => b.StoreUint(val, 32),
    storeRight: (b, val) => b.StoreString(val)
);

// Load either
var result = slice.LoadEither(
    loadLeft: s => s.LoadUint(32),
    loadRight: s => s.LoadString(s.RemainingBits / 8)
);

if (result.IsLeft)
    Console.WriteLine($"Left value: {result.Left}");
else
    Console.WriteLine($"Right value: {result.Right}");

BOC Serialization

// Serialize to BOC
byte[] boc = cell.ToBoc();

// Save to file
File.WriteAllBytes("contract.boc", boc);

// Deserialize from BOC
byte[] bocData = File.ReadAllBytes("contract.boc");
Cell[] cells = Cell.FromBoc(bocData);
Cell rootCell = cells[0];  // First cell is root

Cell Properties

Cell cell = builder.EndCell();

// Basic properties
int bitCount = cell.Bits.Length;
int refCount = cell.Refs.Length;

// Hashing (level-based, 0 is most common)
byte[] hash = cell.Hash(level: 0);
Console.WriteLine($"Cell hash: {Convert.ToHexString(hash)}");

// Depth
int depth = cell.Depth(level: 0);

// Type
CellType type = cell.Type;  // Ordinary, MerkleProof, MerkleUpdate, PrunedBranch
bool isExotic = cell.IsExotic;

Exotic Cells

TON has special cell types for proofs and optimization:

// MerkleProof cells (used in lite client responses)
Cell proofCell = Cell.FromBoc(headerProof)[0];

if (proofCell.Type == CellType.MerkleProof)
{
    // Extract actual data from proof
    Cell dataCell = proofCell.UnwrapProof();
    Slice data = dataCell.BeginParse();
    // ... parse actual data
}

// Automatic unwrapping for convenience
Cell unwrapped = cell.UnwrapProof();  // Returns self if not a proof

Addresses

Parsing Addresses

using Ton.Core.Addresses;

// Parse any format
Address addr1 = Address.Parse("EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N");
Address addr2 = Address.Parse("0:83dfd552e63729b472fcbcc8c45ebcc6691702558b68ec7527e1ba403a0f31a8");

// Parse specific format
Address friendly = Address.ParseFriendly("EQCD39VS...").Address;
Address raw = Address.ParseRaw("0:83dfd5...");

// Create from components
Address addr = new Address(
    workchain: 0,
    hash: hashBytes  // 32 bytes
);

Address Validation

// Check if valid address
bool isFriendly = Address.IsFriendly("EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N");
bool isRaw = Address.IsRaw("0:83dfd552e63729b472fcbcc8c45ebcc6691702558b68ec7527e1ba403a0f31a8");

// Try parse
if (Address.TryParse("somestring", out Address? addr))
{
    Console.WriteLine($"Valid address: {addr}");
}

Address Components

Address addr = Address.Parse("EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N");

// Access components
int workchain = addr.Workchain;  // 0 = basechain, -1 = masterchain
byte[] hash = addr.Hash;         // 32 bytes

// Convert to formats
string friendly = addr.ToString(AddressType.Base64);
string raw = addr.ToString(AddressType.Raw);

// With flags
string bounceable = addr.ToString(AddressType.Base64, bounceableTag: true);      // EQ...
string nonBounceable = addr.ToString(AddressType.Base64, bounceableTag: false);  // UQ...
string testnet = addr.ToString(AddressType.Base64, testOnly: true);

Address Comparison

Address addr1 = Address.Parse("EQCD39VS5...");
Address addr2 = Address.Parse("0:83dfd552...");

// Equality (ignores format)
bool areEqual = addr1.Equals(addr2);  // true if same workchain and hash

// Operators
bool same = addr1 == addr2;
bool different = addr1 != addr2;

Coins

Working with TON amounts:

using Ton.Core.Utils;
using System.Numerics;

// Create from nanotons
BigInteger nanotons = 1_500_000_000;  // 1.5 TON
Coins coins = Coins.FromNano(nanotons);

// Create from TON string
Coins amount1 = Coins.Parse("1.5");      // 1.5 TON
Coins amount2 = Coins.Parse("0.1");      // 0.1 TON

// Convert to string (human readable)
string formatted = coins.ToString();  // "1.5"

// Get nanotons
BigInteger nano = coins.ToNano();

// Arithmetic
Coins sum = amount1 + amount2;
Coins diff = amount1 - amount2;
Coins product = amount1 * 2;
bool greater = amount1 > amount2;

Dictionaries

TON dictionaries are hash maps stored in cells:

using Ton.Core.Dict;
using System.Numerics;

// Create empty dictionary
var dict = TonDict.Dictionary<TonDict.DictKeyUint, BigInteger>.Empty();

// Set values
dict.Set(1u, BigInteger.Parse("1000000"));
dict.Set(2u, BigInteger.Parse("2000000"));
dict.Set(100u, BigInteger.Parse("5000000"));

// Get values
BigInteger? value1 = dict.Get(1u);
if (value1 != null)
{
    Console.WriteLine($"Value for key 1: {value1}");
}

// Check if key exists
bool has = dict.Has(2u);

// Delete key
dict.Delete(2u);

// Get all keys
IEnumerable<uint> keys = dict.Keys;

// Get all values
IEnumerable<BigInteger> values = dict.Values;

// Iterate
foreach (var (key, value) in dict)
{
    Console.WriteLine($"{key}: {value}");
}

// Store in cell
Builder builder = Builder.BeginCell();
dict.Store(builder);
Cell cell = builder.EndCell();

// Load from cell
Slice slice = cell.BeginParse();
var loadedDict = TonDict.Dictionary<TonDict.DictKeyUint, BigInteger>.Load(
    slice,
    32,  // key size in bits
    valLoader: s => s.LoadUint(64)
);

Dictionary Key Types

// Uint keys
DictKeyUint key1 = new(123);
DictKeyUint key2 = 456u;  // implicit conversion

// Int keys
DictKeyInt key3 = new(-123);

// Address keys
DictKeyAddress key4 = new(address);

// BigInt keys
DictKeyBigInt key5 = new(BigInteger.Parse("12345678901234567890"));

// Buffer keys
DictKeyBuffer key6 = new(bytes);

Tuples

Tuples are used for contract get method parameters and return values:

using Ton.Core.Tuple;

// Create tuple
TupleBuilder tb = new();
tb.WriteNumber(123);
tb.WriteString("hello");
tb.WriteAddress(address);
tb.WriteCell(cell);
tb.WriteSlice(slice);
tb.WriteBoolean(true);

Tuple tuple = tb.Build();

// Read tuple
TupleReader tr = tuple.BeginRead();
BigInteger num = tr.ReadNumber();
string str = tr.ReadString();
Address addr = tr.ReadAddress();
Cell c = tr.ReadCell();
Slice s = tr.ReadSlice();
bool flag = tr.ReadBoolean();

// Nested tuples
TupleBuilder inner = new();
inner.WriteNumber(1);
inner.WriteNumber(2);
tb.WriteTuple(inner.Build());

Types

Core blockchain types:

StateInit

Contract initialization data:

using Ton.Core.Types;

StateInit stateInit = new()
{
    Code = codeCell,      // Contract code
    Data = dataCell,      // Initial data
    Libraries = null,     // Optional libraries
};

// Store in cell
Builder builder = Builder.BeginCell();
stateInit.Store(builder);
Cell cell = builder.EndCell();

// Load from cell
Slice slice = cell.BeginParse();
StateInit loaded = StateInit.Load(slice);

Message

Internal/external messages:

// Internal message (between contracts)
Message msg = new Message.InternalMessage
{
    Info = new CommonMessageInfoIntRelaxed
    {
        IhrDisabled = true,
        Bounce = true,
        Bounced = false,
        Src = senderAddress,
        Dest = destAddress,
        Value = Coins.FromNano(1_000_000_000),  // 1 TON
        IhrFee = BigInteger.Zero,
        FwdFee = BigInteger.Zero,
        CreatedLt = 0,
        CreatedAt = 0
    },
    Body = bodyCell
};

// External message (from outside)
Message extMsg = new Message.ExternalInMessage
{
    Info = new CommonMessageInfoExternalIn
    {
        Src = ExternalAddress.None,
        Dest = destAddress,
        ImportFee = BigInteger.Zero
    },
    Body = bodyCell
};

Account

Account state representation:

Account account = Account.Load(slice);

Console.WriteLine($"Address: {account.Address}");
Console.WriteLine($"Balance: {account.Storage.Balance.Coins}");

if (account.Storage.State is AccountState.Active activeState)
{
    Cell? code = activeState.State?.Code;
    Cell? data = activeState.State?.Data;
    Console.WriteLine("Account is active");
}

Transaction

Transaction data:

Transaction tx = Transaction.Load(slice, cell);

Console.WriteLine($"Account: {tx.AccountAddr}");
Console.WriteLine($"LT: {tx.Lt}");
Console.WriteLine($"Hash: {Convert.ToHexString(tx.Hash)}");
Console.WriteLine($"Now: {tx.Now}");

// Transaction description
if (tx.Description is TransactionDescription.Ordinary ord)
{
    // Parse ordinary transaction
    Console.WriteLine($"Destroyed: {ord.Destroyed}");
    // ... more fields
}

Utilities

Bits

Low-level bit manipulation:

using Ton.Core.Boc;

// Create bit string
BitString bits = new(new byte[] { 0b10101010 }, 8);

// Bit builder (dynamic)
BitBuilder bb = new();
bb.WriteBit(true);
bb.WriteBit(false);
bb.WriteUint(123, 8);
BitString result = bb.Build();

// Bit reader
BitReader br = new(bits);
bool bit1 = br.LoadBit();
uint value = br.LoadUint(8);

Hashing

using Ton.Crypto.Primitives;

// SHA-256
byte[] hash = Sha256.Hash(data);

// SHA-512
byte[] hash512 = Sha512.Hash(data);

// HMAC-SHA512
byte[] hmac = HmacSha512.Hash(message, key);

Best Practices

1. Reuse Builders

// ❌ Avoid: Creating many builders
for (int i = 0; i < 100; i++)
{
    Builder b = Builder.BeginCell();
    // ...
}

// ✅ Good: Reuse when possible
Builder builder = Builder.BeginCell();
for (int i = 0; i < 100; i++)
{
    builder.StoreUint((uint)i, 32);
}

2. Check Remaining Space

Builder builder = Builder.BeginCell();

// Check before storing
if (builder.AvailableBits >= 256)
{
    builder.StoreUint(value, 256);
}

if (builder.AvailableRefs >= 1)
{
    builder.StoreRef(cell);
}

3. Validate Addresses

// ✅ Good: Validate user input
string userInput = GetUserInput();
if (Address.TryParse(userInput, out Address? addr))
{
    // Use addr
}
else
{
    Console.WriteLine("Invalid address");
}

4. Handle BOC Errors

try
{
    Cell[] cells = Cell.FromBoc(bocData);
}
catch (ArgumentException ex)
{
    Console.WriteLine($"Invalid BOC: {ex.Message}");
}

See Also