在本课中,我们将回顾经典的 NFT 项目的代码。此外,我们还将学习 SoulBound (SBT) Item。 # ~) J7 S" u3 \( F
NFT Item合约- int min_tons_for_storage() asm "50000000 PUSHINT"; ;; 0.05 TON
& F- q- ?; V: c$ j' \3 x8 a
复制代码就像在前面的课程中一样,我们看到有一些常量显示了我们应该在合约上存储的最低代币数量,以便能够支付租金。 / a; Y$ X* r' ]2 E
- (int, int, slice, slice, cell) load_data() {+ G; ~& O/ w1 p4 r7 j$ F! ?5 E; g
- slice ds = get_data().begin_parse();1 S4 O3 Q/ g' _2 [
- var (index, collection_address) = (ds~load_uint(64), ds~load_msg_addr());
- ]) Q6 \& o; b) p. y - if (ds.slice_bits() > 0) {
* S& H: }8 v& H) m2 v5 v/ x1 X - return (-1, index, collection_address, ds~load_msg_addr(), ds~load_ref());9 z6 Q! w/ H! n* c/ R
- } else {
5 W! Y, G1 t% o5 c - return (0, index, collection_address, null(), null()); ;; nft not initialized yet; D D9 B' V% h, D4 A. V
- }, I) u$ }: j S4 W% \
- }
复制代码然后我们看到 load_data. 这个 load_data 与我们以前看到的有些不同。通常,我们只是从本地存储区读取数据,但在这里,我们有一些更复杂的逻辑。因此,我们打开本地存储的Cell进行读取,然后读出 index 和 collection_address. 我们知道什么是索引和 Collections 地址。 然后,我们尝试读取 NFT Item的元数据。首先,我们检查它是否存在。如果是,我们返回 –1, 以及Collections地址、所有者地址和元数据。或者我们只返回 0, 索引和Collections地址时,没有所有者和元数据。这意味着 NFT 尚未初始化。基于这一点,我们将在后面的代码中做出一些决定。 - () store_data(int index, slice collection_address, slice owner_address, cell content) impure {/ h4 H2 B, g1 N
- set_data(
# q5 H: O$ |6 v$ u: B$ C5 b6 ]% d - begin_cell(). B( W6 \3 {" \: O# x' \
- .store_uint(index, 64)7 Y. A% b+ Y4 Y; |
- .store_slice(collection_address)4 H& D) j$ N" s: A' H/ G
- .store_slice(owner_address)
7 s$ X- H2 I. O) O. x0 C - .store_ref(content)
! Q4 Q: W. l4 W* M& G; m - .end_cell()1 h7 h0 B$ z- l$ v
- );1 l. b( c9 Q/ u0 I6 }
- }
复制代码我们还看到 store_data 就像我们调查的其他合约一样,这只是正常情况。 ) S* J: M5 L- [( Y! |' K
- () send_msg(slice to_address, int amount, int op, int query_id, builder payload, int send_mode) impure inline {- S' r) u9 S7 `$ f$ R4 a
- var msg = begin_cell()
( c3 r! o& [2 C1 U# s - .store_uint(0x10, 6) ;; nobounce - int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 010000( a e1 B$ ^( z7 [% w% n: Z
- .store_slice(to_address)' z+ L% E( I/ Y0 W
- .store_coins(amount)! d% M. N( T7 B1 W* P+ n' N
- .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1)
. c0 o- ]6 }0 c+ S, w( L) G - .store_uint(op, 32)
! e- ~% k+ S% _" y, n) H - .store_uint(query_id, 64);
( b5 `% Y) S0 \% C3 h) X2 y3 C/ j - , X$ |8 v1 @7 R$ ]% v
- if (~ builder_null?(payload)) {, F( S1 u, ?7 A4 N( }
- msg = msg.store_builder(payload);. u8 ^- x9 I' v+ a
- }
复制代码在这里,我们封装了发送消息的逻辑,因此我们可以在合约的其他逻辑中使用这个函数。因此,只要我们想发送信息,就可以使用这个函数。它接受目标地址、我们应附加的金额、操作代码、查询 ID、可能的有效载荷(这次是以生成器格式)以及消息的发送模式。这就是我们发送信息的逻辑。 我们的另一个函数是 transfer_ownership. 让我们稍后再研究,一旦我们在 recv_internal 逻辑,因为它是这里最重要的功能。 - () recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {8 s. v& j% {5 J2 ]
- if (in_msg_body.slice_empty?()) { ;; ignore empty messages
# j. g( L8 s7 y+ O - return ();
6 z) E C1 ^- N" z - }7 X1 h( t8 ^- O$ j8 W3 ^2 s- S
) Q- f& w/ d) z% G- slice cs = in_msg_full.begin_parse();7 J% z6 g9 M" K' ?5 n) B6 D
- int flags = cs~load_uint(4);$ c i/ K8 d8 X5 v+ G9 X& T6 j
- 1 n& ~- e r: G4 _" D
- if (flags & 1) { ;; ignore all bounced messages
' f0 {+ n$ w; a - return ();
( s3 ?/ I7 Y1 X8 u - }. t% ~3 V; w) r6 S( G. f
- slice sender_address = cs~load_msg_addr();
5 A2 s) m G% F3 l) D
' F S* W3 `% Q- K0 X- Z, F, z. C8 ^- cs~load_msg_addr(); ;; skip dst! c% {* P& p. v( k6 `' r
- cs~load_coins(); ;; skip value1 ^- T$ V# |. O( j# }( u/ m
- cs~skip_bits(1); ;; skip extracurrency collection) c( D5 E6 B1 R: S7 j& g4 o
- cs~load_coins(); ;; skip ihr_fee
6 R& d; K! K; F: l' s/ v - int fwd_fee = muldiv(cs~load_coins(), 3, 2); ;; we use message fwd_fee for estimation of forward_payload costs7 l9 E/ a" z0 a" R
2 `* B4 q1 K% N+ _- (int init?, int index, slice collection_address, slice owner_address, cell content) = load_data();
2 {2 [% v, N8 C$ z) O" ^. X - if (~ init?) {- U! V- N: q9 b$ P# p) p% d
- throw_unless(405, equal_slices(collection_address, sender_address));8 b! k# H, V" B( [ O/ Z
- store_data(index, collection_address, in_msg_body~load_msg_addr(), in_msg_body~load_ref());. ^* `% K1 b7 C$ C; `# ] K
- return ();6 R1 O8 X( C7 g3 Q% y
- }
复制代码我们来看看它是怎么做的。我们再次忽略空的 in_msg_body 消息,读取标志,忽略退回的信息,并检查发件人是谁。现在我们跳过一些内容,比如信息的目标地址之类的,因为你已经知道这个Cell的内容是什么了。接下来是 fwd_fee 再次根据上一笔交易估算发送下一笔交易的成本。 然后加载数据。正如你所记得的,我们加载数据的方式与这里的实现方式相同。因此,我们要检查它是否未初始化,然后我们期待实际获得这些数据,期待 in_msg_body 以获得所有者的地址和带有元数据的Cell。但这只能通过 collection_address, 因此我们要检查它是否等于发件人地址。然后我们就可以初始化 NFT Item了。 - int op = in_msg_body~load_uint(32);
$ i# ]. C2 y# r$ S j - int query_id = in_msg_body~load_uint(64);/ ~! t4 R+ a" D3 W Y6 y
9 d4 Y3 M2 N5 q( W$ w- if (op == op::transfer()) {7 N0 g' }; a7 ^5 b, P0 |, S8 F
- transfer_ownership(my_balance, index, collection_address, owner_address, content, sender_address, query_id, in_msg_body, fwd_fee);
1 I. f( `# F: Z - return ();! r6 A# C. T: H4 O# r* h9 c) r
- }9 `, D: _5 d' T% M) p
- if (op == op::get_static_data()) {% n, G: E) H: A6 N7 F. h0 W/ y9 ?
- send_msg(sender_address, 0, op::report_static_data(), query_id, begin_cell().store_uint(index, 256).store_slice(collection_address), 64); ;; carry all the remaining value of the inbound message
( S2 U) m5 ~4 O+ B - return ();" z1 ]6 U& s; z8 g
- }
复制代码接下来是操作代码,可以是 op::transfer 或 op::get_static_data. 这是我们要处理的两个可能值。当我们说 get_static_data, 我们会立即向发送静态数据请求的用户发送一条信息。我们只是报告数据,所以我们会向他发送 index 和 collection_address. 这是 get_static_data 函数。正如你所看到的,这是我们的第一个 send_ msg 用例
2 j$ x0 d0 E% I0 M5 S- () transfer_ownership(int my_balance, int index, slice collection_address, slice owner_address, cell content, slice sender_address, int query_id, slice in_msg_body, int fwd_fees) impure inline {( \4 ~# @1 G2 y; E
- throw_unless(401, equal_slices(sender_address, owner_address));
. `7 m/ n/ _/ Z0 K
' F' i) Z) m1 P o- y- slice new_owner_address = in_msg_body~load_msg_addr();' q% d. E' R) G9 A2 s
- force_chain(new_owner_address);
# h2 T/ ?+ w( V+ o" Z# M# } - slice response_destination = in_msg_body~load_msg_addr();- J% d, ?( t: |; s$ u W
- in_msg_body~load_int(1); ;; this nft don't use custom_payload/ c9 S/ V, }3 q7 n) v
- int forward_amount = in_msg_body~load_coins();
8 S% [1 i" h9 J. [ - throw_unless(708, slice_bits(in_msg_body) >= 1);
8 n0 a8 l) R% a - 4 S( v6 W/ Q5 b, E( ]
- int rest_amount = my_balance - min_tons_for_storage();6 s# E# r# P1 n4 W: y$ m& i0 V; ^
- if (forward_amount) {
' g0 ?. S0 R3 r2 F8 \- T* ` - rest_amount -= (forward_amount + fwd_fees);# J2 D( t7 b: ~& m
- }3 @$ `& u9 o, _7 ~ J
- int need_response = response_destination.preload_uint(2) != 0; ;; if NOT addr_none: 003 q9 T4 G1 R- S( P2 {
- if (need_response) {
1 Z6 ?- J( E( p. M - rest_amount -= fwd_fees;
* v; J1 e; X- C- X% A - }
0 }) [; X) s1 { w0 C% X - % F, b5 W0 P9 L7 k# j% e0 W
- throw_unless(402, rest_amount >= 0); ;; base nft spends fixed amount of gas, will not check for response4 v6 w7 t2 N2 \
- $ a# [% \2 o i+ w! G" t/ S
- if (forward_amount) {
; k6 h" x) r1 h0 R - send_msg(new_owner_address, forward_amount, op::ownership_assigned(), query_id, begin_cell().store_slice(owner_address).store_slice(in_msg_body), 1); ;; paying fees, revert on errors
: j$ s' Z9 y$ N } - }, R: s4 j$ K$ ?: i! E0 i3 k' p
- if (need_response) {2 w$ \) S4 ]$ G! r- C- p! P& H2 D
- force_chain(response_destination);/ Z1 s; v$ }6 J2 X& ^8 B2 [# t
- send_msg(response_destination, rest_amount, op::excesses(), query_id, null(), 1); ;; paying fees, revert on errors
. K4 w E6 H& a y& s5 g - }! q7 M& A4 M8 t
* G% k# m5 y* ]! ]- store_data(index, collection_address, new_owner_address, content);
3 J2 _% |% H# f7 Q, z3 [; Z - }
复制代码这里有一个转移所有权的功能。首先,我们要检查物品的所有者地址是否就是发送信息的人,因为只有他才能转移所有权。然后,我们读出新所有者的地址。我们 force_chain 以确保他在同一条链上。然后我们检查是否有 response_destination, 因此,一旦我们转移了这个所有权,我们是否应该告诉某人向另一个人发送信息。我们检查 forward_amount 是多少,如果有人要求的话。然后,我们就可以计算出,在发出信息之后,我们就会有足够的钱 forward_amount 到目的地。然后,我们要弄清楚是否需要回应,如果存在 response_destination 。如果存在,其剩余金额必须大于 0。 下面我就向大家简单介绍一下它的工作原理。如果存在 forward_amount ,我们将发送一条信息,通知用户所有权已由该 forward_amount 分配。如果我们需要回应,我们将 force_chain 以确保响应目的地在同一链上。我们发送的回复通常包括超额费用部分和所有额外资金;它们只是被转发到 response_destination. 然后,我们只需使用 new_owner_address 保存数据。基本上,这是唯一会发生变化的地方、 transfer_ownership 是这里的核心函数之一。您拥有一件物品,有时您会将其所有权转让给其他人或出售,这取决于交易的类型。
Y( N* u9 l# Q6 _6 Q' d9 b* G SBT item合约GetGems repository 正在主办一些不同的 NFT 合约、藏品、销售和市场以及一些拍卖活动。这里是了解更多与 NFT 藏品、物品及其相关的合约的好地方。但我想向您展示一份确切的合约,我们可以对其进行深入研究。 sbt-item 是一种灵魂绑定代币(SBT)。灵魂绑定与 NFT 非常相似,但不同之处在于灵魂绑定代币不能擅自转让给其他人。我们仔细看看它的代码。 - global int storage::index;
3 F9 q( P5 Y b, d2 K8 Q - global int init?;
( B- |5 T: | g/ c0 ]5 }% H- ] - global slice storage::collection_address;
( d7 Z5 v: C3 h' P - global slice storage::owner_address;4 N! R# m8 ]1 D9 W1 ?+ H& x2 }6 W
- global slice storage::authority_address;
1 `- }/ Z4 D; ?7 F: W1 U - global cell storage::content;
& k B- ~ A* E2 L$ ~" Y Y - global int storage::revoked_at;- u* J) {6 b* x
- () load_data() impure {
: \7 v$ J0 J! ]7 a3 \6 x. p4 z - slice ds = get_data().begin_parse();
, Q5 _: P$ y3 g - 5 U1 b! f3 V1 h+ U
- storage::index = ds~load_uint(64);: g% Z3 _ A1 o$ R
- storage::collection_address = ds~load_msg_addr();6 G+ S8 J# o @1 x0 A/ y
- init? = false;
" Z8 S6 [ C; i- g7 T! |; e - $ O+ Q) ^" V: E* V- x
- if (ds.slice_bits() > 0) {
% v1 G; R1 x( {0 c a9 ` - init? = true;
4 d$ [: k: j/ ?, h; D1 w2 M% _" \ - storage::owner_address = ds~load_msg_addr();
$ l6 x* G: |, B) x5 `7 @% b+ p - storage::content = ds~load_ref();" m u! t- Z# |3 r& y7 H3 a" ~
- storage::authority_address = ds~load_msg_addr();, l/ e& p! i0 {3 G
- storage::revoked_at = ds~load_uint(64);( [, h- L: |; `( n% T
- }
, N, J5 i3 G- b* U9 p9 j; \ - }
复制代码这里有一些变量: storage::index, storage::collection_address. 我们还定义了其他全局变量,这只是 GetGems 实现的一个例子。你不必用同样的方法。我想在这里重点谈谈其他部分:
9 n# \& C6 }. H2 m3 _- if (op == op::request_owner()) {
& p1 k ~% X( O. h0 w9 Q# w - slice dest = in_msg_body~load_msg_addr();6 l0 i4 B- o$ @- m3 ~, W0 H! Q
- cell body = in_msg_body~load_ref();
1 E; p% p' t$ j: A0 u - int with_content = in_msg_body~load_uint(1);. T5 v% f3 W' L
- 3 O* H z+ a& u1 u) n
- var msg = begin_cell()& P: ]% d( s, _, W' p
- .store_uint(storage::index, 256)
: B5 a0 K0 V5 [: x+ j% D - .store_slice(sender_address)
( }! @9 [5 Q* k3 I - .store_slice(storage::owner_address)
8 y1 T: v3 a6 h* g5 u, e( F - .store_ref(body)
$ x% z w1 Q% I% ~ - .store_uint(storage::revoked_at, 64)
( j' G( ]; G+ f - .store_uint(with_content, 1);0 s6 n7 U. H' K0 O8 S
- " n% p7 \' R# E3 R9 |
- if (with_content != 0) {0 Y" l; T+ @0 j: p5 [/ I. f
- msg = msg.store_ref(storage::content);; I: i) k, Y, a. `0 W! o1 s
- }, y; g- ?6 O' z4 @: j7 h
% {: J$ F9 z. o4 N% i- ;; mode 64 = carry all the remaining value of the inbound message
! J+ i" v+ ^; \1 L1 g( G - send_msg(flag::regular() | flag::bounce(), dest, 0, op::owner_info(), query_id, msg, 64);
1 H6 a! _# W e( v; ^7 q - return ();, b9 X( j, m, L, W, _
- }' m1 }: E' d; s- g
- if (op == op::prove_ownership()) {
- i4 b* i. F4 Y/ ^* L - throw_unless(401, equal_slices(storage::owner_address, sender_address));9 r6 Q, l6 H# t
- , O/ g7 \' ?" k2 D+ G9 Y4 X
- slice dest = in_msg_body~load_msg_addr();1 A2 _' ]+ D6 d: G# B* X
- cell body = in_msg_body~load_ref();! T4 g7 p- | G9 T9 K
- int with_content = in_msg_body~load_uint(1);
+ V& {% }" R: L* t - 7 B3 `! Z5 B1 ^
- var msg = begin_cell()
+ y( `% \( X" Q5 {' w, @ - .store_uint(storage::index, 256), Y% _/ a$ [* ^6 `
- .store_slice(storage::owner_address)4 J, i5 A0 o; g' n5 Z, O
- .store_ref(body)8 n% y4 L" {* ?) C
- .store_uint(storage::revoked_at, 64)- c3 G+ Q A; e
- .store_uint(with_content, 1);8 I/ T6 G B7 q! L, m9 [2 `
- # V# J y% Q( _) R4 ]& d- Y3 V5 R
- if (with_content != 0) {
% c, c' B7 Q' {7 x* L5 P - msg = msg.store_ref(storage::content);( h! j8 E2 m8 q9 L( D
- }( z1 [ s, ^$ D# e
- 0 [2 y) h& }7 Y- q; z* r
- ;; mode 64 = carry all the remaining value of the inbound message
0 P8 h# w* T# [) O) W+ W - send_msg(flag::regular() | flag::bounce(), dest, 0, op::ownership_proof(), query_id, msg, 64);
$ x, C o; T6 \4 E+ h - return ();
( M4 x+ A& Y! @ - }8 J! e( x* U' a2 d
- if (op == op::get_static_data()) {
2 q! M4 {6 V- X$ ^ - var msg = begin_cell().store_uint(storage::index, 256).store_slice(storage::collection_address);5 N4 q( c% ~; ^) ?: N E+ {
5 n( y1 H4 y' L2 Q: Y3 d V- ;; mode 64 = carry all the remaining value of the inbound message1 Z4 ^4 W& w; i, n0 ^# q+ i
- send_msg(flag::regular(), sender_address, 0, op::report_static_data(), query_id, msg, 64);4 v) i# {2 i3 Q9 Y6 T. ^( j0 h
- return ();
% R4 h( w/ ^$ c* {. } - } V- K% _2 L( z' `6 y4 f' e" Y
- if (op == op::destroy()) {/ P* X3 `6 b, @! Y5 [' q
- throw_unless(401, equal_slices(storage::owner_address, sender_address));
# z, ?+ q" s1 g2 w: `$ g E4 l7 Y - 3 q: e) C0 w: t7 E7 _
- send_msg(flag::regular(), sender_address, 0, op::excesses(), query_id, null(), 128);/ u6 S/ a' I; ]
, Z, E9 Y% `6 }# m) h. {4 ^- storage::owner_address = null_addr();* @! R8 K2 M, w! q
- storage::authority_address = null_addr();: k; S; P' @0 p
- store_data();
L2 B: f3 v" F, K - return ();
; s+ j) i; L0 A: Q; z - }
3 D% R7 w4 S6 P [ - if (op == op::revoke()) {
& Y" x* a1 ~2 t5 o5 J j - throw_unless(401, equal_slices(storage::authority_address, sender_address));9 }/ s/ ^2 c! a; i, Q. V* D5 Q) ?
- throw_unless(403, storage::revoked_at == 0);
$ P5 o0 T' ~& T3 M; X \+ P - - f7 e9 r2 k9 ]8 z* H
- storage::revoked_at = now();$ @ V% L% J9 y8 [, @
- store_data();
9 u) X" Q- u7 E& C/ r6 o3 M - return ();; b; w/ O! ? h: u! L
- }4 C' E- Y& ?0 v; f
- if (op == op::take_excess()) {
. @/ S9 F& P9 n0 t A3 Z8 z* {' w/ d! X - throw_unless(401, equal_slices(storage::owner_address, sender_address));- A2 }- A* k/ K4 J' t1 ~
- # y `% K1 y, j- r2 S
- ;; reserve amount for storage
+ I0 d( o4 e4 k) N0 a: T, [$ w - raw_reserve(min_tons_for_storage(), 0);. y/ N3 t* f: m ?4 O
9 U( q8 F n) N! {9 ?6 t5 C- send_msg(flag::regular(), sender_address, 0, op::excesses(), query_id, null(), 128);
# `( P( @& `' k$ n - return ();& q/ f% m; e& j5 h8 {) v v, ~ Y
- }
7 d& C& T/ O7 E% q* _& U6 f - if (op == op::transfer()) {, h/ w8 d2 j! ?+ s$ _1 O' v1 W$ d
- throw(413);
2 T, L- \, ?; P: |: ~# Y - }
复制代码如你所见,这里有许多注释,但我们不会逐行深入研究。你可以看到,这个通证将被用于多种不同的用途,但与普通的 NFT 不同。这里是合约,你可以申请这个合约的所有者。一些操作代码可以请求所有权证明。您可以获取静态数据。你还可以销毁或撤销这个标记。你可以拿走多余的东西,比如,存放在这里的一些钱。如果你是这个合约的所有者,你就可以申请。但你显然不能转让它,就像你在这里看到的一样。这是一种完全不同类型的代币,如果你对 NFT 感兴趣,就应该在 GetGems 存储库里多花点时间。 我想向你展示的是,NFT collections 的所有代码可以保持不变,但collections合约部署的 item 可以不同。例如,你可以用 SBT item 替换 NFT item。这样您就会有一个 SBT item collection,然后您只需更改 code 来初始化你的 collection: - (slice, int, cell, cell, cell) load_data() inline {+ }8 w4 N/ {' E% j3 {/ m6 l( b
- var ds = get_data().begin_parse();
' I; T7 G2 C6 d! {/ b1 d - return 0 v. `% U+ w# s, c2 z
- (ds~load_msg_addr(), ;; owner_address
& p% Y# d, \7 s$ v+ _ - ds~load_uint(64), ;; next_item_index2 H& H& z9 W5 j* w; g$ S
- ds~load_ref(), ;; content
- ]( u0 b4 Z, n7 B - ds~load_ref(), ;; nft_item_code
0 I% E) n( L' j9 X - ds~load_ref() ;; royalty_params
- s9 u" t2 c: ]; `" z# g; h/ y - );
) U6 A3 ?* ?5 E9 \ - }
复制代码这样,您就可以实际部署 SBT item collection。此外,您还可以创建一个独立的没有collection SBT item。我们不会掌握这些item的编程,我只是想向大家展示它在更大范围内的工作原理。
; p5 V9 o+ S- ^+ I; F1 Z2 `2 h 结论
1 p3 q5 e( V5 g5 x6 p从第 3 章中非常简单的逻辑开始,我们已经了解了 NFT 或 Jettons 等复杂合约中的许多内容。这就是一个很好的例子,你可以一步一步地学习某些语法和概念,然后就可以学习更复杂的逻辑。希望这几节课能帮助你理解 Jettons 和 NFTs 的真正含义,以及如何处理它们的代码库--弄清楚它们是如何工作的,并在使用 TON 构建时学习更多可用的语法和架构。 非常感谢您的关注。对你们来说,这可能是最难的一章,虽然我们没有编写任何代码;我们只是在阅读一些你们从未见过的东西,而且非常复杂。你们能坚持到最后一课,我真的很骄傲,我期待着在接下来的章节中看到你们。希望你们喜欢目前的课程。 0 N$ a2 H( M& P7 f
- ]' e+ d! ^ N9 m P. x$ X8 h3 d1 P/ n; z
" D* Q" l/ _, E' l5 E
8 T2 I2 U( W8 m4 W' x3 N8 V% E* f M
: ^# a$ Q2 k6 S
5 f' M+ b; a. l' c8 W3 F) k |