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

Обработка NFT

Обзор

В этом разделе нашей документации мы предоставим читателям более полное представление о NFT. Это научит читателя взаимодействовать с NFT и как принимать их через транзакции, отправленные в блокчейн TON.

Информация, представленная ниже, предполагает, что читатель уже глубоко изучил наш предыдущий [раздел, подробно описывающий обработку платежей в Toncoin] (/v3/guidelines/dapps/asset-processing/payments-processing), и что он также обладает базовым пониманием того, как программно взаимодействовать с смарт-контрактами кошелька.

Понимание основ NFT

NFT, работающие на блокчейне TON, представлены стандартами TEP-62 и TEP-64.

Блокчейн The Open Network (TON) разработан с учетом высокой производительности и включает в себя функцию автоматического сегментирование на основе адресов контрактов на TON (которые используются для предоставления конкретных проектов NFT). Чтобы достичь оптимальной производительности, отдельные NFT должны использовать свой собственный смарт-контракт. Это позволяет создавать коллекции NFT любого размера (как большие, так и маленькие), а также снижает затраты на разработку и проблемы с производительностью. Однако такой подход также вносит новые соображения в разработку коллекций NFT.

Поскольку каждый NFT использует собственный смарт-контракт, невозможно получить информацию об отдельных NFT в коллекции, используя один контракт. Чтобы извлечь информацию о всей коллекции в целом, а также о каждом индивидуальном NFT внутри коллекции, необходимо запросить как контракт коллекции, так и контракт каждого отдельного NFT в отдельности. По той же причине для отслеживания переводов NFT необходимо отслеживать все транзакции для каждого отдельного NFT в рамках конкретной коллекции.

NFT Collections

NFT Collection - это контракт, который служит для индексирования и хранения содержимого NFT и должен содержать следующие интерфейсы:

Get method get_collection_data

(int next_item_index, cell collection_content, slice owner_address) get_collection_data()

Получает общую информацию о коллекции, которая представлена следующим образом:

  1. next_item_index - если коллекция упорядочена, эта классификация указывает на общее количество NFT в коллекции, а также на следующий индекс, используемый для выпуска. Для неупорядоченных коллекций значение next_item_index равно -1, что означает, что коллекция использует уникальные механизмы для отслеживания NFT (например, хэш или домены TON DNS).
  2. collection_content - ячейка, представляющая содержимое коллекции в формате, совместимом с TEP-64.
  3. owner_address - фрагмент, содержащий адрес владельца коллекции (это значение также может быть пустым).

Get method get_nft_address_by_index

(slice nft_address) get_nft_address_by_index(int index)

Этот метод можно использовать для проверки достоверности NFT и подтверждения, действительно ли он принадлежит определенной коллекции. Он также позволяет пользователям извлекать адрес NFT, указав его индекс в коллекции. Метод должен вернуть фрагмент, содержащий адрес NFT, соответствующий указанному индексу.

Get method get_nft_content

(cell full_content) get_nft_content(int index, cell individual_content)

Поскольку коллекция служит общим хранилищем данных для NFT, этот метод необходим для заполнения содержимого NFT. Чтобы использовать этот метод, сначала необходимо получить individual_content NFT, вызвав соответствующий метод get_nft_data(). После получения individual_content можно вызвать метод get_nft_content() с индексом NFT и ячейкой individual_content. Метод должен вернуть ячейку TEP-64, содержащую полное содержимое NFT.

NFT Items

Основные NFT должны быть реализованы:

Get method get_nft_data()

(int init?, int index, slice collection_address, slice owner_address, cell individual_content) get_nft_data()

Обработчик встроенных сообщений для 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

Давайте посмотрим на каждый параметр, который нужно заполнить в вашем сообщении:

  1. OP - 0x5fcc3d14 - константа, определенная стандартом TEP-62 в рамках сообщения о передаче.
  2. queryId - uint64 - число uint64, используемое для отслеживания сообщения.
  3. newOwnerAddress - MsgAddress - адрес контракта, на который переводится NFT.
  4. responseAddress - MsgAddress - адрес, на который будут переведены лишние средства. Как правило, в контракт NFT отправляется дополнительная сумма в TON (например, 1 TON), чтобы обеспечить наличие достаточного количества средств для оплаты комиссии за транзакцию и создания нового перевода в случае необходимости. Все неиспользованные средства в рамках транзакции отправляются на responseAddress.
  5. forwardAmount - Coins - количество TON, используемое вместе с сообщением о пересылке (обычно устанавливается 0,01 TON). Поскольку TON использует асинхронную архитектуру, новый владелец NFT не будет уведомлен сразу после успешного получения транзакции. Чтобы уведомить нового владельца, смарт-контракт NFT отправляет внутреннее сообщение на адрес newOwnerAddress со значением, указанным с помощью forwardAmount. Сообщение о пересылке начнется с операции ownership_assigned OP (0x05138d91), за которым следует адрес предыдущего владельца forwardPayload (если он присутствует).
  6. forwardPayload - Slice | Cell - отправляется как часть сообщения уведомления ownership_assigned.

Это сообщение (как было объяснено выше) является основным способом взаимодействия с NFT, который изменяет свой владелец после получения уведомления в результате указанного ранее сообщения.

Например, этот тип сообщения часто используется для отправки смарт-контракта NFT Item из смарт-контракта Wallet. Когда смарт-контракт NFT получает это сообщение и выполняет его, хранилище (внутренние данные контракта) NFT-контракта обновляется вместе с ID владельца. Таким образом, NFT Item (контракт) правильно меняет своего владельца. Этот процесс описывает стандартную передачу NFT

В этом случае сумма пересылки должна быть установлена в подходящее значение (0,01 TON для обычного кошелька или больше, если вы хотите выполнить контракт, передавая NFT), чтобы обеспечить новому владельцу уведомление о передаче собственности. Это важно, поскольку без этого уведомления новый владелец не получит уведомления о том, что он получил NFT.

Получение данных NFT

Большинство SDK используют готовые обработчики для извлечения данных NFT, включая: tonweb(js), tonutils-go, pytonlib и другие.

Чтобы получить данные NFT, необходимо использовать механизм извлечения get_nft_data(). Например, мы должны проверить следующий адрес NFT Item EQB43-VCmf17O7YMd51fAvOjcMkCw46N_3JMCoegH_ZDo40e (также известный как домен foundation.ton).

Сначала необходимо выполнить get метод извлечения, используя API toncenter.com следующим образом:.

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": []
}'

Ответ обычно что-то вроде следующего:

{
"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"
}
}

Возвращаемые параметры:

  • init - boolean — значение -1 означает, что NFT инициализирован и может быть использован
  • index - uint256 - индекс NFT в коллекции. Может быть последовательным или полученным каким-либо другим способом. Например, он может обозначать хэш домена NFT, используемый с контрактами TON DNS, тогда как коллекции должны иметь только один уникальный NFT для данного индекса.
  • collection_address - Cell - ячейка, содержащая адрес коллекции NFT (может быть пустой).
  • owner_address - Cell - ячейка, содержащая адрес текущего владельца NFT (может быть пустой).
  • content - Cell - ячейка, содержащая содержимое элемента NFT (если требуется синтаксический разбор, необходимо обратиться к стандарту TEP-64).

Получение всех NFT в коллекции

Процесс извлечения всех NFT в коллекции зависит от того, упорядочена ли коллекция или нет. Давайте рассмотрим оба процесса ниже.

Упорядоченные коллекции

Извлечь все NFT из упорядоченной коллекции относительно просто, поскольку количество NFT, необходимых для извлечения, уже известно, и их адреса можно легко получить. Чтобы завершить этот процесс, необходимо выполнить следующие шаги по порядку:

  1. Вызовите метод get_collection_data используя TonCenter API в контракте коллекции, и извлеките значение next_item_index из ответа.
  2. Используйте метод get_nft_address_by_index, передав в него значение индекса i (изначально установив его на 0), чтобы получить адрес первого NFT в коллекции.
  3. Используйте полученный в предыдущем шаге адрес для извлечения данных NFT Item. Далее проверьте, что начальный адрес умного контракта NFT Collection совпадает со адресом контракта NFT Collection, о котором сообщил NFT Item(чтобы убедиться, что Collection не присвоил адрес умного контракта NFT другого пользователя).
  4. Вызовите метод get_nft_content, передав в него значение i и individual_content из предыдущего шага.
  5. Увеличьте значение i на 1 и повторите шаги 2-5, пока i не станет равным next_item_index.
  6. На этом этапе у вас будет доступна необходимая информация о коллекции и ее отдельных элементах.

Неупорядоченные коллекции

Получение списка NFT в неупорядоченной коллекции сложнее, так как нет встроенного способа получить адреса NFT, принадлежащих коллекции. Поэтому необходимо разобрать все транзакции в контракте коллекции и проверить все исходящие сообщения, чтобы определить те, которые соответствуют NFT, принадлежащим коллекции.

Для этого необходимо извлечь данные NFT и вызвать метод get_nft_address_by_index в коллекции с ID, возвращенным NFT контрактом. Если адрес NFT контракта и адрес, возвращенный методом get_nft_address_by_index, совпадают, это указывает на то, что NFT принадлежит текущей коллекции. Однако разбор всех сообщений для коллекции может быть длительным процессом и может потребовать доступа к архивному узлу.

Работа с NFT вне сети TON

Отправка NFT

Чтобы передать право собственности на NFT, необходимо отправить внутреннее сообщение из кошелька владельца NFT в контракт NFT, создавая ячейку, содержащую сообщение об передаче. Это можно сделать с помощью библиотек (например, tonweb(js), ton(js), tonutils-go(go)) для конкретного языка.

После создания сообщения об передаче его необходимо отправить на адрес контракта NFT item из кошелька владельца, с достаточным количеством TON, чтобы покрыть соответствующую комиссию за транзакцию.

Чтобы передать NFT от другого пользователя себе, необходимо использовать TON Connect 2.0 или простой QR-код, содержащий ссылку ton://. Например: ton://transfer/{nft_address}?amount={message_value}&bin={base_64_url(transfer_message)}.

Получение NFT

Процесс отслеживания отправленных на определенный адрес умного контракта NFT (то есть кошелек пользователя) аналогичен механизму, используемому для отслеживания платежей. Это выполняется путем прослушивания всех новых транзакций в вашем кошельке и их анализа.

Следующие шаги могут отличаться в зависимости от конкретного случая использования. Давайте рассмотрим несколько различных сценариев ниже.

Сервис ожидает передачи известных адресов NFT:

  • Проверьте новые транзакции, отправленные с адреса умного контракта NFT item.
  • Прочитайте первые 32 бита тела сообщения как тип uint, и убедитесь, что они равны op::ownership_assigned()(0x05138d91)
  • Посчитайте следующие 64 бита из тела сообщения как query_id.
  • Прочитайте адрес из тела сообщения как prev_owner_address.
  • Теперь вы можете управлять своим новым NFT.

Сервис прослушивает все типы передачи NFT:

  • Проверьте все новые транзакции и игнорируйте те, у которых длина тела меньше 363 бит (OP - 32, QueryID - 64, Address - 267).
  • Повторите шаги, описанные в предыдущем списке выше.
  • Если процесс работает правильно, необходимо проверить подлинность NFT путем его анализа и коллекции, к которой он принадлежит. Затем необходимо убедиться, что NFT принадлежит указанной коллекции. Подробнее об этом процессе можно узнать в разделе Получение всех NFT в коллекции. Этот процесс может быть упрощен с помощью белого списка NFT или коллекций.
  • Теперь вы можете управлять своим новым NFT.

Привязка переводов NFT к внутренним транзакциям:

При получении транзакции такого типа необходимо повторить действия из предыдущего списка. После завершения этого процесса можно получить параметр RANDOM_ID, считав uint32 из тела сообщения после чтения значения prev_owner_address.

NFT, отправленные без уведомления:

Все вышеуказанные стратегии основываются на том, что упомянутые службы правильно создают переадресованное сообщение с передачей NFT. Если они этого не делают, мы не узнаем, что они передали нам NFT. Однако есть несколько обходных путей:

Все вышеуказанные стратегии основываются на том, что упомянутые службы правильно создают сообщение о пересылке в рамках передачи NFT. Если этот процесс не выполняется, не будет ясно, была ли передана NFT правильной стороне. Однако есть несколько возможных обходных путей в этом сценарии:

  • Если предполагается небольшое количество NFT, можно периодически их анализировать и проверять, изменился ли владелец на соответствующий тип контракта.
  • Если предполагается большое количество NFT, можно разобрать все новые блоки и проверить наличие вызовов, отправленных в адрес назначения NFT с использованием метода op::transfer. Если такая транзакция инициирована, можно проверить владельца NFT и получить передачу.
  • Если разбор новых блоков в процессе передачи невозможен, пользователи могут сами запустить процесс проверки владения NFT. Таким образом, можно запустить процесс проверки владения NFT после передачи NFT без уведомления.

Взаимодействие с NFT через смарт-контракты

Теперь, когда мы рассмотрели основы отправки и получения NFT, давайте рассмотрим, как получать и передавать NFT из смарт-контрактов с помощью примера контракта NFT Sale.

Отправка NFT

В этом примере сообщение о передаче NFT находится на строке 67:

var nft_msg = begin_cell()
.store_uint(0x18, 6)
.store_slice(nft_address)
.store_coins(0)
.store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) ;; default message headers (see sending messages page)
.store_uint(op::transfer(), 32)
.store_uint(query_id, 64)
.store_slice(sender_address) ;; new_owner_address
.store_slice(sender_address) ;; response_address
.store_int(0, 1) ;; empty custom_payload
.store_coins(0) ;; forward amount to new_owner_address
.store_int(0, 1); ;; empty forward_payload


send_raw_message(nft_msg.end_cell(), 128 + 32);

Давайте рассмотрим каждую строку кода:

  • store_uint(0x18, 6) - хранит флаги сообщения.
  • store_slice(nft_address) - хранит адреса назначения сообщений (адреса NFT).
  • store_coins(0) - количество TON для отправки с сообщением устанавливается равным 0, так как 128 режим сообщения используется для отправки сообщения с оставшимся балансом. Чтобы отправить сумму, отличную от всего баланса пользователя, необходимо изменить число. Обратите внимание, что оно должно быть достаточно большим, чтобы оплатить бензин, а также сумму пересылки.
  • store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) - оставшиеся компоненты заголовка сообщения остаются пустыми.
  • store_uint(op::transfer(), 32) - это начало msg_body. Здесь мы начинаем с использования операции передачи, чтобы получатель понял сообщение о передаче собственности.
  • store_uint(query_id, 64) - хранит query_id
  • store_slice(sender_address) ;; new_owner_address - первый сохраненный адрес используется для передачи NFT и отправки уведомлений.
  • store_slice(sender_address) ;; response_address - второй сохраненный адрес является адресом ответа.
  • store_int(0, 1) - флаг пользовательской полезной нагрузки устанавливается на 0, указывая на то, что пользовательская полезная нагрузка не требуется.
  • store_coins(0) - количество TON, которое будет переслано вместе с сообщением. В данном примере установлено значение 0, однако рекомендуется установить это значение на большую сумму (например, не менее 0,01 TON), чтобы создать сообщение о пересылке и уведомить нового владельца о том, что он получил NFT. Сумма должна быть достаточной для покрытия любых сопутствующих сборов и расходов.
  • .store_int(0, 1) - пользовательский флаг полезной нагрузки. Необходимо установить значение 1, если ваш сервис должен передавать нагрузку как ссылку на данные ref.

Получение NFT

Как только мы отправили NFT, становится крайне важно определить, когда он был получен новым владельцем. Хороший пример того, как это сделать, можно найти в том же умном контракте продажи NFT:

slice cs = in_msg_full.begin_parse();
int flags = cs~load_uint(4);

if (flags & 1) { ;; ignore all bounced messages
return ();
}
slice sender_address = cs~load_msg_addr();
throw_unless(500, equal_slices(sender_address, nft_address));
int op = in_msg_body~load_uint(32);
throw_unless(501, op == op::ownership_assigned());
int query_id = in_msg_body~load_uint(64);
slice prev_owner_address = in_msg_body~load_msg_addr();

Давайте еще раз проанализируем каждую строку кода:

  • slice cs = in_msg_full.begin_parse(); - используется для парсинга входящего сообщения.
  • int flags = cs~load_uint(4); - используется для загрузки флагов из первых 4 битов сообщения.
  • if (flags & 1) { return (); } ;; ignore all bounced messages - используется для проверки того, что сообщение не было отклонено. Важно выполнять этот процесс для всех Ваших входящих сообщений, если нет причин поступать иначе. Отклоненные сообщения - это сообщения, которые столкнулись с ошибками при попытке получить транзакцию и были возвращены отправителю.
  • slice sender_address = cs~load_msg_addr(); - далее загружается адрес отправителя сообщения. В данном случае специально с помощью адреса NFT.
  • throw_unless(500, equal_slices(sender_address, nft_address)); - используется для проверки того, что отправителем действительно является NFT, который должен был быть передан через контракт. Очень сложно разбирать данные NFT из умных контрактов, поэтому в большинстве случаев адрес NFT предопределяется при создании контракта.
  • int op = in_msg_body~load_uint(32); - загружает код операции (OP code) сообщения.
  • throw_unless(501, op == op::ownership_assigned()); - гарантирует, что полученный код операции (OP code) соответствует значению константы присвоение владения.
  • slice prev_owner_address = in_msg_body~load_msg_addr(); - адрес предыдущего владельца, который извлекается из тела входящего сообщения и загружается в переменную prev_owner_address типа slice. Это может быть полезно, если предыдущий владелец решит отменить контракт и вернуть NFT себе.

Теперь, когда мы успешно проанализировали и проверили сообщение уведомления, мы можем перейти к реализации нашей бизнес-логики, которая используется для инициализации умного контракта продажи (который служит для обработки процессов продажи NFT item на аукционах, таких как getgems.io).