本帖最后由 riyad 于 2025-3-15 05:47 编辑
% X+ N/ A, y& l; p* m2 c$ n d" m5 n$ K
这里还有两种类型的合约:collections合约和 NFT item智能合约。collections合约就是部署 NFT item智能合约。这里有一个很好的例子,如果你想发布一个包含 10,000 个item的集合,你将部署 10,001 个智能合约。您将自行部署。 在item智能合约中,所有者将不断变化。此外,collections的所有者也可以更改。你需要理解这个逻辑。我们要审查的第一个合约是 NFT collections,然后审查 NFT item智能合约。再次强调,这不仅仅是一个合约,这是一个智能合约系统。 NFT collection 智能合约和其他合约一样,都是从读取存储的封装逻辑开始的: - (slice, int, cell, cell, cell) load_data() inline {
/ U+ s/ L. C a y - var ds = get_data().begin_parse();
' n( N8 K' Y& ]7 m - return 0 }/ V' y# J1 k, H x4 _! `. j
- (ds~load_msg_addr(), ;; owner_address
- c# D# \' v; |2 g" J k) o - ds~load_uint(64), ;; next_item_index
% A- c; U: [* ]7 e% { - ds~load_ref(), ;; content
; P8 U' X$ a. b' T1 X, k - ds~load_ref(), ;; nft_item_code
, r, m0 X4 l, g; g- i0 c/ u% E* F - ds~load_ref() ;; royalty_params5 q8 v* P2 c. t
- );. `" @( L' l! w3 P
- }
复制代码 在这里,我们有 owner_address, next_item_index, 我们有 content. 同样,这 content 是根据元数据标准制作的cell,在同一标准中也有提供 repository 我们已经复习过了。那么我们有 nft_item_code 的cell royalty_params. 我们很快就会了解到更多。- () save_data(slice owner_address, int next_item_index, cell content, cell nft_item_code, cell royalty_params) impure inline {
0 x% S: x! V+ ~! N z - set_data(begin_cell()$ E/ f+ T( [- z% y4 d9 {
- .store_slice(owner_address); R& _7 L/ [) I: d0 d8 T$ ^: X
- .store_uint(next_item_index, 64)3 H; l& x' y( z3 ^5 w0 w* |: O9 t5 O
- .store_ref(content)
K) b& s; p F - .store_ref(nft_item_code)7 u5 e- @. `5 }9 P$ c6 s8 v
- .store_ref(royalty_params)
4 ?0 ~$ |1 m8 ]' O% c% c" d - .end_cell());
P& Y9 u1 J; }- f8 J. Q - }$ D0 i1 ~, z, `9 z
- * m0 j& J2 ^; L" V( K6 p
- cell calculate_nft_item_state_init(int item_index, cell nft_item_code) {5 \; I( t n4 h L7 [* L4 F& A
- cell data = begin_cell().store_uint(item_index, 64).store_slice(my_address()).end_cell();6 f j# r9 L0 J* K V* {
- return begin_cell().store_uint(0, 2).store_dict(nft_item_code).store_dict(data).store_uint(0, 1).end_cell();
/ t5 O1 b/ Y7 l4 X - }9 ?; r& a; U- Z
- - v0 H; W2 ~) {2 x7 f/ t
- slice calculate_nft_item_address(int wc, cell state_init) {
! a1 l2 `" p( y: b - return begin_cell().store_uint(4, 3)
6 {* \. h1 D+ `1 Z8 c3 K# B - .store_int(wc, 8)
6 ^' W; Z0 o- i4 o - .store_uint(cell_hash(state_init), 256)
* ]/ j% h3 j" N: t+ u2 j - .end_cell(). Q, l9 z& s' r9 G7 C, v: C z
- .begin_parse();
5 \5 f2 O' G5 O# c - }
复制代码就像 load_data, 我们还有 save_data 来重写本地存储的cell。在上一个使用 Jettons 的示例中,我们实际上看到了这些 calculate_nft_item_state_init 和 calculate_nft_item_address 函数封装在不同的文件中。但在这里,我们将它们放在了这个标准实现中的同一个文件中。这是因为我们在 NFT item合约中并没有真正使用这些函数,所以这就是我们不必将其保存在单独文件中的原因。大家还记得,在 Jettons 中,我们在矿工合约和钱包合约中都使用了这些函数。但在这里,我们把它们都放在一个文件中。 您可能不需要对 calculate_nft_item_state_init 进行更多的解释,因为你在Jettons的课程中学习了一个非常复杂的函数。这与 calculate_nft_item_address 相似,因为你们已经经历过这些。我们还传递我们熟悉的 load_data 和 save_data . 接下来我们看到 deploy_nft_item 和 send_royalty_params 函数: - () deploy_nft_item(int item_index, cell nft_item_code, int amount, cell nft_content) impure {
( A. @1 }& Z/ z6 D+ S* N& ?' @ - cell state_init = calculate_nft_item_state_init(item_index, nft_item_code);. |/ A6 R' o$ U8 q) F+ K
- slice nft_address = calculate_nft_item_address(workchain(), state_init);
( [6 @4 [4 t; e; K - var msg = begin_cell()
. Z x7 z ]; Z" f - .store_uint(0x18, 6)( _( D2 @, Q- t! k
- .store_slice(nft_address)
+ q- ]0 B2 D7 w0 l - .store_coins(amount)
' G) P( I/ l1 c4 N7 u E( G - .store_uint(4 + 2 + 1, 1 + 4 + 4 + 64 + 32 + 1 + 1 + 1)9 e. M* g2 t, P$ h. |" h! @
- .store_ref(state_init): p, U: j1 |: c$ e# D
- .store_ref(nft_content);
( O& z& [1 M5 {0 t" [; F( G - send_raw_message(msg.end_cell(), 1); ;; pay transfer fees separately, revert on errors
7 b$ e H4 U! v - }0 S. ? I3 c8 r5 U5 S
# q* `: H7 Y* V9 }1 M/ `3 Y: z- () send_royalty_params(slice to_address, int query_id, slice data) impure inline {
3 |% d$ u' h4 k& X, S, T) a Q - var msg = begin_cell() d7 `6 H* K# w& m8 I' F
- .store_uint(0x10, 6) ;; nobounce - int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 0110008 M, w7 Q$ g1 ]# i) Q! J
- .store_slice(to_address)
w$ k6 ~; W& P6 @. ^7 W9 b" Q - .store_coins(0)! _( H4 C( A" q+ Y6 D
- .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1)
+ Q9 t3 |! }# @6 B" ~ - .store_uint(op::report_royalty_params(), 32)
# I" o) _7 n' G8 O0 _7 x6 L" k f% R - .store_uint(query_id, 64)
; R( O( i. W W/ F# D9 s- C - .store_slice(data);4 W7 R) X$ {! t3 Z- B; A c
- send_raw_message(msg.end_cell(), 64); ;; carry all the remaining value of the inbound message
' W, X$ t! o6 |* n - }
复制代码这一切都要按照标准的要求进行。由于您必须遵守标准,因此您的合约将被视为 NFT 合约。
. k, T' U% e$ M+ u# d+ R- () recv_internal(cell in_msg_full, slice in_msg_body) impure {! r6 d* r* t3 r' P, [8 w3 s# k( c
- if (in_msg_body.slice_empty?()) { ;; ignore empty messages
) Q5 z1 p# \, v, F' ~ - return ();
- R" D) \- E+ M8 C4 B8 t - }8 [& Y1 b& `1 Z& K
- slice cs = in_msg_full.begin_parse();' A* k& o' ~0 e: r; J
- int flags = cs~load_uint(4);
) B x& r6 C5 e8 ^ - : x B! v' A6 U) f1 ?
- 7 o3 e" B, M! L+ ^) F
- if (flags & 1) { ;; ignore all bounced messages3 Y0 A6 y4 t8 [( I5 q
- return ();. D2 n+ u- W5 e2 X r; o
- }
6 Q9 m( [! g( h3 k. [: a# d" [ - slice sender_address = cs~load_msg_addr();
! r0 C6 n- Z B) i2 L - 5 ~* X" i! n& p
- int op = in_msg_body~load_uint(32);0 N6 |! e, ^# y' ~3 {" r
- int query_id = in_msg_body~load_uint(64);9 T* T5 E/ K+ W% k! H3 ~
- V% H2 U' W2 J5 K# v& H& |
- var (owner_address, next_item_index, content, nft_item_code, royalty_params) = load_data();
复制代码 我们接收到一条内部信息,我们发现我们不想处理空的正文信息,因此我们要检查它是否为空。然后我们检查标记,忽略跳转信息,得到 sender_address, 阅读 in_msg_body, 运行代码、 query_id, 然后是本地存储中的所有内容。
- W4 `4 c8 _7 `5 G! O' C- if (op == op::get_royalty_params()) {& U1 r. a5 Y* b8 L
- send_royalty_params(sender_address, query_id, royalty_params.begin_parse());
% m3 X5 t4 ~1 y9 Y/ f) O - return ();1 f8 S4 A6 Z4 x4 C) z$ A
- }
1 I/ ^" x2 m8 e' ]$ x
4 H- ^. ]6 B3 s$ F9 F9 ~- throw_unless(401, equal_slices(sender_address, owner_address));
复制代码 如果操作码为 get_royalty_params, 那我们将发送版税参数。如你所见,我们从本地存储读取版税参数,并将版税参数传递给 begin_parse. royalty_params 是一个Cell,我们要传递一个片段,这样它就能被立即读取或写入我们要发送的信息中。- () send_royalty_params(slice to_address, int query_id, slice data) impure inline {, X! ?3 S& x. Z
- var msg = begin_cell()
3 L$ E) c- N$ H# p - .store_uint(0x10, 6) ;; nobounce - int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 011000/ @2 t) a8 m0 X% o% y
- .store_slice(to_address)
- U" J" m2 |# g7 A - .store_coins(0)
/ W" P" r& i5 x+ ~; U" c+ Q% ~ - .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1)
1 L% |; Z; ]: O2 A# ] - .store_uint(op::report_royalty_params(), 32)# ^0 Y, m8 Y% E8 z& a+ [
- .store_uint(query_id, 64)
8 s0 M1 F# d+ X9 r% f - .store_slice(data);
! `( E4 B5 x! u+ H$ c- U4 ? - send_raw_message(msg.end_cell(), 64); ;; carry all the remaining value of the inbound message! j# ]; I, {' x. W8 G+ S4 B
- }
复制代码 我们看看 send_royalty_params 已经实现。我们得到 to_address,我们得到 query_id, 和 slice_data, 这个片段是 royalty_params. 我们不想让这条信息跳转,因此将快捷标志改为 0x10. 这一切你们都很熟悉。我们将其发送给 to_address, 我们将操作代码、 query_id, 当然,我们把这些 royalty_params. 就是这么简单。你已经知道它是如何工作的了。对于版税参数,我们甚至不检查是谁要求的。接下来呢?- if (op == 1) { ;; deploy new nft$ R' m) `7 q+ ?+ h
- int item_index = in_msg_body~load_uint(64);
& q H3 X% {: T# H# p - throw_unless(402, item_index <= next_item_index);
7 X# i' a. E+ S F. w# c - var is_last = item_index == next_item_index; 0 y, t' s, g4 Q& Q: ?# ]2 S
- deploy_nft_item(item_index, nft_item_code, in_msg_body~load_coins(), in_msg_body~load_ref());
0 C. V* {% M$ M9 h6 ?! m+ ` - if (is_last) {
8 D, Q1 r. o. n* w& ^- A( ^ - next_item_index += 1;
1 Q) K' U3 e) `# t+ y' R - save_data(owner_address, next_item_index, content, nft_item_code, royalty_params);
9 Y6 G5 `0 w: S- H7 a0 S% h( g+ P - }
5 }5 [' b* M1 R/ D - return ();: k/ a4 p; L% Q0 r* S& F! @+ q
- }
复制代码 我们看到这个操作代码,这意味着我们要部署一个新的 NFT。这只能由所有者完成。这里最重要的是索引。您需要跟踪当前 Collections 的索引。例如 item_index 您要在这里部署的 next_item_index 存储在我们的数据库中。如果它们相等,我们实际上会增加 next_item_index 并将其再次放入存储中,这样我们的索引就会增加。你可以部署先前的索引 NFT,也可以部署当前的索引 NFT,然后增加 next_item_index.- () deploy_nft_item(int item_index, cell nft_item_code, int amount, cell nft_content) impure {9 K, z R, Q) i6 Y2 C3 f
- cell state_init = calculate_nft_item_state_init(item_index, nft_item_code);
8 D: f1 J; J5 J3 Q8 n( A' P# a+ l - slice nft_address = calculate_nft_item_address(workchain(), state_init);
# j) G% g7 o7 Y' Q& y - var msg = begin_cell()% G! Y! V( V: F& I
- .store_uint(0x18, 6)4 k. X" H- M- |# c1 p
- .store_slice(nft_address)- [ M0 Y9 ~ \* x+ r& D
- .store_coins(amount)
r5 h, J( s3 @* p8 i - .store_uint(4 + 2 + 1, 1 + 4 + 4 + 64 + 32 + 1 + 1 + 1)7 n/ _: l, V9 ?& q( }2 |3 T
- .store_ref(state_init)
* p1 L6 r: E8 t8 M) U; a1 a2 i - .store_ref(nft_content);, }; M" Y0 c4 v8 b9 w1 c
- send_raw_message(msg.end_cell(), 1); ;; pay transfer fees separately, revert on errors- l8 I; f. M6 n( v- {" u: X5 ?
- }
复制代码现在来看看这里是如何实现部署功能的。 deploy_nft_item 部署合约,这就是为什么我们在这里计算 state_init 的原因。我们正在与 state_init 一起计算地址。 此外,我们正在发送一条消息,其中包含我们放置在此处的一定金额。 让我们看看如何在部署时获得这笔金额。在 msg_body, 根据标准,您将看到您想要传递的代币数量,因为我们将其设置为 amount. 因此,我们用 state_init 来编写这条信息,然后发送并部署此合约。我们将在下一课回顾 NFT item合约时了解本地存储方案。部署 NFT item比我们使用 Jettons 时要简单得多,但工作方式类似。 - if (op == 2) { ;; batch deploy of new nfts
9 \( S# M* X5 p6 g; K. v$ {& J/ k1 j3 ] - int counter = 0;7 V/ J/ H, Y t' D- w" |; [" j/ l
- cell deploy_list = in_msg_body~load_ref();
& U- u) [; o% N( @ - do {/ A) G& P2 c6 t! G: i) K; }
- var (item_index, item, f?) = deploy_list~udict::delete_get_min(64);3 g5 _0 d1 E0 F/ n
- if (f?) {
, C2 A6 S% m: r2 \* q, ^9 w - counter += 1;
6 y: ?/ I" R$ i& H0 C& w4 v - if (counter >= 250) { ;; Limit due to limits of action list size
! B4 t7 C& o* Q0 N1 o - throw(399);) m: Y! U0 S/ s3 C( [' h! J9 F
- }- e4 D- e# S/ P; O. P$ @: K2 T3 G
- * y/ |" t- S2 s
- throw_unless(403 + counter, item_index <= next_item_index);
6 j3 ]# G+ r9 t. j2 G - deploy_nft_item(item_index, nft_item_code, item~load_coins(), item~load_ref()); m) F) s1 b; B8 N+ ?' M6 K; W
- if (item_index == next_item_index) {
$ g" {/ X8 g' D) L% S* B - next_item_index += 1;
$ ?' R7 P' `, Z: V% |9 V- u - }1 P; F& I: P1 s% b" T. |" C
- }
: ]" h& L" y6 e! ?: c" s - } until ( ~ f?);
; |8 o* f' o- z* E0 m - save_data(owner_address, next_item_index, content, nft_item_code, royalty_params);3 q& `2 l1 Q) e1 P9 W$ f
- return ();3 A' Y0 ^4 X# b8 _+ v9 `$ B X; `9 S+ p
- }0 U4 M8 H! y2 q' H2 M) l) |! N
- ! d3 `' i* F) \2 u
- if (op == 3) { ;; change owner0 _" x# J! G- D+ o6 k
- slice new_owner = in_msg_body~load_msg_addr();
) }% { j/ u; T3 \& L - save_data(new_owner, next_item_index, content, nft_item_code, royalty_params);: |2 W) Z2 U, Q8 `' ~: I! U+ \
- return ();
复制代码还有两个操作代码我们应该弄清楚。 Change owner 只是一个标准的,非常简单易懂。所以,我就不深入探讨了。 更重要的是 batch deploy of new nfts, 要复杂一些。对于批量部署,你实际上是把一个Cell--比如你把一个Cell放在 msg_body. 这个Cell将有一个字典,因此您将浏览整个字典,一步一步地部署您的 NFT item。确保你的计数器永远不会超过 250, 因为这是你可以在这里部署 NFT item 的最大合约数。此外,你最好确保跟踪你的下一个 item 索引,以及它在本地存储中是如何不断增加的。当然,运行 deploy_nft_item 功能。
4 I/ y/ v- M+ n4 ?, g
这份合约比我们与Jetton签订的合约要简单得多。同样重要的是,在 TON 中,所有东西都是一个合约。一个非常简单的逻辑可以创建一个复杂的合约系统。例如,在 Jettons 中,我们有矿工、钱包,而钱包又在部署其他钱包。在 NFT 中,我们有 collections, collections 部署的item也是合约。 在下一课中,我们将回顾 NFT item合约。我们将看看它是如何根据我们给出的初始状态进行初始化的。这将不仅仅是一个单一的合约,因为item可以有不同类型的合约。我们将在下一课讨论它们。我们很快就会再见面。
) e/ r7 A! q+ Y# w1 \ |