LXDAO Expert WG|Telegram & TON 生态开发者入门
文章一共9000字左右,一共分为 5 个部分,阅读大概需要 20分钟。
Expert WG:主要由各个领域资深专家组成。工作内容包括但不限于:专家咨询、Code Review、专题研究、技术分享等。
导读
——撰文 | Lewis、Howe、Kahn
自 2023 年 7 月份以来,Unibot 的火爆,带起了一波 "Telegram Crypto" 的热潮,再加上 Telegram Wallet 的上线,将 TON 再次带回了大家的视线。不同于 2018 年 TON 在 Web3 行业的中文关注者比较少,本次热潮吸引来了众多中文开发者,为此 LXDAO 社区也为大家带来了一些 Telegram & TON 生态开发者入门知识,带你了解这个生态。
正文
文章一共9000字左右,一共分为 5 个部分,阅读大概需要 20分钟。
01 Telegram 应用
Telegram(非正式简称TG、电报、纸飞机)是跨平台的即时通信软件,官方提供手机版(Android、iOS、Windows Phone)、桌面版(Windows、macOS、Linux)和网页版等多种平
台客户端;同时开放应用程序接口(API),因此拥有许多第三方的客户端可供选择。2013 年 8 月,Pavel Durov 与 Nikolai Durov 兄弟创立了 Telegram 。至今,Telegram 仍在稳步发展, 其用户规模持续扩大,也在不断优化产品和功能,月均活跃超 8.6 亿人。
1.1 Telegram Bot
1.1.1 简介
Telegram Bot 可以理解为完全运行在 Telegram 应用程序中的“插件”。用户通过灵活的界面与 Bot 进行交互,这些界面可以支持任何类型的任务或服务。你可以给它发送指令让它完成操作或是实现一些功能(付钱、游戏等等),或者可以在 Channel 或 Group 中发送特定消息。
1.1.2 开发相关
Telegram Bot 的技术原理基于 Telegram Bot API,这是一个基于 HTTP 的接口,允许开发者通过简单的 HTTPS 请求来控制他们的 Bots。下面是一些核心要点:
-
创建 Bot:开发者首先需要通过与 Telegram 的 @BotFather(一个官方的 Bot,用于创建和管理其他 Bots)的交互来创建一个新的 Bot。BotFather 会提供一个 token,这个 token 是访问 Telegram Bot API 并控制 Bot 的钥匙。
-
Bot API:Telegram Bot API 提供了一系列的方法,允许 Bot 接收消息、发送消息、修改消息、处理回调查询等。开发者可以通过编程方式调用这些方法来控制 Bot 的行为。
-
消息更新:Bot 可以通过两种方式获取消息更新:Webhooks 和轮询(polling)。
-
Webhooks:开发者可以设置一个 URL(Webhook),Telegram 会在有新的消息时推送更新到这个 URL。
-
Polling:Bot 通过周期性地向 Telegram Bot API 请求未处理的更新来获取消息。
-
-
编程语言:开发者可以使用任何能够发送 HTTPS 请求的编程语言来创建和管理 Telegram Bot。常用的语言包括 Python、JavaScript、Java、PHP 等。
-
托管:Bot 运行在开发者自己的服务器上,而不是 Telegram 的服务器。这意味着开发者需要负责维护 Bot 的服务器和网络基础设施。
-
集成:Bot 可以集成各种外部服务,例如数据库、支付网关、其他 API 服务等,以实现更复杂的功能。
Telegram Bot 的核心优势在于其简单性和高度的可定制性,使得开发者可以快速地开发出功能丰富的 Bots 来满足各种需求。
02 TON 链
TON 是一个去中心化和开放的互联网平台,由几个组件组成。其中包括:TON 区块链、TON DNS、TON Storage 和 TON Site。TON 区块链是将 TON 的底层基础设施连接在一起以形成更大的 TON 生态系统的核心协议。
TON 专注于实现广泛的跨链互操作性,同时在高度可扩展的安全框架中运行。TON 旨在每秒处理数百万笔交易 (TPS),目标是最终达到数亿用户。
TON Blockchain 被设计为分布式超级计算机或“超级服务器”,旨在提供各种产品和服务,为新互联网的去中心化愿景的发展做出贡献。
2.1 TON 技术特点
-
多区块链架构:TON 采用了一种独特的多区块链架构,包括一个主链和多个附属链(工作链、分片链、虚拟账户链)。这种设计允许系统在保持高度安全性的同时,实现极高的可扩展性和效率。相对于比特币和以太坊主要基于单一区块链架构,有明显区别。
-
安全性和去中心化:TON 强调网络的安全性和去中心化特性,采用了先进的加密技术和分布式共识机制来保护网络免受攻击,并确保去中心化。
-
自适应分片机制:TON 的分片机制是自适应的,意味着它可以根据网络负载动态调整分片数量,从而优化处理能力和资源利用率。
-
异步通信机制:在 TON 中,节点之间的通信是异步的。这意味着节点在发送消息或执行交易时不需要等待即时的响应,从而大大提高了网络的效率和响应速度。相对于其他区块链的同步调用,TON 的异步通信机制可谓独树一帜,开发者需要重点理解此机制。
-
高吞吐量和可扩展性:TON 旨在处理大量的交易和活动,具有高吞吐量和低延迟的特点。它通过动态分片和优化的区块生成算法来实现这一目标,理论上能够支持数百万 TPS(每秒交易数)。
以下是 TON、Ethereum、Solana 的一些数据对比:

2.2 TON 钱包
用户要想使用 TON 区块链上的资产必然离不开钱包,目前 TON 的钱包有 TonKeeper、Wallet、TonSpace、Tonhub、MyTonWallet、OpenMask、Crypto Bot、Trust Wallet、SafePal 等。篇幅有限这里着重介绍 TonKeeper、Wallet、TonSpace 这三款钱包 。
2.2.1 TonKeeper
介绍:保护您的加密资产、NFT 并轻松访问 TON 应用程序。
支持平台:苹果、安卓、Chrome、Firefox等。
托管:非托管。
下载入口:官网(https://tonkeeper.com/)下载钱包
2.2.2 Wallet
介绍:Wallet 是 Telegram 的认证钱包,可以直接在 Telegram 中打开。
支持平台:Telegram
托管:托管
入口:Telegram 搜索框中输入 @Wallet
2.2.3 TON Space
介绍:集成在 Wallet 中的自托管钱包,无法在同一个账号的不同终端中共享信息。
支持平台: Telegram
托管:自托管
入口:Wallet 中选择 TON Space
03 TON和 Telegram 的关系
TON 最初是由 Telegram 的创始人 Pavel 和 Nikolai Durov 兄弟为 Telegram 设计的 Layer 1 链。2019 年,受到美国证券交易委员会 (SEC) 起诉,导致 TON 的发展受阻。2020 年,Telegram 与 SEC 达成和解,停止了 TON 的开发。后来开源社区接手了 TON 的开发, 开源社区后更名为 TON 基金会。
2023 年 9 月 Telegram 和 TON 基金会,宣布双方达成正式合作关系,整合了 TON 所推出的自托管加密钱包“TON Space” 。两者的合作是双赢关系, Telegram 为 TON 带来用户,得益于 Telegram 的 8 亿的海量用户, TON 也存在被 Mass Adoption (大规模采用) 的可能;TON 为在 Telegram 上构建 DApp 的开发者提供了 Web3 基础设施的支持。
TON 官方公布的生态系统中已经收录了 500 多个应用程序,这些应用覆盖了质押、钱包、浏览器、跨链桥、公共设施、NFT、社交、游戏、去中心化交易所、游戏等多个领域。
04 Telegram Bot 与 TON 集成
4.1 生态对象调用流程图
以下是一张简单的 Telegram Bot 与 TON 生态对象调用流程图,理清它们之间的调用关系更有助于早期技术学习。

-
TON Blockchain 指的是 TON 链。
-
TON Client 指的是应用程序与 TON 链交互的 SDK。
-
Bot 的存在可以有多种形式。
-
后端集成 Bot 是打通 Telegram 各个功能的关键。
-
与 TON 进行交互,可以是 Mini App,也可以是 Bot 后端。
4.2 Telegram Bot 创建
在 Telegram 搜索框输入 @BotFather, 点击进入,然后输入 "/newbot" 创建新 Bot, 保存 token、link等信息。

4.3 Telegram Bot 开发
下面是一个 Bot 的开发流程讲解:
-
首先 Clone 仓库( https://github.com/ton-connect/demo-telegram-bot )到本地。
-
按照 4.2 指引创建一个 telegram Bot。
-
拷贝.env.example 为 .env 文件, 修改 TELEGRAM_BOT_TOKEN、TELEGRAM_BOT_LINK 地址修改成 4.2 中获取的值, MANIFEST_URL 则是传递元信息给钱包, 包括 url、name、iconUrl、 termsOfUseUrl、privacyPolicyUrl等内容。

- 打开工程,查看 src 下的目录结
├── bot.ts
├── commands-handlers.ts
├── connect-wallet-menu.ts
├── main.ts
├── ton-connect
│ ├── connector.ts
│ ├── storage.ts
│ └── wallets.ts
└── utils.ts
- Telegram Bot 对接
- 先查看 Bot.ts 文件
import TelegramBot from 'node-telegram-bot-api';
import * as process from 'process';
const token = process.env.TELEGRAM_BOT_TOKEN!;
export const bot = new TelegramBot(token, { polling: true });
这里引入了 node-telegram-bot-api 包,导入了 token 的环境变量 。“new TelegramBot(token, { polling: true })” ,参数 token 为 Telegram Bot 的 toekn, “{ polling : ture}” 表示消息轮训的方式采用轮询的方式。
- 再看 main.ts 文件
async function main(): Promise<void> {
...
bot.onText(/\/start/, (msg: TelegramBot.Message) => {
bot.sendMessage(
msg.chat.id,
`
This is an example of a telegram bot for connecting to TON wallets and sending transactions with TonConnect.
Commands list:
/connect - Connect to a wallet
/my_wallet - Show connected wallet
/send_tx - Send transaction
/disconnect - Disconnect from the wallet
GitHub: https://github.com/ton-connect/demo-telegram-bot
`
);
});
}
这里是 Bot 处理命令的例子, bot.onText 为 TelegramBot 命名空间下的 onText 函数。“/\start/” 对应的命令, 后面为其对应的处理函数。使用 “ bot.sendMessage” 给当前的 msg.chat.id 会话发送信息。
4.4 Example 1: Telegram Bot 集成 Wallet
接下来根据 TON 文档(https://docs.ton.org/develop/dapps/ton-connect/tg-bot-integration),看一个 Telegram Bot 集成 Wallet 的例子。下面是一个调用流程图。

TON Connect: 支持 TON 生态系统中的钱包和应用程序之间的通信的组件。
Wallets:红色图标钱包可以是 TON 钱包段落提到的。
TON BlockChain: 指的是 TON 链。
EVM BlockChain:指的是 EVM 兼容链(现在有很多 Apps Center 中的钱包就是支持以太坊生态的)。
4.4.1 代码讲解
- 先看 tonconnect/connector.ts 文件
import TonConnect from '@tonconnect/sdk';
...
export function getConnector(
chatId: number,
onConnectorExpired?: (connector: TonConnect) => void
): TonConnect {
...
storedItem = {
connector: new TonConnect({
manifestUrl: process.env.MANIFEST_URL,
storage: new TonConnectStorage(chatId)
}),
onConnectorExpired: []
} as unknown as StoredConnectorData;
...
}
- 再看 wallet.ts 文件
import { isWalletInfoRemote, WalletInfoRemote, WalletsListManager } from '@tonconnect/sdk';
export async function getWallets(): Promise<WalletInfoRemote[]> {
const wallets = await walletsListManager.getWallets();
return wallets.filter(isWalletInfoRemote);
}
- 然后看 commands-handlers.ts 文件
...
import { getConnector } from './ton-connect/connector';
...
export async function handleConnectCommand(msg: TelegramBot.Message): Promise<void> {
...
const connector = getConnector(chatId, () => {
unsubscribe();
newConnectRequestListenersMap.delete(chatId);
deleteMessage();
});
...
const wallets = await getWallets();
const link = connector.connect(wallets);
...
}
handleConnectCommand 为连接钱包命令的处理函数, 使用初始化 TonnConnect, 等待选择钱包后,在进行连接钱包。
4.4.2 运行效果

输入“/connect”命令后,对应选择钱包的效果,点击 @wallet 后(也可选择 Choose Wallet 按钮),弹出。

点击 Wallet 之后,就连接上了 wallet,然后 Bot 中显示如下内容。

4.5 TON 智能合约开发语言
对于公链而言,智能合约就是生态的核心,编程语言、性能、安全等都是开发者比较关心的,那么 TON 智能合约的开发语言是怎么样呢?以下主要介绍 FunC 和 Tact。
4.5.1 FunC
FunC 是一种专门针对 TON 智能合约开发的编程语言,具备以下特性:
-
原子类型:
-
int:表示 257 位的有符号整数,自带溢出检测功能。
-
cell:代表 TVM(区块链虚拟机)单元,是 TON 区块链的基础数据存储结构。每个单元可以包含最多 1023 位数据和四个引用。
-
slice:单元片段,用于操作 cell 中的数据和引用。
-
builder:单元构建器,允许存储数据和引用,并最终构建成一个新的 cell。
-
tuple:是 TVM 中的元组类型,最多可包含 255 个不同类型的元素。
-
cont:是 TVM 的延续的类型,用于控制 TVM 程序的执行流程。
-
布尔类型的处理:
-
在 FunC 中,布尔值以整数形式表示,0 代表假,-1(257 个二进制1 )代表真。
-
空值处理:
-
用 Null 类型来表示某个原子类型值的缺失。某些函数可能会返回空值。
-
类型推断:
-
使用 _ 和 var 表示类型空缺,这些空缺会在类型检查过程中被实际类型填充。
-
复合类型:
-
函数类型:如 A -> B,表示从 A 类型到 B 类型的函数。
-
张量类型:如 (A, B, ...),表示多个有序的 A、B 等类型值的集合。
-
元组类型:如 [A, B, ...],表示具有特定长度和组件类型的 TVM 元组。
-
泛型和类型变量:
-
支持泛型函数和类型变量。例如,forall X -> (X, X) duplicate(X value) 是一个泛型函数。
-
用户自定义类型:
-
目前,FunC 不支持定义除上述类型构造之外的类型。
-
类型宽度
-
类型宽度指的是一个类型的所有值占用的堆栈条目数量。目前,只有已知固定宽度的类型可以定义泛型函数。
总结来说,FunC 是一个专为区块链设计的语言,其独特的类型系统和结构特别适用于处理 TON 区块链上的复杂数据和操作。
4.5.2 Tact
Tact 注重效率和简单性,且其设计易于学习和使用,非常适合智能合约。同时 Tact 是一种静态类型语言,具有简单的语法和强大的类型系统。
-
类型
-
基本类型
- Int:所有整数都是 257 位有符号整数。
- Bool:经典的布尔类型,具有真 / 假值。
- Address:标准地址。
- Slice、Cell、Builder:TVM 的低级原语。
- String:代表 TON VM 中的文本字符串。
- StringBuilder:辅助类型,允许以高效的方式连接字符串。
-
-
映射:用于将数据与相应的键关联的类型。键和值的类型有限,包括整数、布尔、单元、地址以及结构体/消息。
-
结构体和消息:结构体和消息几乎相同,唯一的区别是消息在其序列化中有一个头部,因此可以用作接收器。同时需要注意的是结构体和消息目前不支持循环类型。
-
合约:合约是 TON 区块链上智能合约的主要入口,包含初始化函数、获取器和接收器。同时合约中可以定义变量,通过 receive 函数处理外部调用,且有助于实现状态管理。
-
特性:引入了 “特质” 的概念,定义了函数、接收器和必需字段。但不规定字段存储的方式。所有特性中的字段必须在合同中显式声明。特性本身没有构造函数,所有字段的初始化也必须在主合同中完成。
4.5.3 FunC 与 Tact 的关系
由于 FunC 的学习门槛比较高,所以 TON 社区开发了类似于 TypeScript 和 Rust 的高级编程语言 Tact。
由此我们可以得出 FunC 与 Tact 之间的关系如下:
FunC — compile to --> Fift
Tact — compile to --> FunC — compile to --> Fift
这里提到的 Fift 是一种基于堆栈的通用编程语言,针对创建、调试和管理 TON 区块链智能合约进行了优化。Fift 专门设计用于与 TON 虚拟机(TVM)和 TON 区块链交互。
4.6 TON 简单合约源码分析
下面是一个参考 TON 文档(https://ton-community.github.io/tutorials/02-contract/)的简单例子,完整代码可参考:GitHub - DestinyWei/Ton-Counter: TON Counter Contract,接下来我们进行源码分析。
- 首先我们需要查看配置文件示例.env.example,复制创建名为.env的文件****
MNEMONIC= word1 word2 word3 word4
MNEMONIC 填写的是 TON 钱包的助记词,这里使用的是 Tonkeeper 钱包。
- 项目目录结构如下
Ton-Counter
├─ .gitignore
├─ .env.example
├─ contracts
│ ├─ counter.fc
│ └─ imports
│ └─ stdlib.fc
├─ jest.config.ts
├─ package-lock.json
├─ package.json
├─ README.md
├─ scripts
│ ├─ deployCounter.ts
│ ├─ getCounter.ts
│ └─ sendIncrement.ts
├─ tests
│ └─ Counter.spec.ts
├─ tsconfig.json
└─ wrappers
├─ Counter.compile.ts
└─ Counter.ts
counter.fc是关于 Counter 的 FunC 合约代码,主要是关于发送链上消息、读写链上数据。
wrappers 中的Counter.ts则是用于封装用于与 FunC 合约的交互接口。
scrpits 中的deployCounter.ts、getCounter.ts、sendIncrement.ts则是用于部署、读写 Counter 合约。
3.counter.fc合约源码分析
#include "imports/stdlib.fc";
(int) load_data() inline { ;; read function declaration - returns int as result
var ds = get_data().begin_parse(); ;; load the storage cell and start parsing as a slice
return (ds~load_uint(64)); ;; read a 64 bit unsigned int from the slice and return it
}
() save_data(int counter) impure inline { ;; write function declaration - takes an int as arg
set_data(begin_cell() ;; store the storage cell and create it with a builder
.store_uint(counter, 64) ;; write a 64 bit unsigned int to the builder
.end_cell()); ;; convert the builder to a cell
}
() recv_internal(int msg_value, cell in_msg, slice in_msg_body) impure { ;; well known function signature
if (in_msg_body.slice_empty?()) { ;; check if incoming message is empty (with no body)
return (); ;; return successfully and accept an empty message
}
int op = in_msg_body~load_uint(32); ;; parse the operation type encoded in the beginning of msg body
var (counter) = load_data(); ;; call our read utility function to load values from storage
if (op == 1) { ;; handle op #1 = increment
save_data(counter + 1); ;; call our write utility function to persist values to storage
}
}
int counter() method_id { ;; getter declaration - returns int as result
var (counter) = load_data(); ;; call our read utility function to load value
return counter;
}
-
load_data()是一个内联函数,用于从存储单元中加载数据并返回一个整数结果。具体步骤如下:
-
调用get_data()函数获取存储单元,并将其作为切片开始解析。
-
从切片中读取一个 64 位无符号整数(uint64)。
-
将读取到的整数作为结果返回。
-
-
save_data(int counter)是一个内联函数,用于将数据写入存储单元。具体步骤如下:
-
创建一个存储单元的构建器,并将其作为初始状态。
-
将整数counter以 64 位无符号整数的形式写入构建器。
-
将构建器转换为一个存储单元,并保存在存储单元中。
-
-
recv_internal(int msg_value, cell in_msg, slice in_msg_body)是一个接收函数,用于处理接收到的内部消息。具体步骤如下:
-
检查接收到的消息体是否为空,如果为空则返回。
-
解析消息体开头编码的操作类型,使用 load_uint(32) 从切片中读取一个 32 位无符号整数。
-
调用load_data()函数从存储中加载计数器的值。
-
如果操作类型为 1(代表增量操作),则将计数器的值加 1,并调用save_data()函数将新值保存到存储中。
-
-
counter() method_id函数用于获取计数器的值。具体步骤如下:
- 调用load_data()函数从存储中加载计数器的值,并将其作为结果返回。
4.Counter.ts分析
export default class Counter implements Contract {
static createForDeploy(code: Cell, initialCounterValue: number): Counter {
const data = beginCell()
.storeUint(initialCounterValue, 64)
.endCell();
const workchain = 0; // deploy to workchain 0
const address = contractAddress(workchain, { code, data });
return new Counter(address, { code, data });
}
constructor(readonly address: Address, readonly init?: { code: Cell, data: Cell }) {}
async sendDeploy(provider: ContractProvider, via: Sender) {
await provider.internal(via, {
value: "0.01", // send 0.01 TON to contract for rent
bounce: false
});
}
async sendIncrement(provider: ContractProvider, via: Sender) {
const messageBody = beginCell()
.storeUint(1, 32) // op (op #1 = increment)
.storeUint(0, 64) // query id
.endCell();
await provider.internal(via, {
value: "0.002", // send 0.002 TON for gas
body: messageBody
});
}
async getCounter(provider: ContractProvider) {
const { stack } = await provider.get("counter", []);
return stack.readBigNumber();
}
}
-
createForDeploy方法用于创建一个 Counter 合约的实例,用于部署合约。
-
sendDeploy方法用于部署合约。它接受一个合约提供者provider和一个发送者via作为参数。在部署过程中,它会向合约发送 0.01 TON 的价值用于支付租金,同时不允许合约反弹(bounce)。
-
sendIncrement方法用于发送增量操作。它接受一个合约提供者provider和一个发送者via作为参数。在调用过程中,它会创建一个消息体(message body),其中包含增量操作的指令和查询 ID,并向合约发送 0.002 TON 的价值用于支付 gas 费用。
-
getCounter方法用于获取计数器值。它接受一个合约提供者provider作为参数,并从合约的状态中获取计数器的值,并返回该值。
5.deployCounter.ts分析
export async function run() {
// initialize ton rpc client on testnet
const endpoint = await getHttpEndpoint({ network: "testnet" });
const client = new TonClient({ endpoint });
// prepare Counter's initial code and data cells for deployment
const counterCode = Cell.fromBoc(fs.readFileSync("build/counter.cell"))[0]; // compilation output from step 6
const initialCounterValue = Date.now(); // to avoid collisions use current number of milliseconds since epoch as initial value
const counter = Counter.createForDeploy(counterCode, initialCounterValue);
// exit if contract is already deployed
console.log("contract address:", counter.address.toString());
if (await client.isContractDeployed(counter.address)) {
return console.log("Counter already deployed");
}
// open wallet v4 (notice the correct wallet version here)
const mnemonic = process.env.MNEMONIC!; // your 24 secret words (replace ... with the rest of the words)
const key = await mnemonicToWalletKey(mnemonic.split(" "));
const wallet = WalletContractV4.create({ publicKey: key.publicKey, workchain: 0 });
if (!await client.isContractDeployed(wallet.address)) {
return console.log("wallet is not deployed");
}
// open wallet and read the current seqno of the wallet
const walletContract = client.open(wallet);
const walletSender = walletContract.sender(key.secretKey);
const seqno = await walletContract.getSeqno();
// send the deploy transaction
const counterContract = client.open(counter);
await counterContract.sendDeploy(walletSender);
// wait until confirmed
let currentSeqno = seqno;
while (currentSeqno == seqno) {
console.log("waiting for deploy transaction to confirm...");
await sleep(1500);
currentSeqno = await walletContract.getSeqno();
}
console.log("deploy transaction confirmed!");
}
function sleep(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
}
-
run()函数的主要功能是部署 Counter 合约和相关的钱包合约。具体步骤如下:
-
初始化 TonClient,并连接到测试网(testnet)的 Ton RPC 端点。
-
准备 Counter 合约的代码和数据单元(cells)用于部署。
-
创建 Counter 合约的实例。
-
检查合约是否已经部署,如果已经部署,则退出脚本。
-
打开钱包合约(WalletContractV4)。
-
检查钱包合约是否已经部署,如果未部署,则退出脚本。
-
打开钱包合约并获取当前序列号(seqno)。
-
发送部署交易并等待确认。
-
当部署交易确认后,输出相应的信息。
-
6.getCounter.ts分析
export async function run() {
// initialize ton rpc client on testnet
const endpoint = await getHttpEndpoint({ network: "testnet" });
const client = new TonClient({ endpoint });
// open Counter instance by address
const counterAddress = Address.parse("EQDYOmv_rQXifWNqkrHzPERRJEBwHyBOplPgXJDGbu7p3mp8"); // replace with your address from step 8
const counter = new Counter(counterAddress);
const counterContract = client.open(counter);
// call the getter on chain
const counterValue = await counterContract.getCounter();
console.log("value:", counterValue.toString());
}
-
run()函数的主要功能是通过合约地址打开 Counter 合约实例,并调用合约上的getCounter()方法获取计数器的值。具体步骤如下:
-
初始化 TonClient,并连接到测试网(testnet)的 Ton RPC 端点。
-
解析 Counter 合约的地址,创建 Counter 合约的实例。
-
打开 Counter 合约实例。
-
调用合约的getCounter()方法获取计数器的值。
-
输出计数器的值。
-
7.sendIncrement.ts分析
export async function run() {
// initialize ton rpc client on testnet
const endpoint = await getHttpEndpoint({ network: "testnet" });
const client = new TonClient({ endpoint });
// open wallet v4 (notice the correct wallet version here)
const mnemonic = process.env.MNEMONIC!; // your 24 secret words (replace ... with the rest of the words)
const key = await mnemonicToWalletKey(mnemonic.split(" "));
const wallet = WalletContractV4.create({ publicKey: key.publicKey, workchain: 0 });
if (!await client.isContractDeployed(wallet.address)) {
return console.log("wallet is not deployed");
}
// open wallet and read the current seqno of the wallet
const walletContract = client.open(wallet);
const walletSender = walletContract.sender(key.secretKey);
const seqno = await walletContract.getSeqno();
// open Counter instance by address
const counterAddress = Address.parse("EQDYOmv_rQXifWNqkrHzPERRJEBwHyBOplPgXJDGbu7p3mp8"); // replace with your address from step 8
const counter = new Counter(counterAddress);
const counterContract = client.open(counter);
// send the increment transaction
await counterContract.sendIncrement(walletSender);
// wait until confirmed
let currentSeqno = seqno;
while (currentSeqno == seqno) {
console.log("waiting for transaction to confirm...");
await sleep(1500);
currentSeqno = await walletContract.getSeqno();
}
console.log("transaction confirmed!");
}
function sleep(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
}
-
run()函数的主要功能是执行以下操作:
-
初始化 TonClient,并连接到测试网(testnet)的 Ton RPC 端点。
-
从环境变量中获取钱包的助记词(mnemonic)。
-
将助记词转换为钱包密钥。
-
创建钱包合约实例(WalletContractV4)。
-
检查钱包合约是否已经部署,如果未部署,则退出脚本。
-
打开钱包合约并获取当前序列号(seqno)。
-
解析 Counter 合约的地址,创建 Counter 合约的实例。
-
打开 Counter 合约实例。
-
向 Counter 合约发送增量交易。
-
等待交易确认。
-
输出交易确认的信息。
-
4.7 Example 2: Telegram Bot 与 TON 合约集成
4.7.1 代码讲解
讲解完简单的合约例子源码,下一步我们讲解 DApp 非常核心的部分, Telegram Bot 与 TON 合约是如何集成的。
-
这里用 4.6 节的 Counter 合约作为集成的目标,实现在 Bot 里获取 counter 值,自增 counter 值的效果。
-
首先新增一个 counter.ts 文件,内容可以直接拷贝 4.6节中的 Counter 合约操作的封装(class Counter)。其中有 Counter类对合约的调用的封装,其中 getCounter 函数去获取当前 counter 的值, sendIncrement 是自增 counter 的值。
-
新增 Counter 值的读取函数。
在 commands-handlers.ts 文件中调用 getCounter 函数, 新增 handleGetCounterCommand 获取 Counter 值
import { TonClient } from '@ton/ton';
export async function handleGetCounterCommand(msg: TelegramBot.Message): Promise<void> {
const chatId = msg.chat.id;
const client = new TonClient({
endpoint: `https://testnet.toncenter.com/api/v2/jsonRPC`,
apiKey: process.env.TONCENTER_API_KEY,
});
const contract = new Counter(
Address.parse('EQBYLTm4nsvoqJRvs_L-IGNKwWs5RKe19HBK_lFadf19FUfb') // replace with your address from tutorial 2 step 8
);
const counterContract = client.open(contract) as unknown as OpenedContract<Counter>;
const val = await counterContract.getCounter();
console.log(val);
await bot.sendMessage(chatId, 'counter value : ' + val);
}
handleGetCounterCommand 函数主要功能是执行以下操作:
a. 从 “@ton/ton” 包中引入 TonClient 类,TonClient 可以理解为连接 TON 相关接口的封装类库
b. 使用 TON RPC 地址和 TONCENTER_API_KEY (从 “@tonapibot” 的 Telegram Bot 中获取), 使用 Counter 合约地址初始化 Counter
c. 调用 “counterContract.getCounter()” 获取 Counter 合约中 counter 的值
- 新增 Counter 值的自增函数
在 commands-handlers.ts 文件中新增 handleIncrementCounterCommand 函数,执行自增 counter 值
export async function handleIncrementCounterCommand(msg: TelegramBot.Message): Promise<void> {
const chatId = msg.chat.id;
const connector = getConnector(chatId);
await connector.restoreConnection();
if (!connector.connected) {
await bot.sendMessage(chatId, "You didn't connect a wallet");
return;
}
const client = new TonClient({
endpoint: `https://testnet.toncenter.com/api/v2/jsonRPC`,
apiKey: process.env.TONCENTER_API_KEY,
});
const contract = new Counter(
Address.parse('EQBYLTm4nsvoqJRvs_L-IGNKwWs5RKe19HBK_lFadf19FUfb') // replace with your address from tutorial 2 step 8
);
const counterContract = client.open(contract) as unknown as OpenedContract<Counter>;
const sender = {
send: async (args: SenderArguments) => {
connector.sendTransaction({
network: CHAIN.TESTNET,
validUntil: Math.round(
(Date.now() + Number(process.env.DELETE_SEND_TX_MESSAGE_TIMEOUT_MS)) / 1000
),
messages: [
{
address: args.to.toString(),
amount: args.value.toString(),
payload: args.body?.toBoc().toString('base64'),
}
]
});
}
}
counterContract?.sendIncrement(sender);
//Todo: seqno verfiy
await bot.sendMessage(chatId, "increment counter success");
}
handleIncrementCounterCommand 函数主要功能是执行以下操作:
a. 从 “@ton/ton” 包中引入 TonClient 类,TonClient 可以理解为连接 TON 相关接口的封装类库
b. 使用 TON RPC 地址和 TONCENTER_API_KEY (从 “@tonapibot” 的 Telegram Bot 中获取), 使用 Counter 合约地址初始化 Counter
c.client.open 建立 TonClient 和 链上合约的联系
d. 定义 sender 函数,用于 TonConnect 发送交易的封装
e. 调用counterContract的 sendIncrement 函数, 这里可以优化的点是对 seqno 的判断,可以参考4.6节 sendIncrement 中对 seqno 处理。
- 在 main.ts 文件, main 函数中添加获取 counter, 自增 counter 的入口。
bot.onText(/\/get_counter/, handleGetCounterCommand);
bot.onText(/\/increment_counter/, handleIncrementCounterCommand);
修改完代码完成后,运行 bot 服务 。
4.7.2 运行效果
“/get_counter” 如下图

"/increment_counter" 如下图

05 总结
以上就是 Telegram & TON 生态的一些概念科普、关系梳理、example 代码讲解&效果展示。对于初学开发者来说,弄清楚官方提供的技术栈与各种功能特性如何相结合,是非常重要的,特别是与 Bot 部分,当然涉及到产品和开发相关细节是远不止这些的。
另外 TON 采用非 EVM 的标准,创造了一套完全不同于市面上任何一种(Solidity、Yul、Rust、Move、C++ 等)智能合约的技术栈,所以开发者在 Telegram & TON 生态开发 DApp,是有一定门槛的。当前除了官方文档,现存的技术参考资料相对较少,另外相对容易上手的 Tact 编程语言官方库也正处于建设阶段,离成熟还有不小距离,开发者们要有足够的心理预期。
最后希望此篇文章能给读者们带来一些帮助!
撰文 | Lewis、Howe、Kahn
编辑&排版 | Cikey
设计 | WangTeng