|
本帖最后由 riyad 于 2025-3-20 00:45 编辑 5 f* G2 F7 e- R( a* @) c$ M
, L( Q' _& _$ k( @$ \
今天,我将带大家一起探索如何在 TON 区块链上用 FunC 语言和 Blueprint 工具开发一个红包智能合约。这篇教程是第一部分,重点介绍项目搭建、合约编写和成功编译的过程。我会分享完整的代码和项目结构,适合对 TON 开发感兴趣的新手。让我们开始吧!
. [8 {9 a+ R8 |0 d5 F( r0 N5 k什么是 TON 和红包合约? TON(The Open Network)是一个高效的区块链平台,以低成本和高吞吐量著称,非常适合开发去中心化应用。红包是中国文化中常见的分享方式,我想通过智能合约实现一个简单功能:用户存入 TON,指定份数,其他人可以领取。这篇文章将展示我到目前为止的进展——编写并成功编译合约。 3 z A+ l1 T1 o6 o0 f
准备工作 在动手之前,确保你的环境准备好: 安装 Node.js:Blueprint 依赖 Node.js,建议使用最新 LTS 版本,从 nodejs.org 下载安装。 安装 Blueprint:在终端运行以下命令全局安装: 代码编辑器:我用的是 VS Code,安装 TON FunC 插件可以高亮语法,提升编码体验。 步骤 1:创建项目 我们用 Blueprint 初始化一个 TON 项目。打开终端,输入: 项目名称:我输入了 redpacket。 模板:选择 func-empty,因为我想从头写合约。 创建完成后,进入项目目录: 项目文件夹结构 Blueprint 生成的项目结构非常清晰,以下是 redpacket 文件夹的内容:
5 \# y$ W6 ?2 c, ^* {7 c5 V ' D" O7 T" I( z/ [- _6 v0 r
步骤 2:编写红包合约代码 在 contracts/redpacket.fc 中,我编写了红包智能合约。以下是代码和详细注释: - #include "imports/stdlib.fc";& s: l+ Q( R. a d6 C
/ w% T5 r/ h) ]% Y- // 准备内部消息的函数
) y9 P5 D1 Y0 l( Z9 N6 C - (cell) prepare_internal_msg(slice recipient, int amount, int bounce) {
: ~# S# |& V) m" o- c1 D - return begin_cell()) [& ]% Z; I; p+ _' b; W5 p# x5 T
- .store_uint(0, 4) ;; 内部消息标记 (0x0)
4 G H0 S1 b' }: F" I. |- B0 v0 s - .store_slice(recipient) ;; 目标地址7 P/ p6 Y' a5 P9 b' J
- .store_coins(amount) ;; 发送金额
5 H" B0 B% l& f2 m7 x+ Q' b+ e - .store_uint(bounce, 1) ;; 是否可退回标志
+ [% _4 O$ i. V5 P S F1 N - .store_uint(0, 1 + 4 + 4) ;; ihr_disabled: 0, 无源地址和目标地址扩展& x4 I3 A7 b$ }* Q2 v! m; G
- .store_uint(0, 64) ;; ihr_fee1 i8 m1 ^2 O* L' W9 J2 {7 ?
- .store_uint(0, 64) ;; fwd_fee [1 Y; a- A4 }, ?9 L
- .store_uint(0, 64) ;; 创建逻辑时间
+ r9 {3 j* [5 q9 F9 z7 v4 K$ |4 @% I - .store_uint(0, 64) ;; 创建时间戳- G/ \( S; g/ n
- .store_uint(0, 1) ;; 无状态初始化- L" T( o5 J6 s* V* f
- .store_uint(0, 1) ;; 消息体为空
6 P6 W/ f+ r B9 X9 } - .end_cell();
8 x* A" L0 y# h# L+ ~$ u - }! N& a% X5 Q( r6 A, e9 A5 L
7 L) |: k, u2 R0 L8 O8 j- // 处理内部消息的主函数& ]+ m5 q8 v4 k
- () recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {
9 i, @9 @1 t1 F7 n4 V% H% W4 h - ;; 检查合约是否有余额可用
0 C& m: C$ Z* g H8 I4 P3 o - if (my_balance <= 0) { throw(100); }/ ]' W9 U6 x2 G$ g# I
- Q& p* S. c# v- ;; 解析传入消息
9 i$ V; V# h: N F6 J* U0 P- D - var parser = in_msg_full.begin_parse();9 W: g# ?% N4 U; k4 }# F
- int flags = parser~load_uint(4);
( i, b5 s8 F3 { v" _1 U& i - slice sender_address = parser~load_msg_addr();( V6 K# r7 P. M ~& e9 E( r
- int sender_hash = sender_address.slice_hash();# ]3 p3 K4 \3 [1 r: z
( A0 @# y* g/ c! u/ O% A* }- ;; 加载持久化存储(红包字典)
; a( C+ b" O' ]! z* b E - var data = get_data();
1 g2 m9 R, Y" Y* l1 c) z - cell red_packets = new_dict();# ?, D0 Z& w0 D* j8 i2 J" m! ]9 a
- if (~ slice_empty?(data.begin_parse())) {
' R/ p0 m w. q* |2 l - var data_slice = data.begin_parse();, r4 N0 B% Y; c8 n% ~
- red_packets = data_slice~load_dict();& s) N- u% x, z) u5 q
- }4 w4 i7 o7 b) d* Y$ |
8 G3 p! c! k% E3 l# j7 W0 G$ Y# d5 X- ;; 检查消息体是否有操作(创建或领取)+ s) W1 N5 V( ]& b
- if (~ slice_empty?(in_msg_body)) {
/ M6 n8 N0 a0 u4 i# l2 A - var body_parser = in_msg_body;* x7 W7 p/ V$ B1 q3 X# ^( D
- int op = body_parser~load_uint(8); ;; 操作码:1 = 创建,2 = 领取
# K( s$ ]" u0 N- _9 m S: r$ g+ V - int red_packet_id = body_parser~load_uint(256); ;; 红包唯一ID* H4 b3 D$ e$ D7 ~2 r$ _
- ! l" t; {, Q* Z' Q! T, T
- if ((op == 1) & (msg_value > 0)) { ;; 创建新红包
5 |* g R5 u! I/ r; t6 K - int claim_amount = body_parser~load_int(256); ;; 发送者指定每份领取金额7 d# @- w4 c: p5 f, k( U1 w
- if ((claim_amount <= 0) | (claim_amount > msg_value)) { throw(103); } ;; 无效金额检查
1 u& s0 t8 q! g- C
$ t2 ]' b% {( e" V, s2 \( V* F- var packet_data = begin_cell()2 F' b' O+ q2 E& }4 E
- .store_int(msg_value, 256) ;; 红包总金额! ^; ~4 {5 X4 j, q0 H
- .store_int(claim_amount, 256) ;; 每份领取金额: b; s* i5 H0 ]; g1 l5 S
- .store_dict(new_dict()) ;; 空领取记录字典
6 @. _' h5 k6 h- \8 }5 l: ^ - .end_cell();
& p+ i6 Z: L+ k. b& S - 0 F7 c: [6 X9 d+ {/ s" p: K. ?5 p" G
- red_packets = udict_set_ref(red_packets, 256, red_packet_id, packet_data);/ }5 y" V: K2 Q9 }
- set_data(begin_cell().store_dict(red_packets).end_cell());
8 O# T$ N8 L* s0 Q) J! } - return ();
+ k3 }0 F \# e. B% i C C - } else {+ S% R# R. H! A# e# v# P- U o
- if (op == 2) { ;; 领取红包0 {7 r# i5 Z: y: A
- var (packet_cell, exists) = udict_get_ref?(red_packets, 256, red_packet_id);
2 X: \8 b) X% S9 J0 N - if (exists == 0) { throw(104); } ;; 红包不存在
( ?7 m1 B3 O: r/ @( b" P
6 J D9 B' n* M, O. j, i& W+ c! r+ P- var packet_parser = packet_cell.begin_parse();* g4 }+ D/ Z8 `
- int stored_balance = packet_parser~load_int(256);
7 L6 q7 \, o' _% E* P" f - int claim_amount = packet_parser~load_int(256);
6 e7 D. _4 v* Z/ @/ R( { - cell claimed = packet_parser~load_dict();
& N: U( `% s0 d) _( N' O
( T5 ?' z" b1 [& W+ @8 v1 h- ;; 检查发送者是否已领取
: N, c8 A2 P1 t( y" o+ z/ N7 l3 U3 Z5 Q - var (claimed_value, claimed_exists) = udict_get_ref?(claimed, 256, sender_hash);
: u" l2 w& n/ A6 S - if (claimed_exists == 1) { throw(101); } ;; 已领取过
; \ u5 j8 y1 P/ V8 ?- q8 H( K
8 [/ E4 U4 Z8 G& D- b! t- ;; 检查余额是否足够, l# ]% ~) a& \. v% x+ {
- if (stored_balance < claim_amount) { throw(102); } ;; 余额不足
" {1 ]# z1 u/ m( K* a
, v9 w k" `& L- ;; 更新红包状态
. c* B- K) ^4 Z6 P! x - stored_balance = stored_balance - claim_amount;
9 Y/ q0 A; y1 h! b5 p6 h - var value_cell = begin_cell().store_int(1, 8).end_cell();
3 u/ X; \- [8 u2 M: w - claimed = udict_set_ref(claimed, 256, sender_hash, value_cell);
1 S* l. o# o: e
) ]) D. P3 X3 K' B9 [3 A, u1 \; x: M0 D- ;; 保存更新后的红包数据
' \3 g+ i; ]' X$ ` - red_packets = udict_set_ref(red_packets, 256, red_packet_id, - L& S7 [4 }& S7 o/ I( f) F
- begin_cell()- z0 p! ]0 b. I$ H" E
- .store_int(stored_balance, 256)! l4 X8 A9 g" I, |: ?" ^0 D2 q H0 X( j
- .store_int(claim_amount, 256)
" J3 q- ?& V: p ]' e7 C - .store_dict(claimed)+ \) J; i5 x# R: J) R
- .end_cell());
. T! R) y) ?" c4 s - ( L4 Q+ y q8 G' \' l6 N% F8 z j
- set_data(begin_cell().store_dict(red_packets).end_cell());. Y6 f ]) J/ K
$ i% n' x0 W& w5 T6 R4 g: G/ H- ;; 将领取金额发送给用户/ l" ~2 S$ y' {
- send_raw_message(
" ]+ l2 a, X& P/ ^% U. q - prepare_internal_msg(sender_address, claim_amount, 1),
: _- E# B) i* O: d, h - 64 ;; 单独支付 gas 费用
" r/ V0 F8 ]" e9 q8 X - );
7 |" d, J, O3 G/ V7 e" P - return ();
7 }" `; l! e" g% p2 j$ d9 \+ h - }' S% _9 ^1 N1 d
- }
+ j; T7 k$ w* f - }% G0 H; p4 v" V, Z
- throw(105); ;; 无效消息(未指定操作)+ Z! l9 w$ \" Q1 x [
- }
8 K& t0 H9 S3 ]! |8 \5 V% U: U2 i: V - 0 b0 S. M+ F) [) k, S; D
- // 查询红包状态的外部方法
2 ~6 C+ k) `) N& o - ([int, int, cell]) get_red_packet_data(int red_packet_id) method_id {3 @3 X u' \4 L( O" y# v D
- var data = get_data();
. @! R. x0 G0 K- u1 O$ |% s4 U) K& \ - cell red_packets = new_dict();8 M0 @( C# Z) C; H$ t
- if (~ slice_empty?(data.begin_parse())) {
7 d% F. v5 f+ y; I - var data_slice = data.begin_parse();4 {, [( o" F: k6 s7 l
- red_packets = data_slice~load_dict();1 E3 M7 t2 _" _5 E) [
- }
' x- w/ q' [8 N% _
6 {2 v5 Y/ p# a: j( w M" s) m- var (packet_cell, exists) = udict_get_ref?(red_packets, 256, red_packet_id);$ P" ~- R/ e; a$ J; A
- if (exists == 0) {
" `) B$ M3 u5 g1 F5 x' k7 w - return [0, 0, new_dict()];2 g& A) N; B. n2 D" Y6 R. S. \
- }$ [( [( v, N( K; R9 K0 |
! V. w! d4 j& N: l" Q- var packet_parser = packet_cell.begin_parse();
" L/ Y5 L: M5 c- L) q' M - int stored_balance = packet_parser~load_int(256);- H5 h" [; Y7 J$ y, \ @6 L
- int claim_amount = packet_parser~load_int(256);
0 o3 k" k7 z, M/ d, ] - cell claimed = packet_parser~load_dict();
! z1 ]5 m) M$ E: R9 U) k - return [stored_balance, claim_amount, claimed];" F# @: v: r" n$ Q! B
- }
复制代码代码说明 1. prepare_internal_msg 函数 这个函数用于构造一个内部消息,用于将 TON 转账给领取者。
) `: a5 [1 F/ h9 y9 k+ o8 U8 Q功能:生成一个标准的 TON 内部消息单元(cell),包含目标地址、金额和是否可退回等信息。 参数: - recipient:目标地址(slice 类型)。
- amount:转账金额(单位:nanoTON)。
- bounce:是否可退回(1 表示可退回,0 表示不可退回)。
1 l3 y. |+ V, s! C+ b
实现: - 使用 begin_cell() 创建一个新的单元。
- 按 TON 消息格式依次存储:消息标记(0x0)、目标地址、金额、可退回标志等。
- 其他字段(如手续费、时间戳等)设为默认值 0。
0 C X# z+ m: Y- f4 x
用途:在领取红包时,将金额发送给用户时调用。
. A# W) _. I8 P2 |+ `) S( C( _/ H# Y2. recv_internal 函数 这是合约的核心函数,处理所有内部消息,实现创建和领取红包的逻辑。
: P; c1 l) T+ v G7 N2 j功能:根据消息内容执行红包的创建或领取操作。 参数: - my_balance:合约当前余额。
- msg_value:消息附带的 TON 金额。
- in_msg_full:完整消息单元。
- in_msg_body:消息体(操作指令)。- H) K3 Y8 J: u; |
& ^. h; J9 p' s: [实现: - 余额检查:如果合约余额小于等于 0,抛出错误(代码 100)。
- 解析消息:提取发送者地址并计算其哈希值,用于记录领取状态。
- 加载存储:从合约持久化存储中读取红包字典(red_packets),如果为空则初始化一个新字典。
- 处理操作:6 o' o6 J+ a5 j9 X
创建红包(op == 1): - 检查消息附带金额是否大于 0。
- 从消息体读取每份领取金额(claim_amount),并验证其有效性(大于 0 且不超过总金额)。
- 创建红包数据(总金额、每份金额、空领取记录),存储到字典中。
- 更新合约存储。
. l( A/ p d4 b& t% m9 V/ r
领取红包(op == 2): - 根据红包 ID 从字典中查找红包数据,若不存在则抛出错误(代码 104)。
- 解析红包数据:总余额、每份金额、已领取记录。
- 检查发送者是否已领取(若已领取则抛出错误 101)。
- 检查余额是否足够(不足则抛出错误 102)。
- 更新红包状态:扣除领取金额,记录发送者为已领取。
- 保存更新后的红包数据。
- 调用 prepare_internal_msg 发送领取金额给用户。
; e8 Q% |3 A+ v0 Q. j, B2 z
错误处理:如果消息体为空或操作无效,抛出错误(代码 105)。
, y7 l/ O/ w, k9 t3. get_red_packet_data 函数 这是一个外部查询方法,用于查看某个红包的状态。 功能:返回指定红包的总余额、每份金额和领取记录。 参数: - red_packet_id:红包唯一 ID。
" L+ Q r7 ?9 A7 c+ U* P% w' [, I
返回值: - [stored_balance, claim_amount, claimed]:分别为红包剩余金额、每份领取金额和已领取记录字典。$ l% \" P) I7 h
实现: - 从合约存储中读取红包字典。
- 根据红包 ID 查找对应数据,若不存在则返回默认值 [0, 0, new_dict()]。
- 解析红包数据并返回。$ X( I; y# S }( \, N4 V1 E
/ ]+ \7 L, I+ @0 a这段代码实现了一个支持多红包的智能合约:
4 G& [! |6 ~ h+ Z' ~- 用户可以用操作码 1 创建一个新红包,指定每份金额。
- 用户可以用操作码 2 领取指定红包,每次领取固定金额。
- 合约通过字典存储多个红包的状态,并防止重复领取。
- 提供外部查询方法查看红包详情。& e6 i1 M6 l' f. ]
; w, L1 V4 e0 I l6 e$ c( T( Z( M9 p# j# q! j
步骤 3:编译合约编写完成后,我运行以下命令编译代码:
/ B A% P7 S4 \( X: r% C终端显示“Compilation successful”,证明代码语法正确,生成了字节码和 ABI 文件。以下是我编译成功的截图: 7 C2 `0 Q% v: H8 t% X' _
' I9 E/ J$ A8 G$ y3 b
下一步计划+ J% I, { X5 H6 K' Y5 D5 U3 S- |
目前,我已经完成了合约的编写和编译,接下来我计划: & G3 c" z8 V) c" q' ^- V) {. q
C: z. @* K8 H! b$ k0 n
- 测试:用 Blueprint 的 Sandbox 在本地测试合约逻辑。
- 部署:将合约部署到 TON 测试网,验证实际效果。 这些内容会在后续教程中分享,敬请期待!. }, U8 u! a/ \9 w
' ?' ^, q2 `' a. c6 U7 V$ N2 L总结; [# K; x2 l5 A$ {3 i' y$ ^
这篇教程展示了如何用 FunC 和 Blueprint 开始一个 TON 红包合约项目。到目前为止,我成功搭建了项目、编写了合约并编译通过。完整代码和文件夹结构都已分享,你可以跟着试试。如果你也想尝试 TON 开发,不妨从这个简单项目入手。有什么问题,欢迎留言交流!
' o$ w% U; T' O- C6 p R/ ^" N" t3 u; A0 r4 p( [
|
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
x
|