NFT Processing
Overview
In this section of our documentation we’ll provide readers with a better understanding of NFTs. This will teach the reader how to interact with NFTs, and how to accept NFTs via transactions sent on TON Blockchain.
The information provided below assumes the reader has already taken a deep dive into our previous section detailing Toncoin payment processing, while also assuming that they possess a basic understanding of how to interact with wallet smart contracts programmatically.
Understanding the Basics of NFTs
NFTs operating on TON Blockchain are represented by the TEP-62 and TEP-64 standards.
The Open Network (TON) Blockchain is designed with high performance in mind and includes a feature that makes use of automatic sharding based on contract addresses on TON (which are used to help provision specific NFT designs). In order to achieve optimal performance, individual NFTs must make use of their own smart contract. This enables the creation of NFT collections of any size (whether large or small in number), while also reducing development costs and performance issues. However, this approach also introduces new considerations for the development of NFT collections.
Because each NFT makes use of its own smart contract, it is not possible to obtain information about each individualized NFT within an NFT collection using a single contract. To retrieve information on an entire collection as a whole, as well as each individual NFT within a collection, it is necessary to query both the collection contract and each individual NFT contract individually. For the same reason, to track NFT transfers, it is necessary to track all transactions for each individualized NFT within a specific collection.
NFT Collections
NFT Collection is a contract that serves to index and store NFT content and should contain the following interfaces:
Get method get_collection_data
(int next_item_index, cell collection_content, slice owner_address) get_collection_data()
Retrieves general information about collection, which represented with the following:
next_item_index
- if the collection is ordered, this classification indicates the total number of NFTs in the collection, as well as the next index used for minting. For unordered collections, thenext_item_index
value is -1, meaning the collection uses unique mechanisms to keep track of NFTs (e.g., the hash of TON DNS domains).collection_content
- a cell that represents the collection content in TEP-64 compatible format.owner_address
- a slice that contains the collection owner's address (this value can also be empty).
Get method get_nft_address_by_index
(slice nft_address) get_nft_address_by_index(int index)
This method can be used to verify the authenticity of an NFT and confirm whether it truly belongs to a specific collection. It also enables users to retrieve the address of an NFT by providing its index in the collection. The method should return a slice containing the address of the NFT that corresponds to the provided index.
Get method get_nft_content
(cell full_content) get_nft_content(int index, cell individual_content)
Since the collection serves as a common data storage for NFTs, this method is necessary to complete the NFT content. To use this method, first, it’s necessary to obtain the NFT’s individual_content
by calling the corresponding get_nft_data()
method. After obtaining the individual_content
, it’s possible to call the get_nft_content()
method with the NFT index and the individual_content
cell. The method should return a TEP-64 cell containing the full content of the NFT.
NFT Items
Basic NFTs should implement:
Get method get_nft_data()
(int init?, int index, slice collection_address, slice owner_address, cell individual_content) get_nft_data()
Inline message handler for transfer
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
Let's look at each parameter you need to fill in your message:
OP
-0x5fcc3d14
- a constant defined by the TEP-62 standard within the transfer message.queryId
-uint64
- a uint64 number used to track the message.newOwnerAddress
-MsgAddress
- the address of the contract used to transfer the NFT to.responseAddress
-MsgAddress
- the address used to transfer excess funds to. Typically, an extra amount of TON (e.g., 1 TON) is sent to the NFT contract to ensure it has enough funds to pay transaction fees and create a new transfer if needed. All unused funds within the transaction are sent to theresponseAddress
.forwardAmount
-Coins
- the amount of TON used in conjunction with the forward message (usually set to 0.01 TON). Since TON uses an asynchronous architecture, the new owner of the NFT will not be notified immediately upon successfully receiving the transaction. To notify the new owner, an internal message is sent from the NFT smart contract to thenewOwnerAddress
with a value denoted using theforwardAmount
. The forward message will begin with theownership_assigned
OP (0x05138d91
), followed by the previous owner's address and theforwardPayload
(if present).forwardPayload
-Slice | Cell
- is sent as a part ofownership_assigned
notification message.
This message (as explained above) is the primary way used to interact with an NFT that changes ownership after a notification is received as a result of the above message.
For example, this message type above is often used to send a NFT Item Smart Contract from a Wallet Smart Contract. When an NFT Smart Contract receives this message and executes it, the NFT Contract's storage (inner contract data) is updated along with the Owner's ID. In this way, the NFT Item(contract) changes owners correctly. This process details a standard NFT Transfer
In this case, the forward amount should be set to an appropriate value(0.01 TON for a regular wallet or more if you want to execute a contract by transferring an NFT), to ensure that the new owner receives a notification regarding the ownership transfer. This is important because the new owner won’t be notified that they have received the NFT without this notification.
Retrieving NFT Data
Most SDKs make use of ready to use handlers for retrieving NFT data, including: tonweb(js), tonutils-go, pytonlib, and others.
To receive NFT data it is necessary to make use of the get_nft_data()
retrieval mechanism. For example, we must verify the following NFT item address EQB43-VCmf17O7YMd51fAvOjcMkCw46N_3JMCoegH_ZDo40e
(also known as foundation.ton domain).
First it is necessary to execute the get method by using the toncenter.com API as follows:.
curl -X 'POST' \
'https://toncenter.com/api/v2/runGetMethod' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"address": "EQB43-VCmf17O7YMd51fAvOjcMkCw46N_3JMCoegH_ZDo40e",
"method": "get_nft_data",
"stack": []
}'
The response is generally something similar to the following:
{
"ok": true,
"result": {
"@type": "smc.runResult",
"gas_used": 1581,
"stack": [
// init
[ "num", "-0x1" ],
// index
[ "num", "0x9c7d56cc115e7cf6c25e126bea77cbc3cb15d55106f2e397562591059963faa3" ],
// collection_address
[ "cell", { "bytes": "te6cckEBAQEAJAAAQ4AW7psr1kCofjDYDWbjVxFa4J78SsJhlfLDEm0U+hltmfDtDcL7" } ],
// owner_address
[ "cell", { "bytes": "te6cckEBAQEAJAAAQ4ATtS415xpeB1e+YRq/IsbVL8tFYPTNhzrjr5dcdgZdu5BlgvLe" } ],
// content
[ "cell", { "bytes": "te6cckEBCQEA7AABAwDAAQIBIAIDAUO/5NEvz/d9f/ZWh+aYYobkmY5/xar2cp73sULgTwvzeuvABAIBbgUGAER0c3/qevIyXwpbaQiTnJ1y+S20wMpSzKjOLEi7Jwi/GIVBAUG/I1EBQhz26hlqnwXCrTM5k2Qg5o03P1s9x0U4CBUQ7G4HAUG/LrgQbAsQe0P2KTvsDm8eA3Wr0ofDEIPQlYa5wXdpD/oIAEmf04AQe/qqXMblNo5fl5kYi9eYzSLgSrFtHY6k/DdIB0HmNQAQAEatAVFmGM9svpAE9og+dCyaLjylPtAuPjb0zvYqmO4eRJF0AIDBvlU=" } ]
],
"exit_code": 0,
"@extra": "1679535187.3836682:8:0.06118075068995321"
}
}
Return parameters:
init
-boolean
- -1 means NFT is initialized and can be usedindex
-uint256
- index of the NFT in the collection. Can be sequential or derived in some other way. For example, this can denote an NFT doman hash used with TON DNS contracts, while collections should only have only one unique NFT within a given index.collection_address
-Cell
- a cell containing the NFT collection address (can be empty).owner_address
-Cell
- a cell containing the current owner’s NFT address (can be empty).content
-Cell
- a cell containing NFT item content (if parsing is needed it is necessary to consult the TEP-64 standard).
Retrieving all NFTs within a collection
The process for retrieving all NFTs within a collection differs depending on whether the collection is ordered or not. Let’s outline both processes below.
Ordered collections
Retrieving all NFTs in an ordered collection is relatively straightforward since the number of NFTs needed for retrieval is already known and their addresses can easily be easily obtained. To complete this process, the following steps should be followed in order:
- Invoke the
get_collection_data
method using the TonCenter API within the collection contract and retrieve thenext_item_index
value from the response. - Use the
get_nft_address_by_index
method, passing in the index valuei
(initially set to 0), to retrieve the address of the first NFT in the collection. - Retrieve the NFT Item data using the address obtained in the previous step. Next, verify that the initial NFT Collection smart contract coincides with the NFT Collection smart contract reported by the NFT item itself (to ensure the Collection didn't appropriate another user’s NFT smart contract).
- Call the
get_nft_content
method withi
andindividual_content
from the previous step. - Increase
i
by 1 and repeat steps 2-5 untili
is equal to thenext_item_index
. - At this point, you will be in possession of the necessary information from the collection and its individual items.
Unordered collections
Retrieving the list of NFTs in an unordered collection is more difficult because there is no inherent way to obtain the addresses of the NFTs that belong to the collection. Therefore, it is necessary to parse all transactions in the collection contract and check all outgoing messages to identify the ones that correspond to NFTs belonging to the collection.
To do so, the NFT data must be retrieved, and the get_nft_address_by_index
method is called in the collection with the ID returned by the NFT. If the NFT contract address and the address returned by the get_nft_address_by_index
method match, it indicates that the NFT belongs to the current collection. However, parsing all messages to the collection can be a lengthy process and may require archive nodes.
Working with NFTs outside of TON
Sending NFTs
To transfer NFT ownership it is necessary to send an internal message from the NFT owner’s wallet to the NFT contract by creating a cell that contains a transfer message. This can be accomplished using libraries (such as tonweb(js), ton(js), tonutils-go(go)) for the specific language.
Once the transfer message has been created, it must be sent to the NFT item contract address from the owner's wallet contract, with an adequate amount of TON to cover the associated transaction fee.
To transfer an NFT from another user to yourself, it is necessary to use TON Connect 2.0 or a simple QR code that contains a ton:// link. For example:
ton://transfer/{nft_address}?amount={message_value}&bin={base_64_url(transfer_message)}
Receiving NFTs
The process of tracking NFTs sent to a certain smart contract address (i.e. a user's wallet) is similar to the mechanism used to track payments. This is completed by listening to all new transactions in your wallet and parsing them.
The next steps may vary depending on the specific use case. Let’s examine several different scenarios below.
Service waiting for known NFT address transfers:
- Verify new transactions sent from the NFT item smart contract address.
- Read the first 32 bits of the message body as using the
uint
type and verify that it is equal toop::ownership_assigned()
(0x05138d91
) - Read the next 64 bits from the message body as the
query_id
. - Read the address from the message body as the
prev_owner_address
. - It is now possible to manage your new NFT.
Service listening to all types of NFT transfer:
- Check all new transactions and ignore those with a body length less than 363 bits (OP - 32, QueryID - 64, Address - 267).
- Repeat the steps detailed in the previous list above.
- If the process is working correctly, it is necessary to verify the authenticity of the NFT by parsing it and the collection it belongs to. Next, it is necessary to ensure that the NFT belongs to the specified collection. More information detailing this process can be found in the
Getting all collection NFTs
section. This process can be simplified by using a whitelist of NFTs or collections. - It is now possible to manage your new NFT.
Tying NFT transfers to internal transactions:
When a transaction of this type is received, it's necessary to repeat the steps from the previous list. Once this process is completed, it is possible to retrieve the RANDOM_ID
parameter by reading a uint32 from the message body after reading the prev_owner_address
value.
NFTs sent without a notification message:
All of the strategies outlined above rely on the services correctly creating a forward message with the NFT transfer. If they don't do this, we won't know that they transferred the NFT to us. However, there are a few workarounds:
All of the strategies outlined above rely on the service correctly creating a forward message within the NFT transfer. If this process is not carried out, it won’t be clear whether the NFT was transferred to the correct party. However, there are a several workarounds that are possible in this scenario:
- If a small number of NFTs is expected, it is possible to periodically parse them and verify if the owner has changed to the corresponding contract type.
- If a large number of NFTs is expected, it is possible to parse all new blocks and verify if there were any calls sent to the NFT destination using the
op::transfer
method. If a transaction like this is initiated, it is possible to verify the NFT owner and receive the transfer. - If it's not possible to parse new blocks within the transfer, it is possible for users to trigger NFT ownership verification processes themselves. This way, it is possible to trigger the NFT ownership verification process after transferring an NFT without a notification.