English 简体中文 繁體中文 한국 사람 日本語 Deutsch русский بالعربية TÜRKÇE português คนไทย french

简体中文 繁體中文 English 日本語 Deutsch 한국 사람 بالعربية TÜRKÇE คนไทย Français русский

回答

收藏

7.4 NFT Collection 智能合约

开源社区 开源社区 6612 人阅读 | 0 人回复 | 2025-03-15

本帖最后由 riyad 于 2025-3-15 05:47 编辑 4 ]' e/ U; {. z5 k: E

  f( D' L/ Q3 |5 {$ k! P
same repository 我们还可以看到 non-fungible token code. 这只是像 Jettons 一样描述的 NFT 标准的标准实施。Here 您可以在这里找到所有元数据示例,这些示例都符合我们在前几课中复习过的元数据标准。
这里还有两种类型的合约:collections合约和 NFT item智能合约。collections合约就是部署 NFT item智能合约。这里有一个很好的例子,如果你想发布一个包含 10,000 个item的集合,你将部署 10,001 个智能合约。您将自行部署。
在item智能合约中,所有者将不断变化。此外,collections的所有者也可以更改。你需要理解这个逻辑。我们要审查的第一个合约是 NFT collections,然后审查 NFT item智能合约。再次强调,这不仅仅是一个合约,这是一个智能合约系统。
NFT collection 智能合约和其他合约一样,都是从读取存储的封装逻辑开始的:
  1. (slice, int, cell, cell, cell) load_data() inline {
    ! t! u) u) W8 W/ N
  2.   var ds = get_data().begin_parse();. Q/ ~6 T4 g; _# b% r6 [+ ?- x( t
  3.   return
    1 l0 A, v$ {- c! P/ @& `8 A
  4.     (ds~load_msg_addr(), ;; owner_address8 t8 @  X3 [: n+ m) Y
  5.      ds~load_uint(64), ;; next_item_index  ?% d6 @; N! y& n+ r/ r6 S$ ~# p
  6.      ds~load_ref(), ;; content
    1 r" J( E0 r, a3 A# Q# d/ [
  7.      ds~load_ref(), ;; nft_item_code: ?% I( }4 \' Q: z
  8.      ds~load_ref()  ;; royalty_params  k$ B; O) {3 Y2 i( p
  9.      );
    9 w7 |) Y* z  K( O0 ^" A
  10. }
复制代码
在这里,我们有 owner_address, next_item_index, 我们有 content. 同样,这 content 是根据元数据标准制作的cell,在同一标准中也有提供 repository 我们已经复习过了。那么我们有 nft_item_code 的cell royalty_params. 我们很快就会了解到更多。
  1. () save_data(slice owner_address, int next_item_index, cell content, cell nft_item_code, cell royalty_params) impure inline {" m+ `4 }1 C1 }: V4 q/ r
  2.   set_data(begin_cell()
    # P- [% C2 I; c" d; b5 ^
  3.     .store_slice(owner_address)0 ~6 x4 q: L0 G8 o
  4.     .store_uint(next_item_index, 64)
    ; E$ m( r8 A# }1 ^! [) ~
  5.     .store_ref(content)
    , ^, P' a- o9 R# j9 t6 m
  6.     .store_ref(nft_item_code)% U# S( K  s; U  S+ {. j4 S7 d# j
  7.     .store_ref(royalty_params)
    ! a/ ?. ~# ~& d  t( E
  8.     .end_cell());
    2 _: |0 O+ A5 L6 n, a7 I$ A
  9. }5 x5 N8 m  c, v( _4 M

  10. 1 F9 G0 u1 S( @
  11. cell calculate_nft_item_state_init(int item_index, cell nft_item_code) {! ?! A# D# E2 n% P& U- m
  12.   cell data = begin_cell().store_uint(item_index, 64).store_slice(my_address()).end_cell();2 R+ b: n) }+ |( J9 c( r
  13.   return begin_cell().store_uint(0, 2).store_dict(nft_item_code).store_dict(data).store_uint(0, 1).end_cell();
    # q+ [) n8 R- y4 v4 Y9 E3 h% `/ {' Y
  14. }& G) n# c( g' c' h, K* g8 j- S5 n

  15. ; f, R# w8 e5 d2 D! X8 [) Z( `
  16. slice calculate_nft_item_address(int wc, cell state_init) {
    . ~5 t3 G, a0 B# Z
  17.   return begin_cell().store_uint(4, 3)3 ~' Y5 R7 C% W% q# e4 X/ U
  18.                      .store_int(wc, 8)1 w* g2 ]* L* n1 ]8 j3 Y# _+ v& o
  19.                      .store_uint(cell_hash(state_init), 256)2 a* Y9 u+ R: e/ }4 ?
  20.                      .end_cell()
    3 e+ ^" Q2 v9 d( A) R+ n
  21.                      .begin_parse();( D% h% c2 u6 @) Q2 _. |* g( {
  22. }
复制代码
就像 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 函数:
  1. () deploy_nft_item(int item_index, cell nft_item_code, int amount, cell nft_content) impure {( d* a; ]3 ?% J7 q+ Q
  2.   cell state_init = calculate_nft_item_state_init(item_index, nft_item_code);3 W& @, B$ z: a6 g+ n: s
  3.   slice nft_address = calculate_nft_item_address(workchain(), state_init);( O+ n4 V: ~/ p- w* I; X! [* c
  4.   var msg = begin_cell()
    ( y; g, n; r. @
  5.             .store_uint(0x18, 6)
    & j) g" v' R' j9 E' q6 c7 @
  6.             .store_slice(nft_address)
    # }. [9 G+ o5 g! `" n3 H
  7.             .store_coins(amount)
    1 i/ |, r, X6 R9 o% |. V
  8.             .store_uint(4 + 2 + 1, 1 + 4 + 4 + 64 + 32 + 1 + 1 + 1)! Y7 Y9 |1 N" I6 [; {( @) n2 c
  9.             .store_ref(state_init)+ Q. y9 P6 k- o. \0 D' {6 j: w4 V
  10.             .store_ref(nft_content);
    2 _/ J; ~0 P2 q
  11.   send_raw_message(msg.end_cell(), 1); ;; pay transfer fees separately, revert on errors
    ! F8 L/ X$ L# o3 I; T
  12. }# @5 S: v3 b* t* h, g- h$ |1 l

  13. 3 M9 F1 i( F* L
  14. () send_royalty_params(slice to_address, int query_id, slice data) impure inline {8 [* h5 u2 B- \! T$ ]
  15.   var msg = begin_cell(), K7 _8 e9 H0 Y- Q. m" m$ W& z9 j, Q
  16.     .store_uint(0x10, 6) ;; nobounce - int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 011000
    ( V) F) [9 A1 K6 |) B3 J0 K
  17.     .store_slice(to_address)
    , y. L8 L. d" P! A
  18.     .store_coins(0)
    . ]' \9 X0 A* ~, f. N/ x
  19.     .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1)
    ( r  F  U, {5 E7 _5 H( s, b+ \
  20.     .store_uint(op::report_royalty_params(), 32)
    . D2 m0 E# V4 Z; b: o
  21.     .store_uint(query_id, 64)+ X: G6 ?+ i2 N9 j
  22.     .store_slice(data);4 E5 T; |! Y- C' O# c% C% {
  23.   send_raw_message(msg.end_cell(), 64); ;; carry all the remaining value of the inbound message
    : f& z: d5 m; ~& C5 ]$ x& D
  24. }
复制代码
这一切都要按照标准的要求进行。由于您必须遵守标准,因此您的合约将被视为 NFT 合约。
- t+ g5 c! t) R: @2 H
  1. () recv_internal(cell in_msg_full, slice in_msg_body) impure {  A  p, z' N' ], l' t5 p" G' s
  2.     if (in_msg_body.slice_empty?()) { ;; ignore empty messages
    ) w1 J4 {* m& r9 C( Q4 F3 _) D
  3.         return ();  F4 h5 J, f" P4 ~) s
  4.     }1 t6 ]) h  s* L& O. @
  5.     slice cs = in_msg_full.begin_parse();
    ' p" z: u; s6 b8 w. A
  6.     int flags = cs~load_uint(4);
    % N# B4 Z+ d* a( K6 h

  7. " p6 T2 e& x0 P! n

  8. ( b$ R( d: l' m* D
  9.     if (flags & 1) { ;; ignore all bounced messages1 E5 W1 [9 _8 q& S
  10.         return ();) p5 _5 v+ b; X" N$ p
  11.     }
    6 b. A. a9 }& ^( Q7 k
  12.     slice sender_address = cs~load_msg_addr();/ j% c/ h7 f  L  ]: a

  13. & w# B6 F8 X. H1 L$ g
  14.     int op = in_msg_body~load_uint(32);; K5 |* ]7 U0 k5 a& I/ ~& C
  15.     int query_id = in_msg_body~load_uint(64);4 `6 T3 t, p7 @* v1 x7 [
  16. 1 c2 E! c  r; g# J( p! d  c
  17.     var (owner_address, next_item_index, content, nft_item_code, royalty_params) = load_data();
复制代码
我们接收到一条内部信息,我们发现我们不想处理空的正文信息,因此我们要检查它是否为空。然后我们检查标记,忽略跳转信息,得到 sender_address, 阅读 in_msg_body, 运行代码、 query_id, 然后是本地存储中的所有内容。
& s* Q1 M& R: \+ v& i; W, D
  1. if (op == op::get_royalty_params()) {. K, i2 o8 {: B1 K) o
  2.     send_royalty_params(sender_address, query_id, royalty_params.begin_parse());5 D. J0 A% W1 d  g8 t4 m! m( f$ u
  3.     return ();2 H4 N4 A9 n* l  [0 O
  4. }
      a- k$ _$ ?7 A

  5.   d7 _) }3 @0 s2 I- s$ y1 w
  6. throw_unless(401, equal_slices(sender_address, owner_address));
复制代码
如果操作码为 get_royalty_params, 那我们将发送版税参数。如你所见,我们从本地存储读取版税参数,并将版税参数传递给 begin_parse. royalty_params 是一个Cell,我们要传递一个片段,这样它就能被立即读取或写入我们要发送的信息中。
  1. () send_royalty_params(slice to_address, int query_id, slice data) impure inline {' }( e" u$ |% C% D, i& u
  2.   var msg = begin_cell(): s7 Q; ]; H& G, B6 }$ p
  3.     .store_uint(0x10, 6) ;; nobounce - int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 011000
    1 e6 Y6 W- U; x! e
  4.     .store_slice(to_address)
    8 h; h  D' v  A$ Q; @
  5.     .store_coins(0)% L' W6 ?. ?1 Z. H# a& B7 a, ?1 v
  6.     .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1)4 S8 ~$ X$ s$ b0 z
  7.     .store_uint(op::report_royalty_params(), 32)/ n9 y! u/ O! y. m, o; j
  8.     .store_uint(query_id, 64)$ H- c% N' z& [- y1 o
  9.     .store_slice(data);* v2 x: \8 R+ n1 w1 c6 P( v
  10.   send_raw_message(msg.end_cell(), 64); ;; carry all the remaining value of the inbound message
    7 l- e4 l% h% S! {5 V2 w
  11. }
复制代码
我们看看 send_royalty_params 已经实现。我们得到 to_address,我们得到 query_id, 和 slice_data, 这个片段是 royalty_params. 我们不想让这条信息跳转,因此将快捷标志改为 0x10. 这一切你们都很熟悉。我们将其发送给 to_address, 我们将操作代码、 query_id, 当然,我们把这些 royalty_params. 就是这么简单。你已经知道它是如何工作的了。对于版税参数,我们甚至不检查是谁要求的。接下来呢?
  1. if (op == 1) { ;; deploy new nft$ [+ y6 W& b1 F$ T0 N3 L" o
  2.   int item_index = in_msg_body~load_uint(64);- ~# k* {% j/ |! m2 n8 z' {
  3.   throw_unless(402, item_index <= next_item_index);
    $ c+ ]+ U2 y$ e" f+ H
  4.   var is_last = item_index == next_item_index;
    / I. S! Y5 n9 Z7 \  o: {# x8 b
  5.   deploy_nft_item(item_index, nft_item_code, in_msg_body~load_coins(), in_msg_body~load_ref());/ e+ K  G, }$ b" H/ u6 ~( d; |: ~7 Q
  6.   if (is_last) {
    : L, p1 F$ L5 F/ }% t/ y, b
  7.     next_item_index += 1;
    ) h3 r7 W! F* t. F8 e: F
  8.     save_data(owner_address, next_item_index, content, nft_item_code, royalty_params);
    ; r2 ~7 M" D% E2 S
  9.   }* f% f! k8 o) l' U  ^  R5 ?
  10.   return ();
    4 W/ k6 f3 p5 P
  11. }
复制代码
我们看到这个操作代码,这意味着我们要部署一个新的 NFT。这只能由所有者完成。这里最重要的是索引。您需要跟踪当前 Collections 的索引。例如 item_index 您要在这里部署的 next_item_index 存储在我们的数据库中。如果它们相等,我们实际上会增加 next_item_index 并将其再次放入存储中,这样我们的索引就会增加。你可以部署先前的索引 NFT,也可以部署当前的索引 NFT,然后增加 next_item_index.
  1. ​​() deploy_nft_item(int item_index, cell nft_item_code, int amount, cell nft_content) impure {: j# g3 G" i, i% b# T
  2.   cell state_init = calculate_nft_item_state_init(item_index, nft_item_code);
    ' k( g/ I3 [, o( ], @: g+ Y
  3.   slice nft_address = calculate_nft_item_address(workchain(), state_init);
    1 Y, ?( v5 k/ Y2 |9 b3 ]6 a
  4.   var msg = begin_cell()% \( [  b- O' p) [9 ^7 d% Z
  5.             .store_uint(0x18, 6)
    % D( n3 X0 u  i
  6.             .store_slice(nft_address); a3 b7 w: @$ `) S& r
  7.             .store_coins(amount)0 H+ H( e/ ], {' f2 y% Z( K
  8.             .store_uint(4 + 2 + 1, 1 + 4 + 4 + 64 + 32 + 1 + 1 + 1)
    # C& l& M; R  B- @3 q" D
  9.             .store_ref(state_init)! u1 M7 e+ \1 a2 {) e% S' i7 E
  10.             .store_ref(nft_content);) q/ l. F+ O3 ^! H2 s
  11.   send_raw_message(msg.end_cell(), 1); ;; pay transfer fees separately, revert on errors2 G: C: n' z+ P, a" C0 s. [8 u" H8 A
  12. }
复制代码
现在来看看这里是如何实现部署功能的。 deploy_nft_item 部署合约,这就是为什么我们在这里计算 state_init 的原因。我们正在与 state_init 一起计算地址。 此外,我们正在发送一条消息,其中包含我们放置在此处的一定金额。
让我们看看如何在部署时获得这笔金额。在 msg_body, 根据标准,您将看到您想要传递的代币数量,因为我们将其设置为 amount. 因此,我们用 state_init  来编写这条信息,然后发送并部署此合约。我们将在下一课回顾 NFT item合约时了解本地存储方案。部署 NFT item比我们使用 Jettons 时要简单得多,但工作方式类似。
  1. if (op == 2) { ;; batch deploy of new nfts
      ]8 H+ r' {! e  P
  2.       int counter = 0;
    $ _% v' A+ u0 D: u9 V5 f
  3.       cell deploy_list = in_msg_body~load_ref();' P- y6 U2 T% Y* o  U% H
  4.       do {# l/ a$ ?1 J$ T. d. b' j1 |' \
  5.         var (item_index, item, f?) = deploy_list~udict::delete_get_min(64);6 ]% F# h& i) E. j* g2 i
  6.         if (f?) {
    : Q4 q5 L! O+ S8 Q8 x
  7.           counter += 1;
    ) o/ ~) p( H& U! Y, [8 `8 h
  8.           if (counter >= 250) { ;; Limit due to limits of action list size, G1 v3 V1 G. j8 K  O, C
  9.             throw(399);
    & O- ^! H3 c" h7 c* u$ d1 N, j7 }
  10.           }
    0 {% `0 h0 p+ s, ]: m
  11. : L, ?7 I; S) k4 r9 a
  12.           throw_unless(403 + counter, item_index <= next_item_index);
    + L/ G* A- ]' g& @$ b
  13.           deploy_nft_item(item_index, nft_item_code, item~load_coins(), item~load_ref());
    - m2 m  Y+ ]6 {& m5 C3 ~
  14.           if (item_index == next_item_index) {
    0 e5 A# b/ h) d" d, H" @4 l
  15.             next_item_index += 1;
    ' L) }6 w8 r, A0 z" ~' x/ u2 ~! L
  16.           }
    / j1 Q4 J7 I# O
  17.         }
    ! f' _  b# I. Z( k0 _
  18.       } until ( ~ f?);
    " S# G! P/ _% V- z
  19.       save_data(owner_address, next_item_index, content, nft_item_code, royalty_params);
    ! s6 f$ u2 F% f$ @9 P, C" ~
  20.       return ();# k5 z) P$ O% r  L7 R+ P. a
  21.     }7 {4 A/ D; L: P0 W5 y$ s
  22. & {/ R- J& f& ^
  23. if (op == 3) { ;; change owner
    ; N2 c3 f/ a" q; {2 f; `
  24.   slice new_owner = in_msg_body~load_msg_addr();
    8 O# v' _- l; m, ?3 o. r0 ]
  25.   save_data(new_owner, next_item_index, content, nft_item_code, royalty_params);
      |/ N' A( c3 q+ {' c1 U
  26.   return ();
复制代码
还有两个操作代码我们应该弄清楚。 Change owner 只是一个标准的,非常简单易懂。所以,我就不深入探讨了。
更重要的是 batch deploy of new nfts, 要复杂一些。对于批量部署,你实际上是把一个Cell--比如你把一个Cell放在 msg_body. 这个Cell将有一个字典,因此您将浏览整个字典,一步一步地部署您的 NFT item。确保你的计数器永远不会超过 250, 因为这是你可以在这里部署 NFT item 的最大合约数。此外,你最好确保跟踪你的下一个 item 索引,以及它在本地存储中是如何不断增加的。当然,运行 deploy_nft_item 功能。

1 I5 \5 s4 i% L2 b
这份合约比我们与Jetton签订的合约要简单得多。同样重要的是,在 TON 中,所有东西都是一个合约。一个非常简单的逻辑可以创建一个复杂的合约系统。例如,在 Jettons 中,我们有矿工、钱包,而钱包又在部署其他钱包。在 NFT 中,我们有 collections, collections 部署的item也是合约。
在下一课中,我们将回顾 NFT item合约。我们将看看它是如何根据我们给出的初始状态进行初始化的。这将不仅仅是一个单一的合约,因为item可以有不同类型的合约。我们将在下一课讨论它们。我们很快就会再见面。
  j6 r! `' {# C
分享到:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则