Skip to main content
Read Library cells first. That page explains the cell format that on-chain libraries use.

Fee savings

Library cells can reduce forward fees and storage costs:
  • In jettons, the StateInit must be forwarded with each transfer, which increases forwarding fees. Moving the code into a library cell reduces that overhead.
  • A library must be hosted in the masterchain, where storage is much more expensive than in the basechain. If fewer than about 1,000 contracts share the same code, storing a copy in the basechain can be cheaper. The exact ratio depends on blockchain config parameter 18. Compare both storage and forwarding costs before choosing this approach.
Everything in TON is stored in cells, including account code. A common use case for libraries is shared code across multiple contracts. When a library cell is part of an account’s code, the runtime dereferences it on first access. This makes it possible to replace part of the contract code, or even the entire code, with a library cell. Replacing the entire code with a library cell is common in TON smart contracts. Examples include:
  1. USDT and other popular jetton wallet contracts
  2. The Order contract in multisig v2
  3. NFT item contracts in popular collections
Check whether a contract uses a library as its code by inspecting its code cell in an explorer.
Partial explorer snippet of a USDT jetton wallet account
...
code:(just
      value:(raw@^Cell
        x{}
         SPECIAL x{028F452D7A4DFD74066B682365177259ED05734435BE76B5FD4BD5D8AF2B7C3D68}
        ))
...
The exact representation shown in the TON Explorer is not important. The key point is that the contract code contains a single SPECIAL cell, which indicates an exotic cell. The first byte equals 2, which marks a library cell. The remaining bytes contain the hash of the referenced cell. In this form, the entire contract code consists of the 8-bit tag 2 and the 256-bit representation hash of the referenced cell. If only part of the code should live in a library cell, move the shared function into a library instead. This works well when multiple contracts reuse the same function. The build process for that setup usually needs custom tooling.

Using @ton/core

Construct a library cell in TypeScript with the @ton/core library. The following example shows the pattern in a Blueprint project:
import { Cell, beginCell } from '@ton/core';

const libPrep = beginCell().storeUint(2, 8).storeBuffer(jwalletCodeRaw.hash()).endCell();
const jwalletCode = new Cell({ exotic: true, bits: libPrep.bits, refs: libPrep.refs });
Reference implementation: JettonWallet.spec.ts.

Publishing an ordinary cell in the masterchain library context

The following example comes from the multisig v2 repository.
;; Simple library keeper

#include "../imports/stdlib.fc";
#include "../messages.func";

const int DEFAULT_DURATION = 3600 * 24 * 365 * 10; ;; 10 years, can top-up in any time
const int ONE_TON = 1000000000;

;; https://docs.ton.org/tvm.pdf, page 138, SETLIBCODE
() set_lib_code(cell code, int mode) impure asm "SETLIBCODE";

cell empty_cell() asm "<b b> PUSHREF";

() recv_internal(int msg_value, cell in_msg_full, slice in_msg_body) impure {
    slice in_msg_full_slice = in_msg_full.begin_parse();
    int msg_flags = in_msg_full_slice~load_msg_flags();
    slice sender_address = in_msg_full_slice~load_msg_addr();

    cell lib_to_publish = get_data();

    int initial_gas = gas_consumed();
    (int order_cells, int order_bits, _) = compute_data_size(lib_to_publish, 1000); ;; according network config, max cells in library = 1000
    int size_counting_gas = gas_consumed() - initial_gas;

    int to_reserve = get_simple_compute_fee(MASTERCHAIN, size_counting_gas) +
                     get_storage_fee(MASTERCHAIN, DEFAULT_DURATION, order_bits, order_cells);
    raw_reserve(to_reserve, RESERVE_BOUNCE_ON_ACTION_FAIL);

    send_message_with_only_body(sender_address, 0, begin_cell(), NON_BOUNCEABLE, SEND_MODE_CARRY_ALL_BALANCE);
    ;; https://docs.ton.org/tvm.pdf, page 138, SETLIBCODE
    set_lib_code(lib_to_publish, 2); ;; if x = 2, the library is added as a public library (and becomes available to all smart contracts if the current smart contract resides in the masterchain);
    ;; brick contract
    set_code(empty_cell());
    set_data(empty_cell());
}
This contract requires enough TON for at least 10 years of storage. That prevents the library from freezing shortly after publication and becoming inaccessible. The key line is set_lib_code(lib_to_publish, 2);. This call publishes an ordinary cell with the flag set to 2, which makes the library public.

Testing in Blueprint

When testing smart contracts locally, there are two ways to register libraries in the blockchain environment: automatically and manually.

Automatic library deployment

Enable automatic library detection by passing the autoDeployLibs flag when creating the blockchain:
const blockchain = await Blockchain.create({ autoDeployLibs: true });
For contracts deployed in the masterchain, publish the library with the librarian example above. This lets the contract install and register the library at runtime, while the environment tracks and uses it automatically.

Manual library deployment

If autoDeployLibs is not enabled, register libraries manually:
const blockchain = await Blockchain.create();
const code = await compile('Contract');

// Create a dictionary of library hash → library cell
const libsDict = Dictionary.empty(Dictionary.Keys.Buffer(32), Dictionary.Values.Cell());
libsDict.set(code.hash(), code);

// Manually assign the libraries
blockchain.libs = beginCell().storeDictDirect(libsDict).endCell();
This approach gives full control, but it requires explicit management of the libraries available during testing. Reference implementation: JettonWallet.spec.ts.

Get methods for library-backed contracts

When working with a jetton wallet whose code is stored in a library cell, check its balance by executing a get method. When methods run through the HTTP API or LiteServer, the library cell is resolved automatically and the method runs against the resolved code. For local execution, pull the account state and resolve every library reference cell first. In most cases, the entire code cell is itself a library reference. To resolve a library, call the /getLibraries method.

Retrieving a library cell with LiteServer

To retrieve library cells from LiteServer, use the liteServer.getLibraries method.
import { LiteClient, LiteRoundRobinEngine, LiteSingleEngine } from "ton-lite-client";
import { Cell } from "@ton/core";
import { z } from "zod";

function intToIP(int: number): string {
    const buf = Buffer.alloc(4);
    buf.writeUInt32BE(int >>> 0, 0);
    return Array.from(buf).join(".");
}

const Server = z.object({
    ip: z.number(),
    port: z.number(),
    id: z.object({ key: z.string() }),
});

const Response = z.object({
    liteservers: z.array(Server),
});

// testnet https://ton.org/testnet-global.config.json
// mainnet https://ton.org/global.config.json
const configEndpoint = "https://ton.org/global.config.json";

async function getServers() {
    const data = Response.parse(await (await fetch(configEndpoint)).json());
    return data.liteservers.map((server) => {
        return new LiteSingleEngine({
            host: `tcp://${intToIP(server.ip)}:${server.port}`,
            publicKey: Buffer.from(server.id.key, 'base64'),
        });
    });
}

async function getLibraryByHash(hash: Buffer) {
    const engine = new LiteRoundRobinEngine(await getServers());
    const client = new LiteClient({ engine });
    const libs = await client.getLibraries([hash]);
    const lib = libs.result[0];
    if (!lib || libs.result.length !== 1) {
        throw new Error("Library not found");
    }
    const roots = Cell.fromBoc(lib.data);
    const root = roots[0];
    if (!root || roots.length !== 1) {
        throw new Error("Malformed BoC for a library");
    }
    return root.toBoc().toString("hex");
}

async function main() {
    const hash = Buffer.from("8F452D7A4DFD74066B682365177259ED05734435BE76B5FD4BD5D8AF2B7C3D68", "hex");
    const code = await getLibraryByHash(hash);
    console.log(code);
}

void main();