|
本帖最后由 riyad 于 2025-3-20 00:45 编辑
9 n7 n! k0 A+ F# K
, k/ \6 V& H/ a9 h2 c" O; F$ P今天,我将带大家一起探索如何在 TON 区块链上用 FunC 语言和 Blueprint 工具开发一个红包智能合约。这篇教程是第一部分,重点介绍项目搭建、合约编写和成功编译的过程。我会分享完整的代码和项目结构,适合对 TON 开发感兴趣的新手。让我们开始吧! $ i' P! K$ b! R6 k5 L3 k. G
什么是 TON 和红包合约? TON(The Open Network)是一个高效的区块链平台,以低成本和高吞吐量著称,非常适合开发去中心化应用。红包是中国文化中常见的分享方式,我想通过智能合约实现一个简单功能:用户存入 TON,指定份数,其他人可以领取。这篇文章将展示我到目前为止的进展——编写并成功编译合约。 8 a3 B6 ^* B, d! y; r
准备工作 在动手之前,确保你的环境准备好: 安装 Node.js:Blueprint 依赖 Node.js,建议使用最新 LTS 版本,从 nodejs.org 下载安装。 安装 Blueprint:在终端运行以下命令全局安装: 代码编辑器:我用的是 VS Code,安装 TON FunC 插件可以高亮语法,提升编码体验。 步骤 1:创建项目 我们用 Blueprint 初始化一个 TON 项目。打开终端,输入: 项目名称:我输入了 redpacket。 模板:选择 func-empty,因为我想从头写合约。 创建完成后,进入项目目录: 项目文件夹结构 Blueprint 生成的项目结构非常清晰,以下是 redpacket 文件夹的内容:
5 k0 @3 X! n' P0 E# [+ n , B1 g" r! s+ r- X! g; Q% c+ }& F
步骤 2:编写红包合约代码 在 contracts/redpacket.fc 中,我编写了红包智能合约。以下是代码和详细注释: - #include "imports/stdlib.fc";. l& V# M# Q9 u5 }9 }- q8 ]3 S3 P" p
. F3 q/ k! a/ }$ c- // 准备内部消息的函数8 [& w7 x: C5 {+ s9 A3 v
- (cell) prepare_internal_msg(slice recipient, int amount, int bounce) {# l+ {; s* T" T3 W& {7 t1 W% K8 C6 v
- return begin_cell()3 B0 [: V1 R0 w6 Y/ K; j9 B, C4 f
- .store_uint(0, 4) ;; 内部消息标记 (0x0)
/ s, Q! f6 s. |/ J9 ]/ C - .store_slice(recipient) ;; 目标地址
" p9 E K2 Z! Z - .store_coins(amount) ;; 发送金额
/ j0 c5 K' X1 C N e - .store_uint(bounce, 1) ;; 是否可退回标志
- |3 m( E& _0 \' z! u - .store_uint(0, 1 + 4 + 4) ;; ihr_disabled: 0, 无源地址和目标地址扩展2 I/ @) M. s& g' a3 K, S6 q
- .store_uint(0, 64) ;; ihr_fee# Q) _, M2 \6 c$ ]
- .store_uint(0, 64) ;; fwd_fee
. r: e; N- M1 \9 R* r/ J: w0 L, V* o - .store_uint(0, 64) ;; 创建逻辑时间 w. M' Q3 A/ n
- .store_uint(0, 64) ;; 创建时间戳
6 B0 Y1 L* M+ t5 m, L - .store_uint(0, 1) ;; 无状态初始化
: k% {; @; ^4 P6 R" r8 _$ @: z - .store_uint(0, 1) ;; 消息体为空
& T) d) M. a3 ], D6 y6 s! z* F - .end_cell();* p) N5 V8 W6 E$ m0 [
- }6 L" F; Q4 w; l" L9 m, x4 p
6 C; g8 j1 U2 p; _' i# [2 I- // 处理内部消息的主函数* a* P* k! t+ d0 C8 h/ V& C8 m
- () recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {- y' T8 B2 N) k
- ;; 检查合约是否有余额可用! q8 y" ]8 y' c8 R7 z& O
- if (my_balance <= 0) { throw(100); }
& w4 Q3 w4 K, n m
5 c+ U9 ~$ E) ~( z' {- ;; 解析传入消息, x* S: z5 O1 }& G+ t2 K, T/ q' m
- var parser = in_msg_full.begin_parse();
- `* y. w- Z2 I" \ - int flags = parser~load_uint(4);: R# L. r8 d$ Z, ]2 |% \ [. `/ M
- slice sender_address = parser~load_msg_addr();; G+ Z4 d6 J4 }
- int sender_hash = sender_address.slice_hash();
% z/ k; E( z y" x- P
# [: G& d& G) W; G3 w, j' W! K# J- ;; 加载持久化存储(红包字典)+ P& X! I+ r3 P. n
- var data = get_data();
+ v; S2 |' P. q2 @" V7 e - cell red_packets = new_dict();
. [3 ^6 L6 z2 Y4 k/ ?/ ~ - if (~ slice_empty?(data.begin_parse())) {
3 i; Z* [) K" R+ p - var data_slice = data.begin_parse();
4 f3 P# O# o" {' y- s" @ - red_packets = data_slice~load_dict();
1 W, d% C" ^9 M9 F% w3 H0 s - }
0 `$ q/ f7 b& @+ a) H9 n) N - 6 Z/ e3 F& e0 c* h; j
- ;; 检查消息体是否有操作(创建或领取)
" q/ R* [' @3 g" {% _+ n. ^" Q - if (~ slice_empty?(in_msg_body)) {
3 d& Z' R6 ]: @9 V: c+ [$ S - var body_parser = in_msg_body;5 o3 _: g! |$ {& r2 @. k% ]7 V& P# ~
- int op = body_parser~load_uint(8); ;; 操作码:1 = 创建,2 = 领取3 x" R, o" b: t1 C+ O" K1 C
- int red_packet_id = body_parser~load_uint(256); ;; 红包唯一ID
8 P4 \! P h- q: b# k
8 N- ? ^* W* f- if ((op == 1) & (msg_value > 0)) { ;; 创建新红包5 @! M6 v. {. Z: S
- int claim_amount = body_parser~load_int(256); ;; 发送者指定每份领取金额; o. u: U& C) D' F+ F
- if ((claim_amount <= 0) | (claim_amount > msg_value)) { throw(103); } ;; 无效金额检查
9 O) p0 l8 b A- H5 { - ( E3 ]( |+ J, d" R* I9 R
- var packet_data = begin_cell()3 E( W1 G; {' N# L0 Q; [2 f0 g
- .store_int(msg_value, 256) ;; 红包总金额
9 h9 |4 b4 B8 G - .store_int(claim_amount, 256) ;; 每份领取金额
o2 |9 H4 M {1 z6 V - .store_dict(new_dict()) ;; 空领取记录字典
& D: U3 Y& T1 g: ^* [" n - .end_cell();# O% o; Z3 u b: W' q L4 |: u; k
- 2 d' h" A3 l1 a: \2 e/ J
- red_packets = udict_set_ref(red_packets, 256, red_packet_id, packet_data);1 ?; z# g4 P5 Z, _+ B
- set_data(begin_cell().store_dict(red_packets).end_cell());- |4 N. F6 N0 B6 B* m- R% J" u# `: M
- return ();+ j4 H u/ K; d
- } else {4 O1 h2 j) x! T1 n
- if (op == 2) { ;; 领取红包% ]& w* Z* ^. F$ M2 K7 Y' h
- var (packet_cell, exists) = udict_get_ref?(red_packets, 256, red_packet_id);
6 Y2 F1 z+ Z7 P - if (exists == 0) { throw(104); } ;; 红包不存在
2 T/ Y- ?$ G9 a6 b9 ?, T' M
8 v/ N, x9 y3 r5 n- var packet_parser = packet_cell.begin_parse();
4 J1 S! s) g" W' n - int stored_balance = packet_parser~load_int(256);
9 R6 z) K; m# w* T, U, R* T0 {9 Q. g - int claim_amount = packet_parser~load_int(256);
+ v+ @3 b$ D" q+ B& v- \$ j1 J - cell claimed = packet_parser~load_dict();# o( _5 D* {' S* y6 a l
- ' _& A9 s! A6 w0 C8 o6 u0 o
- ;; 检查发送者是否已领取
% ]# v2 q5 [% p h - var (claimed_value, claimed_exists) = udict_get_ref?(claimed, 256, sender_hash);
7 j/ L: p G, [5 Y; G - if (claimed_exists == 1) { throw(101); } ;; 已领取过" C9 n, U& ]. n0 x/ [& g
- E- a, R! Q0 I0 R- p# l$ g- ;; 检查余额是否足够
/ L; a7 k$ [) `9 {' C' s - if (stored_balance < claim_amount) { throw(102); } ;; 余额不足& Q. ]7 d! r0 I2 D5 L
+ J7 a1 l; b# q7 u, b/ h- ;; 更新红包状态
- x1 I" n4 b# V7 f - stored_balance = stored_balance - claim_amount;1 p2 j- p+ w G1 |% e: a* h( u
- var value_cell = begin_cell().store_int(1, 8).end_cell();6 k4 m' B ~. `2 A3 i
- claimed = udict_set_ref(claimed, 256, sender_hash, value_cell);7 Y9 J; o @8 Q' F- y
- : k! o2 t& `( |
- ;; 保存更新后的红包数据
) G' H s! z o9 e - red_packets = udict_set_ref(red_packets, 256, red_packet_id, 6 i# U) F2 Q+ Y% X' S
- begin_cell()/ `, [: `7 |, Q) P3 e# g x+ K
- .store_int(stored_balance, 256)( X& X w0 f! d' e7 m/ n* v
- .store_int(claim_amount, 256)
7 [0 S9 D. p) U; x - .store_dict(claimed)
7 ^/ J: k! m3 ?# i! ]* j& A - .end_cell());/ J4 C* s! f; ?
+ Z! N& ]3 w) V1 @ q- set_data(begin_cell().store_dict(red_packets).end_cell());- z n8 B* c9 _
2 h0 ?9 |# L2 \8 S; h- L- ;; 将领取金额发送给用户5 o$ y! }! A+ g3 c$ y7 u0 k% a' i9 D! b8 v: |
- send_raw_message(
) {7 |4 q! K# v) O - prepare_internal_msg(sender_address, claim_amount, 1),
2 S$ } Z# u1 d1 \6 d$ N& V - 64 ;; 单独支付 gas 费用! c* p* C0 }6 g: f: p6 _# v" J
- );
' f. ?1 u" T5 T+ f/ E - return ();
& Z5 Z1 D4 i+ O$ |1 C - }2 L M4 y. x$ Q C( z
- }
9 X) B5 u( ^9 J0 f/ P# K, R( x - } \/ r2 v+ J8 |: Q
- throw(105); ;; 无效消息(未指定操作)4 V1 m* Y: N: ^3 F0 _3 L
- }
3 Z7 c3 Z0 g! b6 C% ~
& _" e3 }" |3 W1 E- // 查询红包状态的外部方法
& k/ I, S) L% ]" e+ G& K" K) i - ([int, int, cell]) get_red_packet_data(int red_packet_id) method_id {
6 Q/ b. s8 |1 { _' W* [. I9 u5 J6 C( O+ f% h - var data = get_data();
+ D% s3 e) t) n9 g. z# @ - cell red_packets = new_dict();2 Z) u2 R9 `. Y) E3 W) E' U% a
- if (~ slice_empty?(data.begin_parse())) {7 s7 k- j; {, g4 F
- var data_slice = data.begin_parse();
# t2 n6 W* _- ~" J - red_packets = data_slice~load_dict();) p5 V1 {( i+ P* Q. G/ q( S4 g# t
- }! P# j. P& I: {7 E: l
# W7 q# S/ g: W" ]. n- var (packet_cell, exists) = udict_get_ref?(red_packets, 256, red_packet_id);
( g! q0 Y0 ^# E0 a$ o' { - if (exists == 0) {, c8 l p# {- {6 V9 {
- return [0, 0, new_dict()];
7 B/ z' X" S6 A0 r7 F3 _$ ] - }
+ P p& A: d0 k: y - 3 i7 F1 k3 K& w/ O( ~9 s4 ~
- var packet_parser = packet_cell.begin_parse();& j9 L+ m# ^. [% R$ {5 A
- int stored_balance = packet_parser~load_int(256);5 q$ t. F* b/ k" s0 N
- int claim_amount = packet_parser~load_int(256);
" v0 I R" N1 l1 [9 t5 Z - cell claimed = packet_parser~load_dict();$ x2 {" a+ A; N9 g
- return [stored_balance, claim_amount, claimed]; J, H& D7 _2 g: s4 {$ r
- }
复制代码代码说明 1. prepare_internal_msg 函数 这个函数用于构造一个内部消息,用于将 TON 转账给领取者。
6 `: h& j1 M9 x0 n2 b. ]) [1 q功能:生成一个标准的 TON 内部消息单元(cell),包含目标地址、金额和是否可退回等信息。 参数: - recipient:目标地址(slice 类型)。
- amount:转账金额(单位:nanoTON)。
- bounce:是否可退回(1 表示可退回,0 表示不可退回)。
4 I+ }' E s# V w7 w. t! k
实现: - 使用 begin_cell() 创建一个新的单元。
- 按 TON 消息格式依次存储:消息标记(0x0)、目标地址、金额、可退回标志等。
- 其他字段(如手续费、时间戳等)设为默认值 0。: r. `7 v# r4 e. S
用途:在领取红包时,将金额发送给用户时调用。
8 Q! C) I; O& A7 @2. recv_internal 函数 这是合约的核心函数,处理所有内部消息,实现创建和领取红包的逻辑。 - w Y! z1 z: h
功能:根据消息内容执行红包的创建或领取操作。 参数: - my_balance:合约当前余额。
- msg_value:消息附带的 TON 金额。
- in_msg_full:完整消息单元。
- in_msg_body:消息体(操作指令)。
! w! |2 M- L1 {
. Q0 f# N) e2 ?. A6 \7 @实现: - 余额检查:如果合约余额小于等于 0,抛出错误(代码 100)。
- 解析消息:提取发送者地址并计算其哈希值,用于记录领取状态。
- 加载存储:从合约持久化存储中读取红包字典(red_packets),如果为空则初始化一个新字典。
- 处理操作:) N* D" N+ K) ] f) l6 Y j: v
创建红包(op == 1): - 检查消息附带金额是否大于 0。
- 从消息体读取每份领取金额(claim_amount),并验证其有效性(大于 0 且不超过总金额)。
- 创建红包数据(总金额、每份金额、空领取记录),存储到字典中。
- 更新合约存储。3 J8 Z5 R- S+ |0 ~6 |
领取红包(op == 2): - 根据红包 ID 从字典中查找红包数据,若不存在则抛出错误(代码 104)。
- 解析红包数据:总余额、每份金额、已领取记录。
- 检查发送者是否已领取(若已领取则抛出错误 101)。
- 检查余额是否足够(不足则抛出错误 102)。
- 更新红包状态:扣除领取金额,记录发送者为已领取。
- 保存更新后的红包数据。
- 调用 prepare_internal_msg 发送领取金额给用户。
* I: d5 q; F+ K" Z
错误处理:如果消息体为空或操作无效,抛出错误(代码 105)。 / I5 {% n3 J% W! @* r
3. get_red_packet_data 函数 这是一个外部查询方法,用于查看某个红包的状态。 功能:返回指定红包的总余额、每份金额和领取记录。 参数: - red_packet_id:红包唯一 ID。
9 A5 @2 S8 @' n+ a0 k# E# [
返回值: - [stored_balance, claim_amount, claimed]:分别为红包剩余金额、每份领取金额和已领取记录字典。
6 B$ V/ S1 Q8 ]7 O
实现: - 从合约存储中读取红包字典。
- 根据红包 ID 查找对应数据,若不存在则返回默认值 [0, 0, new_dict()]。
- 解析红包数据并返回。
1 c y3 ^5 p2 {& N. a
: J% k% R6 v @; O这段代码实现了一个支持多红包的智能合约:
: g+ D+ R) W, s' [# g; N8 F. c- 用户可以用操作码 1 创建一个新红包,指定每份金额。
- 用户可以用操作码 2 领取指定红包,每次领取固定金额。
- 合约通过字典存储多个红包的状态,并防止重复领取。
- 提供外部查询方法查看红包详情。6 l( K& O' f" j" t, a6 N: a# m8 m2 e
) S+ U, W7 {$ [- p3 h/ R
. r* Y3 {0 v" {9 y9 D
步骤 3:编译合约编写完成后,我运行以下命令编译代码:
6 U3 t+ y/ |( x+ Y: n, L终端显示“Compilation successful”,证明代码语法正确,生成了字节码和 ABI 文件。以下是我编译成功的截图:
1 L# ^/ o: A" N3 L4 u( r. P4 e& y% {9 r9 U2 |5 @
下一步计划
& `, a3 ~5 |, J7 k2 X U. y目前,我已经完成了合约的编写和编译,接下来我计划:
* ~* u7 H; V! q$ K+ V. n# H0 b4 }
& B& g! {! q* Q+ q x& N$ O+ S- 测试:用 Blueprint 的 Sandbox 在本地测试合约逻辑。
- 部署:将合约部署到 TON 测试网,验证实际效果。 这些内容会在后续教程中分享,敬请期待!
U4 _6 f2 M# H% q8 R
! X3 x/ v j* k+ g9 t总结: ?3 B8 r) T% b/ A5 p2 F* u
这篇教程展示了如何用 FunC 和 Blueprint 开始一个 TON 红包合约项目。到目前为止,我成功搭建了项目、编写了合约并编译通过。完整代码和文件夹结构都已分享,你可以跟着试试。如果你也想尝试 TON 开发,不妨从这个简单项目入手。有什么问题,欢迎留言交流! $ z! F9 J2 Z; m. f! P
1 Z2 V$ {& J: f1 V% Q; _ |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
x
|