

Issue, transfer, and query **fungible tokens** on CKB. UDT (User Defined Token) amounts are stored in the `data` field of cells; CCC's `ccc.udt.Udt` class handles encoding, SSRI execution, and legacy xUDT fallback automatically.

## What you'll get [#what-youll-get]

After this guide you'll be able to:

* **Transfer UDT tokens** with automatic input selection and change handling
* **Mint new tokens** (requires owner-mode authority)
* **Read on-chain metadata** (name, symbol, decimals, icon) for SSRI-compliant tokens
* Understand how xUDT and sUDT relate

<Callout type="info">
  All examples assume you already have a connected `signer`. See [Connect Wallets](/docs/guides/connect-wallets) (browser) or [Node.js Backend](/docs/guides/node-js-backend) (server) first.
</Callout>

## xUDT vs sUDT [#xudt-vs-sudt]

|                     | sUDT                                                                                                    | xUDT                                                                                                            |
| ------------------- | ------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| Standard            | [CKB RFC 25](https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0025-simple-udt/0025-simple-udt.md) | [CKB RFC 52](https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0052-extensible-udt/0052-extensible-udt.md) |
| Extension support   | No                                                                                                      | Yes (owner-mode, RCE rules)                                                                                     |
| `KnownScript` value | — (not in CCC)                                                                                          | `ccc.KnownScript.XUdt`                                                                                          |
| Production use      | Legacy                                                                                                  | Recommended                                                                                                     |

The `Udt` class in CCC supports both. By default it uses the **SSRI** execution model (on-chain script execution), and falls back gracefully to off-chain construction for legacy xUDT tokens.

## Installation [#installation]

```bash
npm install @ckb-ccc/ccc
```

UDT support is bundled in the main package — no extra install is required.

## Import [#import]

```typescript
import { ccc } from "@ckb-ccc/ccc";
```

The `Udt` class is available as `ccc.udt.Udt`.

## Construct a `Udt` instance [#construct-a-udt-instance]

Before you can transfer or mint, you need a `Udt` object. It takes two pieces of information:

* **`code`** — the `OutPoint` of the cell that holds the UDT script code (cell dep)
* **`script`** — the type script that uniquely identifies this token (its `args` is typically the issuer's lock hash)

```typescript
const type = await ccc.Script.fromKnownScript(
  signer.client,
  ccc.KnownScript.XUdt,
  // The args uniquely identify this token (issuer lock hash)
  "0xf8f94a13dfe1b87c10312fb9678ab5276eefbe1e0b2c62b4841b1f393494eff2",
);

const code = (
  await signer.client.getCellDeps(
    (await signer.client.getKnownScript(ccc.KnownScript.XUdt)).cellDeps,
  )
)[0].outPoint;

const udt = new ccc.udt.Udt(code, type);
// Optional 3rd arg: { executor } for SSRI execution. Omit for legacy xUDT.
```

## Transfer tokens [#transfer-tokens]

**Use this when:** you want to send UDT tokens from the signer to another address. The four-step pattern mirrors CKB transfers, with an extra UDT-specific input-filling step:

<Steps>
  <Step title="Build the transfer outputs">
    `udt.transfer` creates a transaction with the desired output cells. It does **not** yet balance inputs — that's the next two steps.

    ```typescript
    const receiver = await signer.getRecommendedAddress();
    const { script: lock } = await ccc.Address.fromString(receiver, signer.client);

    let { res: tx } = await udt.transfer(signer, [
      { to: lock, amount: ccc.fixedPointFrom(1) }, // 1 token unit
    ]);
    ```
  </Step>

  <Step title="Fill UDT inputs">
    `udt.completeBy` collects the signer's UDT cells until there is enough token balance to cover the outputs. Any surplus UDT is returned as a change cell automatically.

    ```typescript
    tx = await udt.completeBy(tx, signer);
    ```
  </Step>

  <Step title="Fill CKB capacity inputs">
    UDT cells also need CKB capacity to exist on-chain. This step adds CKB inputs to cover all output cells:

    ```typescript
    await tx.completeInputsByCapacity(signer);
    ```
  </Step>

  <Step title="Pay fee and broadcast">
    ```typescript
    await tx.completeFeeBy(signer);
    const txHash = await signer.sendTransaction(tx);
    console.log("UDT transfer hash:", txHash); // "0x..." — 32-byte tx hash
    ```
  </Step>
</Steps>

## Mint tokens [#mint-tokens]

**Use this when:** you are the token issuer and want to create new token supply. The signer must have **mint authority** (typically `owner-mode` in xUDT — the signer's lock hash matches the type script args).

Minting creates new token outputs without requiring existing UDT inputs:

```typescript
const { script: to } = await ccc.Address.fromString(receiver, signer.client);

let { res: tx } = await udt.mint(signer, [
  { to, amount: ccc.fixedPointFrom(1000) },
]);

await tx.completeInputsByCapacity(signer);
await tx.completeFeeBy(signer);
const txHash = await signer.sendTransaction(tx);
```

## Read token metadata (SSRI tokens) [#read-token-metadata-ssri-tokens]

**Use this when:** you want to display token name, symbol, decimals, or icon in your UI. Only works for tokens that implement the SSRI `UDT` interface on-chain:

```typescript
const { res: name }     = await udt.name();
const { res: symbol }   = await udt.symbol();
const { res: decimals } = await udt.decimals();
const { res: icon }     = await udt.icon();

console.log(`${name} (${symbol}), ${decimals} decimals`);
```

<Callout type="warning">
  These methods return `undefined` for legacy sUDT/xUDT tokens that do not implement the SSRI `UDT` interface. Check the return value before using it.
</Callout>

## API reference [#api-reference]

| Method                                       | Description                                 |
| -------------------------------------------- | ------------------------------------------- |
| `new ccc.udt.Udt(code, script)`              | Construct a UDT instance                    |
| `udt.transfer(signer, transfers, tx?)`       | Build transfer outputs                      |
| `udt.mint(signer, mints, tx?)`               | Build mint outputs                          |
| `udt.completeBy(tx, signer)`                 | Fill UDT inputs and add change              |
| `udt.completeChangeToLock(tx, signer, lock)` | Fill UDT inputs with a specific change lock |
| `udt.name()`                                 | Token name (SSRI only)                      |
| `udt.symbol()`                               | Token symbol (SSRI only)                    |
| `udt.decimals()`                             | Token decimals (SSRI only)                  |
| `udt.icon()`                                 | Token icon URI (SSRI only)                  |

## Troubleshooting [#troubleshooting]

**`udt.completeBy` throws "not enough UDT balance"**
The signer doesn't own enough UDT cells to cover the transfer amount. Check the signer's UDT balance or fund the address with more tokens.

**`udt.transfer` returns `{ res: tx }` — what is `res`?**
All UDT methods return an `ssri.ExecutorResponse<T>` wrapper. The actual `Transaction` is in the `.res` property. This enables CCC to attach SSRI execution metadata alongside the result.

**`udt.name()` / `udt.symbol()` returns `undefined`**
The token does not implement the SSRI `UDT` metadata interface. This is expected for legacy sUDT/xUDT tokens. You'll need to source metadata from an off-chain registry.

**I want to transfer UDT to an address that has never held this token**
That works out of the box. `udt.transfer` creates a new output cell with the token type script at the recipient's lock. The recipient just needs enough CKB capacity (occupied by the new cell) — which the signer pays for via `completeInputsByCapacity`.

## Next steps [#next-steps]

* [Compose transactions](/docs/guides/compose-transactions) — the declare → fill → fee → send pattern that UDT builds on.
* [Spore Protocol](/docs/guides/spore-protocol) — create non-fungible digital objects on CKB.
* [Node.js backend](/docs/guides/node-js-backend) — issue and transfer tokens from a server-side script.


---

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