> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cowboy.inc/llms.txt
> Use this file to discover all available pages before exploring further.

# Working with Tokens

> Create and move CIP-20 fungible tokens from the CLI and from actor code

## Overview

Cowboy has protocol-native fungible tokens ([CIP-20](/cips/cip-20-fungible-tokens)): token state lives in the chain's state tree and is manipulated through system instructions, not per-token smart contracts. You interact with tokens two ways:

* **From the CLI** — `cowboy token <subcommand>` for scripting and operations
* **From actor code** — `runtime.token_*` host functions, so actors can create and move tokens as part of their logic

Amounts are integers in the token's base unit (`u128`); `decimals` is display metadata only.

## From the CLI

The full command surface is specified in [cowboy token](/cli-specs/cowboy-token). A typical lifecycle:

```bash theme={null}
# Create (you become owner, mint and freeze authority)
cowboy token create --name "Demo Token" --symbol DEMO \
  --decimals 9 --initial-supply 1000000 --max-supply 2000000

# Inspect and query (read-only — no key required)
cowboy token info --token-id 0x<TOKEN_ID>
cowboy token balance --token-id 0x<TOKEN_ID> --address 0x<ADDR>
cowboy token list

# Move
cowboy token transfer --token-id 0x<TOKEN_ID> --to 0x<ADDR> --amount 100
cowboy token approve --token-id 0x<TOKEN_ID> --spender 0x<ADDR> --amount 50

# Supply management (authorities only)
cowboy token mint --token-id 0x<TOKEN_ID> --to 0x<ADDR> --amount 500
cowboy token burn --token-id 0x<TOKEN_ID> --amount 100

# Compliance controls (freeze authority only)
cowboy token freeze --token-id 0x<TOKEN_ID> --account 0x<ADDR>
cowboy token unfreeze --token-id 0x<TOKEN_ID> --account 0x<ADDR>
```

## From actor code

Actors get the same operations through `cowboy_sdk.runtime`. The caller is the actor itself — the actor's address is the token owner / sender of transfers:

```python theme={null}
from cowboy_sdk import actor, public, runtime


@actor
class Treasury:
    @public
    def create_token(self, payload):
        token_id = runtime.token_create(
            b"Demo Token",      # name
            b"DEMO",            # symbol
            6,                  # decimals
            1_000_000_000_000,  # initial_supply (base units, credited to this actor)
            None,               # max_supply (None = uncapped)
            None,               # transfer_hook (None = no hook)
            None,               # metadata_uri
        )
        self.storage["token_id"] = token_id.hex()
        return b"ok"

    @public
    def pay(self, payload):
        token_id = bytes.fromhex(self.storage["token_id"])
        to = bytes.fromhex("...")        # 20-byte recipient address
        runtime.token_transfer(token_id, to, 1_000_000)
        return b"ok"
```

The query/move surface mirrors the CLI:

| Function                                                                                                | Purpose                                        |
| ------------------------------------------------------------------------------------------------------- | ---------------------------------------------- |
| `runtime.token_create(name, symbol, decimals, initial_supply, max_supply, transfer_hook, metadata_uri)` | Create a token; returns the 32-byte `token_id` |
| `runtime.token_balance_of(token_id, owner)`                                                             | Balance query                                  |
| `runtime.token_total_supply(token_id)`                                                                  | Supply query                                   |
| `runtime.token_transfer(token_id, to, amount)`                                                          | Transfer from the calling actor                |
| `runtime.token_approve(token_id, spender, amount)`                                                      | Set an allowance                               |
| `runtime.token_allowance(token_id, owner, spender)`                                                     | Allowance query                                |
| `runtime.token_transfer_from(token_id, from_addr, to, amount)`                                          | Spend an allowance                             |
| `runtime.token_mint(token_id, to, amount)`                                                              | Mint (requires mint authority)                 |
| `runtime.token_burn(token_id, amount)`                                                                  | Burn from the caller's balance                 |

Each operation has a fixed gas cost (e.g. a transfer is 1,000 Cycles + 64 Cells) — see [CIP-20](/cips/cip-20-fungible-tokens) for the schedule.

## Transfer hooks

A token can name a **hook actor** at creation (`transfer_hook`) to enforce custom transfer policy — blocklists, pausing, compliance logic. The hook implements two handlers:

* **`can_transfer(payload)`** — called *before* balances move. Runs read-only (state writes are not committed). The check is **fail-closed**: the transfer proceeds only if the hook returns `"true"`, `"1"`, or JSON `true`; **any other output** (including `b"ok"` or an error) rejects it, reverting with `TokenHookRejected`.
* **`on_transfer(payload)`** — called *after* balances move, and may update the hook actor's own state. Best-effort: a failure here is logged but does **not** revert the transfer.

Both receive a JSON payload:

```json theme={null}
{
  "token_id": "<hex>",
  "from": "<hex 20-byte address>",
  "to": "<hex 20-byte address>",
  "amount": "<decimal string>"
}
```

```python theme={null}
import json
from cowboy_sdk import actor, public


@actor
class ComplianceHook:
    @public
    def can_transfer(self, payload):
        req = json.loads(payload)
        blocked = self.storage.get("blocklist", [])
        if req["from"] in blocked or req["to"] in blocked:
            return b"false"
        return b"true"

    @public
    def on_transfer(self, payload):
        # notification only — a failure here never reverts the transfer
        return b"ok"
```

<Warning>
  Hooks run under hard sub-limits (`token_hook_max_cycles` / `token_hook_max_cells`, 50,000 each) regardless of the transaction's gas budget — keep them small. Hook reentrancy into the same token is blocked by the runtime.
</Warning>

## Worked example

`examples/core/05-tokens-and-balances/` in the repo pairs a CLI walkthrough (`demo.sh`) with an actor that creates and transfers a token programmatically (`token_actor_example.py`) — a good starting template for both styles.

## Further reading

* [CIP-20 — Fungible Tokens](/cips/cip-20-fungible-tokens) — authoritative spec: storage layout, system instructions, events, authority model
* [cowboy token CLI](/cli-specs/cowboy-token) — every subcommand, flag, and edge case
* [Fee model overview](/architecture/fees/overview) — how Cycles and Cells are charged
