Skip to content
This repository has been archived by the owner on Dec 12, 2024. It is now read-only.

feat: add jetton cookbook #380

Merged
merged 5 commits into from
Sep 24, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 154 additions & 3 deletions pages/cookbook/jettons.mdx
Original file line number Diff line number Diff line change
@@ -1,9 +1,160 @@
# Fungible Tokens (Jettons)

import { Callout } from 'nextra/components'
import { Callout } from 'nextra-theme-docs';

<Callout emoji="🚨">
This page lists common examples of working with [jettons](https://docs.ton.org/develop/dapps/asset-processing/jettons).

This page is a stub. [Contributions are welcome!](https://github.com/tact-lang/tact-docs/issues)
## Accepting jetton transfer

Transfer notification message have the following structure.

```tact
message(0x7362d09c) JettonTransferNotification {
queryId: Int as uint64;
amount: Int as coins;
sender: Address;
forwardPayload: Slice as remaining;
}
```

Use [receiver](/book/receive) function to accept token notification message.

<Callout type="warning" emoji="⚠️">

Sender of transfer notification must be validated!

</Callout>

Validation can be done using jetton wallet state init and calculating jetton address.
Note, that notifications are coming from YOUR contract's jetton wallet, so [`myAddress()`](/ref/core-common#myaddress) should be used in owner address field.
Wallet initial data layout is shown below, but sometimes it can differ.
Gusarich marked this conversation as resolved.
Show resolved Hide resolved
Note that `myJettonWalletAddress` may also be stored in contract storage to use less gas in every transaction.

```tact
struct JettonWalletData {
balance: Int as coins;
ownerAddress: Address;
jettonMasterAddress: Address;
jettonWalletCode: Cell;
}

fun calculateJettonWalletAddress(ownerAddress: Address, jettonMasterAddress: Address, jettonWalletCode: Cell): Address {
let initData = JettonWalletData{
balance: 0,
ownerAddress,
jettonMasterAddress,
jettonWalletCode,
};

return contractAddress(StateInit{code: jettonWalletCode, data: initData.toCell()});
}

contract Sample {
jettonWalletCode: Cell;
jettonMasterAddress: Address;

init(jettonWalletCode: Cell, jettonMasterAddress: Address) {
self.jettonWalletCode = jettonWalletCode;
self.jettonMasterAddress = jettonMasterAddress;
}

receive(msg: JettonTransferNotification) {
let myJettonWalletAddress = calculateJettonWalletAddress(myAddress(), self.jettonMasterAddress, self.jettonWalletCode);
require(sender() == myJettonWalletAddress, "Notification not from your jetton wallet!");

// your logic of processing token notification
}
}
```

## Sending jetton transfer

To send jetton transfer use [`send(){:tact}`](/book/send) function.
Note that `myJettonWalletAddress` may also be stored in contract storage to use less gas in every transaction.

```tact
message(0xf8a7ea5) JettonTransfer {
queryId: Int as uint64;
amount: Int as coins;
destination: Address;
responseDestination: Address?;
customPayload: Cell? = null;
forwardTonAmount: Int as coins;
forwardPayload: Slice as remaining;
}

receive("send") {
let myJettonWalletAddress = calculateJettonWalletAddress(myAddress(), self.jettonMasterAddress, self.jettonWalletCode);
send(SendParameters{
to: myJettonWalletAddress,
value: ton("0.05"),
body: JettonTransfer{
queryId: 42,
amount: jettonAmount, // jetton amount you want to transfer
destination: msg.userAddress, // address you want to transfer jettons. Note that this is address of jetton wallet owner, not jetton wallet itself
responseDestination: msg.userAddress, // address where to send a response with confirmation of a successful transfer and the rest of the incoming message Toncoins
customPayload: null, // in most cases will be null and can be omitted. Needed for custom logic on Jetton Wallets itself
forwardTonAmount: 1, // amount that will be transferred with JettonTransferNotification. Needed for custom logic execution like in example below. If the amount is 0 notification won't be sent
forwardPayload: rawSlice("F") // precomputed beginCell().storeUint(0xF, 4).endCell().beginParse(). This works for simple transfer, if needed any struct can be used as `forwardPayload`
}.toCell(),
});
}
```

## Burning jetton

```tact
message(0x595f07bc) JettonBurn {
queryId: Int as uint64;
amount: Int as coins;
responseDestination: Address?;
customPayload: Cell? = null;
}

receive("burn") {
let myJettonWalletAddress = calculateJettonWalletAddress(myAddress(), self.jettonMasterAddress, self.jettonWalletCode);
send(SendParameters{
to: myJettonWalletAddress,
body: JettonBurn{
queryId: 42,
amount: jettonAmount, // jetton amount you want to burn
responseDestination: someAddress, // address where to send a response with confirmation of a successful burn and the rest of the incoming message coins
customPayload: null, // in most cases will be null and can be omitted. Needed for custom logic on jettons itself
}.toCell(),
});
}
```

## USDT jetton operations

Operations with USDT (on TON) remain the same, except that the `JettonWalletData` will have the following structure:

```tact
struct JettonWalletData {
status: Ins as uint4;
balance: Int as coins;
ownerAddress: Address;
jettonMasterAddress: Address;
}
```

Function to calculate wallet address will look like this:

```tact
fun calculateJettonWalletAddress(ownerAddress: Address, jettonMasterAddress: Address, jettonWalletCode: Cell): Address {
let initData = JettonWalletData{
status: 0,
balance: 0,
ownerAddress,
jettonMasterAddress,
};

return contractAddress(StateInit{code: jettonWalletCode, data: initData.toCell()});
}
```

<Callout type="info" emoji="🤔">

Didn't find your favorite example of a jettons communication? Have cool implementations in mind? [Contributions are welcome!](https://github.com/tact-lang/tact-docs/issues)

</Callout>
Loading