本帖最后由 riyad 于 2025-3-15 05:47 编辑
- u$ x p* c+ w; [. t; K5 g( g( P- P4 n! R# {! O5 {, c
这里还有两种类型的合约: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 {
# [) r" z! X. S V - var ds = get_data().begin_parse();
% R: n) }) X9 F1 C; v* k( m* s - return
, Q3 C i' t5 `% ]) }4 v - (ds~load_msg_addr(), ;; owner_address
; F+ N* t9 y4 \7 d! p8 S* x - ds~load_uint(64), ;; next_item_index
* _ `# Z4 K. y- ~/ B5 Z- {8 j - ds~load_ref(), ;; content
" _# }) C1 \) ~* U- Y( B - ds~load_ref(), ;; nft_item_code
2 o& k: J/ I; B: ^% b0 _+ t - ds~load_ref() ;; royalty_params; Q1 H! g* B: q) p
- );4 B1 x* H& d9 i
- }
复制代码 在这里,我们有 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 {7 L1 e& Z. j8 K+ z
- set_data(begin_cell()
" p; B& ~0 f1 M+ z( a - .store_slice(owner_address)3 X) R4 [; S+ G. L, a# l8 F* N
- .store_uint(next_item_index, 64)) j' o; m* l/ q0 E% @ I" e
- .store_ref(content). N" H# k! C$ l; \
- .store_ref(nft_item_code)( B. q- z2 s" U% r( |! @4 H! G
- .store_ref(royalty_params)# O- \( h& b" q* y! D. E, H0 c
- .end_cell());3 c. A# Z. G* Z2 y
- }
; I/ |7 n) h s, \+ {$ N& Q% ]! G
7 C/ V1 i7 o# r5 V. E" k- cell calculate_nft_item_state_init(int item_index, cell nft_item_code) {0 L) j, e2 F, q5 ~7 ]+ X4 Z) A
- cell data = begin_cell().store_uint(item_index, 64).store_slice(my_address()).end_cell();
3 n" d, p8 D! Y7 O% O - return begin_cell().store_uint(0, 2).store_dict(nft_item_code).store_dict(data).store_uint(0, 1).end_cell();
( J Z" k9 f* o% V - }
; z% L9 c4 C( J. \% j
0 z- K: U2 Q9 K5 a2 l- slice calculate_nft_item_address(int wc, cell state_init) {
9 w& V) y w2 B0 d1 [. B - return begin_cell().store_uint(4, 3)
# E, ~3 i& i4 v+ V- J8 e C$ H - .store_int(wc, 8)4 i2 i, Y% T$ ]: s& }
- .store_uint(cell_hash(state_init), 256)6 P$ B; s# O* }3 _6 J
- .end_cell()7 W9 X/ r7 @0 s9 e6 D
- .begin_parse();
7 h# R2 A; U: D! b7 @ - }
复制代码就像 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 {
6 ^3 w4 Y8 ^) g4 r/ j$ H' e - cell state_init = calculate_nft_item_state_init(item_index, nft_item_code);. e. H1 G# B' p% ?1 R
- slice nft_address = calculate_nft_item_address(workchain(), state_init);) _3 o4 k: i4 G/ U u
- var msg = begin_cell()" a( V& Z; @6 S$ Z! |
- .store_uint(0x18, 6)+ v/ z! J2 e3 T3 c& u1 j, Q. F' \
- .store_slice(nft_address)
" B- ~, M* n1 w0 p2 Y) F0 \/ ? - .store_coins(amount)
0 } i8 F4 j% O; @0 M. w* N - .store_uint(4 + 2 + 1, 1 + 4 + 4 + 64 + 32 + 1 + 1 + 1)& i t' b* q2 n& z3 H Q
- .store_ref(state_init)
4 b- r8 p: ]( ]: o8 o - .store_ref(nft_content);" S7 D' {7 Y# y" `2 H* X
- send_raw_message(msg.end_cell(), 1); ;; pay transfer fees separately, revert on errors
# V9 b/ L: H b5 B5 e/ _. I3 u - }
' v4 v0 F7 }" f9 h& [. k9 s - : V) j* v2 U1 b7 P7 ^+ |6 p$ P" D
- () send_royalty_params(slice to_address, int query_id, slice data) impure inline {
7 K. U# d# [0 [* M; i' ? - var msg = begin_cell()
4 m: g) {* G7 r6 r5 s7 t& j* K0 i - .store_uint(0x10, 6) ;; nobounce - int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 011000/ D0 v8 D: z9 R2 D7 F: L/ _
- .store_slice(to_address)* B+ C2 q' R% P( K' u' N4 y# N
- .store_coins(0)2 @% ~' P7 v6 |
- .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1)
( x, b# m9 I: ?1 b: U- I - .store_uint(op::report_royalty_params(), 32)2 u) z6 a5 B5 a( ]2 I ~5 F! P
- .store_uint(query_id, 64)
" ?' c' f3 H! m, M0 ` - .store_slice(data);
9 c! Y) [: k, v/ s - send_raw_message(msg.end_cell(), 64); ;; carry all the remaining value of the inbound message9 @" v0 i/ v& g) {6 M
- }
复制代码这一切都要按照标准的要求进行。由于您必须遵守标准,因此您的合约将被视为 NFT 合约。 9 b- e' Z0 `+ L/ _
- () recv_internal(cell in_msg_full, slice in_msg_body) impure {
% L) V: I, y/ d2 A( o# ^ - if (in_msg_body.slice_empty?()) { ;; ignore empty messages1 x) u* g4 n: P+ O* t3 H6 V
- return ();# x' K- x& x. w
- }) F! z) G; `1 U+ y% P/ @8 p7 U: m
- slice cs = in_msg_full.begin_parse();! l4 C5 ~ y# J' q6 _% j2 \& |9 t" |
- int flags = cs~load_uint(4);
* i+ x+ }) E3 P! J$ x; D
5 h3 a- {3 h* v* V+ i e, x
# O; a+ R6 v* e9 \7 l3 m/ S- if (flags & 1) { ;; ignore all bounced messages
% v, g6 m; M2 E/ N - return ();+ k* m3 |' N. O# F# V
- }
0 v6 e$ T! \8 [1 X9 v - slice sender_address = cs~load_msg_addr();+ C! i U( `2 S5 p3 z3 K; @
- / Y- |$ C2 b1 c0 }
- int op = in_msg_body~load_uint(32);. Q5 L* B) L, {. B2 S' ?- }
- int query_id = in_msg_body~load_uint(64);
* W3 z' p* X0 Q! D0 ] T
' R& K, U( E+ D- var (owner_address, next_item_index, content, nft_item_code, royalty_params) = load_data();
复制代码 我们接收到一条内部信息,我们发现我们不想处理空的正文信息,因此我们要检查它是否为空。然后我们检查标记,忽略跳转信息,得到 sender_address, 阅读 in_msg_body, 运行代码、 query_id, 然后是本地存储中的所有内容。
9 N7 F) T& q7 z" @2 A! N1 x3 ~9 r* T- if (op == op::get_royalty_params()) { j; u* x- G* R2 `
- send_royalty_params(sender_address, query_id, royalty_params.begin_parse());& |/ s5 [3 J3 P+ Y2 U
- return ();( M. y; ~9 V0 Y
- }% o. \, O- F0 A& j
- 9 J/ s+ O, T h4 |* C$ Q+ b
- 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 {
% n; \5 V. w, x2 l# U& m - var msg = begin_cell()
' I/ G4 b# r( d/ H5 q0 W - .store_uint(0x10, 6) ;; nobounce - int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 011000' {, M `% b& ~# Q8 ^% t
- .store_slice(to_address)8 I$ F5 h) e/ z& Z; K7 H2 [
- .store_coins(0)
( f* L1 p% D# ~& K' f+ F - .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1)2 V5 ]$ E# @7 R% C: C9 M
- .store_uint(op::report_royalty_params(), 32)/ H' W( Y6 Y0 ` u9 }
- .store_uint(query_id, 64), R, w: ^( `5 l/ Q7 _0 a
- .store_slice(data);
$ X6 @4 q+ D0 N0 V/ ~! k5 O0 {: _3 t - send_raw_message(msg.end_cell(), 64); ;; carry all the remaining value of the inbound message7 Z8 y, h+ F3 l
- }
复制代码 我们看看 send_royalty_params 已经实现。我们得到 to_address,我们得到 query_id, 和 slice_data, 这个片段是 royalty_params. 我们不想让这条信息跳转,因此将快捷标志改为 0x10. 这一切你们都很熟悉。我们将其发送给 to_address, 我们将操作代码、 query_id, 当然,我们把这些 royalty_params. 就是这么简单。你已经知道它是如何工作的了。对于版税参数,我们甚至不检查是谁要求的。接下来呢?- if (op == 1) { ;; deploy new nft
b- I; Q$ U& Q" f8 ^! N - int item_index = in_msg_body~load_uint(64);! b }* H. f& N/ M' |! M' Y
- throw_unless(402, item_index <= next_item_index);1 i' x. e: n5 m2 R/ n% U: I
- var is_last = item_index == next_item_index; % Z& E7 V' I* B, }2 _1 k6 {
- deploy_nft_item(item_index, nft_item_code, in_msg_body~load_coins(), in_msg_body~load_ref());
" L& H( J* S1 x - if (is_last) {
; y: \% K8 z% {& m) |! W - next_item_index += 1;
7 b5 H) U& d/ Z% C) x6 w: q - save_data(owner_address, next_item_index, content, nft_item_code, royalty_params);6 T# d7 | x# v# g
- }
: }& \ y! G8 t5 l! b1 _ - return ();, d, S, \+ R0 R% h" p
- }
复制代码 我们看到这个操作代码,这意味着我们要部署一个新的 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 l5 v G+ p) V$ a% {
- cell state_init = calculate_nft_item_state_init(item_index, nft_item_code);
8 \" T9 C3 ^4 [2 ^! y - slice nft_address = calculate_nft_item_address(workchain(), state_init);
1 [1 m" d2 {3 h - var msg = begin_cell()
$ O Q: g2 G) G2 [( ` - .store_uint(0x18, 6)
# ^5 o! r: a y7 p6 n - .store_slice(nft_address): N" }' Y- l( ]7 c3 D8 i
- .store_coins(amount)5 Q1 |* X1 }; r5 ]& x
- .store_uint(4 + 2 + 1, 1 + 4 + 4 + 64 + 32 + 1 + 1 + 1)
z( R3 n6 ]8 w1 w A; q$ |( @ - .store_ref(state_init)
9 y+ ~8 p1 `/ e - .store_ref(nft_content);# x1 k! q: P# d( Y. Z
- send_raw_message(msg.end_cell(), 1); ;; pay transfer fees separately, revert on errors
5 n+ ?" ?- B N- g4 J - }
复制代码现在来看看这里是如何实现部署功能的。 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
5 n# f6 O( H$ q4 N |) K - int counter = 0;
) A. a. a) h8 |; m - cell deploy_list = in_msg_body~load_ref();: ~7 J) k, v& d# g1 g
- do {
7 o7 Y Y! A% {3 h - var (item_index, item, f?) = deploy_list~udict::delete_get_min(64);! m: B) o" x* m1 `7 ]
- if (f?) {
5 ^8 G) b: _# G! B; d+ v - counter += 1;5 I2 G( F9 j0 D. {
- if (counter >= 250) { ;; Limit due to limits of action list size
6 }3 P* e W* M d$ z- _3 x7 y - throw(399);' N1 [6 T# f. ^
- }0 m6 G% E- t' M/ s
- 7 ]' Q/ a s6 l4 n' c) F8 N8 \: _) f
- throw_unless(403 + counter, item_index <= next_item_index);
# S1 }- N$ i0 Z% ~( a* \ - deploy_nft_item(item_index, nft_item_code, item~load_coins(), item~load_ref());4 Y i% J; h! i: M8 K& D6 x
- if (item_index == next_item_index) {2 g5 H0 `7 D% y" ?1 `2 p8 P$ T
- next_item_index += 1;1 d7 g2 o: a% e! O) W/ i7 X5 K
- }
' U) ]6 Y, a+ i# H9 ~; C% { - }
0 \0 [$ \+ T' Z9 E9 V) Z) Y$ n - } until ( ~ f?);4 k; w! d; r0 Z9 a6 z7 T
- save_data(owner_address, next_item_index, content, nft_item_code, royalty_params);
- H) `- |) ~7 y) { - return ();
# [5 ?6 n) S; C - }
4 H5 D, W5 T; w( P: X3 S5 Y1 ]
% q e3 g5 M; T# j2 k8 T1 K& x% I/ E- if (op == 3) { ;; change owner
* a, E2 D: J8 I4 Q9 y) Z! d - slice new_owner = in_msg_body~load_msg_addr();
0 Q7 c8 M6 Y- T" \9 Y) y - save_data(new_owner, next_item_index, content, nft_item_code, royalty_params);
& {. {( t0 l7 m1 m: F1 J - return ();
复制代码还有两个操作代码我们应该弄清楚。 Change owner 只是一个标准的,非常简单易懂。所以,我就不深入探讨了。 更重要的是 batch deploy of new nfts, 要复杂一些。对于批量部署,你实际上是把一个Cell--比如你把一个Cell放在 msg_body. 这个Cell将有一个字典,因此您将浏览整个字典,一步一步地部署您的 NFT item。确保你的计数器永远不会超过 250, 因为这是你可以在这里部署 NFT item 的最大合约数。此外,你最好确保跟踪你的下一个 item 索引,以及它在本地存储中是如何不断增加的。当然,运行 deploy_nft_item 功能。
" \2 N$ Q, h X! o
这份合约比我们与Jetton签订的合约要简单得多。同样重要的是,在 TON 中,所有东西都是一个合约。一个非常简单的逻辑可以创建一个复杂的合约系统。例如,在 Jettons 中,我们有矿工、钱包,而钱包又在部署其他钱包。在 NFT 中,我们有 collections, collections 部署的item也是合约。 在下一课中,我们将回顾 NFT item合约。我们将看看它是如何根据我们给出的初始状态进行初始化的。这将不仅仅是一个单一的合约,因为item可以有不同类型的合约。我们将在下一课讨论它们。我们很快就会再见面。 : _7 Q4 h" p1 j& V" i7 {! D
|