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

Commit

Permalink
feat: enhance jetton page
Browse files Browse the repository at this point in the history
  • Loading branch information
a-bahdanau committed Sep 30, 2024
1 parent d285e25 commit 1d3c247
Showing 1 changed file with 179 additions and 44 deletions.
223 changes: 179 additions & 44 deletions pages/cookbook/jettons.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,38 @@

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

## Overview

This page lists common examples of working with [jettons](https://docs.ton.org/develop/dapps/asset-processing/jettons).

## Accepting jetton transfer
Jettons are token standards on the TON (The Open Network) blockchain, designed for creating fungible tokens (similar to ERC-20 on Ethereum) with a decentralized approach. They are implemented as a pair of smart contracts, typically consisting of two core components:

* Jetton Master Contract (Jetton Master)
* Jetton Wallet Contract (Jetton Wallet)

These contracts interact with each other to manage token supply, distribution, transfers, and other operations related to the jetton.

### Jetton Master Contract

The Jetton Master Contract serves as the central entity for a specific token. It maintains critical information about the jetton itself and facilitates the creation of individual wallets for token holders. Key responsibilities and data stored in the Jetton Master Contract include:

* Jetton Metadata: Information such as the token's name, symbol, total supply, and decimals.
* Jetton Wallet Creation: The Jetton Master can generate individual Jetton Wallet contracts for token holders.
* Minting and Burning: When new jettons are minted (created), the Jetton Master handles the creation process and distributes them to the relevant wallets. Similarly, it manages burning (destruction) of tokens when required.
* Supply Management: The Jetton Master keeps track of the total supply of the token, ensuring proper accounting for all issued jettons.

### Jetton Wallet Contract

The Jetton Wallet Contract represents an individual holder's token wallet and is responsible for managing the balance and token-related operations for a specific user. Each user or entity holding jettons will have their own unique Jetton Wallet Contract. Key features of the Jetton Wallet Contract include:

* Balance Tracking: The wallet contract stores the token balance of the user, recorded in a TON cell.
* Token Transfers: The wallet is responsible for handling token transfers between users. When a user sends jettons, the Jetton Wallet Contract ensures the proper transfer and communication with the recipient's wallet.
* Token Burning: The Jetton Wallet interacts with the Jetton Master to burn tokens.
* Owner Control: The wallet contract is owned and controlled by a specific user, meaning only the owner of the wallet can initiate transfers or other token operations.

## Examples

### Accepting jetton transfer

Transfer notification message have the following structure.

Expand All @@ -25,12 +54,34 @@ Use [receiver](/book/receive) function to accept token notification message.

</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.
Note that `myJettonWalletAddress` may also be stored in contract storage to use less gas in every transaction.
The sender of a transfer notification must be validated because malicious actors could attempt to spoof notifications from an unauthorized account.
If this validation is not done, the contract may accept unauthorized transactions, leading to potential security vulnerabilities.

Validation is done using the jetton address from the contract.

```mermaid
graph LR
A[Sender] --1--> B(Sender's jetton wallet)
B --2--> C(Contract's jetton wallet)
C --3, 4--> D[Contract]
```
1. Sender send message with `op::transfer` to his jetton wallet.
2. Jetton wallet transfer funds to contract's jetton wallet.
3. After successful transfer accept, contract's jetton wallet sends transfer notification to his owner - contract.
4. Contract validates jetton message.

The calculation of the contract’s jetton wallet is done using the [`contractAddress(){:tact}`](/ref/core-common#contractaddress) function, which helps determine the address of the contract's jetton wallet.
To obtain the jetton wallet's state init, you need the wallet's data and code. While there is a common structure for the initial data layout, it may differ in some cases, such as with [USDT](#usdt-jetton-operations).

Since notifications originate from your contract's jetton wallet, as illustrated in the diagram, the function [`myAddress(){:tact}`](/ref/core-common#myaddress) should be used in `ownerAddress{:tact}` field.

Calculation of contract's jetton wallet is done via function.
To obtain jetton wallet state init we need jetton wallets data and code. Common initial data layout is shown below but in may differ in some case, like in .
Since notifications come from your contract's jetton wallet as shown in diagram, the function should be used in state init the owner address field.

```tact
import "@stdlib/deploy";
struct JettonWalletData {
balance: Int as coins;
ownerAddress: Address;
Expand All @@ -49,30 +100,41 @@ fun calculateJettonWalletAddress(ownerAddress: Address, jettonMasterAddress: Add
return contractAddress(StateInit{code: jettonWalletCode, data: initData.toCell()});
}
contract Sample {
jettonWalletCode: Cell;
jettonMasterAddress: Address;
message(0x7362d09c) JettonTransferNotification {
queryId: Int as uint64;
amount: Int as coins;
sender: Address;
forwardPayload: Slice as remaining;
}
contract Example with Deployable {
myJettonWalletAddress: Address;
myJettonAmount: Int as coins = 0;
init(jettonWalletCode: Cell, jettonMasterAddress: Address) {
self.jettonWalletCode = jettonWalletCode;
self.jettonMasterAddress = jettonMasterAddress;
self.myJettonWalletAddress = calculateJettonWalletAddress(myAddress(), jettonMasterAddress, jettonWalletCode);
}
receive(msg: JettonTransferNotification) {
let myJettonWalletAddress = calculateJettonWalletAddress(myAddress(), self.jettonMasterAddress, self.jettonWalletCode);
require(sender() == myJettonWalletAddress, "Notification not from your jetton wallet!");
require(sender() == self.myJettonWalletAddress, "Notification not from your jetton wallet!");
self.myJettonAmount += msg.amount;
// your logic of processing token notification
// return exceeses
self.forward(msg.sender, null, false, null);
}
}
```

## Sending jetton transfer
### Sending jetton transfer

A jetton transfer is the process of sending a specified amount of jettons (fungible tokens) from one wallet (contract) to another.

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
import "@stdlib/deploy";
message(0xf8a7ea5) JettonTransfer {
queryId: Int as uint64;
amount: Int as coins;
Expand All @@ -83,51 +145,124 @@ message(0xf8a7ea5) JettonTransfer {
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(),
});
const JettonTransferGas: Int = ton("0.05");
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()});
}
message Withdraw {
amount: Int as coins;
}
contract Example with Deployable {
myJettonWalletAddress: Address;
myJettonAmount: Int as coins = 0;
init(jettonWalletCode: Cell, jettonMasterAddress: Address) {
self.myJettonWalletAddress = calculateJettonWalletAddress(myAddress(), jettonMasterAddress, jettonWalletCode);
}
receive(msg: Withdraw) {
require(msg.amount <= self.myJettonAmount, "Not enough funds to withdraw");
send(SendParameters{
to: self.myJettonWalletAddress,
value: JettonTransferGas,
body: JettonTransfer{
queryId: 42,
amount: msg.amount, // jetton amount you want to transfer
destination: sender(), // address you want to transfer jettons. Note that this is address of jetton wallet owner, not jetton wallet itself
responseDestination: sender(), // 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
### Burning jetton

Jetton burning is the process of permanently removing a specified amount of jettons (fungible tokens) from circulation, with no possibility of recovery.

```tact
import "@stdlib/deploy";
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(),
});
const JettonBurnGas: Int = ton("0.05");
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()});
}
message ThrowAway {
amount: Int as coins;
}
contract Example with Deployable {
myJettonWalletAddress: Address;
myJettonAmount: Int as coins = 0;
init(jettonWalletCode: Cell, jettonMasterAddress: Address) {
self.myJettonWalletAddress = calculateJettonWalletAddress(myAddress(), jettonMasterAddress, jettonWalletCode);
}
receive(msg: ThrowAway) {
require(msg.amount <= self.myJettonAmount, "Not enough funds to throw away");
send(SendParameters{
to: self.myJettonWalletAddress,
value: JettonBurnGas,
body: JettonBurn{
queryId: 42,
amount: msg.amount, // jetton amount you want to burn
responseDestination: sender(), // 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
### USDT jetton operations

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

```tact
struct JettonWalletData {
Expand Down

0 comments on commit 1d3c247

Please sign in to comment.