
Tutoriel de développement du projet TON (1) : comment créer un NFT sur TON Chain vu à partir du code source ?
TechFlow SélectionTechFlow Sélection

Tutoriel de développement du projet TON (1) : comment créer un NFT sur TON Chain vu à partir du code source ?
Développer des DApp sur l'écosystème TON est vraiment une chose passionnante.
Auteur : @Web3Mario
Résumé : Suite à l'article précédent sur l'introduction technique de TON, j'ai récemment étudié en profondeur la documentation officielle du développement TON. L'apprentissage comporte encore certains seuils d'entrée, car le contenu actuel ressemble davantage à une documentation interne et n'est pas très convivial pour les nouveaux développeurs. J'essaie donc de partager une série d'articles sur le développement de projets TON Chain selon mon propre parcours d'apprentissage, dans l'espoir d'aider chacun à démarrer rapidement avec le développement de DApp sur TON. N'hésitez pas à corriger les erreurs éventuelles — apprenons ensemble.
Quelles sont les différences entre développer un NFT sur EVM et sur TON Chain ?
Émettre un jeton fongible (FT) ou non fongible (NFT) est généralement la demande la plus fondamentale pour un développeur de DApp. C’est pourquoi je choisis cela comme point d’entrée. Commençons par comprendre les différences entre le développement d’un NFT dans la pile technologique EVM et celui sur TON Chain. Pour les NFT basés sur EVM, on choisit habituellement d’hériter de la norme ERC-721. Un NFT désigne un type d’actif cryptographique indivisible, chaque actif étant unique — c’est-à-dire qu’il possède certaines caractéristiques propres. ERC-721 constitue ainsi une méthodologie de développement générique pour ce type d’actifs. Examinons quelles fonctions doivent être implémentées et quelles informations enregistrées dans un contrat ERC-721 typique. L'image ci-dessous montre une interface ERC-721. Contrairement aux FT, l’interface de transfert requiert ici l’identifiant du jeton (tokenId), plutôt que la quantité. Ce tokenId illustre précisément l’unicité fondamentale d’un NFT. Bien sûr, afin de supporter davantage d’attributs, on associe généralement à chaque tokenId un « metadata », sous forme de lien externe contenant des données extensibles telles qu’une URL vers une image PFP ou certains noms d’attributs.

Pour les développeurs familiers avec Solidity ou la programmation orientée objet, implémenter un tel contrat intelligent est chose aisée : il suffit de définir les types de données requis dans le contrat, tels que des mappings clés, puis d’écrire la logique permettant de modifier ces données selon les fonctionnalités souhaitées.
Cependant, sur TON Chain, tout change. Deux raisons principales expliquent ces différences :
l Sur TON, le stockage des données repose sur les Cellules (Cells), et les cellules d’un même compte sont organisées via un graphe orienté acyclique (DAG). Par conséquent, les données devant être persistantes ne peuvent croître indéfiniment, car la profondeur du DAG détermine directement le coût de recherche. Si cette profondeur devient trop grande, le coût de requête peut devenir prohibitif, entraînant potentiellement un blocage du contrat.
l Afin d’atteindre de hautes performances en parallèle, TON a abandonné l’architecture d’exécution séquentielle au profit d’un paradigme spécialement conçu pour le parallélisme : le modèle Acteur (Actor model). Cela signifie que les contrats intelligents ne peuvent s’appeler qu’asynchronement via l’envoi de messages internes. Notez bien que cela vaut aussi bien pour les modifications d’état que pour les lectures. En outre, il faut soigneusement envisager la gestion du retour arrière (rollback) en cas d’échec d’un appel asynchrone.
D’autres différences techniques ont été détaillées dans l’article précédent ; ici, nous nous concentrons uniquement sur le développement de contrats intelligents. Ces deux principes de conception rendent le développement de contrats intelligents sur TON très différent de celui sur EVM. Comme mentionné précédemment, un contrat NFT doit normalement définir certains mappings pour stocker les données associées au NFT. Le plus important est le mapping owners, qui associe un tokenId à l’adresse du propriétaire, déterminant ainsi la propriété du NFT. Un transfert consiste alors à modifier cette propriété. Or, théoriquement, cette structure de données peut croître indéfiniment, ce qu’il faut éviter autant que possible. La documentation officielle recommande donc d’utiliser l’existence ou non de structures de données illimitées comme critère de segmentation. Lorsqu’un besoin similaire se présente, on privilégiera un modèle de contrat maître-esclave, où chaque donnée clé est gérée par un contrat enfant, tandis que le contrat principal supervise les paramètres globaux et facilite les interactions entre les contrats enfants.
Cela implique que les NFT sur TON doivent adopter une architecture similaire : chaque NFT est un contrat enfant indépendant, conservant des données spécifiques telles que l’adresse du propriétaire et les métadonnées, tandis qu’un contrat maître gère les données globales comme le nom, le symbole ou l’offre totale du NFT.
Une fois l’architecture clarifiée, vient la question des fonctionnalités centrales : il faut décider quelles fonctions relèvent du contrat maître, lesquelles du contrat enfant, comment ils communiquent via des messages internes, et comment assurer le rollback en cas d’erreur. Avant de développer un projet complexe, il est essentiel de dessiner un diagramme de classes, de clarifier les flux d’information et de réfléchir attentivement à la logique de rollback en cas d’échec. Même si le cas du NFT est simple, ce genre de vérification reste utile.

Apprendre le développement de contrats intelligents TON à partir du code source
TON a choisi de concevoir un langage statique de type proche du C, appelé Func, comme langage de développement de contrats intelligents. Voyons maintenant, à travers le code source, comment développer un contrat TON. Je m'appuierai sur l'exemple NFT fourni dans la documentation officielle de TON — les curieux peuvent consulter eux-mêmes. Ce cas implémente un exemple simple de NFT TON. Observons la structure du contrat : deux contrats fonctionnels principaux et trois bibliothèques nécessaires.

Ces deux contrats fonctionnels suivent le principe évoqué plus haut. Examinons d’abord le code du contrat maître, nft-collection :

Nous abordons ici notre premier point clé : comment stocker durablement des données dans un contrat intelligent TON. Dans Solidity, le stockage persistant est géré automatiquement par l’EVM selon le type des variables. En général, les variables d’état du contrat sont automatiquement sauvegardées après exécution, sans intervention du développeur. Mais dans Func, ce n’est pas le cas : le développeur doit implémenter lui-même la logique correspondante. C’est un peu comme en C ou C++, où l’on doit gérer manuellement la mémoire (GC), contrairement aux langages modernes qui automatisent ce processus. Regardons le code : après avoir importé les bibliothèques nécessaires, nous trouvons la fonction load_data, utilisée pour lire les données persistantes. Elle commence par récupérer via get_data la cellule contenant les données persistantes — cette fonction fait partie de la bibliothèque standard stdlib.fc, que l’on peut considérer comme une fonction système.
La valeur retournée est de type cell, propre à la TVM. Comme vu précédemment, toutes les données persistantes sur TON Blockchain sont stockées dans des arbres de cellules. Chaque cellule peut contenir jusqu’à 1023 bits de données arbitraires et jusqu’à quatre références vers d'autres cellules. En TVM (machine virtuelle basée sur une pile), les cellules servent de mémoire. Les données y sont stockées sous forme compacte (tight encoding). Pour accéder aux données lisibles, il faut convertir la cellule en un type appelé slice. On utilise la fonction begin_parse pour cette conversion, puis on extrait les données ou références via le slice. Remarquez la syntaxe à la ligne 15 : il s’agit d’un sucre syntaxique en Func permettant d’appeler directement une méthode sur le résultat de la fonction précédente. Enfin, les données sont chargées dans l’ordre de leur persistance. Attention : contrairement à Solidity, cela ne fonctionne pas par hachage, donc l’ordre est crucial.
Dans la fonction save_data, le principe est similaire mais inverse. Cela introduit un nouveau type : builder, le constructeur de cellules. On peut y stocker des données et références, puis finaliser le builder en une nouvelle cellule. On crée d’abord un builder via la fonction standard begin_cell, puis on y insère les données avec les fonctions store appropriées — l’ordre doit correspondre exactement à celui du chargement. Enfin, end_cell finalise la création de la cellule, qui est alors en mémoire. On l’enregistre définitivement via set_data.

Passons maintenant aux fonctions métier. Introduisons un nouveau concept : comment créer un nouveau contrat depuis un contrat existant — une opération fréquente dans l’architecture maître-esclave. Nous savons que sur TON, les contrats s’appellent mutuellement via des messages internes, réalisés par la fonction send_raw_message. Le premier paramètre est une cellule encodant le message, le second est un indicateur (flag) précisant le mode d’exécution. TON définit plusieurs modes et flags pour les messages internes : actuellement 3 modes et 3 flags. Un mode unique peut être combiné avec zéro ou plusieurs flags (leur somme numérique donne le mode final). Le tableau ci-dessous décrit les modes et flags :

Examinons la première fonction principale : deploy_nft_item. Comme son nom l’indique, elle crée (ou frappe) une nouvelle instance NFT. Après avoir encodé un message, elle l’envoie via send_raw_message avec le flag 1, qui limite les frais de gaz à ceux spécifiés dans l’encodage. D’après ce qui précède, on comprend facilement que ce format d’encodage correspond à la création d’un nouveau contrat intelligent.
Voyons comment cela fonctionne concrètement.
Allons directement à la ligne 51. Les deux fonctions précédentes sont des aides pour générer les informations nécessaires au message — nous les verrons plus tard. Ici, on encode un message interne destiné à créer un contrat. Les chiffres intermédiaires sont des indicateurs spécifiant les besoins du message. Introduisons un nouveau concept : TON utilise un langage binaire appelé TL-B pour décrire l’exécution des messages, en activant différents indicateurs pour obtenir des fonctionnalités spécifiques. Deux cas d’usage typiques : création d’un nouveau contrat ou appel de fonction sur un contrat existant. La ligne 51 correspond au premier cas — créer un nouveau contrat nft-item — spécifié par les lignes 55, 56 et 57. La longue chaîne de chiffres à la ligne 55 représente une série d’indicateurs. Rappelons que store_uint prend deux paramètres : la valeur et sa longueur en bits. Parmi eux, les trois derniers bits indiquent que ce message crée un contrat. Leur valeur binaire 111 (soit 7 en décimal = 4+2+1) signifie que : les deux premiers indiquent que le message transporte des données StateInit (le code source du nouveau contrat et ses données d’initialisation), et le troisième indique que le message porte une charge utile — c’est-à-dire qu’il souhaite exécuter une logique avec des paramètres. Vous remarquerez à la ligne 66 que ces trois bits ne sont pas définis, ce qui indique un appel de fonction sur un contrat déjà déployé. Les règles d’encodage sont disponibles ici.
L’encodage de StateInit correspond à la ligne 49, calculé par calculate_nft_item_state_init. L’encodage de StateInit suit également une règle TL-B prédéfinie. Outre les indicateurs, il inclut principalement deux parties : le code du nouveau contrat et ses données d’initialisation. L’ordre d’encodage des données doit correspondre à l’ordre de stockage dans la cellule persistante du nouveau contrat. À la ligne 36, les données d’initialisation sont item_index (analogue au tokenId d’ERC-721) et collection_address, l’adresse du contrat courant obtenue via la fonction standard my_address. Cet ordre correspond à la déclaration dans nft-item.
Un autre point clé : sur TON, on peut pré-calculer l’adresse d’un contrat avant même sa création — similaire à la fonction create2 en Solidity. Sur TON, la nouvelle adresse est composée de deux parties : l’identifiant workchain et le hachage de StateInit. Le premier, comme vu précédemment, est nécessaire pour l’architecture de sharding infini de TON — actuellement fixé à une valeur unique, obtenue via la fonction standard workchain. Le second provient de la fonction standard cell_hash. Ainsi, dans cet exemple, calculate_nft_item_address calcule préalablement l’adresse du nouveau contrat, dont la valeur est encodée à la ligne 53 comme adresse de destination du message. Quant à nft_content, il correspond à l’appel d’initialisation du contrat créé — son implémentation sera détaillée dans un prochain article.
Enfin, send_royalty_params répond à une requête de lecture via un message interne. Comme souligné plus tôt, sur TON, les messages internes concernent aussi bien les opérations modifiant l’état que les lectures. Ce contrat illustre justement ce dernier cas. À noter : le marqueur à la ligne 67 indique la fonction de rappel (callback) à invoquer chez le demandeur. Viennent ensuite les données retournées : l’item index demandé et les données de royalties correspondantes.
Introduisons maintenant un autre concept : sur TON, les contrats intelligents n’ont que deux points d’entrée unifiés, nommés recv_internal et recv_external. Le premier traite tous les messages internes, le second tous les messages externes. Le développeur doit utiliser une structure conditionnelle (comme un switch) à l’intérieur de ces fonctions pour traiter différents messages selon leurs indicateurs. Ces marqueurs correspondent notamment à celui de la ligne 67. Dans cet exemple, on commence par vérifier si le message est vide, puis on analyse ses informations. À la ligne 83, on extrait sender_address, utilisé pour les contrôles d’autorisation. Notez l’opérateur ~, un autre sucre syntaxique que nous n’approfondirons pas ici. Ensuite, on analyse l’opcode, puis on traite chaque requête selon sa valeur. Certaines fonctions présentées plus tôt sont appelées selon la logique — par exemple, répondre à une requête de royalties ou frapper un nouveau NFT tout en incrémentant l’index global.
Un dernier point clé à la ligne 108 : le nom de la fonction laisse deviner sa logique. Similaire à require en Solidity, Func utilise throw_unless pour lever une exception. Le premier paramètre est un code d’erreur, le second un booléen : si faux, une exception est levée avec ce code. Ici, equal_slices vérifie si l’adresse expéditrice extraite correspond à owner_address stocké de façon persistante, assurant ainsi le contrôle d’accès.

Enfin, pour améliorer la clarté du code, plusieurs fonctions d’aide ont été ajoutées afin de récupérer facilement les informations persistantes. Nous n’entrerons pas dans les détails ici, mais les développeurs peuvent s’inspirer de cette structure pour leurs propres contrats.

Développer des DApps dans l’écosystème TON est vraiment passionnant, avec un paradigme très différent de celui d’EVM. Je publierai donc une série d’articles pour guider au développement de DApps sur TON Chain — apprenons ensemble et saisissons cette opportunité. N’hésitez pas à m’interagir sur Twitter pour échanger sur de nouvelles idées de DApps intéressantes, et développons-les ensemble.
Bienvenue dans la communauté officielle TechFlow
Groupe Telegram :https://t.me/TechFlowDaily
Compte Twitter officiel :https://x.com/TechFlowPost
Compte Twitter anglais :https://x.com/BlockFlow_News














