THE COIN CLOUD LANGUAGE · VERSION 1

Smart contracts you can read.

TCCL is the smart-contract language of The Coin. It looks like Python — indentation-based blocks, few keywords — but every value has a declared type, every step costs fuel, and the same code gives exactly the same result on every node.

01 — PRINCIPLES

Strict by design.

Anything ambiguous is rejected before it reaches the network. What runs on-chain is exactly what anyone can read.

TYPES

Strictly typed

Every constant, state variable, parameter, and local variable declares its type. There are no implicit conversions: adding an int to a text is a compile error. A view cannot change state, and only payable functions accept TCN.

EXECUTION

Deterministic

No floating-point numbers, no clock, no randomness, no network access. Integers are 128-bit, and any overflow or division by zero stops execution. The only notion of time is the block height (height), so every node reaches the same result.

METERING

Fuel-limited

Every statement, storage read or write, hash, and payment consumes fuel. A transaction reserves a limit (max_fuel). If the fuel runs out or a require fails, everything is reverted — the value sent is returned, nothing is stored — and only the fee is charged. Infinite loops are impossible. Read-only view queries are free.

TRANSPARENCY

Source code on-chain

A deployment transaction carries the source code, not a binary: every node compiles the same text with the same compiler. The contract stores the hash of its source, and anyone can read exactly what will run — in the Explorer, on every contract address.

02 — INSTALL

Get the tools.

You need tccl to check and simulate contracts, and thecoin-wallet to deploy and call them.

WITH A NODE

One-line installer

The node installer also installs the thecoin-wallet wallet and the tccl developer tool:

curl -fsSL https://the-coin.cloud/install.sh | sudo bash -s -- --yes

Only want to write and test contracts? Add --no-mine, or use just the binaries: the tccl run simulator does not need a node. See the validator guide for everything the installer does.

WITHOUT A NODE

Binaries or source

The release packages in the-coin.cloud/releases contain thecoind, thecoin-wallet, and tccl. Or build them with Rust:

git clone https://github.com/LucasBolla94/thecoin
cd thecoin
cargo build --release -p tccl -p thecoin-wallet
./target/release/tccl --version
03 — YOUR FIRST CONTRACT

From an idea to the network, step by step.

The outputs below are real runs of version 0.2 with the counter.tccl example.

01

Write the contract.

Save it as counter.tccl: a counter anyone can increase, with an event and two queries.

# The smallest useful contract: a counter anyone can increase.
contract Counter

state count: int
state last_caller: address

event Increased(by: address, amount: int, total: int)

action increment(amount: int):
    require amount > 0, "amount must be positive"
    require amount <= 100, "at most 100 per call"
    count += amount
    last_caller = caller
    emit Increased(caller, amount, count)

view get() -> int:
    return count

view last() -> address:
    return last_caller

The first line of code names the contract. state is data stored on the blockchain (it starts as zero, empty text, or the zero address); event describes a record that appears in the transaction receipt and the explorer; an action changes state and is called by a transaction; a view only reads and is free. caller is whoever signed the call.

02

Check it with tccl check.

The compiler checks types, return paths, and view rules exactly like the network, and prints the interface:

$ tccl check counter.tccl
✔ Counter compiles (484 bytes of source, 323 bytes compiled)
  state: count: int, last_caller: address
  action increment(amount: int)
  view get() -> int
  view last() -> address
03

Simulate it with tccl run.

A local blockchain on your computer, with test accounts (alice, bob, …) that start with 1,000,000 TCN. State is kept in tccl-state.json.

$ tccl run counter.tccl deploy
deployed Counter at daa436158c1dcdc0242085b6dd9451e33496cbee
ok · fuel used: 2420 · height: 2 · alice balance: 100000000000000 motes

$ tccl run counter.tccl call increment 5
event Increased(by: tcr1zqygp3t5ugluq2uqf7gq60mc9z9t09lpkg42sa, amount: 5, total: 5)
ok · fuel used: 1674 · height: 3 · alice balance: 100000000000000 motes

$ tccl run counter.tccl --from bob call increment 7
event Increased(by: tcr14vqs008pe0yx022r6pneffl2mnn0x2lkzh7452, amount: 7, total: 12)
ok · fuel used: 1674 · height: 4 · bob balance: 100000000000000 motes

$ tccl run counter.tccl view get
result: 12
ok · fuel used: 273 · height: 4 · alice balance: 100000000000000 motes

$ tccl run counter.tccl --from bob call increment 500
FAILED: requirement failed: at most 100 per call · fuel used: 33 (all changes reverted)
error: call failed

Useful options: --from <account>, --value 2.5tcn (for payable functions), --height <n>, --state <file>, and @alice as an address argument. Delete tccl-state.json to start over.

04

Deploy it with the wallet.

The wallet compiles the contract, simulates it on the node to measure the fuel, reserves a margin, shows the fee, and asks for confirmation (-y skips the prompt). Add --network testnet while learning. This output was recorded on a private regtest network (addresses tcr1…), whose default fees are ten times lower than mainnet’s:

$ thecoin-wallet -y contract deploy counter.tccl
Fuel:     2420 measured, limit 8146
From:     tcr1a9edrkqqhylclwfdaf8sk78zgfkkc0ctvk4awq
Action:   deploy contract Counter (484 bytes of TCCL)
Fee:      0.00001948 TCN (643 bytes, priority Normal)
Broadcast OK. txid: 28e99a814f77e26f776274cef2e2e80db1703f15b360690b26d3aef0a03fdd60
Contract address: tcr17kp5hrqhpdkwmukkrsfp6xft6y9l8j02rhxmvz

Options: --max-deposit (the most you accept to lock as a storage deposit; default 1 TCN), --max-fuel, --value, and --priority. Arguments for init go after the file name.

05

Call an action.

Calls are simulated first, too: if a call would fail, the wallet tells you and sends nothing.

$ thecoin-wallet -y contract invoke tcr17kp5hrqhpdkwmukkrsfp6xft6y9l8j02rhxmvz increment 5
Fuel:     1777 measured, limit 7310
Preview:  Increased(by: tcr1a9edrkqqhylclwfdaf8sk78zgfkkc0ctvk4awq, amount: 5, total: 5) (simulated on the current state)
From:     tcr1a9edrkqqhylclwfdaf8sk78zgfkkc0ctvk4awq
Action:   increment(5) on tcr17kp5hrqhpdkwmukkrsfp6xft6y9l8j02rhxmvz
Fee:      0.00001295 TCN (205 bytes, priority Normal)
Broadcast OK. txid: 04a24a2dd4268cc4fa0ff1d3c54aba9f953f03abc8aad56a9e451a84da4c7e5b
06

Query it.

Views create no transaction and cost nothing:

$ thecoin-wallet contract view tcr17kp5hrqhpdkwmukkrsfp6xft6y9l8j02rhxmvz get
5
$ thecoin-wallet contract program tcr17kp5hrqhpdkwmukkrsfp6xft6y9l8j02rhxmvz   # balance, storage, deposit, interface
$ thecoin-wallet tx <txid>   # receipt: success, error, fuel_used, events, return_value

In the Explorer, search for the contract address to see its functions, source code, deposit, the events of each call, and forms to call its views from your browser.

04 — LANGUAGE GUIDE

The language in one page.

The full reference, including operators, error messages, and a security checklist, is in TCCL.md ↗ and the cookbook.

Contract layout

# Contract layout: constants, state, events and functions.
contract PiggyBank

const MINIMUM: int = 1 * TCN
state owner: address
state label: text = "Piggy bank"
state deposits: map[address, int]
state amounts: list[int]

event Deposited(who: address, amount: int)

init():
    owner = caller

action deposit() payable:
    require value >= MINIMUM, "minimum deposit is 1 TCN"
    deposits[caller] += value
    amounts.push(value)
    emit Deposited(caller, value)

action withdraw(amount: int):
    require amount > 0 and deposits[caller] >= amount, "insufficient balance"
    deposits[caller] -= amount
    send(caller, amount)

view balance_of(who: address) -> int:
    return deposits[who]

view total_held() -> int:
    return balance

fn half(x: int) -> int:
    return x / 2
Declaration Purpose
contract Name The first line of code; one file is one contract. The name appears in the explorer.
const NAME: type = value Constant computed at compile time from literals, earlier constants, operators, and address("tc1…").
state name: type [= value] Data stored on the blockchain. Only scalar types accept an initial value; maps exist only as state.
event Name(field: type, …) Record emitted with emit; it appears in the transaction receipt.
init(…) [payable]: Optional; runs once, at deployment.
action name(…) [-> type] [payable]: Entry point called by a transaction; can change state and send TCN. The return type comes before payable.
view name(…) -> type: Free, read-only query; must return a value.
fn name(…) [-> type]: Private helper. Helpers are the only functions code can call; entry points cannot be called from code.

Types

Type Example Notes
int 42, -7, 1_000_000, 5 * TCN Signed 128-bit integer with overflow checks; / truncates toward zero. Amounts are in motes: TCN = 100,000,000.
bool true, false Result of comparisons; and, or, not. Conditions must be bool.
text "hello" UTF-8; escapes \n \t \" \\; join with +.
bytes 0xdeadbeef Join with +; b[i] returns an int.
address caller, address("tc1…") An account or contract; zero_address() is the empty address.
list[T] [1, 2, 3] Up to 4,096 items in a local list; xs[i], len(xs), xs.push(x); xs.pop() on state lists.
map[K, V] balances[caller] += 1 State only; key int, bool, text, bytes, or address. A missing key reads as the type’s zero value; m.has(k), m.remove(k). Maps cannot be iterated.

Default values are never stored: assigning 0, false, "", empty bytes or list, or the zero address deletes the storage entry. 5tcn is accepted only in command-line arguments — in source code write 5 * TCN.

Statements, context, and built-in functions

# Control flow, lists, maps and built-in functions.
contract Examples

state seen: map[bytes, bool]
state heights: list[int]

view sum_even(n: int) -> int:
    let total: int = 0
    for i in range(0, n):
        if i % 2 == 0:
            total += i
        elif i > 1000:
            break
        else:
            continue
    return total

view largest(xs: list[int]) -> int:
    require len(xs) > 0, "empty list"
    let m: int = xs[0]
    for x in xs:
        m = max(m, x)
    return m

view digest(t: text) -> bytes:
    return slice(sha256(t), 0, 4) + blake3(to_bytes(len(t)))

view label(n: int) -> text:
    return "value: " + to_text(abs(n))

view signature_ok(key: bytes, msg: bytes, sig: bytes) -> bool:
    return verify_ed25519(key, msg, sig)

view address_for_key(key: bytes) -> address:
    return address_of(key)

action mark(key: bytes):
    require not seen.has(key), "already marked"
    seen[key] = true
    heights.push(height)

action unmark(key: bytes):
    seen.remove(key)
    let n: int = 0
    while n < 3 and len(heights) > 0:
        heights.pop()
        n += 1
Statement Effect
let x: type = value Local variable; the type and value are mandatory.
x = v · x += v · x -= v · x *= v Assignment; += also joins text and bytes.
if / elif / else Condition (always bool).
while cond: Loop, bounded by the fuel limit.
for i in range(a, b): From a to b − 1.
for x in xs: Iterates over a local or state list.
break · continue · pass Loop control / empty block.
return value Required on every path when the function declares -> type.
require cond, "message" If false, the call fails and every effect is reverted.
send(address, amount) Sends motes from the contract balance. It never runs code at the receiver: contracts cannot call contracts, so there is no re-entrancy.
emit Event(…) Records an event in the receipt.
destroy(address) Actions only: deletes the contract once its lists and maps are empty, and pays its balance and storage deposit to the address.
Name Type / effect
caller address that signed the call (the zero address inside views).
value int: motes sent with the call (not available in views).
balance int: the contract balance, including value.
height int: block height (about one block a minute).
self The contract’s own address.
len(x) Length of a list, text (in bytes), or bytes.
sha256(x) · blake3(x) Hash of bytes or text → 32 bytes.
to_bytes(x) int, bool, text, address, or bytes → bytes.
to_text(n) · to_int(b) int → decimal text · up to 15 bytes → unsigned int.
min(a, b) · max(a, b) · abs(n) Arithmetic on int.
slice(b, start, end) Part of a bytes value.
verify_ed25519(key, msg, signature) bool; false for malformed input.
ring_verify(ring, msg, signature, key_image) bool: linkable ring signature (bLSAG over Ristretto255), up to 64 keys.
address_of(key) The Coin address of a 32-byte public key.
zero_address() The empty address.

Closing a contract

# destroy(): deletes the contract and returns its balance and storage deposit.
contract Temporary

state owner: address
state notes: map[int, text]

init():
    owner = caller

action write(n: int, note: text):
    require caller == owner, "only the owner"
    notes[n] = note

action erase(n: int):
    require caller == owner, "only the owner"
    notes.remove(n)

action close():
    require caller == owner, "only the owner"
    destroy(owner)

destroy only works after every map entry and list item has been removed (scalar variables are cleared automatically). The balance and the whole storage deposit go to the given address.

05 — LIMITS AND FUEL

Bounded, and priced by work.

Fuel prices are calibrated to roughly 20 ns of CPU per unit on a 2-vCPU server, so a completely full block executes in about a second in the worst case.

Limits
Limit Value
Source code 48,000 bytes
Deploy transaction · other transactions 64,000 · 16,384 bytes
Compiled program 262,144 bytes
Functions · state variables · locals per function 256 · 256 · 1,024
Call depth 16
Nesting of blocks, parentheses, types 32
Operators per precedence level · expression depth 64 · 128
Arguments per call · events per call 32 · 64
Text, bytes, or list value · local list items 65,536 bytes · 4,096
Fuel per transaction 10,000,000
Fuel per block (mainnet default) 50,000,000
Fuel per view via the API · in the simulator 2,000,000 · 5,000,000
Fuel costs
Operation Fuel
Statement · expression 2 · 1
Values read or combined 1 per 32 bytes
Function call 20
Storage read 250
Storage write or delete 400 + 4 per byte
sha256 · blake3 60 + 20 per 64 bytes
send · emit 300 · 100 + 1 per byte
verify_ed25519 3,500 + 1 per 64 bytes
ring_verify 5,000 + 10,000 per key
destroy 1,000
Deploy: compile 5 per source byte
Invoke: load the contract 100 + 1 per 100 bytes of code
06 — FEES, FUEL, AND DEPOSITS

No surprises in the price.

01

Fee

Paid when you deploy or call, like any transaction: a base amount, plus the size in bytes, plus the reserved fuel (max_fuel). It is charged even if the contract fails after being mined — the work of running it was done.

02

Fuel

Measures the work. You pay for the limit you reserve, not for what you used, so the wallet measures the call with a simulation and reserves only a margin (measured × 1.3 + 5,000). View queries cost nothing.

03

Storage deposit

Data on the blockchain uses disk space on every node, so whoever makes a contract grow locks a deposit per started kB. It is not spent: it is returned proportionally when space is freed, and in full on destroy.

The exact formulas (each division rounds up):

minimum fee = (base_fee + size × fee_per_kb ÷ 1,000 + max_fuel × fee_per_kfuel ÷ 1,000) × congestion
deposit = ⌈contract bytes ÷ 1,000⌉ × storage_deposit_per_kb

Worked example: the counter on mainnet

Mainnet defaults: base_fee 1,000 motes, fee_per_kb 10,000 motes, fee_per_kfuel 1,000 motes, and a storage_deposit_per_kb of 100,000 motes (1 TCN = 100,000,000 motes). Sizes and fuel come from the deployment above; no congestion (1×). The wallet’s normal priority pays 25% over the minimum.

Operation Calculation Minimum Normal priority (+25%)
Deploy (643 bytes, reserves 8,146 fuel) 1,000 + 6,430 + 8,146 15,576 motes
0.00015576 TCN
19,470 motes
0.0001947 TCN
Deposit (323-byte contract → 1 kB) 1 × 100,000 100,000 motes
0.001 TCN · refundable
increment 5 (205 bytes, reserves 7,310, uses 1,777) 1,000 + 2,050 + 7,310 10,360 motes
0.0001036 TCN
12,950 motes
0.0001295 TCN
view get free free
For comparison: a simple 159-byte transfer 1,000 + 1,590 2,590 motes
0.0000259 TCN
3,238 motes
0.00003238 TCN

After the first call the counter uses 367 bytes — still within 1 kB, so no extra deposit is locked. When blocks fill up, the congestion multiplier raises the minimum fee (at 2×, the deployment costs at least 31,152 motes) and the surcharge is burned rather than paid to the miner. These parameters can change through governance; see the live values in Explorer → Current fees.

07 — PRIVACY

Private payments with contract pools.

How it works

The Coin is transparent: balances and transfers are public. Privacy exists only inside user-written TCCL contracts that use ring_verify — linkable ring signatures (bLSAG over Ristretto255).

  1. Deposit: send the pool’s fixed amount (10 TCN in the example) together with a fresh ring public key.
  2. Wait: other people deposit the same amount.
  3. Withdraw: sign with a ring made of several deposits. The contract learns only that one of them signed, never which, and pays a new address. The key image prevents the same deposit from being withdrawn twice.
  4. Relayer (optional): another account submits the withdrawal and earns a small fee, so the destination address does not need TCN beforehand.

Full example: private_pool.tccl.

In the wallet

Ring keys are derived from your wallet’s recovery phrase. The wallet works with any pool that follows the standard interface:

thecoin-wallet privacy keygen --key 0
thecoin-wallet privacy deposit tc1…pool --key 0
thecoin-wallet privacy status tc1…pool --key 0
thecoin-wallet privacy withdraw tc1…pool --key 0 \
    --to tc1…new --ring-size 16

Optional withdrawal flags: --relayer <address> and --fee <TCN>.

TCCL-PRIV-1 INTERFACE

TCCL-PRIV-1 interface
Function Purpose
denomination() -> int fixed deposit amount
deposits() -> int number of deposits
key_at(index: int) -> bytes public key of a deposit
is_withdrawn(key_image: bytes) -> bool already withdrawn?
message_for(to, relayer, fee) -> bytes message to sign
deposit(public_key: bytes) payable deposit action
withdraw(to, relayer, fee, members, signature, key_image) withdrawal action
Use pools carefully.

Privacy is only as good as the number of deposits in the ring: wait for other people to deposit before withdrawing, withdraw to an address that has never been used, and, if you can, submit the withdrawal from another account or through a relayer (withdrawing from the address that deposited reveals the link — the wallet warns you). Pools are user contracts, not part of the protocol: read the pool’s source code in the explorer before depositing.

08 — RESOURCES

Keep building.

Ready-made examples

File What it does
counter.tccl The smallest useful contract: a counter with an event.
shop.tccl A shop with per-item prices, commented part by part — a good way to learn every kind of declaration.
tip_jar.tccl A tip jar: anyone sends TCN with a message; only the owner withdraws.
token.tccl A token with a fixed maximum supply, transfers, and allowances.
crowdfund.tccl Crowdfunding with a goal and a deadline in blocks; backers are refunded if the goal is missed.
escrow.tccl Escrow payment with an arbiter and a deadline.
savings.tccl Savings locked until a block height you choose.
poll.tccl A poll with fixed options and registered voters.
names.tccl A name service: human-readable names that point to addresses, rented by the year.
treasury.tccl A treasury with several owners (M-of-N approvals).
private_pool.tccl A private payments pool (TCCL-PRIV-1).

Every example compiles with tccl check and is exercised by the test suite (cargo test -p tccl).

Want a node to deploy from?

One command installs the node, the wallet, and the tccl tool on a Linux server.