Cookbook
The core reason for creating the Tact Cookbook is to collect all the experience from Tact developers in one place so that future developers will use it. Compared to the rest of the documentation, this page is more focused on everyday tasks every Tact developer resolves during the development of smart contracts.
Basics
How to write a Hello World smart contract
contract HelloWorld {
// Note, that an empty init function won't be needed starting with Tact 1.3.0,
// see: https://github.com/tact-lang/tact/pull/167
init() {}
get fun greeting(): String {
return "Hello, World!";
}
}
How to write an 'if' statement in Tact
Tact supports if
statements in a similar syntax to most programming languages. The condition of the statement can be any boolean expression.
let value: Int = 9001;
if (value > 10) {
// do something
}
if (value > 100) {
// do something
} else {
// do something else
}
if (value > 9000) {
// do something
} else if (value > 500) {
// do another thing
} else {
// do something else
}
Loops
How to write a repeat loop
Please make sure the input number for the repeat loop statement is within the range of an int32 data type, as an exception will be thrown otherwise.
let sum: Int = 0;
let i: Int = 0;
repeat (10) { // repeat exactly 10 times
i = i + 1;
sum = sum + i;
}
How to write a while loop
let i: Int = 0;
while (i < 10) {
i = i + 1;
}
How to write a do until loop
When we need the cycle to run at least once, we use the do-until loop.
let num: Int; // A variable to store the random number
// A do until loop that repeats until num is equal to 5
do {
num = random(0, 9); // get a random number between 0 and 9
} until (num == 5); // stop loop if num is equal to 5
dump("The loop is over!");
Map
// Create empty map
let m: map<Int, String> = emptyMap();
// Add key/value to the map
m.set(1, "a");
// Get value by key from the map
let first: String? = m.get(1);
// Check key is exists
if (first == null) {
// do something...
} else {
// Cast value if key exists
let firstStr: String = first!!;
// do something...
}
Useful links:
Map type in the Book
Slice
How to determine if slice is empty
A Slice is considered empty if it has no stored data
and no stored references
.
Use empty()
method to check if a Slice is empty.
// Create an empty slice with no data and no refs
let empty_slice: Slice = emptyCell().asSlice();
// Returns `true`, because `empty_slice` doesn't have any data or refs
empty_slice.empty();
// Create a slice with some data
let slice_with_data: Slice = beginCell().
storeUint(42, 8).
asSlice();
// Returns `false`, because the slice has some data
slice_with_data.empty();
// Create a slice with a reference to an empty cell
let slice_with_refs: Slice = beginCell().
storeRef(emptyCell()).
asSlice();
// Returns `false`, because the slice has a reference
slice_with_refs.empty();
// Create a slice with data and a reference
let slice_with_data_and_refs: Slice = beginCell().
storeUint(42, 8).
storeRef(emptyCell()).
asSlice();
// Returns `false`, because the slice has both data and reference
slice_with_data_and_refs.empty();
How to determine if a slice has no refs (but may have bits)
let slice_with_data: Slice = beginCell().
storeUint(0, 1).
asSlice(); // create a slice with data but without refs
let refsCount: Int = slice_with_data.refs(); // 0
let hasNoRefs: Bool = slice_with_data.refsEmpty(); // true
How to determine if a Slice has no data (no bits, but may have refs)
let slice_with_data: Slice = beginCell().
storeRef(emptyCell()).
asSlice(); // create a slice with ref but without data
let bitsCount: Int = slice_with_data.bits(); // 0
let hasNoData: Bool = slice_with_data.dataEmpty(); // true
How to determine if Slices are equal
Direct comparison:
let firstSlice: Slice = "A".asSlice();
let secondSlice: Slice = "A".asSlice();
let areEqual: Bool = firstSlice == secondSlice;
let areNotEqual: Bool = firstSlice != secondSlice;
dump(areEqual) // true;
dump(areNotEqual) // false;
Note, that direct comparison via ==
or !=
operators implicitly uses hashes under the hood.
Explicit comparisons using .hash()
are also available:
fun areSlicesEqual(a: Slice, b: Slice): Bool {
return a.hash() == b.hash();
}
let firstSlice: Slice = "A".asSlice();
let secondSlice: Slice = "A".asSlice();
let result: Bool = areSlicesEqual(firstSlice, secondSlice);
dump(result) // true;
Useful links:
String.asSlice()
in Language→Reference
Cell
How to determine if a Cell is empty
To check if there is any data in the Cell, we should first convert it to the Slice. If we are only interested in having bits, we should use dataEmpty()
, if only refs - refsEmpty()
. In case we want to check for the presence of any data, regardless of whether it is a bit or ref, we need to use empty()
.
// Create an empty cell with no data and no refs
let empty_cell: Cell = emptyCell(); // alias for beginCell().endCell()
// Present `cell` as a `slice` to parse it.
let slice: Slice = empty_cell.asSlice();
// Returns `true`, because `slice` doesn't have any data or refs
slice.empty();
// Create a cell with bits and references
let cell_with_data_and_refs: Cell = beginCell().
storeUint(42, 8).
storeRef(emptyCell()).
endCell();
// Change `cell` type to slice with `begin_parse()`
let slice: Slice = cell_with_data_and_refs.asSlice();
// Returns `false`, because `slice` has both data and refs
slice.empty();
How to determine if Cells are equal
Direct comparison:
let a: Cell = beginCell()
.storeUint(123, 16)
.endCell();
let b: Cell = beginCell()
.storeUint(123, 16)
.endCell();
let areCellsEqual: Bool = a == b; // true
let areCellsNotEqual: Bool = a != b; // false
Note, that direct comparison via ==
or !=
operators implicitly uses hashes under the hood.
Explicit comparisons using .hash()
are also available:
let a: Cell = beginCell()
.storeUint(123, 16)
.endCell();
let b: Cell = beginCell()
.storeUint(123, 16)
.endCell();
let areCellsEqual: Bool = a.hash() == b.hash(); // true
let areCellsNotEqual: Bool = a.hash() != b.hash(); // false
Useful links:
Cell.hash
in Language→Reference
Sending messages
How to send a simple message
send(SendParameters{
to: destinationAddress,
value: ton("0.01"), // attached amount of Tons to send
body: "Hello from Tact!".asComment() // comment (optional)
});
How to send a message with the entire balance
If we need to send the whole balance of the smart contract, then we should use the SendRemainingBalance
send mode. Alternatively, we can use mode 128
, which has the same meaning.
send(SendParameters{
to: ctx.sender,
value: 0,
mode: SendRemainingBalance, // or mode: 128
bounce: true
});
Useful links:
"Sending messages" in the Book
"Message mode
" in the Book
How to convert String to Int
// Dangerously casts string as slice for parsing. Use it only if you know what you are doing.
// Try to parse the string as an slice
let string: Slice = "26052021".asSlice();
// A variable to store the number
let number: Int = 0;
while (!string.empty()) { // A loop until slice has bytes
let char: Int = string.loadUint(8); // load slice bytes
number = (number * 10) + (char - 48); // we use ASCII table to get number
}
dump(number);
How to convert Int to String
let number: Int = 261119911;
// Converting the [number] to String
let numberString: String = number.toString();
// Converting the [number] to Float String
// passed argument `3` is is a exponentiation parameter of expression 10^(-3) that will be used for computing float number. This parameter required to be `0 <= digits < 77`
let floatString: String = number.toFloatString(3);
// Converting the [number] as coins to human readable Strign
let coinsString: String = number.toCoinsString();
dump(numberString); // "261119911"
dump(floatString); // "261119.911"
dump(coinsString); // "0.261119911"
How to get current time
Use the now()
method to obtain the current standard Unix time (opens in a new tab).
If you need to store the time in state or encode it in a message, use Int as uint32
.
let currentTime: Int = now();
if (currentTime > 1672080143) {
// do something
}
How to generate a random number
// Declare a variable to store the random number
let number: Int;
// Generate a new random number, which is an unsigned 256-bit integer
number = randomInt();
// Generate a random number between 1 and 12
number = random(1, 12);
How to throw errors
The throw function in a contract is useful when we don't know how often to perform a specific action.
It allows intentional exception or error handling, which leads to the termination of the current transaction and reverts any state changes made during that transaction.
let number: Int = 198;
// the error will be triggered anyway
throw(36);
// the error will be triggered only if the number is greater than 50
nativeThrowWhen(35, number > 50);
// the error will be triggered only if the number is NOT EQUAL to 198
nativeThrowUnless(39, number == 198);
How to send a message with a long text comment
If we need to send a message with a lengthy text comment, we should create a String that consists of more than 127
characters. To do this, we can utilize the StringBuilder primitive type and its methods called beginComment()
and append()
. Prior to sending, we should convert this string into a cell using the toCell()
method.
let comment: StringBuilder = beginComment();
let longString = "..."; // Some string with more than 127 characters.
str_builder.append(longString);
send(SendParameters{
to: ctx.sender,
value: 0,
mode: SendIgnoreErrors,
body: longString.toCell(),
bounce: true,
});
How to calculate NFT item address by its index
For Tacts' example, you should have the Tact code of the NFT item contract, placed in the same file. You can use the function as you wish, both inside and outside of a contract.
For FunCs' example, you should have the code of item contract as a Cell. The function can be used outside of a contract by changing self.nftItemCode
with a preassigned nftItemCode
.
Tact:
get fun getNftItemInit(item_index: Int): StateInit {
// Arguments for NftItem may vary, depending on contract
return initOf NftItem(collectionAddress, item_index, self.owner_address, self.collection_content);
}
let itemIndex: Int = 0; // put your index
let itemInit: StateInit = self.getNftItemInit(itemIndex);
let itemAddress: Address = contractAddress(nft_init);
FunC (may also vary depending on collection's deploy_item()
function):
fun getNftItemInit(item_index: Int): StateInit {
let data: Cell = beginCell().storeUint(item_index,64).storeSlice(self.nFTContractAddress.asSlice()).endCell();
let itemInit: StateInit = StateInit{
data: data,
code: self.nftItemCode
};
return itemInit;
}
let itemIndex: Int = 0; // put your index
let itemAddress: Address = contractAddress(self.getNftItemInit(itemIndex));