|
本帖最后由 riyad 于 2025-3-20 00:45 编辑 : T% `- R7 h4 U' ^) F- f
7 ~2 V) c: v: `7 K Y1 Z T o L
今天,我将带大家一起探索如何在 TON 区块链上用 FunC 语言和 Blueprint 工具开发一个红包智能合约。这篇教程是第一部分,重点介绍项目搭建、合约编写和成功编译的过程。我会分享完整的代码和项目结构,适合对 TON 开发感兴趣的新手。让我们开始吧! 4 m2 O. {6 d5 a! d6 E# T) F
什么是 TON 和红包合约? TON(The Open Network)是一个高效的区块链平台,以低成本和高吞吐量著称,非常适合开发去中心化应用。红包是中国文化中常见的分享方式,我想通过智能合约实现一个简单功能:用户存入 TON,指定份数,其他人可以领取。这篇文章将展示我到目前为止的进展——编写并成功编译合约。 2 G- J/ {2 `6 q" T
准备工作 在动手之前,确保你的环境准备好: 安装 Node.js:Blueprint 依赖 Node.js,建议使用最新 LTS 版本,从 nodejs.org 下载安装。 安装 Blueprint:在终端运行以下命令全局安装: 代码编辑器:我用的是 VS Code,安装 TON FunC 插件可以高亮语法,提升编码体验。 步骤 1:创建项目 我们用 Blueprint 初始化一个 TON 项目。打开终端,输入: 项目名称:我输入了 redpacket。 模板:选择 func-empty,因为我想从头写合约。 创建完成后,进入项目目录: 项目文件夹结构 Blueprint 生成的项目结构非常清晰,以下是 redpacket 文件夹的内容: + i/ {( I" c7 \6 z- m
* {3 ?: v$ h: |! D步骤 2:编写红包合约代码 在 contracts/redpacket.fc 中,我编写了红包智能合约。以下是代码和详细注释: - #include "imports/stdlib.fc";
% `1 x; B7 ?+ s/ V$ z- y0 K" F
/ t! A4 H% \6 G& W2 G$ [3 H2 w0 |- // 准备内部消息的函数
. k! g- D! V+ t* g# ] - (cell) prepare_internal_msg(slice recipient, int amount, int bounce) {
3 {5 v( k& [- G; P+ ? - return begin_cell()8 D1 u3 |' W: H x9 v! _) r
- .store_uint(0, 4) ;; 内部消息标记 (0x0)
9 e2 m; l! X$ l3 } - .store_slice(recipient) ;; 目标地址2 @# ^5 t( H7 Z7 R, M* O; c/ h- C" |8 r6 s
- .store_coins(amount) ;; 发送金额
: f6 E& o! i' ]# F( Z J - .store_uint(bounce, 1) ;; 是否可退回标志
) x% }- }1 a* d. M- {1 | - .store_uint(0, 1 + 4 + 4) ;; ihr_disabled: 0, 无源地址和目标地址扩展
F3 p! j: a& x* k- N - .store_uint(0, 64) ;; ihr_fee
) w- A/ x6 K9 _ - .store_uint(0, 64) ;; fwd_fee
) g% x1 Y' a% ] - .store_uint(0, 64) ;; 创建逻辑时间
% d, W. b2 K8 M0 U2 J+ \' C - .store_uint(0, 64) ;; 创建时间戳% U5 g7 k( s8 Q6 P9 Q) r- X
- .store_uint(0, 1) ;; 无状态初始化. G: p) Q0 b0 m4 l) t, k* N% |1 A
- .store_uint(0, 1) ;; 消息体为空/ C1 J' h! j+ d1 B$ z
- .end_cell();
! A% H, ?* Y* \9 y2 [ - }4 ]" z5 }! o( X3 \
" @, d, w" V# b5 _* a8 g3 \2 S- // 处理内部消息的主函数8 r: M% k) V! m6 ^
- () recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {* z2 x0 Y4 y! F3 U4 d
- ;; 检查合约是否有余额可用
& D* a- A5 ]/ E2 u* K1 L - if (my_balance <= 0) { throw(100); }
5 u' M( g( a4 Q: v! [9 P; n1 Y; I
' j. J! | e m' M- ;; 解析传入消息
) \) E) ~: q- k" j - var parser = in_msg_full.begin_parse();
- m, G4 n) G) C u* c1 G - int flags = parser~load_uint(4);: z3 n/ B4 W( i4 e# i6 J$ e
- slice sender_address = parser~load_msg_addr();
% M6 O! B0 l! q/ ^* @3 }; b - int sender_hash = sender_address.slice_hash();- R3 F4 `/ w0 o9 C
- * i2 q7 L7 U0 F- \- K: k
- ;; 加载持久化存储(红包字典)
; D3 |* [& h; {1 l - var data = get_data();$ ~7 [% `' A1 d: _
- cell red_packets = new_dict();
+ v( ^1 g( k( Q - if (~ slice_empty?(data.begin_parse())) {
" i9 ~6 r8 q# |- l6 e: v. x9 T - var data_slice = data.begin_parse();" K, m* O1 N, U( Z4 _) W7 u
- red_packets = data_slice~load_dict();
1 j( Q, Z2 E+ u0 i4 r. j - }6 \- ~' d7 }/ l
+ Y8 U. |) h+ k3 a( A3 A; @- ;; 检查消息体是否有操作(创建或领取)$ U/ P0 Y+ H' t1 M& X" D
- if (~ slice_empty?(in_msg_body)) {; ^% K! s: Y1 F/ Z( s, c% I7 ~
- var body_parser = in_msg_body;
1 s9 w z' y Y' Z: k" W g - int op = body_parser~load_uint(8); ;; 操作码:1 = 创建,2 = 领取
4 S6 W1 P7 e5 \7 S# } - int red_packet_id = body_parser~load_uint(256); ;; 红包唯一ID
4 M4 |- G* U; U% G9 x+ L - ! C0 A& H6 r( S3 R' @
- if ((op == 1) & (msg_value > 0)) { ;; 创建新红包/ B3 U4 e- y+ d7 J" u5 K
- int claim_amount = body_parser~load_int(256); ;; 发送者指定每份领取金额* Z6 t R! Q' u/ m; M
- if ((claim_amount <= 0) | (claim_amount > msg_value)) { throw(103); } ;; 无效金额检查
" J6 N! c/ s( j- ~ z2 X/ u
- w5 ^' } x5 d4 b- var packet_data = begin_cell()
+ {# E6 ^+ p; Z0 e, a - .store_int(msg_value, 256) ;; 红包总金额
7 w/ w6 g. ~4 d/ u - .store_int(claim_amount, 256) ;; 每份领取金额
& f' R, x; x3 n2 |1 H8 W7 t! p( k% ]3 t - .store_dict(new_dict()) ;; 空领取记录字典+ S8 j2 d7 q- G! z
- .end_cell();
) u9 N" g" y1 B/ h3 | - + n. \% X% }2 S7 Z
- red_packets = udict_set_ref(red_packets, 256, red_packet_id, packet_data);1 U: z3 I5 S# m( }0 Q
- set_data(begin_cell().store_dict(red_packets).end_cell());- h6 t' E3 ^3 n1 A1 \8 z8 }
- return ();
6 g, E$ W0 G3 U, \3 Z7 Z1 i4 s1 G+ f - } else {
8 k5 L" H4 b) W' L& } - if (op == 2) { ;; 领取红包
0 r& S* q! f2 i# [ - var (packet_cell, exists) = udict_get_ref?(red_packets, 256, red_packet_id);" \$ f# z1 r- }
- if (exists == 0) { throw(104); } ;; 红包不存在1 K- b4 {& w% }, M5 }9 d8 L
. R$ ]! k3 Y( k4 s' U* `) f7 P- var packet_parser = packet_cell.begin_parse();
! |9 b2 |0 U( i3 [* U- K3 S9 w7 A - int stored_balance = packet_parser~load_int(256);* i; g8 R. e7 b9 @: y* X
- int claim_amount = packet_parser~load_int(256);3 v7 t; C8 {( {0 x7 \% G4 D
- cell claimed = packet_parser~load_dict();
V) B' K( t1 d- U
5 G- U( k! U! N6 V2 K- ;; 检查发送者是否已领取; J$ ]7 x2 A( _ N9 _! H) D6 |
- var (claimed_value, claimed_exists) = udict_get_ref?(claimed, 256, sender_hash);% |9 V3 d6 a: I8 |7 ]& q+ F
- if (claimed_exists == 1) { throw(101); } ;; 已领取过
) f, N; \) a6 t- d - - [& v' I1 z/ q0 | [5 H
- ;; 检查余额是否足够% j ~1 ~, A) e
- if (stored_balance < claim_amount) { throw(102); } ;; 余额不足- |) N/ G5 H) Q, F# Q+ z
" {4 u2 b' H2 k. q7 \- ;; 更新红包状态6 n; K0 ]% N8 R4 g8 |
- stored_balance = stored_balance - claim_amount;
/ M4 s1 o- Q' a4 _ - var value_cell = begin_cell().store_int(1, 8).end_cell();5 t% S; S& R" A" U) r! u8 K
- claimed = udict_set_ref(claimed, 256, sender_hash, value_cell);
% x0 C1 i/ d: N4 b' J9 n - . Z q/ T/ P7 o# D3 w# z, s
- ;; 保存更新后的红包数据- |! k2 i- B, X( y# x7 T0 t x
- red_packets = udict_set_ref(red_packets, 256, red_packet_id,
4 F( R- n! s' M+ f) z% T' g - begin_cell()
1 q: m* n5 b( p0 v: m3 D$ ] - .store_int(stored_balance, 256)
9 A# k4 H' s @, |$ \ - .store_int(claim_amount, 256)5 D+ C( P) ^' U0 V5 G0 T7 F* n
- .store_dict(claimed)
' r/ x* H; m) M+ f2 n% n+ [ - .end_cell());
5 a! h& L+ T9 n: J) ^% B/ e7 k - W' Q, C: C6 E( t' e% j- p& e$ V
- set_data(begin_cell().store_dict(red_packets).end_cell());
& i X- o- W1 E* g+ A& Y, z - . q2 p+ i% D$ h8 ]& v# x
- ;; 将领取金额发送给用户- N2 y7 k/ ~( \8 f5 a! n3 I
- send_raw_message(8 l# y. x M- m6 i7 u
- prepare_internal_msg(sender_address, claim_amount, 1),' a8 E- ^- o2 K
- 64 ;; 单独支付 gas 费用* |$ [4 ]; Q' a! |
- );
N; I- Y+ i! U - return ();: K6 v# ~" o# [) D/ a
- }! @6 A8 v* l1 w
- }
8 ^( m' e' |9 L3 ]7 ] - }8 t" j4 W7 T$ }6 ~
- throw(105); ;; 无效消息(未指定操作)
9 T5 q- L6 R- S E - }
; d# R9 Z- B4 C {5 M' j
! D7 C1 W) Y% l; w- // 查询红包状态的外部方法, S0 U: B& v0 Q/ m. ^3 w' \
- ([int, int, cell]) get_red_packet_data(int red_packet_id) method_id {* |2 T% ] v9 x2 f5 B* k, L
- var data = get_data();
: K4 j: T1 W1 A: U/ R8 j - cell red_packets = new_dict();0 N& y' S4 |* X/ A
- if (~ slice_empty?(data.begin_parse())) {2 H: R7 [6 c2 F& @3 Q% O
- var data_slice = data.begin_parse();
2 e* {; S! [. _" s3 m' J0 L& g: p" G - red_packets = data_slice~load_dict();
2 [( e) r4 U. e; r7 H - }
4 i. E# E' P- t" E
5 U; G5 e8 i+ ^; W- var (packet_cell, exists) = udict_get_ref?(red_packets, 256, red_packet_id);% _) W2 o) k0 k: x
- if (exists == 0) {
7 F" U7 I8 s2 S! i - return [0, 0, new_dict()];- T+ [. _; H4 ]% e+ U4 b2 j" ^
- }
7 F. `8 I& o' \0 d8 U+ A; G; r - . q0 L: l: `) X) [$ o
- var packet_parser = packet_cell.begin_parse();
4 h8 o* V0 G U( J1 l: n - int stored_balance = packet_parser~load_int(256);
# u- O3 M9 o( ]4 M' m0 O0 O - int claim_amount = packet_parser~load_int(256);/ i! b3 s: l Q# h
- cell claimed = packet_parser~load_dict();2 |& j# A0 Z2 ^& ]8 Y- X+ {* G; R
- return [stored_balance, claim_amount, claimed];+ i. G! Y! G9 n2 p6 d3 j- _+ S3 ~
- }
复制代码代码说明 1. prepare_internal_msg 函数 这个函数用于构造一个内部消息,用于将 TON 转账给领取者。 5 a9 {9 k1 x) B; Z; k2 O" r
功能:生成一个标准的 TON 内部消息单元(cell),包含目标地址、金额和是否可退回等信息。 参数: - recipient:目标地址(slice 类型)。
- amount:转账金额(单位:nanoTON)。
- bounce:是否可退回(1 表示可退回,0 表示不可退回)。
/ o5 P0 N ^- S0 W* ^. x
实现: - 使用 begin_cell() 创建一个新的单元。
- 按 TON 消息格式依次存储:消息标记(0x0)、目标地址、金额、可退回标志等。
- 其他字段(如手续费、时间戳等)设为默认值 0。
' G* {) k/ b! L; Z" Z. f
用途:在领取红包时,将金额发送给用户时调用。
' l# V7 ~+ |; E; x7 s1 |" P! S) c4 P2. recv_internal 函数 这是合约的核心函数,处理所有内部消息,实现创建和领取红包的逻辑。 4 [1 T/ ]$ g( ]& r0 h8 U
功能:根据消息内容执行红包的创建或领取操作。 参数: - my_balance:合约当前余额。
- msg_value:消息附带的 TON 金额。
- in_msg_full:完整消息单元。
- in_msg_body:消息体(操作指令)。9 F2 }7 n I7 \/ p9 t1 {0 r$ _; Q
4 @+ R: q) h2 u( g4 }7 I( d! ?实现: - 余额检查:如果合约余额小于等于 0,抛出错误(代码 100)。
- 解析消息:提取发送者地址并计算其哈希值,用于记录领取状态。
- 加载存储:从合约持久化存储中读取红包字典(red_packets),如果为空则初始化一个新字典。
- 处理操作:. [( E5 F: c0 S+ W. l$ k4 K$ w
创建红包(op == 1): - 检查消息附带金额是否大于 0。
- 从消息体读取每份领取金额(claim_amount),并验证其有效性(大于 0 且不超过总金额)。
- 创建红包数据(总金额、每份金额、空领取记录),存储到字典中。
- 更新合约存储。
5 V+ |0 j8 ]/ @' h9 m2 x( t
领取红包(op == 2): - 根据红包 ID 从字典中查找红包数据,若不存在则抛出错误(代码 104)。
- 解析红包数据:总余额、每份金额、已领取记录。
- 检查发送者是否已领取(若已领取则抛出错误 101)。
- 检查余额是否足够(不足则抛出错误 102)。
- 更新红包状态:扣除领取金额,记录发送者为已领取。
- 保存更新后的红包数据。
- 调用 prepare_internal_msg 发送领取金额给用户。
2 O, t$ Q) f- }" }$ L# x+ ^0 H
错误处理:如果消息体为空或操作无效,抛出错误(代码 105)。
9 f M/ K( c, D( R3. get_red_packet_data 函数 这是一个外部查询方法,用于查看某个红包的状态。 功能:返回指定红包的总余额、每份金额和领取记录。 参数: - red_packet_id:红包唯一 ID。
p/ t6 Y" c* p+ F2 ` {1 k
返回值: - [stored_balance, claim_amount, claimed]:分别为红包剩余金额、每份领取金额和已领取记录字典。- v3 u3 i. b9 P2 [+ C! {! \
实现: - 从合约存储中读取红包字典。
- 根据红包 ID 查找对应数据,若不存在则返回默认值 [0, 0, new_dict()]。
- 解析红包数据并返回。8 Y* ^% }% i( p
2 J. d3 E* \! H6 Q" B这段代码实现了一个支持多红包的智能合约: 6 _) |9 O8 j+ w, t
- 用户可以用操作码 1 创建一个新红包,指定每份金额。
- 用户可以用操作码 2 领取指定红包,每次领取固定金额。
- 合约通过字典存储多个红包的状态,并防止重复领取。
- 提供外部查询方法查看红包详情。0 W0 W" \+ d) E! N6 k x5 Y% W
L+ h# Y- e, ?$ A+ I. E: b) Q3 q' d8 d" F# W+ h/ [
步骤 3:编译合约编写完成后,我运行以下命令编译代码:
1 f% c4 s7 c1 C6 p终端显示“Compilation successful”,证明代码语法正确,生成了字节码和 ABI 文件。以下是我编译成功的截图:
A9 D) `3 R/ l( V, @( j/ H6 ?$ k
下一步计划3 [# d/ x0 r1 W/ _3 ^
目前,我已经完成了合约的编写和编译,接下来我计划:
) L, p; Z+ D8 w! g
: X" F& s0 C' j" w2 o1 @5 Z- 测试:用 Blueprint 的 Sandbox 在本地测试合约逻辑。
- 部署:将合约部署到 TON 测试网,验证实际效果。 这些内容会在后续教程中分享,敬请期待!
& f7 ]& d c: R% |$ U
* U) a+ K$ A; e" p1 C# |' O N总结
" A- ]( j( D! J' U& I( |5 g这篇教程展示了如何用 FunC 和 Blueprint 开始一个 TON 红包合约项目。到目前为止,我成功搭建了项目、编写了合约并编译通过。完整代码和文件夹结构都已分享,你可以跟着试试。如果你也想尝试 TON 开发,不妨从这个简单项目入手。有什么问题,欢迎留言交流! 8 \0 p2 T! d( ~
( ?- H+ v4 U$ n" j: [/ R |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
x
|