Перейти к основному содержимому

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 basics on the creating TON Connect connection. Learn more with the integration manual.

TON Connect JS SDK Examples

Transaction Template

No matter what level of the task developer are solving, typically it is necessary to use connector entity from @tonconnect/sdk or @tonconnect/ui. Examples created based on @tonconnect/sdk and @tonconnect/ui:

import { useTonConnectUI } from '@tonconnect/ui-react';
const [tonConnectUI] = useTonConnectUI();

const transaction = {
//transaction body
}

export const Settings = () => {
const [tonConnectUI, setOptions] = useTonConnectUI();

return (
<div>
<button onClick={() => tonConnectUI.sendTransaction(transaction)}>
Send transaction
</button>
</div>
);
};

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:

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>
);
};
подсказка

For specific custom transaction, a certain payload must be defined.

Transfer With a Comment

The simplest example is 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:

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

Jetton Transfer

The body for Jetton Transfer(TEP-74) typically should be done according the following way:

    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(1000000) // amount:(VarUInteger 16) - Jetton amount for transfer (decimals = 6 - jUSDT, 9 - default)
.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:

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>
);
};
  • validUntil - UNIX-time until message valid
  • jettonWalletAddress - Address, JettonWallet address, that defined based on JettonMaser and Wallet contracts
  • balance - 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()));
}

Jetton Transfer with Comment

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:

    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, amount * 10^9
.storeAddress(destinationAddress) // TON wallet destination address
.storeAddress(destinationAddress) // response excess destination
.storeBit(0) // no custom payload
.storeCoins(toNano(0.02).toString()) // 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:

    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>
);
};
  • validUntil - UNIX-time until message valid
  • jettonWalletAddress - Address, JettonWallet address, that defined based on JettonMaser and Wallet contracts
  • balance - 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()));
}

Jetton Burn

The body for Jetton Burn(TEP-74) typically should be done according the following way:

    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(1000000) // amount:(VarUInteger 16) - Jetton amount in decimal
.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:

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>
);
};
  • jettonWalletAddress - Jetton Wallet contract address, that defined based on JettonMaser and Wallet contracts
  • amount - Integer, amount of Toncoin for gas payments in nanotons.
  • body - payload for the jetton wallet with the burn#595f07bc op code

NFT Transfer

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(toNano(0.000000001).toString()) // forward_amount:(VarUInteger 16)
.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.

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>
);
};
  • NFTitem - Address - The address of NFT item smart contract which we want transfer to a new owner NEW_OWNER_WALLET.
  • balance - Integer, amount of Toncoin for gas payments in nanotons.
  • body - payload for the NFT contract

NFT Sale (GetGems)

Here is an example of preparing message and transaction for sale on GetGems marketplace, according to contract nft-fixprice-sale-v3r2.

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(1).toString()) // 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() {
const fixPriceV3R2Code = Cell.fromBase64('te6cckECCwEAArkAART/APSkE/S88sgLAQIBIAIDAgFIBAUAfvIw7UTQ0wDTH/pA+kD6QPoA1NMAMMABjh34AHAHyMsAFssfUATPFljPFgHPFgH6AszLAMntVOBfB4IA//7y8AICzQYHAFegOFnaiaGmAaY/9IH0gfSB9AGppgBgYaH0gfQB9IH0AGEEIIySsKAVgAKrAQH30A6GmBgLjYSS+CcH0gGHaiaGmAaY/9IH0gfSB9AGppgBgYOCmE44BgAEqYhOmPhW8Q4YBKGATpn8cIxbMbC3MbK2QV44LJOZlvKAVxFWAAyS+G8BJrpOEBFcCBFd0VYACRWdjYKdxjgthOjq+G6hhoaYPqGAD9gHAU4ADAgB92YIQO5rKAFJgoFIwvvLhwiTQ+kD6APpA+gAwU5KhIaFQh6EWoFKQcIAQyMsFUAPPFgH6AstqyXH7ACXCACXXScICsI4XUEVwgBDIywVQA88WAfoCy2rJcfsAECOSNDTiWnCAEMjLBVADzxYB+gLLaslx+wBwIIIQX8w9FIKAejy0ZSzjkIxMzk5U1LHBZJfCeBRUccF8uH0ghAFE42RFrry4fUD+kAwRlAQNFlwB8jLABbLH1AEzxZYzxYBzxYB+gLMywDJ7VTgMDcowAPjAijAAJw2NxA4R2UUQzBw8AXgCMACmFVEECQQI/AF4F8KhA/y8AkA1Dg5ghA7msoAGL7y4clTRscFUVLHBRWx8uHKcCCCEF/MPRQhgBDIywUozxYh+gLLassfFcs/J88WJ88WFMoAI/oCE8oAyYMG+wBxUGZFFQRwB8jLABbLH1AEzxZYzxYBzxYB+gLMywDJ7VQAlsjLHxPLPyPPFlADzxbKAIIJycOA+gLKAMlxgBjIywUmzxZw+gLLaszJgwb7AHFVUHAHyMsAFssfUATPFljPFgHPFgH6AszLAMntVNZeZYk=');

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).toString(); // 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
.storeBit(0) // can_be_deployed_externally
.endCell();

const stateInit: StateInit = {
code: fixPriceV3R2Code,
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(1).toString()) // forward_amount
.storeBit(0) // we store forward_payload is this cell
// not 32, because we stored 0 bit before | do_sale opcode for deployer
.storeUint(0x0fe0ede, 31)
.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.

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(1.08).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>
);
};

NFT Buy (GetGems)

The process of buy NFT for nft-fixprice-sale-v3r2 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.

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

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!'
)
]
}
подсказка

Jetton Transfer

Example of function for building jetton transfer transaction:

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)
.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 jUSDT it should be (amount * 10**6)
response_address=wallet_address
),
]
}

Jetton Burn

Example of function for building jetton burn transaction:

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)
.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
.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-v3r2.

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

# contract code
nft_sale_code_cell = Cell.one_from_boc('te6cckECCwEAArkAART/APSkE/S88sgLAQIBIAIDAgFIBAUAfvIw7UTQ0wDTH/pA+kD6QPoA1NMAMMABjh34AHAHyMsAFssfUATPFljPFgHPFgH6AszLAMntVOBfB4IA//7y8AICzQYHAFegOFnaiaGmAaY/9IH0gfSB9AGppgBgYaH0gfQB9IH0AGEEIIySsKAVgAKrAQH30A6GmBgLjYSS+CcH0gGHaiaGmAaY/9IH0gfSB9AGppgBgYOCmE44BgAEqYhOmPhW8Q4YBKGATpn8cIxbMbC3MbK2QV44LJOZlvKAVxFWAAyS+G8BJrpOEBFcCBFd0VYACRWdjYKdxjgthOjq+G6hhoaYPqGAD9gHAU4ADAgB92YIQO5rKAFJgoFIwvvLhwiTQ+kD6APpA+gAwU5KhIaFQh6EWoFKQcIAQyMsFUAPPFgH6AstqyXH7ACXCACXXScICsI4XUEVwgBDIywVQA88WAfoCy2rJcfsAECOSNDTiWnCAEMjLBVADzxYB+gLLaslx+wBwIIIQX8w9FIKAejy0ZSzjkIxMzk5U1LHBZJfCeBRUccF8uH0ghAFE42RFrry4fUD+kAwRlAQNFlwB8jLABbLH1AEzxZYzxYBzxYB+gLMywDJ7VTgMDcowAPjAijAAJw2NxA4R2UUQzBw8AXgCMACmFVEECQQI/AF4F8KhA/y8AkA1Dg5ghA7msoAGL7y4clTRscFUVLHBRWx8uHKcCCCEF/MPRQhgBDIywUozxYh+gLLassfFcs/J88WJ88WFMoAI/oCE8oAyYMG+wBxUGZFFQRwB8jLABbLH1AEzxZYzxYBzxYB+gLMywDJ7VQAlsjLHxPLPyPPFlADzxbKAIIJycOA+gLKAMlxgBjIywUmzxZw+gLLaszJgwb7AHFVUHAHyMsAFssfUATPFljPFgHPFgH6AszLAMntVNZeZYk=')

# 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)
.store_uint(int(time.time()), 32)
.store_address(marketplace_address)
.store_address(nft_address)
.store_address(wallet_address)
.store_coins(price)
.store_ref(fees_data_cell)
.store_bit_int(0)
.end_cell())

state_init_cell = StateInit(code=nft_sale_code_cell, data=sale_data_cell).serialize()

sale_body = (begin_cell()
.store_uint(1, 32)
.store_uint(0, 64)
.end_cell())

transfer_nft_body = (begin_cell()
.store_uint(0x5fcc3d14, 32)
.store_uint(0, 64)
.store_address(destination_address)
.store_address(wallet_address)
.store_bit_int(0)
.store_coins(int(1 * 10**9))
.store_bit_int(0)
.store_uint(0x0fe0ede, 31)
.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(1.08 * 10**9)
),
]
}

NFT Buy (GetGems)

The process of buy NFT for nft-fixprice-sale-v3r2 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:

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).
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:

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 to burn
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-v3r2.

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(1.08*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) {
fixPriceV3R2Code := new(cell.Cell)
fixPriceV3R2Code.UnmarshalJSON([]byte("te6cckECCwEAArkAART/APSkE/S88sgLAQIBIAIDAgFIBAUAfvIw7UTQ0wDTH/pA+kD6QPoA1NMAMMABjh34AHAHyMsAFssfUATPFljPFgHPFgH6AszLAMntVOBfB4IA//7y8AICzQYHAFegOFnaiaGmAaY/9IH0gfSB9AGppgBgYaH0gfQB9IH0AGEEIIySsKAVgAKrAQH30A6GmBgLjYSS+CcH0gGHaiaGmAaY/9IH0gfSB9AGppgBgYOCmE44BgAEqYhOmPhW8Q4YBKGATpn8cIxbMbC3MbK2QV44LJOZlvKAVxFWAAyS+G8BJrpOEBFcCBFd0VYACRWdjYKdxjgthOjq+G6hhoaYPqGAD9gHAU4ADAgB92YIQO5rKAFJgoFIwvvLhwiTQ+kD6APpA+gAwU5KhIaFQh6EWoFKQcIAQyMsFUAPPFgH6AstqyXH7ACXCACXXScICsI4XUEVwgBDIywVQA88WAfoCy2rJcfsAECOSNDTiWnCAEMjLBVADzxYB+gLLaslx+wBwIIIQX8w9FIKAejy0ZSzjkIxMzk5U1LHBZJfCeBRUccF8uH0ghAFE42RFrry4fUD+kAwRlAQNFlwB8jLABbLH1AEzxZYzxYBzxYB+gLMywDJ7VTgMDcowAPjAijAAJw2NxA4R2UUQzBw8AXgCMACmFVEECQQI/AF4F8KhA/y8AkA1Dg5ghA7msoAGL7y4clTRscFUVLHBRWx8uHKcCCCEF/MPRQhgBDIywUozxYh+gLLassfFcs/J88WJ88WFMoAI/oCE8oAyYMG+wBxUGZFFQRwB8jLABbLH1AEzxZYzxYBzxYB"))

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, 1). // can_be_deployed_externally
EndCell()

stateInit := &tlb.StateInit{
Data: saleData,
Code: fixPriceV3R2Code,
}

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(1*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(1.08*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-v3r2 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

See Also