Skip to content

Non-Fungible Tokens (NFTs)

This page lists common examples of working with NFTs.

Accepting NFT ownership assignment

The notification message of assigned NFT ownership has the following structure:

message(0x05138d91) NFTOwnershipAssigned {
queryId: Int as uint64;
previousOwner: Address;
forwardPayload: Slice as remaining;
}
▶️ Open in Web IDE

Use the receiver function to accept the notification message.

Validation can be done in two ways:

  1. Directly store the NFT item address and validate against it.
message(0x05138d91) NFTOwnershipAssigned {
queryId: Int as uint64;
previousOwner: Address;
forwardPayload: Slice as remaining;
}
contract SingleNft {
nftItemAddress: Address;
init(nftItemAddress: Address) {
self.nftItemAddress = nftItemAddress;
}
// Empty receiver for the deployment,
// which forwards the remaining value back to the sender
receive() { cashback(sender()) }
receive(msg: NFTOwnershipAssigned) {
require(self.nftItemAddress == sender(), "NFT contract is not the sender");
// your logic of processing NFT ownership assignment notification
}
}
▶️ Open in Web IDE
  1. Use StateInit and the derived address of the NFT item.
message(0x05138d91) NFTOwnershipAssigned {
queryId: Int as uint64;
previousOwner: Address;
forwardPayload: Slice as remaining;
}
struct NFTItemInitData {
index: Int as uint64;
collectionAddress: Address;
}
inline fun calculateNFTAddress(index: Int, collectionAddress: Address, nftCode: Cell): Address {
let initData = NFTItemInitData{
index,
collectionAddress,
};
return contractAddress(StateInit{code: nftCode, data: initData.toCell()});
}
contract NftInCollection {
nftCollectionAddress: Address;
nftItemIndex: Int as uint64;
nftCode: Cell;
init(nftCollectionAddress: Address, nftItemIndex: Int, nftCode: Cell) {
self.nftCollectionAddress = nftCollectionAddress;
self.nftItemIndex = nftItemIndex;
self.nftCode = nftCode;
}
// Empty receiver for the deployment,
// which forwards the remaining value back to the sender
receive() { cashback(sender()) }
receive(msg: NFTOwnershipAssigned) {
let expectedNftAddress = calculateNFTAddress(self.nftItemIndex, self.nftCollectionAddress, self.nftCode); // or you can even store expectedNftAddress
require(expectedNftAddress == sender(), "NFT contract is not the sender");
// your logic of processing NFT ownership assignment notification
}
}
▶️ Open in Web IDE

Since the initial data layout of the NFT item can vary, the first approach is often more suitable.

Transferring an NFT item

To send an NFT item transfer, use the send() function.

message(0x5fcc3d14) NFTTransfer {
queryId: Int as uint64;
newOwner: Address; // Address of the new owner of the NFT item.
responseDestination: Address; // Address to send a response confirming a successful transfer and the remaining incoming message coins.
customPayload: Cell? = null; // Optional custom data. In most cases, this should be null.
forwardAmount: Int as coins; // The amount of nanotons to be sent to the new owner.
forwardPayload: Slice as remaining; // Optional custom data that should be sent to the new owner.
}
contract Example {
nftItemAddress: Address;
init(nftItemAddress: Address) {
self.nftItemAddress = nftItemAddress;
}
// Empty receiver for the deployment,
// which forwards the remaining value back to the sender
receive() { cashback(sender()) }
// ... add more code from previous examples ...
receive("transfer") {
send(SendParameters{
to: self.nftItemAddress,
value: ton("0.1"),
body: NFTTransfer{
queryId: 42,
// FIXME: Change this according to your needs.
newOwner: sender(),
responseDestination: myAddress(),
customPayload: null,
forwardAmount: 1,
forwardPayload: rawSlice("F"), // Precomputed beginCell().storeUint(0xF, 4).endCell().beginParse()
}.toCell(),
});
}
}
▶️ Open in Web IDE

Get NFT static info

Note that TON Blockchain does not allow contracts to call each other’s getters. To retrieve data from another contract, you must exchange messages.

message(0x2fcb26a2) NFTGetStaticData {
queryId: Int as uint64;
}
message(0x8b771735) NFTReportStaticData {
queryId: Int as uint64;
index: Int as uint256;
collection: Address;
}
struct NFTItemInitData {
index: Int as uint64;
collectionAddress: Address;
}
inline fun calculateNFTAddress(index: Int, collectionAddress: Address, nftCode: Cell): Address {
let initData = NFTItemInitData{
index,
collectionAddress,
};
return contractAddress(StateInit{code: nftCode, data: initData.toCell()});
}
contract Example {
nftCollectionAddress: Address;
nftItemIndex: Int as uint64;
nftCode: Cell;
init(nftCollectionAddress: Address, nftItemIndex: Int, nftCode: Cell) {
self.nftCollectionAddress = nftCollectionAddress;
self.nftItemIndex = nftItemIndex;
self.nftCode = nftCode;
}
// Empty receiver for the deployment,
// which forwards the remaining value back to the sender
receive() { cashback(sender()) }
// ... add more code from previous examples ...
receive("get static data") {
// FIXME: Put proper address("[NFT_ADDRESS]") here
let nftAddress = sender();
send(SendParameters{
to: nftAddress,
value: ton("0.1"),
body: NFTGetStaticData{
queryId: 42,
}.toCell(),
});
}
receive(msg: NFTReportStaticData) {
let expectedNftAddress = calculateNFTAddress(msg.index, msg.collection, self.nftCode);
require(expectedNftAddress == sender(), "NFT contract is not the sender");
// Save NFT static data or do something
}
}
▶️ Open in Web IDE

Get NFT royalty params

NFT royalty parameters are described here.

message(0x693d3950) NFTGetRoyaltyParams {
queryId: Int as uint64;
}
message(0xa8cb00ad) NFTReportRoyaltyParams {
queryId: Int as uint64;
numerator: Int as uint16;
denominator: Int as uint16;
destination: Address;
}
contract Example {
nftCollectionAddress: Address;
init(nftCollectionAddress: Address) {
self.nftCollectionAddress = nftCollectionAddress;
}
// Empty receiver for the deployment,
// which forwards the remaining value back to the sender
receive() { cashback(sender()) }
// ... add more code from previous examples ...
receive("get royalty params") {
send(SendParameters{
to: self.nftCollectionAddress,
value: ton("0.1"),
body: NFTGetRoyaltyParams{
queryId: 42,
}.toCell(),
});
}
receive(msg: NFTReportRoyaltyParams) {
require(self.nftCollectionAddress == sender(), "NFT collection contract is not the sender");
// Do something with msg
}
}
▶️ Open in Web IDE

NFT Collection methods

Note that only NFT owners are allowed to use these methods.

Deploy NFT

message(0x1) NFTDeploy {
queryId: Int as uint64;
itemIndex: Int as uint64;
amount: Int as coins; // amount to send when deploying NFT
nftContent: Cell;
}
contract Example {
nftCollectionAddress: Address;
init(nftCollectionAddress: Address) {
self.nftCollectionAddress = nftCollectionAddress;
}
// Empty receiver for the deployment,
// which forwards the remaining value back to the sender
receive() { cashback(sender()) }
// ... add more code from previous examples ...
receive("deploy") {
send(SendParameters{
to: self.nftCollectionAddress,
value: ton("0.14"),
body: NFTDeploy{
queryId: 42,
itemIndex: 42,
amount: ton("0.1"),
nftContent: beginCell().endCell() // FIXME: Replace with your content, usually generated off-chain
}.toCell(),
});
}
}
▶️ Open in Web IDE

Change owner

message(0x3) NFTChangeOwner {
queryId: Int as uint64;
newOwner: Address;
}
contract Example {
nftCollectionAddress: Address;
init(nftCollectionAddress: Address) {
self.nftCollectionAddress = nftCollectionAddress;
}
// Empty receiver for the deployment,
// which forwards the remaining value to the sender
receive() { cashback(sender()) }
// ... add more code from previous examples ...
receive("change owner") {
send(SendParameters{
to: self.nftCollectionAddress,
value: ton("0.05"),
body: NFTChangeOwner{
queryId: 42,
// FIXME: Replace with the appropriate address("NEW_OWNER_ADDRESS")
newOwner: sender(),
}.toCell(),
});
}
}
▶️ Open in Web IDE

On-chain metadata creation

NFT Collection

/// https://github.com/ton-blockchain/TEPs/blob/master/text/0064-token-data-standard.md#nft-metadata-attributes
fun composeCollectionMetadata(
name: String, // full name
description: String, // text description of the NFT
image: String, // link to the image
// There could be other data, see:
// https://github.com/ton-blockchain/TEPs/blob/master/text/0064-token-data-standard.md#nft-metadata-attributes
): Cell {
let dict: map<Int as uint256, Cell> = emptyMap();
dict.set(sha256("name"), name.asMetadataCell());
dict.set(sha256("description"), description.asMetadataCell());
dict.set(sha256("image"), image.asMetadataCell());
return beginCell()
.storeUint(0, 8) // a null byte prefix
.storeMaybeRef(dict.asCell()!!) // 1 as a single bit, then a reference
.endCell();
}
// Taking flight!
fun poorMansLaunchPad() {
let collectionMetadata = composeCollectionMetadata(
"Best Collection",
"A very descriptive description describing the collection descriptively",
"...link to IPFS or somewhere trusted...",
);
}
// Prefixes the String with a single null byte and converts it to a Cell.
// The null byte prefix is used to express metadata in various standards, like NFT or Jetton.
inline extends fun asMetadataCell(self: String): Cell {
return beginTailString().concat(self).toCell();
}
▶️ Open in Web IDE

NFT Item

/// https://github.com/ton-blockchain/TEPs/blob/master/text/0064-token-data-standard.md#nft-metadata-attributes
fun composeItemMetadata(
name: String, // full name
description: String, // text description of the NFT
image: String, // link to the image
// There could be other data, see:
// https://github.com/ton-blockchain/TEPs/blob/master/text/0064-token-data-standard.md#nft-metadata-attributes
): Cell {
let dict: map<Int as uint256, Cell> = emptyMap();
dict.set(sha256("name"), name.asMetadataCell());
dict.set(sha256("description"), description.asMetadataCell());
dict.set(sha256("image"), image.asMetadataCell());
return beginCell()
.storeUint(0, 8) // a null byte prefix
.storeMaybeRef(dict.asCell()!!) // 1 as a single bit, then a reference
.endCell();
}
// Taking flight!
fun poorMansLaunchPad() {
let itemMetadata = composeItemMetadata(
"Best Item",
"A very descriptive description describing the item descriptively",
"...link to ipfs or somewhere trusted...",
);
}
// Prefixes the String with a single null byte and converts it to a Cell
// The null byte prefix is used to express metadata in various standards, like NFT or Jetton
inline extends fun asMetadataCell(self: String): Cell {
return beginTailString().concat(self).toCell();
}
▶️ Open in Web IDE