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
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)
NFTitem
- Address - The address of the NFT item smart contract which we want to transfer to a new ownerNEW_OWNER_WALLET
.balance
- Integer, the amount of Toncoin used for gas payments in nanotons.body
- payload for the NFT contract
Note: For the browser, you must 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 an 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 the GetGems Sale Contract, we should prepare a special message body, transferNftBody
, to transfer NFT to the 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 the message requires a lot of steps, the entire algorithm is huge and can 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();
}
The prepared transferNftBody
should be sent to the NFT Item contract with at least 1.08
TON, which is expected for successful processing. Excess will be returned to a sender's wallet.
- @tonconnect/react-ui
- @tonconnect/ui
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)
NFT buy (GetGems)
- @ton/ton
- assets/sdk
The process of buying NFT for nft-fixprice-sale-v3r3 sale contract could be carried out with a regular transfer without payload. The only important thing is the accurate TON amount, which is calculated as follows:
buyAmount = Nftprice TON + 1.0 TON
.
- @tonconnect/react-ui
- @tonconnect/ui
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)
Note: For the browser, you must 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) })
See also
- TON Connect SDKs
- TON Connect - sending messages
- Smart contract development - sending messages
- TON jetton processing
- NFT processing on TON