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 FunC Documentation, this article is more focused on everyday tasks every FunC developer resolve during the development of smart contracts.
contract HelloWorld {
get fun greeting(): String {
return "hello world";
}
}
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
}
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;
}
let i: Int = 0;
while (i < 10) {
i = i + 1;
}
💡 Useful links
When we need the cycle to run at least once, we use do-until
.
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!");
💡 Useful links
// 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
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();
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
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
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
To check if there is any data in a cell
, we should first convert it to 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();
💡 Useful links
empty()
in docsdataEmpty()
in docsrefsEmpty()
in docsemptyCell()
in docsbeginCell()
in docsendCell()
in docs
We can easily determine cell equality based on their hash.
let a: Cell = beginCell()
.storeUint(123, 16)
.endCell();
let b: Cell = beginCell()
.storeUint(123, 16)
.endCell();
let areCellsEqual: Bool = a.hash() == b.hash(); // true
💡 Useful links
send(SendParameters{
to: destinationAddress,
value: ton("0.01"), // attached amount of Tons to send
body: "Hello from Tact!".asComment() // comment (optional)
});
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
// 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);
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"
💡 Useful links
Use the now()
method to obtain the current standard Unix time.
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
}
💡 Useful links
// 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);
💡 Useful links
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);
💡 Useful links
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,
});
💡 Useful links
For Tact 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 FunC 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));
💡 Useful links