
Hướng dẫn phát triển dự án TON (1): Từ góc nhìn mã nguồn, cách tạo một NFT trên TON Chain như thế nào?
Tuyển chọn TechFlowTuyển chọn TechFlow

Hướng dẫn phát triển dự án TON (1): Từ góc nhìn mã nguồn, cách tạo một NFT trên TON Chain như thế nào?
Việc phát triển DApp trong hệ sinh thái TON thực sự rất thú vị.
Tác giả: @Web3Mario
Tóm tắt: Tiếp nối bài viết trước về giới thiệu công nghệ TON, trong thời gian gần đây mình đã nghiên cứu sâu vào tài liệu phát triển chính thức của TON và cảm thấy việc học vẫn còn một số rào cản. Dường như nội dung tài liệu hiện tại giống hơn với một tài liệu phát triển nội bộ, chưa thực sự thân thiện với các nhà phát triển mới bắt đầu. Vì vậy, mình sẽ cố gắng hệ thống hóa một loạt bài viết về phát triển dự án trên TON Chain theo lộ trình học tập của bản thân, hy vọng có thể giúp mọi người nhanh chóng tiếp cận phát triển DApp trên TON. Nếu trong quá trình viết có sai sót, rất mong được mọi người góp ý để cùng nhau học hỏi.
Phát triển NFT trên EVM khác gì so với phát triển NFT trên TON Chain?
Việc phát hành một FT hoặc NFT thường là nhu cầu cơ bản nhất đối với các nhà phát triển DApp. Do đó, mình cũng lấy đây làm điểm khởi đầu cho việc học tập. Trước tiên hãy cùng tìm hiểu sự khác biệt giữa việc phát triển NFT trên nền tảng EVM và trên TON Chain. Các NFT dựa trên EVM thường kế thừa tiêu chuẩn ERC-721. NFT (Non-Fungible Token) là loại tài sản mã hóa không thể chia nhỏ, mỗi tài sản mang tính duy nhất – tức là sở hữu những đặc điểm riêng biệt. ERC-721 chính là khuôn mẫu phát triển phổ biến cho loại tài sản này. Hãy xem một hợp đồng ERC721 điển hình cần triển khai những hàm nào và lưu trữ thông tin gì. Hình dưới đây minh họa giao diện ERC721. Khác với FT, trong giao diện chuyển tiền, thay vì nhập số lượng, ta phải nhập tokenId cần chuyển. Chính tokenId này thể hiện rõ nhất tính duy nhất của tài sản NFT. Tất nhiên, để chứa nhiều thuộc tính hơn, người ta thường gán cho mỗi tokenId một metadata – là một liên kết bên ngoài, lưu trữ dữ liệu mở rộng khác của NFT như đường dẫn ảnh PFP, tên thuộc tính, v.v.

Đối với các lập trình viên quen thuộc với Solidity hoặc lập trình hướng đối tượng, việc triển khai một hợp đồng thông minh như vậy khá đơn giản: chỉ cần định nghĩa kiểu dữ liệu cần thiết trong hợp đồng, ví dụ một vài ánh xạ (mapping) quan trọng, sau đó xây dựng logic xử lý phù hợp để thay đổi dữ liệu này, là đã có thể tạo ra một NFT.
Tuy nhiên, trên TON Chain, mọi thứ lại khác biệt đáng kể. Có hai nguyên nhân cốt lõi dẫn đến sự khác biệt này:
l Trên TON, dữ liệu được lưu trữ dựa trên Cell, và các Cell thuộc cùng một tài khoản được liên kết với nhau qua đồ thị có hướng không chu trình (DAG). Điều này dẫn đến việc dữ liệu cần lưu trữ lâu dài không thể tăng trưởng vô hạn, bởi chiều sâu của DAG quyết định chi phí truy vấn. Khi chiều sâu kéo dài quá mức, chi phí truy vấn có thể trở nên quá cao, gây ra nguy cơ hợp đồng bị khóa chết.
l Để theo đuổi hiệu năng xử lý song song cao, TON từ bỏ kiến trúc thực thi tuần tự, thay vào đó sử dụng mô hình Actor – một khuôn mẫu phát triển được thiết kế riêng cho xử lý song song – để tái cấu trúc môi trường thực thi. Hệ quả là các hợp đồng thông minh chỉ có thể gọi lẫn nhau một cách bất đồng bộ thông qua việc gửi "tin nhắn nội bộ". Lưu ý rằng cả thao tác sửa trạng thái lẫn thao tác chỉ đọc đều phải tuân thủ nguyên tắc này. Ngoài ra, cũng cần cân nhắc kỹ cách xử lý hoàn tác dữ liệu khi lời gọi bất đồng bộ thất bại.
Tất nhiên, các điểm khác biệt kỹ thuật khác đã được trình bày chi tiết trong bài viết trước. Bài viết này tập trung chủ yếu vào phát triển hợp đồng thông minh nên sẽ không đi sâu. Hai nguyên tắc thiết kế trên khiến việc phát triển hợp đồng thông minh trên TON khác biệt lớn so với EVM. Như đã nói ở phần đầu, một hợp đồng NFT cần định nghĩa một số ánh xạ (mapping) để lưu trữ dữ liệu liên quan đến NFT. Quan trọng nhất là ánh xạ owners – lưu địa chỉ sở hữu tương ứng với từng tokenId, xác định quyền sở hữu NFT; việc chuyển nhượng chính là thay đổi quyền sở hữu này. Vì lý thuyết mà nói đây là cấu trúc dữ liệu có thể mở rộng vô hạn, nên cần tránh tối đa. Do đó, tài liệu chính thức khuyến nghị dùng việc tồn tại cấu trúc dữ liệu vô hạn làm tiêu chí phân mảnh – tức là khi có nhu cầu lưu trữ dữ liệu dạng này, nên thay thế bằng khuôn mẫu hợp đồng chính - phụ, tạo các hợp đồng con để quản lý dữ liệu tương ứng với mỗi khóa. Hợp đồng chính sẽ quản lý các tham số toàn cục hoặc hỗ trợ trao đổi tin nhắn nội bộ giữa các hợp đồng con.
Điều này có nghĩa là NFT trên TON cũng cần được thiết kế theo kiến trúc tương tự: mỗi NFT là một hợp đồng con độc lập, lưu trữ các dữ liệu riêng biệt như địa chỉ chủ sở hữu, metadata,... Trong khi đó, một hợp đồng chính sẽ quản lý dữ liệu toàn cục như tên NFT, ký hiệu (symbol), tổng lượng cung cấp, v.v.
Sau khi xác định kiến trúc, bước tiếp theo là giải quyết các chức năng cốt lõi. Do áp dụng mô hình hợp đồng chính-phụ,
chúng ta cần xác định rõ chức năng nào do hợp đồng chính đảm nhiệm, chức năng nào do hợp đồng con đảm nhiệm, cách thức giao tiếp nội bộ giữa chúng, và cách hoàn tác dữ liệu khi xảy ra lỗi thực thi. Thông thường, trước khi phát triển một dự án phức tạp quy mô lớn, việc vẽ sơ đồ lớp (class diagram), làm rõ luồng thông tin giữa các thành phần, và suy nghĩ kỹ lưỡng về logic hoàn tác khi lời gọi nội bộ thất bại là điều cần thiết. Dù ví dụ NFT ở đây đơn giản, nhưng vẫn nên áp dụng cách kiểm chứng tương tự.

Học phát triển hợp đồng thông minh TON qua mã nguồn
TON chọn phát triển một ngôn ngữ tĩnh kiểu, giống C, tên là Func, để làm ngôn ngữ phát triển hợp đồng thông minh. Bây giờ hãy cùng học cách phát triển hợp đồng thông minh TON qua mã nguồn. Mình chọn ví dụ NFT trong tài liệu chính thức của TON để minh họa – các bạn quan tâm có thể tự tìm hiểu thêm. Ví dụ này triển khai một NFT TON đơn giản. Hãy xem cấu trúc hợp đồng: gồm hai hợp đồng chức năng và ba thư viện cần thiết.

Hai hợp đồng chức năng chính này được thiết kế theo nguyên tắc đã nêu. Trước tiên hãy xem mã nguồn của hợp đồng chính nft-collection:

Đây là điểm kiến thức đầu tiên: cách lưu trữ dữ liệu bền vững trong hợp đồng thông minh TON. Chúng ta biết rằng trong Solidity, việc lưu trữ dữ liệu bền vững được EVM xử lý tự động dựa trên kiểu tham số. Thông thường, các biến trạng thái của hợp đồng sẽ được tự động lưu trữ sau khi thực thi xong, lập trình viên không cần lo lắng về quá trình này. Nhưng trong Func thì khác – lập trình viên phải tự triển khai logic xử lý tương ứng. Tình huống này tương tự như việc C/C++ cần lập trình viên xử lý bộ dọn rác (GC), trong khi các ngôn ngữ lập trình mới thường tự động hóa phần này. Xét mã nguồn: trước tiên đưa vào một số thư viện cần thiết, sau đó xét hàm đầu tiên load_data – dùng để đọc dữ liệu đã lưu trữ bền vững. Logic là dùng get_data trả về cell lưu trữ dữ liệu bền vững của hợp đồng. Lưu ý hàm này do thư viện chuẩn stdlib.fc cung cấp, thường có thể coi một số hàm trong đây như hàm hệ thống.
Hàm này trả về kiểu cell – chính là kiểu cell trong TVM. Như đã giới thiệu trước đó, tất cả dữ liệu bền vững trên blockchain TON đều được lưu trong cây cell. Mỗi cell chứa tối đa 1023 bit dữ liệu tùy ý và tối đa bốn tham chiếu tới các cell khác. Cell đóng vai trò bộ nhớ trong TVM dựa trên ngăn xếp. Dữ liệu trong cell được mã hóa chặt (tightly encoded), muốn lấy dữ liệu rõ cụ thể cần chuyển cell thành kiểu gọi là slice. Cell có thể chuyển thành slice bằng hàm begin_parse, sau đó có thể lấy dữ liệu bit và tham chiếu cell từ slice. Lưu ý phương pháp gọi ở dòng 15 là một cú pháp rút gọn trong func – cho phép gọi trực tiếp hàm thứ hai trên giá trị trả về của hàm thứ nhất. Cuối cùng lần lượt tải dữ liệu theo thứ tự lưu trữ bền vững. Lưu ý quá trình này khác Solidity – không dùng ánh xạ theo hash, do đó thứ tự gọi không được đảo lộn.
Trong hàm save_data, logic tương tự nhưng theo chiều ngược lại. Đây là điểm kiến thức tiếp theo: một kiểu dữ liệu mới – builder (bộ xây dựng), là kiểu bộ tạo cell. Các bit dữ liệu và tham chiếu tới cell khác có thể được lưu vào builder, sau đó builder có thể được đóng gói thành một cell mới. Đầu tiên dùng hàm chuẩn begin_cell tạo một builder, rồi lần lượt dùng các hàm store để lưu dữ liệu, lưu ý thứ tự gọi ở đây phải khớp với thứ tự trong phần trên. Cuối cùng dùng end_cell để hoàn tất việc tạo cell mới – lúc này cell nằm trong bộ nhớ, sau đó dùng set_data ở ngoài cùng để hoàn thành việc lưu trữ bền vững cell này.

Tiếp theo, xét các hàm liên quan nghiệp vụ. Trước tiên cần giới thiệu điểm kiến thức mới: cách tạo một hợp đồng mới từ một hợp đồng – điều này sẽ được dùng thường xuyên trong kiến trúc chính-phụ vừa nêu. Chúng ta biết rằng trên TON, việc gọi giữa các hợp đồng thông minh được thực hiện bằng cách gửi tin nhắn nội bộ. Việc này được thực hiện qua hàm send_raw_message – tham số đầu tiên là cell đã mã hóa tin nhắn, tham số thứ hai là cờ đánh dấu, dùng để phân biệt cách thức thực thi giao dịch. Trên TON có nhiều cách gửi tin nhắn nội bộ khác nhau, hiện có 3 loại Modes và 3 loại Flags. Một Mode đơn lẻ có thể kết hợp với nhiều (hoặc không) cờ để đạt được mode mong muốn. Việc kết hợp chỉ đơn thuần là cộng giá trị các thành phần rồi điền vào.Bảng mô tả Modes và Flags như sau:

Bây giờ hãy xem hàm chính đầu tiên: deploy_nft_item – như tên gọi, đây là hàm dùng để tạo hoặc đúc một instance NFT mới. Sau một loạt thao tác mã hóa tin nhắn, dùng send_raw_message gửi tin nhắn nội bộ này, đồng thời chọn cờ 1 làm cờ gửi – chỉ lấy phí được chỉ định trong mã hóa làm gas fee cho lần thực thi này. Từ phần giới thiệu trước, ta dễ dàng nhận ra quy tắc mã hóa này ứng với cách tạo một hợp đồng thông minh mới.
Vậy hãy xem nó được triển khai cụ thể như thế nào.
Hãy trực tiếp xem dòng 51. Hai hàm phía trên là các hàm trợ giúp để tạo thông tin cần thiết cho tin nhắn, nên ta sẽ xem sau. Đây là quá trình mã hóa tin nhắn nội bộ để tạo hợp đồng thông minh mới. Những con số ở giữa thực chất cũng là các cờ đánh dấu, dùng để mô tả yêu cầu của tin nhắn nội bộ. Điểm kiến thức tiếp theo cần giới thiệu: TON chọn dùng một ngôn ngữ nhị phân tên là TL-B để mô tả cách thức thực thi tin nhắn, và thiết lập các cờ đánh dấu khác nhau để tạo các tin nhắn nội bộ có chức năng cụ thể. Hai trường hợp dùng phổ biến nhất là: tạo hợp đồng mới và gọi hàm trên hợp đồng đã triển khai. Cách viết ở dòng 51 ứng với trường hợp đầu – tạo một hợp đồng nft item mới, chủ yếu được chỉ định qua ba dòng 55, 56, 57. Dãy số dài ở dòng 55 là một loạt cờ đánh dấu, lưu ý store_uint có tham số đầu là giá trị, tham số thứ hai là độ dài bit. Ba cờ cuối quyết định tin nhắn nội bộ này là tạo hợp đồng, với giá trị nhị phân tương ứng là 111 (tức 4+2+1 trong thập phân). Hai cờ đầu cho biết tin nhắn sẽ kèm theo dữ liệu StateInit – chính là mã nguồn của hợp đồng mới và dữ liệu khởi tạo cần thiết. Cờ cuối cho biết tin nhắn có tải nội bộ – tức mong muốn thực thi logic liên quan và các tham số cần thiết. Vì vậy bạn sẽ thấy ở dòng 66 không đặt ba cờ này, cho thấy đây là một lời gọi hàm trên hợp đồng đã triển khai.Quy tắc mã hóa cụ thể xem tại đây.
Quy tắc mã hóa StateInit tương ứng với dòng 49, được tính qua calculate_nft_item_state_init. Lưu ý việc mã hóa dữ liệu stateinit cũng tuân theo quy tắc TL-B định sẵn. Ngoài các cờ đánh dấu, chủ yếu gồm hai phần: code của hợp đồng mới và dữ liệu khởi tạo (data). Thứ tự mã hóa data phải khớp với thứ tự lưu trữ cell bền vững được chỉ định trong hợp đồng mới. Dòng 36 cho thấy dữ liệu khởi tạo gồm item_index – tương tự tokenId trong ERC721 – và collection_address lấy từ hàm chuẩn my_address trả về địa chỉ hợp đồng hiện tại. Thứ tự này khớp với khai báo trong nft-item.
Điểm kiến thức tiếp theo: trên TON, tất cả các hợp đồng thông minh chưa được tạo đều có thể tính trước địa chỉ sau khi tạo – tương tự hàm create2 trong Solidity. Trên TON, địa chỉ mới được tạo từ hai phần: nối workchain ID với giá trị băm của stateinit. Phần đầu – như đã biết từ phần giới thiệu trước – là để phục vụ kiến trúc phân mảnh vô hạn của TON, hiện tại là giá trị cố định, lấy bằng hàm chuẩn workchain. Phần sau lấy bằng hàm chuẩn cell_hash. Quay lại ví dụ này, calculate_nft_item_address chính là hàm tính trước địa chỉ hợp đồng mới, giá trị tạo ra được mã hóa vào tin nhắn ở dòng 53, làm địa chỉ nhận của tin nhắn nội bộ này. Còn nft_content tương ứng với lời gọi khởi tạo trên hợp đồng được tạo – chi tiết triển khai sẽ trình bày trong bài viết tiếp theo.
Còn send_royalty_params là phản hồi cho một yêu cầu chỉ đọc qua tin nhắn nội bộ. Như đã nhấn mạnh trước đó, trên TON tin nhắn nội bộ không chỉ dùng cho thao tác sửa dữ liệu mà cả thao tác chỉ đọc cũng phải dùng cách này. Vì vậy, hợp đồng này phục vụ loại thao tác như vậy. Trước hết, dòng 67 biểu thị cờ đánh dấu cho hàm callback gửi lại cho người yêu cầu sau khi phản hồi, tiếp theo là dữ liệu trả về: item index được yêu cầu và dữ liệu royalty tương ứng.
Tiếp theo, điểm kiến thức mới: hợp đồng thông minh trên TON chỉ có hai cổng vào thống nhất, tên là recv_internal
và recv_external. Trong đó, recv_internal là cổng vào thống nhất cho tất cả tin nhắn nội bộ, recv_external là cổng vào cho tất cả tin nhắn bên ngoài. Lập trình viên cần bên trong hàm dùng cách tương tự switch để dựa vào các cờ đánh dấu khác nhau trong tin nhắn mà phản hồi các yêu cầu khác nhau. Các cờ đánh dấu này chính là cờ callback ở dòng 67 nêu trên. Trở lại ví dụ này, trước tiên kiểm tra tin nhắn có rỗng hay không, nếu hợp lệ thì lần lượt giải mã thông tin trong tin nhắn. Dòng 83 giải mã để lấy sender_address – tham số này dùng cho kiểm tra quyền hạn sau này. Lưu ý toán tử ~ ở đây là một cú pháp rút gọn nữa – tạm thời không đi sâu. Tiếp theo giải mã op – cờ thao tác, rồi dựa vào các cờ khác nhau để xử lý các yêu cầu tương ứng. Trong đó, dựa vào một số logic nhất định mà lần lượt gọi các hàm đã nêu, ví dụ phản hồi yêu cầu tham số royalty, hoặc đúc NFT mới và tự tăng index toàn cục.
Điểm kiến thức tiếp theo ứng với dòng 108 – chắc hẳn mọi người qua tên gọi cũng đoán được logic xử lý. Tương tự hàm require trong Solidity, trong Func dùng hàm chuẩn throw_unless để ném ngoại lệ – tham số đầu là mã lỗi, tham số thứ hai là giá trị boolean kiểm tra, nếu false thì ném ngoại lệ kèm mã lỗi đó. Ở dòng này, dùng equal_slices để kiểm tra xem sender_address đã giải mã ở trên có bằng owner_address được lưu trữ bền vững trong hợp đồng hay không – nhằm thực hiện kiểm tra quyền hạn.

Cuối cùng, để cấu trúc mã rõ ràng hơn, tác giả định nghĩa một loạt hàm trợ giúp để lấy thông tin lưu trữ bền vững – sẽ không trình bày chi tiết ở đây. Các lập trình viên có thể tham khảo cấu trúc này để phát triển hợp đồng thông minh của riêng mình.

Phát triển DApp trong hệ sinh thái TON thực sự là một trải nghiệm thú vị, rất khác biệt so với khuôn mẫu phát triển EVM. Vì vậy mình sẽ viết một loạt bài để giới thiệu cách phát triển DApp trên TON Chain, cùng mọi người học tập và nắm bắt cơ hội này. Rất mong mọi người tương tác với mình trên Twitter, trao đổi những ý tưởng DApp mới lạ thú vị, cùng nhau phát triển.
Chào mừng tham gia cộng đồng chính thức TechFlow
Nhóm Telegram:https://t.me/TechFlowDaily
Tài khoản Twitter chính thức:https://x.com/TechFlowPost
Tài khoản Twitter tiếng Anh:https://x.com/BlockFlow_News














