大家还记得吧,在我们开始 FunC 编程之旅的时候,我们很快就注意到有一个很棒的库/工具叫做Blueprint Blueprint 由 TonTech 团队维护,并得到 TON 基金会的正式支持。我们从零开始创建了自定义脚本,以确保您完全了解 Blueprint 生成的项目中到底发生了什么。 我们甚至建议您使用以下命令启动每个新项目: - npm create ton@latest
7 H4 k& Q0 q2 M+ _0 G: Z
复制代码该命令将使用 Blueprint 为你生成一个新项目,其中包含我们在第 3 章中介绍的所有阶段的代码,也涵盖了合约开发生命周期。 在本章中,我们不会稍微优化项目的设置,因为它的工作方式与 Blueprint 生成的项目完全相同。也就是说,我们将在编译和部署方面稍微更新我们的项目,使其使用从 Blueprint 导入的组件。
r, W% x; Z. A% T将编译过程委托给Blueprint 首先 - 我们需要更新我们的 package.json 文件. 从现在起,我们只在这里留下一个脚本 test. 我们这样做是因为,如前所述,编译和部署将由从 Blueprint 导入的组件处理。 脚本的一部分 package.json 文件现在看起来会是这样: - "scripts": {9 k( t% J! y2 P1 b0 Z: n
- "test": "jest",
; M7 c4 f7 C) f; J' X - }
复制代码但是,如果我们运行 yarn test 命令,它只会在合约上运行测试,而不会预先编译。让我们来解决这个问题。 正如我之前提到的,我们将使用 Blueprint 的编译套件,所以让我们安装这个神奇的库吧: - yarn add @ton/blueprint --dev
4 s) m) i, `6 I5 k8 b# j
复制代码在我们的 tests/main.spec.ts 文件中,我们必须做一些重要的更新。这就是之前的样子(在实际测试之前,我们在此仅引用开始部分): 2 g% t7 {6 x2 U/ V9 C; {) o
- import { Cell, toNano } from "ton-core";3 I: z' r" y4 V; \, t
- import { hex } from "../build/main.compiled.json";
8 S. L" S) W3 b7 {& O& m( B - import { Blockchain, SandboxContract, TreasuryContract } from "@ton/sandbox";
3 I7 P, k2 p3 f+ `: o7 \: ]* i; r - import { MainContract } from "../wrappers/MainContract";4 ~: E) Q' Y' ^1 Z- p: ]
- import "@ton/test-utils";
7 M y0 S u+ y7 q7 r) }
2 i) S" h# l+ L1 E' J0 s$ J- describe("main.fc contract tests", () => {
8 `: p! k2 u; p) T* d - let blockchain: Blockchain;5 f6 {: X" d/ J* h1 i! j$ f
- let myContract: SandboxContract<MainContract>;% B0 R$ h8 ~# G: V9 N, ~
- let initWallet: SandboxContract<TreasuryContract>;) `: |( r+ G" @
- let ownerWallet: SandboxContract<TreasuryContract>;, n7 X3 J$ n1 M. s
( }$ t5 o$ `& V: f+ u) W- E9 H$ q- beforeEach(async () => {
. W1 p! m& s7 f+ d - blockchain = await Blockchain.create();
$ R& H/ V) N6 d: G' J5 d# O - initWallet = await blockchain.treasury("initWallet");+ v* H& J8 f3 |) F) u
- ownerWallet = await blockchain.treasury("ownerWallet");8 e0 H& n! J0 \
- 0 e3 w: C: D% X2 s! h* [
- const codeCell = Cell.fromBoc(Buffer.from(hex, "hex"))[0]; D4 m1 b" E6 P$ T3 d
- " s- ]7 @6 I; A, u$ p: g
- myContract = blockchain.openContract(+ b, f5 o9 Y) F, b& r6 d& c* K
- await MainContract.createFromConfig(- h7 Q$ F. y% A+ l' M
- {
# t+ r$ H+ B! d+ s+ `5 z - number: 0,
1 j- |8 L' `' F9 |0 S( F2 C' e - address: initWallet.address,' w6 h9 P# b! C( w
- owner_address: ownerWallet.address,5 L4 S" ?1 `" t" n. Q: m2 s8 f
- },2 e) y- B) E4 k* p' L$ v3 f% w' _
- codeCell1 W! j# p! z/ T
- )' S8 n0 E( m) z# ] B+ B4 t, ~' z
- );
" K: H! }( X0 G- T" K7 U& ?0 f+ }$ h - });" J; H& c# d u6 l6 A u
- @' I+ F- H% ?) Z
- ; Q! S/ a, D3 u
- // ... the rest of testing code+ z% T, `. N, u' X5 ^) G
- }
复制代码我们将对其进行 3 次更新: 导入来自 @ton/blueprint 库的 compile 函数 执行 jest 的 beforeAll 钩子来编译我们的代码,并为其他钩子和测试提供代码Cell 更新 beforeEach 钩子,以使用 beforeAll 钩子提供的代码Cell。
; P% D7 g6 ]# V/ J. _, c2 H+ m* x
在进行这些更改后,我们的代码会发生这样的变化: - // ...other library imports8 k, v: ?" O$ X) N
- import { compile } from "@ton/blueprint";* L3 E0 W& W2 N( G" o% a3 W8 V
- 7 `. g5 y0 g5 j& O9 k# {
- describe("main.fc contract tests", () => {
. ?/ K k6 g1 i+ D# t, n - let blockchain: Blockchain;
( N0 Y" _: i3 Z; R) s - let myContract: SandboxContract<MainContract>;: ~+ r; H8 C! I! j
- let initWallet: SandboxContract<TreasuryContract>;9 }3 d+ q* N( o4 }+ I G
- let ownerWallet: SandboxContract<TreasuryContract>;: ^, V# U8 v0 ^( R! g$ u* ~
- let codeCell: Cell;, J# \) L2 |( \
- 4 S7 ]# S5 {6 g: o+ a8 d
- beforeAll(async () => {5 \$ _1 i+ x, V; F O% U/ [
- codeCell = await compile("MainContract");
& n0 F' a$ w7 V n/ l - });
, n8 k, ~% R% y" I& m
3 u3 T9 e% A& K- \7 M& p7 R- beforeEach(async () => {
7 w. u4 O- Q5 C6 H, ] - blockchain = await Blockchain.create();
/ J# t7 V4 m" G. B( l6 K0 R - initWallet = await blockchain.treasury("initWallet");
! v: Z. x( V" e4 r# U! Q8 a! B - ownerWallet = await blockchain.treasury("ownerWallet");
J" M! v. `* ]8 z; }! ]
' ?; i& u) \/ A3 @- j- myContract = blockchain.openContract(# d3 ]4 x' W) }
- await MainContract.createFromConfig(
6 [6 t/ I$ B% i4 o: ] - {4 d; C) {" J; I
- number: 0,
# F) T) G+ q4 O: X4 O9 i - address: initWallet.address,8 g: F# d c# G. @
- owner_address: ownerWallet.address,
1 i9 K2 j6 N( b0 C; Y, k; \ - },
/ d" {* b& X% X. @ - codeCell6 ?( P; h( @4 @4 J; p5 h) O& h+ r
- )7 Y, S. p3 k3 `" T0 m' O3 m3 j
- );9 ^5 q; d+ ^9 n
- });; C- Q. Y1 s# q# w* e: B" R, X
-
; c7 y& u. x5 `: ^$ G( @/ Y& P - // ... the rest of testing code % b% E$ f/ h3 n$ J% d6 q
- }
复制代码我们进行这些更改是为了让您习惯运行 npm create ton@latest 时生成的代码。我们正在将编译过程委托给Blueprint 库。
不过,我们还需要向Blueprint 提供一样东西 - compiler config. 为此,只需在 wrappers 文件夹,并将其命名为 MainContract.compile.ts。 下面是该文件的内容: - import { CompilerConfig } from "@ton/blueprint";- e# o( g- F; F
8 i* r3 t% D& l- export const compile: CompilerConfig = {9 f! ?$ [0 {( e0 L/ N z: ~, J0 @ ^
- targets: ["contracts/main.fc"],
- R& M" t. N* j8 l0 ^7 R - };
复制代码此时,您只需删除编译脚本文件 (scripts/compile.ts) 并在项目根目录下运行 yarn test 。您将看到我们的测试正在正常执行!恭喜您! 将部署流程委托给 Blueprint接下来,我们需要使用 Blueprint 的超级功能来更新部署脚本。快速免责声明--我们将使用包装器 MainContract 及其方法 .createFromConfig。这样,我们就能在部署前轻松设置合约的初始状态。 Blueprint 为部署带来了惊人的功能。它以交互方式要求我们选择网络(testnet/mainnet)以及要部署合约的钱包类型。如果你经常开发和部署,这个功能就会非常方便。 在更新部署脚本之前,我们需要做的一件事是在包装器上创建一个名为 sendDeploy 的新方法。正如你所记得的,我们需要使用包装器与我们的合约进行交互。这就是我们方法的代码: - async sendDeploy(provider: ContractProvider, via: Sender, value: bigint) {
0 h. W8 C! |: e6 w - await provider.internal(via, {# }( I9 t+ `6 W' o' ~
- value,( O9 s% e; K$ V; \8 E, R
- sendMode: SendMode.PAY_GAS_SEPARATELY,
& u0 H0 ]6 H5 R Q6 }2 m6 h - body: beginCell().endCell(),
' e$ ~( Y3 x/ V4 T+ \ - });
( _/ F! p$ W& J! c! O/ w - }
复制代码正如你所记得的,部署一个合约就像向其预定地址发送初始数据和代码一样简单。在第 3 章中,我们手动完成了这一工作,因此你可以深入理解其工作原理。现在,为了优化起见,我们将其划分到包装器和Blueprint 中。 我们将用新代码完全替换我们的代码,使其符合Blueprint 的功能。 下面是我们的 scripts/deploy.ts 现在的样子: - import { address, toNano } from "ton-core";' [9 p) l* `5 I) ?% |
- import { MainContract } from "../wrappers/MainContract";! y$ ~/ L: t$ c. n0 @5 J
- import { compile, NetworkProvider } from "@ton/blueprint";
$ n- O6 | C0 W( ]3 ?( C* x - 5 c4 j4 g- Y9 \+ p( k+ E& F3 b. @
- export async function run(provider: NetworkProvider) {
( V9 @; J# D" ?- `9 O! l - const myContract = MainContract.createFromConfig(' Y- Y' d! q( e
- {6 A4 @6 k( [7 o" Z8 G
- number: 0,
: Q+ e6 G" ~ h' p - address: address("kQDU69xgU6Mj-iNDHYsWWuNx7yRPQC_bNZNCpq5yVc7LiE7D"),% D/ k4 h9 J9 E
- owner_address: address(& Y4 e/ c8 p7 O# T2 ]$ H% [' K7 u
- "kQDU69xgU6Mj-iNDHYsWWuNx7yRPQC_bNZNCpq5yVc7LiE7D"- T1 x9 f2 k3 `+ b2 D- B% |
- ),$ v; O+ z. z# N
- },
, U/ `" R: _& g, x& V! i - await compile("MainContract")
7 B' b* Z9 T; G2 k& X2 n* K4 U6 _ - );
5 N0 Z5 G: X' b p4 u
- w% B9 c- o8 o$ D2 r' ~+ q0 J% \- const openedContract = provider.open(myContract);$ Y' |# y2 O F$ r. C
- + }$ T* X6 `' `$ k2 I5 u, o
- openedContract.sendDeploy(provider.sender(), toNano("0.05"));( A6 {/ t- Z) M. {3 N- e! w& |
: b* w( G4 v- L/ d) G/ z! \. z- await provider.waitForDeploy(myContract.address);
% `8 b0 Y# D/ R* s. U4 f$ n! G - }
复制代码正如你所看到的,这一切都简单多了,而且我们可以从Blueprint 中省去许多以前必须处理的事情。 其中最酷的一点是,现在我们可以使用包装器中的 .createFromConfig 方法来启动我们将要部署的合约,并通过传递给它的 config 对象获得初始状态数据。 还有两件事值得解释,它们与配置对象的内容有关。 核心部署功能由 NetworkProvider 类型的提供程序处理,当Blueprint 触发我们的部署脚本时,该类型由Blueprint 传递到 run 函数中。 如果您希望它们与Blueprint 一起工作,那您的所有脚本都必须导出 run 函数. 让我们先回到包装器的 .sendDeploy 方法。看看 body 参数。你可以看到,我们发送的消息正文是一个空Cell。根据我们的合约逻辑,如果信息正文没有设置操作码,即使合约已成功部署,消息也会被退回。稍后您将看到这种行为。 借助BluePrint部署合约让我们进入项目根目录,运行一条命令: - yarn blueprint run' Z& _0 J0 B" g# ?* i" n
复制代码运行此命令后,您会在其中看到这样的内容:
' K3 S) Y' f- p: i% N% p7 ^( ]- user$ yarn blueprint run; R0 y, y8 w) ?$ r1 Y" i
8 { D1 q: G4 C8 H- yarn run v1.22.118 A6 N& c( k* k( w( |
3 { Y3 ~7 |! m) `( H- ? Choose file to use (Use arrow keys)
+ i5 L: Q- s9 v" m* M% I: ? _ - ❯ deploy
; E" n( R4 e" P0 D - onchaintest
复制代码你会看到 Blueprint 通过命令行提示我们选择要运行的脚本。此时,我们仍有 onchaintest.ts 脚本,所以它也会被提供给我们执行,因为 Blueprint 正在捕捉来自 scripts 文件夹所有可用的脚本。我们暂时不会创建任何链上测试,因为在下一章中,我们将创建真正的网络接口来与我们的合约进行交互。你现在可以暂时删除 scripts/onchaintest.ts 文件。
选择脚本 deploy. 系统会提示您现在选择网络: - yarn blueprint run
. z0 D7 v5 ^/ E5 K. a# I
! g4 }6 [7 Y: I, L- yarn run v1.22.11( u L+ K- a6 }7 O9 W
# |$ @8 D, Q" v z6 R- ? Choose file to use (Use arrow keys)' _/ y# L& h7 Q: h& n4 y& I( o
- ? Choose file to use deploy. v# T! y, C* H0 |- A3 m8 B& c9 I
- ? Which network do you want to use? (Use arrow keys)1 p7 Z5 H4 p8 V/ b! _
- ❯ mainnet
. g. ~' Y7 E$ I+ e" n - testnet
复制代码选择网络 testnet. 5 C# F/ M8 S$ r H% c7 o
- yarn blueprint run
S& q$ e+ w! k6 v* A: z2 ?, w }
8 _) A* ]! Y, D) b; K' S4 R- yarn run v1.22.115 _/ S3 g Z6 A' Q2 r: \
- 8 ]) M# W; a- {/ \
- ? Choose file to use (Use arrow keys)" Q1 u+ X3 }1 i6 u- Y1 N
- ? Choose file to use deploy) {+ `6 y. I4 D2 E2 a( H$ a4 C! _4 p
- ? Which network do you want to use? ( L7 P6 V1 h9 j7 [" p
- ? Which network do you want to use? testnet
% z' C* }, |! ]- s - ? Which wallet are you using? (Use arrow keys)
( A0 y( O1 {+ a: z - ❯ TON Connect compatible mobile wallet (example: Tonkeeper)
- {# k- j+ V/ e: Q - Create a ton:// deep link + O/ s* l" U! V9 F }9 L6 Q
- Tonhub wallet
7 f- v3 x" r% X2 ~9 J7 q7 k8 @ - Mnemonic
复制代码现在,系统会提示您选择使用哪个钱包来部署合约。在第 3 章中,我们只使用了一个钱包(TON Whales的Tonhub),而且是以最简单的方式--创建一个深度链接。 Blueprint 可以让您使用任何类型的钱包,并保持最佳会话状态。也就是说,它的工作方式是授权你的本地项目从你的钱包请求交易,然后批准它们。 在本教程中,我们将再次使用 Tonhub 链接测试网和主网。 打开 Tonhub 钱包应用程序。 系统会要求您扫描二维码,授权应用程序与钱包进行交互(请求交易)。 : n1 r& E: t3 }. E; _: t
- yarn blueprint run3 u- l5 R# ~' U6 @* l& l: a% |
- yarn run v1.22.115 [6 F- F% T* ?8 v+ C: Q3 @
- ? Choose file to use (Use arrow keys)
" D0 A$ X4 G; r8 x$ F' U( X - ? Choose file to use deploy
2 I1 j; r& p9 c/ s& r& K7 a - ? Which network do you want to use? (Use arrow keys)
$ Q. J/ z; c2 D - ? Which network do you want to use? mainnet- |# D- O [7 t
- ? Which wallet are you using?
k' S+ b$ ?( v, g, g7 ^$ g/ X - ? Which wallet are you using? Tonhub wallet
+ |9 F0 B$ V1 H: r( Q Y
8 a$ x% F- B% b# t2 u) z3 C, ^
" a7 ~8 f* a k% }, B& I5 P- ton://connect/GzhvQ-zwLhYNxzlEPEf-hP63kgR_0_O8vXsM8mSqQ-0?endpoint=connect.tonhubapi.com4 @# U( u2 f V5 s8 ]& Z5 c0 m* R! p
* T6 K+ G6 D( z1 Q8 _! v- Connected to wallet at address: EQC7zjln0_fghMQg0A-ZhYFar3DU1bDW9A4Vi5Go5uu-tAHe
复制代码扫描二维码并在应用程序中授权后,您将被要求在 Tonhub 应用程序中签署交易。完成后,您将在命令行中看到以下输出: - yarn blueprint run
6 E/ M' J# w: ? - yarn run v1.22.11
$ o4 W/ C6 a, }# ?0 O$ A - ? Choose file to use (Use arrow keys)
1 D* g, G7 V/ J - ? Choose file to use deploy2 V! @# g1 e `0 d6 f' N
- ? Which network do you want to use? % e7 S- w( H1 z$ P; d1 w
- ? Which network do you want to use? testnet5 c4 P' e; |) C( O
- ? Which wallet are you using? 5 m* t6 V* L* i# C- v: N
- ? Which wallet are you using? Tonhub wallet G; c/ T5 R. n: R m+ F
- Connected to wallet at address: EQDU69xgU6Mj-iNDHYsWWuNx7yRPQC_bNZNCpq5yVc7LiPVJ8 K# P A8 a: o2 r. m# y# W
- Contract deployed at address EQCS7PUYXVFI-4uvP1_vZsMVqLDmzwuimhEPtsyQKIcdeNPu
) L7 u3 n$ \$ E' a0 J8 ]0 d- V - You can view it at https://testnet.tonscan.org/address/EQCS7PUYXVFI-4uvP1_vZsMVqLDmzwuimhEPtsyQKIcdeNPu
S1 K9 a- T* p- ~- K# o7 ^* h! ] - ✨ Done in 19.84s.
复制代码 + p& \ h" z, t: P" b* A$ s
2 ^' C/ T4 {- Z B( W" `! c3 a
5 u- f4 {8 Q3 G& w5 q+ _
- M+ m( r. L( q6 Q, @
# b. c5 M" w0 d) m$ G
e2 `- ?, X$ k+ @3 T8 F |