Skip to content

Latest commit

 

History

History
453 lines (325 loc) · 13.1 KB

tact-cookbook.md

File metadata and controls

453 lines (325 loc) · 13.1 KB

Tact 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 FunC Documentation, this article is more focused on everyday tasks every FunC developer resolve during the development of smart contracts.

Basics

How to write a Hello World smart contract

contract HelloWorld {
    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;
}

💡 Useful links

How to write a do until loop

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

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

Slice

How to determine if slice is empty

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

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

Cell

How to determine if a cell is empty

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

How to determine if cells are equal

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

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

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"

💡 Useful links

How to get current time

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

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);

💡 Useful links

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);

💡 Useful links

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,
});

💡 Useful links

How to calculate NFT item address by its index

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