Preparing Messages
While using TON Connect, you should construct the Message Body for the Payload used in various transactions. On this page you can find the most relevant examples of payload for use with the TON Connect SDKs.
It is expected that you learn the basics of creating a TON Connect connection. Learn more with the integration manual.
Cells and Message Serialization
Before diving into building messages, let's introduce the concept of cells, which message bodies are made from.
What is a Cell?
A cell is a basic data structure in the TON Blockchain. It can store up to 1023
bits and hold up to 4
references to other cells, which allows you to store more complex data structures.
Libraries like @ton/core
and @ton-community/assets-sdk
provide efficient ways to handle cells.
You can read more about cells here.
Creating a Cell
To build a cell, you use the beginCell()
function. While the cell is "open" you can store various data types with store...()
functions.
When you're done, you close the cell with the endCell()
function.
import { Address, beginCell } from "@ton/ton";
const cell = beginCell()
.storeUint(99, 64) // Stores uint 99 in 64 bits
.storeAddress(Address.parse('[SOME_ADDR]')) // Stores an address
.storeCoins(123) // Stores 123 as coins
.endCell() // Closes the cell
Parsing a Cell
To read or parse data from a cell, the beginParse()
function is called. You read data in the same order it was stored using similar load...()
functions:
const slice = cell.beginParse();
const uint = slice.loadUint(64);
const address = slice.loadAddress();
const coins = slice.loadCoins();
Larger amounts of data
Each cell has a 1023-bit limit. If you exceed this, an error occurs:
// This will fail due to overflow
const cell = beginCell()
.storeUint(1, 256)
.storeUint(2, 256)
.storeUint(3, 256)
.storeUint(4, 256) // Exceeds 1023-bit limit (256 + 256 + 256 + 256 = 1024)
.endCell()
To store more data, cells can reference other cells. You can use the storeRef() function to create nested cells:
const cell = beginCell()
.storeUint(1, 256)
.storeUint(2, 256)
.storeRef(beginCell()
.storeUint(3, 256)
.storeUint(4, 256)
.endCell())
.endCell()
To load a referenced (nested) cell, use loadRef()
:
const slice = cell.beginParse();
const uint1 = slice.loadUint(256);
const uint2 = slice.loadUint(256);
const innerSlice = slice.loadRef().beginParse(); // Load and parse nested cell
const uint3 = innerSlice.loadUint(256);
const uint4 = innerSlice.loadUint(256);
Optional References and Values
Cells can store optional values (which may be null). These are stored using the storeMaybe...()
functions:
const cell = beginCell()
.storeMaybeInt(null, 64) // Optionally stores an int
.storeMaybeInt(1, 64)
.storeMaybeRef(null) // Optionally stores a reference
.storeMaybeRef(beginCell()
.storeCoins(123)
.endCell());
You can parse optional values using the corresponding loadMaybe...() functions. Returned values are nullable so do not forget to check them for null!
const slice = cell.beginParse();
const maybeInt = slice.loadMaybeUint(64);
const maybeInt1 = slice.loadMaybeUint(64);
const maybeRef = slice.loadMaybeRef();
const maybeRef1 = slice.loadMaybeRef();
if (maybeRef1) {
const coins = maybeRef1.beginParse().loadCoins();
}
Using assets sdk for simpler serialization and deserialization
Manually handling cells can be tedious, so the @ton-community/assets-sdk
provides convenient methods for serializing and deserializing messages.
Using @ton-community/assets-sdk
is more readable and less error-prone.
- @ton-community/assets-sdk
- @ton/ton
import {Address, beginCell} from "@ton/core";
import {storeJettonTransferMessage, loadJettonTransferMessage} from "@ton-community/assets-sdk";
// serialization
const cell = beginCell()
.store(storeJettonTransferMessage({
queryId: 42n,
amount: 100n,
destination: Address.parse('[DESTINATION]'),
responseDestination: Address.parse('[RESPONSE_DESTINATION]'),
customPayload: null,
forwardAmount: 1n,
forwardPayload: null,
}))
.endCell()
// deserialization
const transferMessage = loadJettonTransferMessage(cell.beginParse());
import {Address, beginCell} from "@ton/core";
// serialization
const cell = beginCell()
.storeUint(260734629, 32)
.storeUint(42, 64)
.storeCoins(100)
.storeAddress(Address.parse('[DESTINATION]'))
.storeAddress(Address.parse('[RESPONSE_DESTINATION]'))
.storeMaybeRef(null)
.storeCoins(1)
.storeMaybeRef(null)
.endCell();
// deserialization
const slice = cell.beginParse();
const op = slice.loadUint(32);
const queryId = slice.loadUint(64);
const amount = slice.loadCoins();
const destination = slice.loadAddress();
const responseDestination = slice.loadAddress();
const customPayload = slice.loadMaybeRef();
const fwdAmount = slice.loadCoins();
const fwdPayload = slice.loadMaybeRef();
const transferMessage = { op, queryId, amount, destination, responseDestination, customPayload, fwdAmount, fwdPayload };
TON Connect JS SDK Examples
Transaction Template
No matter what level of task a developer is solving, it is typically necessary to use the connector entity from @tonconnect/sdk
or @tonconnect/ui
.
Examples created based on @tonconnect/sdk
and @tonconnect/ui
:
- @tonconnect/ui-react
- @tonconnect/ui
- @tonconnect/sdk
import { useTonConnectUI } from '@tonconnect/ui-react';
const transaction = {
//transaction body
}
export const Settings = () => {
const [tonConnectUI, setOptions] = useTonConnectUI();
return (
<div>
<button onClick={() => tonConnectUI.sendTransaction(transaction)}>
Send transaction
</button>
</div>
);
};
import TonConnectUI from '@tonconnect/ui';
const tonConnectUI = new TonConnectUI({ //connect application
manifestUrl: 'https://<YOUR_APP_URL>/tonconnect-manifest.json',
buttonRootId: '<YOUR_CONNECT_BUTTON_ANCHOR_ID>'
});
const transaction = {
//transaction body
}
const result = await tonConnectUI.sendTransaction(transaction)
import TonConnect from '@tonconnect/sdk';
const connector = new TonConnect();
await connector.sendTransaction({
//transaction body
})
Regular TON Transfer
TON Connect SDKs include wrappers for sending messages, making it easy to prepare regular transfers of Toncoins between two wallets as default transaction without payload.
A regular TON transfer using the TON Connect JS SDKs can be executed as follows:
- @tonconnect/react-ui
- @tonconnect/ui
- @tonconnect/sdk
import { useTonConnectUI } from '@tonconnect/ui-react';
const [tonConnectUI] = useTonConnectUI();
const transaction = {
messages: [
{
address: "0:412410771DA82CBA306A55FA9E0D43C9D245E38133CB58F1457DFB8D5CD8892F", // destination address
amount: "20000000" //Toncoin in nanotons
}
]
}
export const Settings = () => {
const [tonConnectUI, setOptions] = useTonConnectUI();
return (
<div>
<button onClick={() => tonConnectUI.sendTransaction(transaction)}>
Send transaction
</button>
</div>
);
};
import TonConnectUI from '@tonconnect/ui';
const tonConnectUI = new TonConnectUI({ //connect application
manifestUrl: 'https://<YOUR_APP_URL>/tonconnect-manifest.json',
buttonRootId: '<YOUR_CONNECT_BUTTON_ANCHOR_ID>'
});
const transaction = {
messages: [
{
address: "0:412410771DA82CBA306A55FA9E0D43C9D245E38133CB58F1457DFB8D5CD8892F", // destination address
amount: "20000000" //Toncoin in nanotons
}
]
}
const result = await tonConnectUI.sendTransaction(transaction)
import TonConnect from '@tonconnect/sdk';
const connector = new TonConnect();
await connector.sendTransaction({
messages: [
{
address: "0:412410771DA82CBA306A55FA9E0D43C9D245E38133CB58F1457DFB8D5CD8892F", // destination address
amount: "20000000" //Toncoin in nanotons
}
]
})
Learn more about TON Smart Contract Addresses.
For specific custom transaction, a certain payload must be defined.
Transfer With a Comment
The simplest example involves adding a payload with a comment. See more details on this page.
Before transaction, it is necessary prepare a body
cell via the @ton/ton JavaScript library.
import { beginCell } from '@ton/ton'
const body = beginCell()
.storeUint(0, 32) // write 32 zero bits to indicate that a text comment will follow
.storeStringTail("Hello, TON!") // write our text comment
.endCell();
The transaction body is created by the following:
- @tonconnect/react-ui
- @tonconnect/ui
- @tonconnect/sdk
import { useTonConnectUI } from '@tonconnect/ui-react';
import { toNano } from '@ton/ton'
const myTransaction = {
validUntil: Math.floor(Date.now() / 1000) + 360,
messages: [
{
address: destination,
amount: toNano("0.05").toString(),
payload: body.toBoc().toString("base64") // payload with comment in body
}
]
}
export const Settings = () => {
const [tonConnectUI, setOptions] = useTonConnectUI();
return (
<div>
<button onClick={() => tonConnectUI.sendTransaction(myTransaction)}>
Send transaction
</button>
</div>
);
};
import TonConnectUI from '@tonconnect/ui'
import { toNano } from '@ton/ton'
const transaction = {
validUntil: Math.floor(Date.now() / 1000) + 360,
messages: [
{
address: destination,
amount: toNano("0.05").toString(),
payload: body.toBoc().toString("base64") // payload with comment in body
}
]
}
const result = await tonConnectUI.sendTransaction(transaction)
import TonConnect from '@tonconnect/sdk';
import { toNano } from '@ton/ton'
const connector = new TonConnect();
await connector.sendTransaction({
validUntil: Math.floor(Date.now() / 1000) + 360,
messages: [
{
address: destination,
amount: toNano("0.05").toString(),
payload: body.toBoc().toString("base64") // payload with comment in body
}
]
})
Jetton Transfer
The body
for Jetton Transfers is based on the (TEP-74) standard. Please note that the number of decimals can vary between different tokens: for example, USDT uses 6 decimals (1 USDT = 1 * 109), while TON uses 9 decimals(1 TON = 1 * 109).
You can use assets-sdk
library with the methods out of the box (even with ton-connect
)
- @ton/ton
- assets/sdk
import { beginCell, toNano, Address } from '@ton/ton'
// transfer#0f8a7ea5 query_id:uint64 amount:(VarUInteger 16) destination:MsgAddress
// response_destination:MsgAddress custom_payload:(Maybe ^Cell)
// forward_ton_amount:(VarUInteger 16) forward_payload:(Either Cell ^Cell)
// = InternalMsgBody;
const body = beginCell()
.storeUint(0xf8a7ea5, 32) // jetton transfer op code
.storeUint(0, 64) // query_id:uint64
.storeCoins(toNano("0.001")) // amount:(VarUInteger 16) - Jetton amount for transfer (decimals = 6 - USDT, 9 - default). Function toNano use decimals = 9 (remember it)
.storeAddress(Address.parse(Wallet_DST)) // destination:MsgAddress
.storeAddress(Address.parse(Wallet_SRC)) // response_destination:MsgAddress
.storeUint(0, 1) // custom_payload:(Maybe ^Cell)
.storeCoins(toNano("0.05")) // forward_ton_amount:(VarUInteger 16) - if >0, will send notification message
.storeUint(0,1) // forward_payload:(Either Cell ^Cell)
.endCell();
Next, sending the transaction with this body to sender's jettonWalletContract executed:
- @tonconnect/react-ui
- @tonconnect/ui
- @tonconnect/sdk
import { useTonConnectUI } from '@tonconnect/ui-react';
import { toNano } from '@ton/ton'
const myTransaction = {
validUntil: Math.floor(Date.now() / 1000) + 360,
messages: [
{
address: jettonWalletContract, // sender jetton wallet
amount: toNano("0.05").toString(), // for commission fees, excess will be returned
payload: body.toBoc().toString("base64") // payload with jetton transfer body
}
]
}
export const Settings = () => {
const [tonConnectUI, setOptions] = useTonConnectUI();
return (
<div>
<button onClick={() => tonConnectUI.sendTransaction(myTransaction)}>
Send transaction
</button>
</div>
);
};
import TonConnectUI from '@tonconnect/ui'
import { toNano } from '@ton/ton'
const transaction = {
validUntil: Math.floor(Date.now() / 1000) + 360,
messages: [
{
address: jettonWalletContract, // sender jetton wallet
amount: toNano("0.05").toString(), // for commission fees, excess will be returned
payload: body.toBoc().toString("base64") // payload with jetton transfer body
}
]
}
const result = await tonConnectUI.sendTransaction(transaction)
import TonConnect from '@tonconnect/sdk';
import { toNano } from '@ton/ton'
const connector = new TonConnect();
//...
await connector.sendTransaction({
validUntil: Math.floor(Date.now() / 1000) + 360,
messages: [
{
address: jettonWalletContract, // sender jetton wallet
amount: toNano("0.05").toString(), // for commission fees, excess will be returned
payload: body.toBoc().toString("base64") // payload with jetton transfer body
}
]
})
validUntil
- UNIX-time until message validjettonWalletAddress
- Address, JettonWallet address, that defined based on JettonMaser and Wallet contractsbalance
- Integer, amount of Toncoin for gas payments in nanotons.body
- payload for the jettonContract
Jetton Wallet State Init and Address preparation example
import { Address, TonClient, beginCell, StateInit, storeStateInit } from '@ton/ton'
async function main() {
const client = new TonClient({
endpoint: 'https://toncenter.com/api/v2/jsonRPC',
apiKey: 'put your api key'
})
const jettonWalletAddress = Address.parse('Sender_Jetton_Wallet');
let jettonWalletDataResult = await client.runMethod(jettonWalletAddress, 'get_wallet_data');
jettonWalletDataResult.stack.readNumber();
const ownerAddress = jettonWalletDataResult.stack.readAddress();
const jettonMasterAddress = jettonWalletDataResult.stack.readAddress();
const jettonCode = jettonWalletDataResult.stack.readCell();
const jettonData = beginCell()
.storeCoins(0)
.storeAddress(ownerAddress)
.storeAddress(jettonMasterAddress)
.storeRef(jettonCode)
.endCell();
const stateInit: StateInit = {
code: jettonCode,
data: jettonData
}
const stateInitCell = beginCell()
.store(storeStateInit(stateInit))
.endCell();
console.log(new Address(0, stateInitCell.hash()));
}
Note: For the browser, you need to set a polyfill for Buffer
.
For more examples check documentation
const NETWORK = "testnet";
const api = await createApi(NETWORK);
const provider = new TonConnectUI(); // OR you can use tonConnectUI as a provider from @tonconnect/ui-react
// https://github.com/ton-community/assets-sdk/blob/main/examples/use-tonconnect.ts
const sender = new TonConnectSender(provider);
const storage: PinataStorageParams = {
pinataApiKey: process.env.PINATA_API_KEY!,
pinataSecretKey: process.env.PINATA_SECRET!,
};
const sdk = AssetsSDK.create({
api,
storage,
sender,
});
const jetton = sdk.openJettonWallet(Address.parse("JETTON_ADDRESS"));
const RECEIVER_ADDRESS = Address.parse("RECIEVER_ADDRESS");
jetton.send(sender, RECEIVER_ADDRESS, toNano(10));
OR you can use Jetton Contract with built in methods:
const provider = tonConnectUi;
// https://github.com/ton-community/assets-sdk/blob/main/examples/use-tonconnect.ts
const sender = new TonConnectSender(provider);
const client = new TonClient({
endpoint: "https://testnet.toncenter.com/api/v2/jsonRPC",
});
const jettonMaster = client.open(
JettonMinter.createFromAddress(
Address.parse("[JETTON_WALLET]"),
new DefaultContentResolver()
)
);
const jettonWalletAddress = await jettonMaster.getWalletAddress(
sender.address!
);
const jettonContent = await jettonMaster.getContent();
const jettonDecimals = jettonContent.decimals ?? 9;
const jetton = client.open(JettonWallet.createFromAddress(jettonWalletAddress));
await jetton.send(
sender,
Address.parse("[SENDER_WALLET]"),
BigInt(1 * 10 ** jettonDecimals)
);
Jetton Transfer with Comment
- @ton/ton
- assets/sdk
The messageBody
for Jetton Transfer(TEP-74) with comment we should additionally to the regular transfer body
serialize comment and pack this in the forwardPayload
. Please note that the number of decimals can vary between different tokens: for example, USDT uses 6 decimals (1 USDT = 1 * 109), while TON uses 9 decimals(1 TON = 1 * 109).
import { beginCell, toNano, Address } from '@ton/ton'
// transfer#0f8a7ea5 query_id:uint64 amount:(VarUInteger 16) destination:MsgAddress
// response_destination:MsgAddress custom_payload:(Maybe ^Cell)
// forward_ton_amount:(VarUInteger 16) forward_payload:(Either Cell ^Cell)
// = InternalMsgBody;
const destinationAddress = Address.parse('put destination wallet address');
const forwardPayload = beginCell()
.storeUint(0, 32) // 0 opcode means we have a comment
.storeStringTail('Hello, TON!')
.endCell();
const body = beginCell()
.storeUint(0xf8a7ea5, 32) // opcode for jetton transfer
.storeUint(0, 64) // query id
.storeCoins(toNano("5")) // Jetton amount for transfer (decimals = 6 - USDT, 9 - default). Function toNano use decimals = 9 (remember it)
.storeAddress(destinationAddress) // TON wallet destination address
.storeAddress(destinationAddress) // response excess destination
.storeBit(0) // no custom payload
.storeCoins(toNano("0.02")) // forward amount (if >0, will send notification message)
.storeBit(1) // we store forwardPayload as a reference
.storeRef(forwardPayload)
.endCell();
Next, sending the transaction with this body to sender's jettonWalletContract executed:
- @tonconnect/react-ui
- @tonconnect/ui
- @tonconnect/sdk
import { useTonConnectUI } from '@tonconnect/ui-react';
import { toNano } from '@ton/ton'
const jettonWalletContract = Address.parse('put your jetton wallet address');
const myTransaction = {
validUntil: Math.floor(Date.now() / 1000) + 360,
messages: [
{
address: jettonWalletContract, // sender jetton wallet
amount: toNano("0.05").toString(), // for commission fees, excess will be returned
payload: body.toBoc().toString("base64") // payload with jetton transfer and comment body
}
]
}
export const Settings = () => {
const [tonConnectUI, setOptions] = useTonConnectUI();
return (
<div>
<button onClick={() => tonConnectUI.sendTransaction(myTransaction)}>
Send transaction
</button>
</div>
);
};
import TonConnectUI from '@tonconnect/ui'
import { toNano } from '@ton/ton'
const transaction = {
validUntil: Math.floor(Date.now() / 1000) + 360,
messages: [
{
address: jettonWalletContract, // sender jetton wallet
amount: toNano("0.05").toString(), // for commission fees, excess will be returned
payload: body.toBoc().toString("base64") // payload with jetton transfer and comment body
}
]
}
const result = await tonConnectUI.sendTransaction(transaction)
import TonConnect from '@tonconnect/sdk';
import { toNano } from '@ton/ton'
const connector = new TonConnect();
//...
await connector.sendTransaction({
validUntil: Math.floor(Date.now() / 1000) + 360,
messages: [
{
address: jettonWalletContract, // sender jetton wallet
amount: toNano("0.05").toString(), // for commission fees, excess will be returned
payload: body.toBoc().toString("base64") // payload with jetton transfer and comment body
}
]
})
validUntil
- UNIX-time until message validjettonWalletAddress
- Address, JettonWallet address, that defined based on JettonMaser and Wallet contractsbalance
- Integer, amount of Toncoin for gas payments in nanotons.body
- payload for the jettonContract
Jetton Wallet State Init and Address preparation example
import { Address, TonClient, beginCell, StateInit, storeStateInit } from '@ton/ton'
async function main() {
const client = new TonClient({
endpoint: 'https://toncenter.com/api/v2/jsonRPC',
apiKey: 'put your api key'
})
const jettonWalletAddress = Address.parse('Sender_Jetton_Wallet');
let jettonWalletDataResult = await client.runMethod(jettonWalletAddress, 'get_wallet_data');
jettonWalletDataResult.stack.readNumber();
const ownerAddress = jettonWalletDataResult.stack.readAddress();
const jettonMasterAddress = jettonWalletDataResult.stack.readAddress();
const jettonCode = jettonWalletDataResult.stack.readCell();
const jettonData = beginCell()
.storeCoins(0)
.storeAddress(ownerAddress)
.storeAddress(jettonMasterAddress)
.storeRef(jettonCode)
.endCell();
const stateInit: StateInit = {
code: jettonCode,
data: jettonData
}
const stateInitCell = beginCell()
.store(storeStateInit(stateInit))
.endCell();
console.log(new Address(0, stateInitCell.hash()));
}
Note: For the browser, you need to set a polyfill for Buffer
.
For more examples check documentation
const NETWORK = "testnet";
const api = await createApi(NETWORK);
const provider = new TonConnectUI(); // OR you can use tonConnectUI as a provider from @tonconnect/ui-react
// https://github.com/ton-community/assets-sdk/blob/main/examples/use-tonconnect.ts
const sender = new TonConnectSender(provider);
const storage: PinataStorageParams = {
pinataApiKey: process.env.PINATA_API_KEY!,
pinataSecretKey: process.env.PINATA_SECRET!,
};
const sdk = AssetsSDK.create({
api,
storage,
sender,
});
const jetton = sdk.openJettonWallet(Address.parse("JETTON_ADDRESS"));
const forwardPayload = beginCell()
.storeUint(0, 32) // 0 opcode means we have a comment
.storeStringTail('Hello, TON!')
.endCell();
jetton.send(sender, RECEIVER_ADDRESS, toNano(10), { notify: { payload: forwardPayload } });
Jetton Burn
- @ton/ton
- assets/sdk
The body
for Jetton Burn is based on the (TEP-74) standard. Please note that the number of decimals can vary between different tokens: for example, USDT uses 6 decimals (1 USDT = 1 * 109), while TON uses 9 decimals(1 TON = 1 * 109).
import { beginCell, Address } from '@ton/ton'
// burn#595f07bc query_id:uint64 amount:(VarUInteger 16)
// response_destination:MsgAddress custom_payload:(Maybe ^Cell)
// = InternalMsgBody;
const body = beginCell()
.storeUint(0x595f07bc, 32) // jetton burn op code
.storeUint(0, 64) // query_id:uint64
.storeCoins(toNano("0.001")) // amount:(VarUInteger 16) - Jetton amount in decimal (decimals = 6 - USDT, 9 - default). Function toNano use decimals = 9 (remember it)
.storeAddress(Address.parse(Wallet_SRC)) // response_destination:MsgAddress - owner's wallet
.storeUint(0, 1) // custom_payload:(Maybe ^Cell) - w/o payload typically
.endCell();
Message places into the following request:
- @tonconnect/react-ui
- @tonconnect/ui
- @tonconnect/sdk
import { useTonConnectUI } from '@tonconnect/ui-react';
import { toNano } from '@ton/ton'
const myTransaction = {
validUntil: Math.floor(Date.now() / 1000) + 360,
messages: [
{
address: jettonWalletContract, // owner's jetton wallet
amount: toNano("0.05").toString(), // for commission fees, excess will be returned
payload: body.toBoc().toString("base64") // payload with a jetton burn body
}
]
}
export const Settings = () => {
const [tonConnectUI, setOptions] = useTonConnectUI();
return (
<div>
<button onClick={() => tonConnectUI.sendTransaction(myTransaction)}>
Send transaction
</button>
</div>
);
};
import TonConnectUI from '@tonconnect/ui'
import { toNano } from '@ton/ton'
const transaction = {
validUntil: Math.floor(Date.now() / 1000) + 360,
messages: [
{
address: jettonWalletContract, // owner's jetton wallet
amount: toNano("0.05").toString(), // for commission fees, excess will be returned
payload: body.toBoc().toString("base64") // payload with a jetton burn body
}
]
}
const result = await tonConnectUI.sendTransaction(transaction)
await connector.sendTransaction({
validUntil: Math.floor(Date.now() / 1000) + 360,
messages: [
{
address: jettonWalletContract, // owner's jetton wallet
amount: toNano("0.05").toString(), // for commission fees, excess will be returned
payload: body.toBoc().toString("base64") // payload with a jetton burn body
}
]
})
jettonWalletAddress
- Jetton Wallet contract address, that defined based on JettonMaser and Wallet contractsamount
- Integer, amount of Toncoin for gas payments in nanotons.body
- payload for the jetton wallet with theburn#595f07bc
op code
Note: For the browser, you need to set a polyfill for Buffer
.
For more examples check documentation
const NETWORK = "testnet";
const api = await createApi(NETWORK);
const provider = new TonConnectUI(); // OR you can use tonConnectUI as a provider from @tonconnect/ui-react
// https://github.com/ton-community/assets-sdk/blob/main/examples/use-tonconnect.ts
const sender = new TonConnectSender(provider);
const storage: PinataStorageParams = {
pinataApiKey: process.env.PINATA_API_KEY!,
pinataSecretKey: process.env.PINATA_SECRET!,
};
const sdk = AssetsSDK.create({
api,
storage,
sender,
});
const jetton = sdk.openJettonWallet(Address.parse("JETTON_ADDRESS"));
jetton.sendBurn(sender, toNano(10));
OR you can use Jetton Contract with built in methods:
const provider = tonConnectUi;
// https://github.com/ton-community/assets-sdk/blob/main/examples/use-tonconnect.ts
const sender = new TonConnectSender(provider);
const client = new TonClient({
endpoint: "https://testnet.toncenter.com/api/v2/jsonRPC",
});
const jettonMaster = client.open(
JettonMinter.createFromAddress(
Address.parse("[JETTON_WALLET]"),
new DefaultContentResolver()
)
);
const jettonWalletAddress = await jettonMaster.getWalletAddress(
sender.address!
);
const jettonContent = await jettonMaster.getContent();
const jettonDecimals = jettonContent.decimals ?? 9;
const jetton = client.open(JettonWallet.createFromAddress(jettonWalletAddress));
await jetton.sendBurn(
sender,
BigInt(1 * 10 ** jettonDecimals)
);
NFT Transfer
- @ton/ton
- assets/sdk
The body
message typically should be done according the following way:
import { beginCell, toNano } from '@ton/ton'
// transfer#5fcc3d14 query_id:uint64 new_owner:MsgAddress response_destination:MsgAddress custom_payload:(Maybe ^Cell)
// forward_amount:(VarUInteger 16) forward_payload:(Either Cell ^Cell) = InternalMsgBody;
const body = beginCell()
.storeUint(0x5fcc3d14, 32) // NFT transfer op code 0x5fcc3d14
.storeUint(0, 64) // query_id:uint64
.storeAddress(Address.parse(NEW_OWNER_WALLET)) // new_owner:MsgAddress
.storeAddress(Address.parse(Wallet_DST)) // response_destination:MsgAddress
.storeUint(0, 1) // custom_payload:(Maybe ^Cell)
.storeCoins(1) // forward_amount:(VarUInteger 16) (1 nanoTon = toNano("0.000000001"))
.storeUint(0,1) // forward_payload:(Either Cell ^Cell)
.endCell();
WALLET_DST
- Address - The address of the initial NFT owner for the receiving excess
Transfer the NFTitem
to a new owner NEW_OWNER_WALLET
.
- @tonconnect/react-ui
- @tonconnect/ui
- @tonconnect/sdk
import { useTonConnectUI } from '@tonconnect/ui-react';
import { toNano } from '@ton/ton'
const myTransaction = {
validUntil: Math.floor(Date.now() / 1000) + 360,
messages: [
{
address: jettonWalletContract, // NFT Item address, which will be transferred
amount: toNano("0.05").toString(), // for commission fees, excess will be returned
payload: body.toBoc().toString("base64") // payload with a NFT transfer body
}
]
}
export const Settings = () => {
const [tonConnectUI, setOptions] = useTonConnectUI();
return (
<div>
<button onClick={() => tonConnectUI.sendTransaction(myTransaction)}>
Send transaction
</button>
</div>
);
};
import TonConnectUI from '@tonconnect/ui'
import { toNano } from '@ton/ton'
const transaction = {
validUntil: Math.floor(Date.now() / 1000) + 360,
messages: [
{
address: NFTitem, // NFT Item address, which will be transferred
amount: toNano("0.05").toString(), // for commission fees, excess will be returned
payload: body.toBoc().toString("base64") // payload with a NFT transfer body
}
]
}
const result = await tonConnectUI.sendTransaction(transaction)
await connector.sendTransaction({
validUntil: Math.floor(Date.now() / 1000) + 360,
messages: [
{
address: NFTitem, // NFT Item address, which will be transferred
amount: toNano("0.05").toString(), // for commission fees, excess will be returned
payload: body.toBoc().toString("base64") // payload with a NFT transfer body
}
]
})
NFTitem
- Address - The address of NFT item smart contract which we want transfer to a new ownerNEW_OWNER_WALLET
.balance
- Integer, amount of Toncoin for gas payments in nanotons.body
- payload for the NFT contract
Note: For the browser, you need to set a polyfill for Buffer
.
For more examples check documentation
const NETWORK = "testnet";
const api = await createApi(NETWORK);
const provider = new TonConnectUI(); // OR you can use tonConnectUI as a provider from @tonconnect/ui-react
// https://github.com/ton-community/assets-sdk/blob/main/examples/use-tonconnect.ts
const sender = new TonConnectSender(provider);
const storage: PinataStorageParams = {
pinataApiKey: process.env.PINATA_API_KEY!,
pinataSecretKey: process.env.PINATA_SECRET!,
};
const sdk = AssetsSDK.create({
api,
storage,
sender,
});
const nft = sdk.openNftItem(Address.parse("NFT_ADDRESS"));
const RECEIVER_ADDRESS = Address.parse("RECIEVER_ADDRESS");
nft.send(sender, RECEIVER_ADDRESS);
OR you can use NFT Contract with built in methods:
const client = new TonClient({
endpoint: "https://testnet.toncenter.com/api/v2/jsonRPC",
});
const provider = tonConnectUi;
const nftItem = client.open(
NftItem.createFromAddress(Address.parse("[NFT_WALLET]"))
);
// https://github.com/ton-community/assets-sdk/blob/main/examples/use-tonconnect.ts
const sender = new TonConnectSender(provider);
await nftItem.send(sender, Address.parse("[SENDER_WALLET]"));
// TIP: NFTs can include royalties, allowing creators to earn a percentage from each sale.
// Here is an example of how to get it.
const royalty = await nftItem.getRoyaltyParams();
const royaltyPercent =
Number(royalty.numerator) / Number(royalty.denominator);
NFT Sale (GetGems)
Here is an example of preparing message and transaction for sale on GetGems marketplace, according to contract nft-fixprice-sale-v3r3.
To place NFT on GetGems Sale Contract, we should prepare special message body transferNftBody
that will be transfer NFT to special NFT Sale Contract.
const transferNftBody = beginCell()
.storeUint(0x5fcc3d14, 32) // Opcode for NFT transfer
.storeUint(0, 64) // query_id
.storeAddress(Address.parse(destinationAddress)) // new_owner - GetGems sale contracts deployer, should never change for this operation
.storeAddress(Address.parse(walletAddress)) // response_destination for excesses
.storeBit(0) // we do not have custom_payload
.storeCoins(toNano("0.2")) // forward_amount
.storeBit(0) // we store forward_payload is this cell
.storeUint(0x0fe0ede, 31) // not 32, because previous 0 will be read as do_sale opcode in deployer
.storeRef(stateInitCell)
.storeRef(saleBody)
.endCell();
Because message requires a lot of steps, the entire algorithm huge and could be found here:
Show entire algorithm for the creating NFT Sale message body
import { Address, beginCell, StateInit, storeStateInit, toNano, Cell } from '@ton/ton'
async function main() {
// func:0.4.4 src:op-codes.fc, imports/stdlib.fc, nft-fixprice-sale-v3r3.fc
// If GetGems updates its sale smart contract, you will need to obtain the new smart contract from https://github.com/getgems-io/nft-contracts/blob/main/packages/contracts/nft-fixprice-sale-v3/NftFixpriceSaleV3.source.ts.
const NftFixPriceSaleV3R3CodeBoc = 'te6ccgECDwEAA5MAART/APSkE/S88sgLAQIBYgIDAgLNBAUCASANDgL30A6GmBgLjYSS+CcH0gGHaiaGmAaY/9IH0gfSB9AGppj+mfmBg4KYVjgGAASpiFaY+F7xDhgEoYBWmfxwjFsxsLcxsrZBZjgsk5mW8oBfEV4ADJL4dwEuuk4QEWQIEV3RXgAJFZ2Ngp5OOC2HGBFWAA+WjKFkEINjYQQF1AYHAdFmCEAX14QBSYKBSML7y4cIk0PpA+gD6QPoAMFOSoSGhUIehFqBSkCH6RFtwgBDIywVQA88WAfoCy2rJcfsAJcIAJddJwgKwjhtQRSH6RFtwgBDIywVQA88WAfoCy2rJcfsAECOSNDTiWoMAGQwMWyy1DDQ0wchgCCw8tGVIsMAjhSBAlj4I1NBobwE+CMCoLkTsPLRlpEy4gHUMAH7AATwU8fHBbCOXRNfAzI3Nzc3BPoA+gD6ADBTIaEhocEB8tGYBdD6QPoA+kD6ADAwyDICzxZY+gIBzxZQBPoCyXAgEEgQNxBFEDQIyMsAF8sfUAXPFlADzxYBzxYB+gLMyx/LP8ntVOCz4wIwMTcowAPjAijAAOMCCMACCAkKCwCGNTs7U3THBZJfC+BRc8cF8uH0ghAFE42RGLry4fX6QDAQSBA3VTIIyMsAF8sfUAXPFlADzxYBzxYB+gLMyx/LP8ntVADiODmCEAX14QAYvvLhyVNGxwVRUscFFbHy4cpwIIIQX8w9FCGAEMjLBSjPFiH6Astqyx8Vyz8nzxYnzxYUygAj+gITygDJgwb7AHFwVBcAXjMQNBAjCMjLABfLH1AFzxZQA88WAc8WAfoCzMsfyz/J7VQAGDY3EDhHZRRDMHDwBQAgmFVEECQQI/AF4F8KhA/y8ADsIfpEW3CAEMjLBVADzxYB+gLLaslx+wBwIIIQX8w9FMjLH1Iwyz8kzxZQBM8WE8oAggnJw4D6AhLKAMlxgBjIywUnzxZw+gLLaswl+kRbyYMG+wBxVWD4IwEIyMsAF8sfUAXPFlADzxYBzxYB+gLMyx/LP8ntVACHvOFnaiaGmAaY/9IH0gfSB9AGppj+mfmC3ofSB9AH0gfQAYKaFQkNDggPlozJP9Ii2TfSItkf0iLcEIIySsKAVgAKrAQAgb7l72omhpgGmP/SB9IH0gfQBqaY/pn5gBaH0gfQB9IH0AGCmxUJDQ4ID5aM0U/SItlH0iLZH9Ii2F4ACFiBqqiU'
const NftFixPriceSaleV3R3CodeCell = Cell.fromBoc(Buffer.from(NftFixPriceSaleV3R3CodeBoc, 'base64'))[0]
const marketplaceAddress = Address.parse('EQBYTuYbLf8INxFtD8tQeNk5ZLy-nAX9ahQbG_yl1qQ-GEMS'); // GetGems Address
const marketplaceFeeAddress = Address.parse('EQCjk1hh952vWaE9bRguFkAhDAL5jj3xj9p0uPWrFBq_GEMS'); // GetGems Address for Fees
const destinationAddress = Address.parse("EQAIFunALREOeQ99syMbO6sSzM_Fa1RsPD5TBoS0qVeKQ-AR"); // GetGems sale contracts deployer
const walletAddress = Address.parse('EQArLGBnGPvkxaJE57Y6oS4rwzDWuOE8l8_sghntXLkIt162');
const royaltyAddress = Address.parse('EQArLGBnGPvkxaJE57Y6oS4rwzDWuOE8l8_sghntXLkIt162');
const nftAddress = Address.parse('EQCUWoe7hLlklVxH8gduCf45vPNocsjRP4wbX42UJ0Ja0S2f');
const price = toNano("5"); // 5 TON
const feesData = beginCell()
.storeAddress(marketplaceFeeAddress)
// 5% - GetGems fee
.storeCoins(price / BigInt(100) * BigInt(5))
.storeAddress(royaltyAddress)
// 5% - Royalty, can be changed
.storeCoins(price / BigInt(100) * BigInt(5))
.endCell();
const saleData = beginCell()
.storeBit(0) // is_complete
.storeUint(Math.round(Date.now() / 1000), 32) // created_at
.storeAddress(marketplaceAddress) // marketplace_address
.storeAddress(nftAddress) // nft_address
.storeAddress(walletAddress) // previous_owner_address
.storeCoins(price) // full price in nanotons
.storeRef(feesData) // fees_cell
.storeUint(0, 32) // sold_at
.storeUint(0, 64) // query_id
.endCell();
const stateInit: StateInit = {
code: NftFixPriceSaleV3R3CodeCell,
data: saleData
};
const stateInitCell = beginCell()
.store(storeStateInit(stateInit))
.endCell();
// not needed, just for example
const saleContractAddress = new Address(0, stateInitCell.hash());
const saleBody = beginCell()
.storeUint(1, 32) // just accept coins on deploy
.storeUint(0, 64)
.endCell();
const transferNftBody = beginCell()
.storeUint(0x5fcc3d14, 32) // Opcode for NFT transfer
.storeUint(0, 64) // query_id
.storeAddress(destinationAddress) // new_owner
.storeAddress(walletAddress) // response_destination for excesses
.storeBit(0) // we do not have custom_payload
.storeCoins(toNano("0.2")) // forward_amount
.storeBit(0) // we store forward_payload is this cell
.storeUint(0x0fe0ede, 31) // not 32, because we stored 0 bit before | do_sale opcode for deployer
.storeRef(stateInitCell)
.storeRef(saleBody)
.endCell();
}
Prepared transferNftBody
should be sent to the NFT Item Contract with at least 1.08
TON, that expected for success processing. Excess will be returned to a sender's wallet.
- @tonconnect/react-ui
- @tonconnect/ui
- @tonconnect/sdk
import { useTonConnectUI } from '@tonconnect/ui-react';
import { toNano } from '@ton/ton'
const myTransaction = {
validUntil: Math.floor(Date.now() / 1000) + 360,
messages: [
{
address: NFTitem, //address of the NFT Item contract, that should be placed on market
amount: toNano("0.3").toString(), // amount that will require on gas fees, excess will be return
payload: transferNftBody.toBoc().toString("base64") // payload with the transferNftBody message
}
]
}
export const Settings = () => {
const [tonConnectUI, setOptions] = useTonConnectUI();
return (
<div>
<button onClick={() => tonConnectUI.sendTransaction(myTransaction)}>
Send transaction
</button>
</div>
);
};
import TonConnectUI from '@tonconnect/ui'
import { toNano } from '@ton/ton'
const transaction = {
validUntil: Math.floor(Date.now() / 1000) + 360,
messages: [
{
address: NFTitem, //address of NFT Item contract, that should be placed on market
amount: toNano("0.3").toString(), // amount that will require on gas fees, excess will be return
payload: transferNftBody.toBoc().toString("base64") // payload with the transferNftBody message
}
]
}
const result = await tonConnectUI.sendTransaction(transaction)
await connector.sendTransaction({
validUntil: Math.floor(Date.now() / 1000) + 360,
messages: [
{
address: NFTitem, //address of NFT Item contract, that should be placed on market
amount: toNano("0.3").toString(), // amount that will require on gas fees, excess will be return
payload: transferNftBody.toBoc().toString("base64") // payload with the transferNftBody message
}
]
})
NFT Buy (GetGems)
- @ton/ton
- assets/sdk
The process of buy NFT for nft-fixprice-sale-v3r3 sale contract could be carry out with regular transfer without payload, the only important thing is accurate TON amount, that calculates as follows:
buyAmount = Nftprice TON + 1.0 TON
.
- @tonconnect/react-ui
- @tonconnect/ui
- @tonconnect/sdk
import { useTonConnectUI } from '@tonconnect/ui-react';
import { toNano } from '@ton/ton'
const myTransaction = {
validUntil: Math.floor(Date.now() / 1000) + 360,
messages: [
{
address: nftSaleContract, // NFT Sale contract, that is current desired NFT Item
amount: toNano(buyAmount).toString(), // NFT Price + exactly 1 TON, excess will be returned
}
]
}
export const Settings = () => {
const [tonConnectUI, setOptions] = useTonConnectUI();
return (
<div>
<button onClick={() => tonConnectUI.sendTransaction(myTransaction)}>
Send transaction
</button>
</div>
);
};
import TonConnectUI from '@tonconnect/ui'
import { toNano } from '@ton/ton'
const transaction = {
validUntil: Math.floor(Date.now() / 1000) + 360,
messages: [
{
address: nftSaleContract, // NFT Sale contract, that is current desired NFT Item
amount: toNano(buyAmount).toString(), // NFT Price + exactly 1 TON, excess will be returned
}
]
}
const result = await tonConnectUI.sendTransaction(transaction)
await connector.sendTransaction({
validUntil: Math.floor(Date.now() / 1000) + 360,
messages: [
{
address: nftSaleContract, // NFT Sale contract, that is current desired NFT Item
amount: toNano(buyAmount).toString(), // NFT Price + exactly 1 TON, excess will be returned
}
]
})
Note: For the browser, you need to set a polyfill for Buffer
.
For more examples check documentation
const nft = sdk.openNftSale(Address.parse("NFT_ADDRESS"));
nft.sendBuy(sdk.sender!, { queryId: BigInt(1) })
TON Connect Python SDK
Python examples are using PyTonConnect and pytoniq.
from pytoniq_core import Address
from pytonconnect import TonConnect
Read examples source.
Regular TON Transfer
connector = TonConnect(
manifest_url='https://raw.githubusercontent.com/XaBbl4/pytonconnect/main/pytonconnect-manifest.json')
is_connected = await connector.restore_connection()
transaction = {
'valid_until': int(time.time() + 3600),
'messages': [
{
'address' :'0:0000000000000000000000000000000000000000000000000000000000000000', # destination address
'amount' : 1000000000, # amount should be specified in nanocoins, 1 TON
}
]
}
Transfer With Comment
At first order, implement a message with comment via the following function:
def get_comment_message(destination_address: str, amount: int, comment: str) -> dict:
data = {
'address': destination_address,
'amount': str(amount),
'payload': urlsafe_b64encode(
begin_cell()
.store_uint(0, 32) # op code for comment message
.store_string(comment) # store comment
.end_cell() # end cell
.to_boc() # convert it to boc
)
.decode() # encode it to urlsafe base64
}
return data
Final transaction body for transfer with comment:
transaction = {
'valid_until': int(time.time() + 3600),
'messages': [
get_comment_message(
destination_address='0:0000000000000000000000000000000000000000000000000000000000000000',
amount=int(0.01 * 10**9), # amount should be specified in nanocoins
comment='hello world!'
)
]
}
Learn more about TON Smart Contract Addresses.
Jetton Transfer
Example of function for building jetton transfer transaction. Please note that the number of decimals can vary between different tokens: for example, USDT uses 6 decimals (1 USDT = 1 * 109), while TON uses 9 decimals(1 TON = 1 * 109).
from pytoniq_core import begin_cell
from base64 import urlsafe_b64encode
def get_jetton_transfer_message(jetton_wallet_address: str, recipient_address: str, transfer_fee: int, jettons_amount: int, response_address: str = None) -> dict:
data = {
'address': jetton_wallet_address,
'amount': str(transfer_fee),
'payload': urlsafe_b64encode(
begin_cell()
.store_uint(0xf8a7ea5, 32) # op code for jetton transfer message
.store_uint(0, 64) # query_id
.store_coins(jettons_amount) # Jetton amount for transfer (decimals = 6 - USDT, 9 - default). Exapmple: 1 USDT = 1 * 10**6 and 1 TON = 1 * 10**9
.store_address(recipient_address) # destination address
.store_address(response_address or recipient_address) # address send excess to
.store_uint(0, 1) # custom payload
.store_coins(1) # forward amount
.store_uint(0, 1) # forward payload
.end_cell() # end cell
.to_boc() # convert it to boc
)
.decode() # encode it to urlsafe base64
}
return data
Final transaction body:
transaction = {
'valid_until': int(time.time() + 3600),
'messages': [
get_jetton_transfer_message(
jetton_wallet_address='EQCXsVvdxTVmSIvYv4tTQoQ-0Yq9mERGTKfbsIhedbN5vTVV',
recipient_address='0:0000000000000000000000000000000000000000000000000000000000000000',
transfer_fee=int(0.07 * 10**9),
jettons_amount=int(0.01 * 10**9), # replace 9 for jetton decimal. For example for USDT it should be (amount * 10**6)
response_address=wallet_address
),
]
}
Jetton Burn
Example of function for building jetton burn transaction. Please note that the number of decimals can vary between different tokens: for example, USDT uses 6 decimals (1 USDT = 1 * 109), while TON uses 9 decimals(1 TON = 1 * 109).
from pytoniq_core import begin_cell
from base64 import urlsafe_b64encode
def get_jetton_burn_message(jetton_wallet_address: str, transfer_fee: int, jettons_amount: int, response_address: str = None) -> dict:
data = {
'address': jetton_wallet_address,
'amount': str(transfer_fee),
'payload': urlsafe_b64encode(
begin_cell()
.store_uint(0x595f07bc, 32) # op code for jetton burn message
.store_uint(0, 64) # query_id
.store_coins(jettons_amount) # Jetton amount in decimal (decimals = 6 - USDT, 9 - default)
.store_address(response_address) # address send excess to
.end_cell() # end cell
.to_boc() # convert it to boc
)
.decode() # encode it to urlsafe base64
}
return data
The final transaction body:
transaction = {
'valid_until': int(time.time() + 3600),
'messages': [
get_jetton_burn_message(
jetton_wallet_address='EQCXsVvdxTVmSIvYv4tTQoQ-0Yq9mERGTKfbsIhedbN5vTVV',
transfer_fee=int(0.07 * 10 ** 9),
jettons_amount=int(0.01 * 10 ** 9), # replace 9 for jetton decimal. For example for jUSDT it should be (amount * 10**6)
response_address=wallet_address
),
]
}
NFT Transfer
Example of function for a NFT transfer transaction:
from pytoniq_core import begin_cell
from base64 import urlsafe_b64encode
def get_nft_transfer_message(nft_address: str, recipient_address: str, transfer_fee: int, response_address: str = None) -> dict:
data = {
'address': nft_address,
'amount': str(transfer_fee),
'payload': urlsafe_b64encode(
begin_cell()
.store_uint(0x5fcc3d14, 32) # op code for nft transfer message
.store_uint(0, 64) # query_id
.store_address(recipient_address) # new owner
.store_address(response_address or recipient_address) # address send excess to
.store_uint(0, 1) # custom payload
.store_coins(1) # forward amount (0.000000001 * 10 ** 9) = 1 nanoTon
.store_uint(0, 1) # forward payload
.end_cell() # end cell
.to_boc() # convert it to boc
)
.decode() # encode it to urlsafe base64
}
return data
The final transaction body:
transaction = {
'valid_until': int(time.time() + 3600),
'messages': [
get_nft_transfer_message(
nft_address='EQDrA-3zsJXTfGo_Vdzg8d07Da4vSdHZllc6W9qvoNoMstF-',
recipient_address='0:0000000000000000000000000000000000000000000000000000000000000000',
transfer_fee=int(0.07 * 10**9),
response_address=wallet_address
),
]
}
NFT Sale (GetGems)
Here is an example of preparing message and transaction for sale on GetGems marketplace, according to contract nft-fixprice-sale-v3r3.
To place NFT on GetGems Sale Contract, we should prepare special message body transferNftBody
that will be transfer NFT to special NFT Sale Contract.
Example of creating NFT Sale Body
import time
from base64 import urlsafe_b64encode
from pytoniq_core.boc import Cell, begin_cell, Address
from pytoniq_core.tlb import StateInit
def get_sale_body(wallet_address: str, royalty_address: str, nft_address: str, price: int, amount: int):
# func:0.4.4 src:op-codes.fc, imports/stdlib.fc, nft-fixprice-sale-v3r3.fc
# If GetGems updates its sale smart contract, you will need to obtain the new smart contract from https://github.com/getgems-io/nft-contracts/blob/main/packages/contracts/nft-fixprice-sale-v3/NftFixpriceSaleV3.source.ts.
nft_sale_code_cell = Cell.one_from_boc('te6ccgECDwEAA5MAART/APSkE/S88sgLAQIBYgIDAgLNBAUCASANDgL30A6GmBgLjYSS+CcH0gGHaiaGmAaY/9IH0gfSB9AGppj+mfmBg4KYVjgGAASpiFaY+F7xDhgEoYBWmfxwjFsxsLcxsrZBZjgsk5mW8oBfEV4ADJL4dwEuuk4QEWQIEV3RXgAJFZ2Ngp5OOC2HGBFWAA+WjKFkEINjYQQF1AYHAdFmCEAX14QBSYKBSML7y4cIk0PpA+gD6QPoAMFOSoSGhUIehFqBSkCH6RFtwgBDIywVQA88WAfoCy2rJcfsAJcIAJddJwgKwjhtQRSH6RFtwgBDIywVQA88WAfoCy2rJcfsAECOSNDTiWoMAGQwMWyy1DDQ0wchgCCw8tGVIsMAjhSBAlj4I1NBobwE+CMCoLkTsPLRlpEy4gHUMAH7AATwU8fHBbCOXRNfAzI3Nzc3BPoA+gD6ADBTIaEhocEB8tGYBdD6QPoA+kD6ADAwyDICzxZY+gIBzxZQBPoCyXAgEEgQNxBFEDQIyMsAF8sfUAXPFlADzxYBzxYB+gLMyx/LP8ntVOCz4wIwMTcowAPjAijAAOMCCMACCAkKCwCGNTs7U3THBZJfC+BRc8cF8uH0ghAFE42RGLry4fX6QDAQSBA3VTIIyMsAF8sfUAXPFlADzxYBzxYB+gLMyx/LP8ntVADiODmCEAX14QAYvvLhyVNGxwVRUscFFbHy4cpwIIIQX8w9FCGAEMjLBSjPFiH6Astqyx8Vyz8nzxYnzxYUygAj+gITygDJgwb7AHFwVBcAXjMQNBAjCMjLABfLH1AFzxZQA88WAc8WAfoCzMsfyz/J7VQAGDY3EDhHZRRDMHDwBQAgmFVEECQQI/AF4F8KhA/y8ADsIfpEW3CAEMjLBVADzxYB+gLLaslx+wBwIIIQX8w9FMjLH1Iwyz8kzxZQBM8WE8oAggnJw4D6AhLKAMlxgBjIywUnzxZw+gLLaswl+kRbyYMG+wBxVWD4IwEIyMsAF8sfUAXPFlADzxYBzxYB+gLMyx/LP8ntVACHvOFnaiaGmAaY/9IH0gfSB9AGppj+mfmC3ofSB9AH0gfQAYKaFQkNDggPlozJP9Ii2TfSItkf0iLcEIIySsKAVgAKrAQAgb7l72omhpgGmP/SB9IH0gfQBqaY/pn5gBaH0gfQB9IH0AGCmxUJDQ4ID5aM0U/SItlH0iLZH9Ii2F4ACFiBqqiU')
# fees cell
marketplace_address = Address('EQBYTuYbLf8INxFtD8tQeNk5ZLy-nAX9ahQbG_yl1qQ-GEMS')
marketplace_fee_address = Address('EQCjk1hh952vWaE9bRguFkAhDAL5jj3xj9p0uPWrFBq_GEMS')
destination_address = Address('EQAIFunALREOeQ99syMbO6sSzM_Fa1RsPD5TBoS0qVeKQ-AR')
wallet_address = Address(wallet_address)
royalty_address = Address(royalty_address)
nft_address = Address(nft_address)
marketplace_fee = int(price * 5 / 100) # 5%
royalty_fee = int(price * 5 / 100) # 5%
fees_data_cell = (begin_cell()
.store_address(marketplace_fee_address)
.store_coins(marketplace_fee)
.store_address(royalty_address)
.store_coins(royalty_fee)
.end_cell())
sale_data_cell = (begin_cell()
.store_bit_int(0) # is_complete
.store_uint(int(time.time()), 32) # created_at
.store_address(marketplace_address)
.store_address(nft_address)
.store_address(wallet_address)
.store_coins(price)
.store_ref(fees_data_cell)
.store_uint(0, 32) # sold_at
.store_uint(0, 64) # query_id
.end_cell())
# not needed, just for example
state_init_cell = StateInit(code=nft_sale_code_cell, data=sale_data_cell).serialize()
sale_body = (begin_cell()
.store_uint(1, 32) # just accept coins on deploy
.store_uint(0, 64)
.end_cell())
transfer_nft_body = (begin_cell()
.store_uint(0x5fcc3d14, 32) # Opcode for NFT transfer
.store_uint(0, 64) # query_id
.store_address(destination_address)
.store_address(wallet_address)
.store_bit_int(0) # we do not have custom_payload
.store_coins(int(0.2 * 10**9)) # forward_amount
.store_bit_int(0) # we store forward_payload is this cell
.store_uint(0x0fe0ede, 31) # not 32, because we stored 0 bit before | do_sale opcode for deployer
.store_ref(state_init_cell)
.store_ref(sale_body)
.end_cell())
data = {
'address': nft_address.to_str(),
'amount': str(amount),
'payload': urlsafe_b64encode(transfer_nft_body.to_boc()).decode()
}
return data
The final transaction body:
transaction = {
'valid_until': int(time.time() + 3600),
'messages': [
get_sale_body(
nft_address='EQDrA-3zsJXTfGo_Vdzg8d07Da4vSdHZllc6W9qvoNoMstF-',
wallet_address='0:0000000000000000000000000000000000000000000000000000000000000000',
royalty_address='0:0000000000000000000000000000000000000000000000000000000000000000',
price=int(5 * 10**9),
amount=int(0.3 * 10**9)
),
]
}
NFT Buy (GetGems)
The process of buy NFT for nft-fixprice-sale-v3r3 sale contract could be carry out with regular transfer without payload, the only important thing is accurate TON amount, that calculates as follows:
buyAmount = Nftprice TON + 1.0 TON
.
transaction = {
'valid_until': int(time.time() + 3600),
'messages': [
{
'address': nft_address,
'amount': buyAmount,
}
]
}
TON Connect Go SDK
Go examples are using tonconnect and tonutils-go.
import "github.com/cameo-engineering/tonconnect"
import "github.com/xssnick/tonutils-go/address"
Read tonconnect and tonutils-go examples.
There you can find how to create tonconnect session and send transaction constructed with messages.
s, _ := tonconnect.NewSession()
// create ton links
// ...
// create new message msg and transaction
boc, _ := s.SendTransaction(ctx, *tx)
In further examples only messages and transactions will be created.
Regular TON Transfer
Example of function for building regular TON transfer message:
import (
"fmt"
"github.com/cameo-engineering/tonconnect"
)
func Transfer(dest string, amount uint64) (*tonconnect.Message, error) {
msg, err := tonconnect.NewMessage(
dest,
fmt.Sprintf("%d", amount), // nanocoins to transfer/compute message
)
return msg, err
}
Final transaction body:
msg, err := Transfer("0QBZ_35Wy144n2GBM93YpcV4KOKcIjDJk8DdX4kyXEEHcbLZ", uint64(math.Pow(10, 9)))
if err != nil {
log.Fatal(err)
}
tx, err := tonconnect.NewTransaction(
tonconnect.WithTimeout(10*time.Minute),
tonconnect.WithTestnet(),
tonconnect.WithMessage(*msg),
)
if err != nil {
log.Fatal(err)
}
Transfer with Comment
Example of function for building transfer with comment message:
import (
"fmt"
"github.com/cameo-engineering/tonconnect"
"github.com/xssnick/tonutils-go/tvm/cell"
)
func TransferWithComment(dest string, amount uint64, comment string) (*tonconnect.Message, error) {
payload, _ := cell.BeginCell().
MustStoreUInt(0, 32).
MustStoreStringSnake(comment).
EndCell().MarshalJSON()
msg, err := tonconnect.NewMessage(
dest,
fmt.Sprintf("%d", amount), // nanocoins to transfer/compute message
tonconnect.WithPayload(payload))
return msg, err
}
Final transaction body:
msg, err := TransferWithComment("0QBZ_35Wy144n2GBM93YpcV4KOKcIjDJk8DdX4kyXEEHcbLZ", uint64(math.Pow(10, 9)), "new comment")
if err != nil {
log.Fatal(err)
}
tx, err := tonconnect.NewTransaction(
tonconnect.WithTimeout(10*time.Minute),
tonconnect.WithTestnet(),
tonconnect.WithMessage(*msg),
)
if err != nil {
log.Fatal(err)
}
Jetton Transfer
Example of function for jetton transfer message. Please note that the number of decimals can vary between different tokens: for example, USDT uses 6 decimals (1 USDT = 1 * 109), while TON uses 9 decimals(1 TON = 1 * 109).
import (
"fmt"
"github.com/cameo-engineering/tonconnect"
"github.com/xssnick/tonutils-go/address"
"github.com/xssnick/tonutils-go/tvm/cell"
)
func JettonTransferMessage(jetton_wallet_address string, amount uint64,
jettons_amount uint64, recipient_address, response_address string,
fwd_amount uint64, fwd_payload *cell.Cell) (*tonconnect.Message, error) {
payload, _ := cell.BeginCell().
MustStoreUInt(0xf8a7ea5, 32). // op code for jetton transfer message (op::transfer)
MustStoreUInt(0, 64). // query_id
MustStoreCoins(jettons_amount). // Jetton amount for transfer (decimals = 6 - USDT, 9 - default). Exapmple: 1 USDT = 1 * 10**6 and 1 TON = 1 * 10**9
MustStoreAddr(address.MustParseAddr(recipient_address)). // address send excess to
MustStoreAddr(address.MustParseAddr(response_address)).
MustStoreUInt(0, 1). // custom payload
MustStoreCoins(fwd_amount). // set 0 if don't want transfer notification
MustStoreMaybeRef(fwd_payload).
EndCell().MarshalJSON()
msg, err := tonconnect.NewMessage(
jetton_wallet_address,
fmt.Sprintf("%d", amount), // nanocoins to transfer/compute message
tonconnect.WithPayload(payload))
if err != nil {
return nil, err
}
return msg, nil
}
Final transaction body:
msg, err := JettonTransferMessage("kQA8Q7m_pSNPr6FcqRYxllpAZv-0ieXy_KYER2iP195hBXiX",
uint64(math.Pow(10, 9)),
uint64(10),
"0QBZ_35Wy144n2GBM93YpcV4KOKcIjDJk8DdX4kyXEEHcbL2",
"EQBuObr2M7glm08w6cBGjIuuCbmvBFGwuVs6qb3AQpac9XpX",
uint64(0), nil)
if err != nil {
log.Fatal(err)
}
tx, err := tonconnect.NewTransaction(
tonconnect.WithTimeout(10*time.Minute),
tonconnect.WithTestnet(),
tonconnect.WithMessage(*msg),
)
if err != nil {
log.Fatal(err)
}
Jetton Burn
Example of function for jetton burn message. Please note that the number of decimals can vary between different tokens: for example, USDT uses 6 decimals (1 USDT = 1 * 109), while TON uses 9 decimals(1 TON = 1 * 109).
import (
"fmt"
"github.com/cameo-engineering/tonconnect"
"github.com/xssnick/tonutils-go/address"
"github.com/xssnick/tonutils-go/tvm/cell"
)
func JettonBurnMessage(jetton_wallet_address string, amount uint64,
jettons_amount uint64, response_address string) (*tonconnect.Message, error) {
payload, _ := cell.BeginCell().
MustStoreUInt(0xf8a7ea5, 32). // op code for jetton burn message (op::burn)
MustStoreUInt(0, 64). // query_id
MustStoreCoins(jettons_amount). // Jetton amount in decimal (decimals = 6 - USDT, 9 - default)
MustStoreAddr(address.MustParseAddr(response_address)). // address send excess to
EndCell().MarshalJSON()
msg, err := tonconnect.NewMessage(
jetton_wallet_address,
fmt.Sprintf("%d", amount), // nanocoins to transfer/compute message
tonconnect.WithPayload(payload))
if err != nil {
return nil, err
}
return msg, nil
}
Final transaction body:
msg, err := JettonBurnMessage("kQA8Q7m_pSNPr6FcqRYxllpAZv-0ieXy_KYER2iP195hBXiX",
uint64(math.Pow(10, 9)),
uint64(10),
"EQBuObr2M7glm08w6cBGjIuuCbmvBFGwuVs6qb3AQpac9XpX")
if err != nil {
log.Fatal(err)
}
tx, err := tonconnect.NewTransaction(
tonconnect.WithTimeout(10*time.Minute),
tonconnect.WithTestnet(),
tonconnect.WithMessage(*msg),
)
if err != nil {
log.Fatal(err)
}
NFT Transfer
Example of function for NFT transfer message:
import (
"fmt"
"github.com/cameo-engineering/tonconnect"
"github.com/xssnick/tonutils-go/address"
"github.com/xssnick/tonutils-go/tvm/cell"
)
func NftTransferMessage(nft_address string, amount uint64, recipient_address, response_address string,
fwd_amount uint64, fwd_payload *cell.Cell) (*tonconnect.Message, error) {
payload, _ := cell.BeginCell().
MustStoreUInt(0x5fcc3d14, 32). // op code for nft transfer message (op::transfer())
MustStoreUInt(0, 64). // query_id
MustStoreAddr(address.MustParseAddr(recipient_address)). // new owner
MustStoreAddr(address.MustParseAddr(response_address)). // address send excess to
MustStoreUInt(0, 1). // custom payload
MustStoreCoins(fwd_amount). // set 0 if don't want transfer notification
MustStoreMaybeRef(fwd_payload).
EndCell().MarshalJSON()
msg, err := tonconnect.NewMessage(
nft_address,
fmt.Sprintf("%d", amount), // nanocoins to transfer/compute message
tonconnect.WithPayload(payload))
if err != nil {
return nil, err
}
return msg, nil
}
Final transaction body:
msg, err := NftTransferMessage("EQDrA-3zsJXTfGo_Vdzg8d07Da4vSdHZllc6W9qvoNoMstF-",
uint64(math.Pow(10, 9)),
"0QBZ_35Wy144n2GBM93YpcV4KOKcIjDJk8DdX4kyXEEHcbL2",
"0QBZ_35Wy144n2GBM93YpcV4KOKcIjDJk8DdX4kyXEEHcbL2",
uint64(0), nil)
if err != nil {
log.Fatal(err)
}
tx, err := tonconnect.NewTransaction(
tonconnect.WithTimeout(10*time.Minute),
tonconnect.WithTestnet(),
tonconnect.WithMessage(*msg),
)
if err != nil {
log.Fatal(err)
}
NFT Sale (GetGems)
Here is an example of preparing message and transaction for sale on GetGems marketplace, according to contract nft-fixprice-sale-v3r3.
To place NFT on GetGems Sale Contract, we should prepare special message body transferNftBody
that will be transfer NFT to special NFT Sale Contract.
transferNftBody := cell.BeginCell().
MustStoreUInt(0x5fcc3d14, 32). // opcode for NFT transfer
MustStoreUInt(0, 64). // query_id
MustStoreAddress(destinationAddress). // new_owner - GetGems sale contracts deployer, should never change for this operation
MustStoreAddress(walletAddress). // response_destination for excesses
MustStoreUInt(0, 1). // we do not have custom_payload
MustStoreCoins(0.2*math.Pow(10, 9)). // forward_amount
MustStoreUInt(0, 1). // we store forward_payload is this cell
MustStoreUInt(0x0fe0ede, 31). // not 32, because previous 0 will be read as do_sale opcode in deployer (op::do_sale)
MustStoreRef(stateInitCell).
MustStoreRef(saleBody).
EndCell()
Because message requires a lot of steps, the entire algorithm huge and could be found here:
Show entire algorithm for the creating NFT Sale message body
import (
"fmt"
"math"
"time"
"github.com/cameo-engineering/tonconnect"
"github.com/xssnick/tonutils-go/address"
"github.com/xssnick/tonutils-go/tlb"
"github.com/xssnick/tonutils-go/tvm/cell"
)
func NftSaleMessage(wallet, royalty, nft string, amount, price uint64) (*tonconnect.Message, error) {
// func:0.4.4 src:op-codes.fc, imports/stdlib.fc, nft-fixprice-sale-v3r3.fc
// If GetGems updates its sale smart contract, you will need to obtain the new smart contract from https://github.com/getgems-io/nft-contracts/blob/main/packages/contracts/nft-fixprice-sale-v3/NftFixpriceSaleV3.source.ts.
fixPriceV3R3Code := new(cell.Cell)
fixPriceV3R3Code.UnmarshalJSON([]byte("te6ccgECDwEAA5MAART/APSkE/S88sgLAQIBYgIDAgLNBAUCASANDgL30A6GmBgLjYSS+CcH0gGHaiaGmAaY/9IH0gfSB9AGppj+mfmBg4KYVjgGAASpiFaY+F7xDhgEoYBWmfxwjFsxsLcxsrZBZjgsk5mW8oBfEV4ADJL4dwEuuk4QEWQIEV3RXgAJFZ2Ngp5OOC2HGBFWAA+WjKFkEINjYQQF1AYHAdFmCEAX14QBSYKBSML7y4cIk0PpA+gD6QPoAMFOSoSGhUIehFqBSkCH6RFtwgBDIywVQA88WAfoCy2rJcfsAJcIAJddJwgKwjhtQRSH6RFtwgBDIywVQA88WAfoCy2rJcfsAECOSNDTiWoMAGQwMWyy1DDQ0wchgCCw8tGVIsMAjhSBAlj4I1NBobwE+CMCoLkTsPLRlpEy4gHUMAH7AATwU8fHBbCOXRNfAzI3Nzc3BPoA+gD6ADBTIaEhocEB8tGYBdD6QPoA+kD6ADAwyDICzxZY+gIBzxZQBPoCyXAgEEgQNxBFEDQIyMsAF8sfUAXPFlADzxYBzxYB+gLMyx/LP8ntVOCz4wIwMTcowAPjAijAAOMCCMACCAkKCwCGNTs7U3THBZJfC+BRc8cF8uH0ghAFE42RGLry4fX6QDAQSBA3VTIIyMsAF8sfUAXPFlADzxYBzxYB+gLMyx/LP8ntVADiODmCEAX14QAYvvLhyVNGxwVRUscFFbHy4cpwIIIQX8w9FCGAEMjLBSjPFiH6Astqyx8Vyz8nzxYnzxYUygAj+gITygDJgwb7AHFwVBcAXjMQNBAjCMjLABfLH1AFzxZQA88WAc8WAfoCzMsfyz/J7VQAGDY3EDhHZRRDMHDwBQAgmFVEECQQI/AF4F8KhA/y8ADsIfpEW3CAEMjLBVADzxYB+gLLaslx+wBwIIIQX8w9FMjLH1Iwyz8kzxZQBM8WE8oAggnJw4D6AhLKAMlxgBjIywUnzxZw+gLLaswl+kRbyYMG+wBxVWD4IwEIyMsAF8sfUAXPFlADzxYBzxYB+gLMyx/LP8ntVACHvOFnaiaGmAaY/9IH0gfSB9AGppj+mfmC3ofSB9AH0gfQAYKaFQkNDggPlozJP9Ii2TfSItkf0iLcEIIySsKAVgAKrAQAgb7l72omhpgGmP/SB9IH0gfQBqaY/pn5gBaH0gfQB9IH0AGCmxUJDQ4ID5aM0U/SItlH0iLZH9Ii2F4ACFiBqqiU"))
marketplaceAddress := address.MustParseAddr("EQBYTuYbLf8INxFtD8tQeNk5ZLy-nAX9ahQbG_yl1qQ-GEMS") // GetGems Address
marketplaceFeeAddress := address.MustParseAddr("EQCjk1hh952vWaE9bRguFkAhDAL5jj3xj9p0uPWrFBq_GEMS") // GetGems Address for Fees
destinationAddress := address.MustParseAddr("EQAIFunALREOeQ99syMbO6sSzM_Fa1RsPD5TBoS0qVeKQ-AR") // GetGems sale contracts deployer
walletAddress := address.MustParseAddr(wallet)
royaltyAddress := address.MustParseAddr(royalty)
nftAddress := address.MustParseAddr(nft)
feesData := cell.BeginCell().
MustStoreAddr(marketplaceFeeAddress).
// 5% - GetGems fee
MustStoreCoins(price * 100 * 5).
MustStoreAddr(royaltyAddress).
// 5% - Royalty, can be changed
MustStoreCoins(price / 100 * 5).
EndCell()
saleData := cell.BeginCell().
MustStoreUInt(0, 1). // is_complete
MustStoreUInt(uint64(time.Now().UTC().Unix()), 32). // created_at
MustStoreAddr(marketplaceAddress). // marketplace_address
MustStoreAddr(nftAddress). // nft_address
MustStoreAddr(walletAddress). // previous_owner_address
MustStoreCoins(price). // full price in nanotons
MustStoreRef(feesData). // fees_cell
MustStoreUInt(0, 32). // sold_at
MustStoreUInt(0, 64). // query_id
EndCell()
stateInit := &tlb.StateInit{
Data: saleData,
Code: fixPriceV3R3Code,
}
stateInitCell, err := tlb.ToCell(stateInit)
if err != nil {
return nil, err
}
// not needed, just for example
// saleContractAddress := address.NewAddress(0, 0, stateInitCell.Hash())
saleBody := cell.BeginCell().
MustStoreUInt(1, 32). // just accept coins on deploy
MustStoreUInt(0, 64).
EndCell()
transferNftBody, err := cell.BeginCell().
MustStoreUInt(0x5fcc3d14, 32). // opcode for NFT transfer
MustStoreUInt(0, 64). // query_id
MustStoreAddr(destinationAddress). // new_owner - GetGems sale contracts deployer, should never change for this operation
MustStoreAddr(walletAddress). // response_destination for excesses
MustStoreUInt(0, 1). // we do not have custom_payload
MustStoreCoins(uint64(0.2*math.Pow(10, 9))). // forward_amount
MustStoreUInt(0, 1). // we store forward_payload is this cell
MustStoreUInt(0x0fe0ede, 31). // not 32, because previous 0 will be read as do_sale opcode in deployer (op::do_sale)
MustStoreRef(stateInitCell).
MustStoreRef(saleBody).
EndCell().MarshalJSON()
if err != nil {
return nil, err
}
msg, err := tonconnect.NewMessage(
nftAddress.String(),
fmt.Sprintf("%d", amount),
tonconnect.WithPayload(transferNftBody))
if err != nil {
return nil, err
}
return msg, nil
}
The final transaction body:
msg, err := NftSaleMessage("EQArLGBnGPvkxaJE57Y6oS4rwzDWuOE8l8_sghntXLkIt162",
"EQArLGBnGPvkxaJE57Y6oS4rwzDWuOE8l8_sghntXLkIt162",
"EQCUWoe7hLlklVxH8gduCf45vPNocsjRP4wbX42UJ0Ja0S2f",
uint64(0.3*math.Pow(10, 9)), uint64(5*math.Pow(10, 9)))
if err != nil {
log.Fatal(err)
}
tx, err := tonconnect.NewTransaction(
tonconnect.WithTimeout(10*time.Minute),
tonconnect.WithTestnet(),
tonconnect.WithMessage(*msg),
)
if err != nil {
log.Fatal(err)
}
NFT Buy (GetGems)
The process of buy NFT for nft-fixprice-sale-v3r3 sale contract could be carry out with regular transfer without payload, the only important thing is accurate TON amount, that calculates as follows:
buyAmount = Nftprice TON + 1.0 TON
.
msg, err := tonconnect.NewMessage(nftAddress, buyAmount)
if err != nil {
log.Fatal(err)
}
tx, err := tonconnect.NewTransaction(
tonconnect.WithTimeout(10*time.Minute),
tonconnect.WithTestnet(),
tonconnect.WithMessage(*msg),
)
if err != nil {
log.Fatal(err)
}
Authors
- JavaScript examples provided by @aSpite
- Python examples provided by @yunwine
- Go examples provided by @gleb498