Skip to main content
Tolk provides a dedicated string type. At the TVM level, a string is a cell that stores data in snake format: a chain of cells where each cell holds a portion of the string and a reference to the next.

String literals

Every string literal produces a value of type string:
// string
val str = "hello";

// array<string>
val arr = ["one", "two"];
However, on the TVM, each literal is a cell:
// a cell with 40 bits of data
val str = "hello";
A literal cell is created at compile-time, there is no expensive 500-gas cell creation at runtime.

Snake format

Since a single cell can hold at most 1023 bits (127 bytes), longer strings use the snake format: the start of the string is placed in the root cell, with its continuation in a referenced cell, recursively:
val str = "very1 very2 ... very99 long";
// stored approximately as:
// "very1 ... very30"
//   -> ref "very31 ... very60"
//     -> ref "very61 ... very89"
//       -> ref "very90 ... very99 long"
Short strings may also be stored in snake format — for example, after concatenation. The logical value "abcd" and the snaked "ab" -> ref "cd" are treated identically.

Traversing

To manually traverse contents of a string, call a beginParse() method that returns slice:
val bin = str.beginParse();  // slice
bin.remainingBitsCount();    // 40

String methods

Standard library methods for strings require an explicit import:
import "@stdlib/strings"

string.calculateLength

Loops over all references and returns the total number of characters:
val len = someStr.calculateLength();
Both "abcd" and "a" -> ref "bc" -> ref "d" return 4.

string.equalTo

Compares two strings for equality, accounting for their snaked internals:
if (option.equalTo("--disable")) {
    // ...
}
For instance, "--disable" equals "--dis" -> ref "able".

string.hash

Returns a hash of the string regardless of its internal representation. The hash of "abcd" and "a" -> ref "bcd" is identical.

Compile-time methods

Several methods operate on string literals at compile-time. Such strings are evaluated during compilation and embedded into the contract as constants.
// calculates crc32 of a string
const crc32 = "some_str".crc32() // = 4013618352 = 0xEF3AF4B0

// calculates crc16 (XMODEM) of a string
const crc16 = "some_str".crc16() // = 53407 = 0xD09F

// calculates sha256 of a string and returns 256-bit integer
const hash = "some_crypto_key".sha256()

// calculates sha256 of a string and takes the first 32 bits
const minihash = "some_crypto_key".sha256_32()

// interprets an N-chars ascii string as a number in base 256
const base256 = "AB".toBase256() // = 16706 (65*256 + 66)

// a slice with 2 bytes: 16, 32 (constructed from hex)
const hexSlice = "1020".hexToSlice()

// a slice with 4 bytes: 97, 98, 99, 100
const rawSlice = "abcd".literalSlice()
These methods work only on constant strings: calling them on variables produces a compilation error.

StringBuilder

StringBuilder from @stdlib/strings concatenates several strings into one snake-encoded string:
import "@stdlib/strings"

fun build() {
    var sb = StringBuilder.create();
    sb.append("ab");
    sb.append("cd");
    // "abcd", stored as "ab" -> ref "cd"
    var str = sb.build();
}

StringBuilder.append

The method StringBuilder.append returns self and can be chained:
var str = StringBuilder.create()
         .append("ab")
         .append("cd")
         .build();
A practical example: an NFT collection with common content and per-item content:
get fun get_nft_content(itemIndex: int, individualLink: string) {
    // ...
    var link = StringBuilder.create()
              .append(commonContent)
              .append(individualLink)
              .build();
    // ...
}
The method StringBuilder.append always concatenates strings in the snake format. This does not affect the final representation, since both "abcd" and "ab" -> ref "cd" are the same logical string.

StringBuilder.appendInt

Stores an integer in decimal format. This method is gas-expensive. It is suitable for contract getters executed off-chain, but should be avoided in on-chain production environments:
var link = StringBuilder.create()
           .append("/images/")
           .appendInt(itemIndex)
           .append(".png")
           .build();

TEP-compliant encoding

Per the token metadata standard, strings returned from get-methods must contain prefixes for off-chain explorers and APIs:
  • 0x00 + "<STRING>" — a regular snake-encoded string
  • 0x01 + "<STRING>" — an off-chain content URL
Prefixing does not create a new entity — the result remains a string, a concatenation of one character and the original content. Standard library contains an alias: type string_prefixed0x = string. Use this alias to indicate that a string is intentionally prefixed. Because it is an alias, the value remains a string (a TVM cell) at runtime and stays opaque to clients. Use specialized methods string.prefixWith00 and string.prefixWith01 for prefixing metadata strings:
get fun get_nft_content(itemIndex: int, individualLink: string): string_prefixed0x {
    // ...
    return StringBuilder.create()
        .append(content.commonContent)
        .append(individualLink)
        .build()
        // to comply with TEPs off-chain content layout
        .prefixWith01()
}

Stack layout and serialization

A string is backed by the snake-encoded TVM CELL. Serialized as a single cell reference.