Telegram 机器人的 TON Connect
在本教程中,我们将创建一个示例 Telegram 机器人,使用 Javascript TON Connect SDK 支持 TON Connect 2.0 认证。 我们将分析连接钱包、发送交易、获取关于已连接钱包的数据以及断开钱包连接。
打开演示机器人
查看 GitHub
文档链接
必要条件
- 你需要使用 @BotFather 创建一个 telegram 机器人,并保存其令牌。
- 应安装 Node JS(本教程中我们使用版本 18.1.0)。
- 应安装 Docker。
创建项目
设置依赖
首先,我们要创建一个 Node JS 项目。我们将使用 TypeScript 和 node-telegram-bot-api 库(你也可以选择任何适合你的库)。此外,我们将使用 qrcode 库生成 QR 码,但你可以用任何其他同类库替换。
让我们创建一个目录 ton-connect-bot。在那里添加以下的 package.json 文件:
{
  "name": "ton-connect-bot",
  "version": "1.0.0",
  "scripts": {
    "compile": "npx rimraf dist && tsc",
    "run": "node ./dist/main.js"
  },
  "dependencies": {
    "@tonconnect/sdk": "^3.0.0-beta.1",
    "dotenv": "^16.0.3",
    "node-telegram-bot-api": "^0.61.0",
    "qrcode": "^1.5.1"
  },
  "devDependencies": {
    "@types/node-telegram-bot-api": "^0.61.4",
    "@types/qrcode": "^1.5.0",
    "rimraf": "^3.0.2",
    "typescript": "^4.9.5"
  }
}
运行 npm i 以安装依赖项。
添加 tsconfig.json
创建一个 tsconfig.json:
tsconfig.json 代码
{
  "compilerOptions": {
    "declaration": true,
    "lib": ["ESNext", "dom"],
    "resolveJsonModule": true,
    "experimentalDecorators": false,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "target": "es6",
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictPropertyInitialization": true,
    "noImplicitThis": true,
    "alwaysStrict": true,
    "noUnusedLocals": false,
    "noUnusedParameters": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "module": "commonjs",
    "moduleResolution": "node",
    "sourceMap": true,
    "useUnknownInCatchVariables": false,
    "noUncheckedIndexedAccess": true,
    "emitDecoratorMetadata": false,
    "importHelpers": false,
    "skipLibCheck": true,
    "skipDefaultLibCheck": true,
    "allowJs": true,
    "outDir": "./dist"
  },
  "include": ["src"],
  "exclude": [
    "./tests","node_modules", "lib", "types"]
}
添加简单的机器人代码
创建一个 .env 文件,并在其中添加你的机器人令牌、DAppmanifest 和钱包列表缓存存活时间:
了解更多关于 tonconnect-manifes.json
# .env
TELEGRAM_BOT_TOKEN=<YOUR BOT TOKEN, E.G 1234567890:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA>
TELEGRAM_BOT_LINK=<YOUR TG BOT LINK HERE, E.G. https://t.me/ton_connect_example_bot>
MANIFEST_URL=https://raw.githubusercontent.com/ton-connect/demo-telegram-bot/master/tonconnect-manifest.json
WALLETS_LIST_CACHE_TTL_MS=86400000
创建目录 src 和文件 bot.ts。在其中创建一个 TelegramBot 实例:
// src/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 });
现在我们可以在 src 目录内创建一个入口文件 main.ts:
// src/main.ts
import dotenv from 'dotenv';
dotenv.config();
import { bot } from './bot';
bot.on('message', msg => {
  const chatId = msg.chat.id;
  bot.sendMessage(chatId, 'Received your message');
});
我们可以了。你可以运行 npm run compile 和 npm run start,然后给你的机器人发送任何消息。机器人将回复“Received your message”。我们准备好进行 TonConnect 集成了。
目前我们有以下文件结构:
ton-connect-bot
├── src
│   ├── bot.ts
│   └── main.ts
├── package.json
├── package-lock.json
├── .env
└── tsconfig.json
连接钱包
我们已经安装了 @tonconnect/sdk,因此我们可以直接导入它开始使用。
我们将从获取钱包列表开始。我们只需要 http-bridge 兼容的钱包。在 src 中创建文件夹 ton-connect 并添加 wallets.ts 文件:
我们还定义了函数 getWalletInfo 来通过其 appName 查询详细的钱包信息。
name 和 appName 之间的区别是 name 是钱包的人类可读标签,而 appName 是钱包的唯一标识符。
// src/ton-connect/wallets.ts
import { isWalletInfoRemote, WalletInfoRemote, WalletsListManager } from '@tonconnect/sdk';
const walletsListManager = new WalletsListManager({
    cacheTTLMs: Number(process.env.WALLETS_LIST_CACHE_TTL_MS)
});
export async function getWallets(): Promise<WalletInfoRemote[]> {
    const wallets = await walletsListManager.getWallets();
    return wallets.filter(isWalletInfoRemote);
}
export async function getWalletInfo(walletAppName: string): Promise<WalletInfo | undefined> {
    const wallets = await getWallets();
    return wallets.find(wallet => wallet.appName.toLowerCase() === walletAppName.toLowerCase());
}
现在我们需要定义一个 TonConnect 存储。TonConnect 在浏览器中运行时使用 localStorage 来保存连接详情,但是 NodeJS 环境中没有 localStorage。这就是为什么我们应该添加一个自定义的简单存储实现。
在 ton-connect 目录内创建 storage.ts:
// src/ton-connect/storage.ts
import { IStorage } from '@tonconnect/sdk';
const storage = new Map<string, string>(); // temporary storage implementation. We will replace it with the redis later
export class TonConnectStorage implements IStorage {
  constructor(private readonly chatId: number) {} // we need to have different stores for different users
  private getKey(key: string): string {
    return this.chatId.toString() + key; // we will simply have different keys prefixes for different users
  }
  async removeItem(key: string): Promise<void> {
    storage.delete(this.getKey(key));
  }
  async setItem(key: string, value: string): Promise<void> {
    storage.set(this.getKey(key), value);
  }
  async getItem(key: string): Promise<string | null> {
    return storage.get(this.getKey(key)) || null;
  }
}
我们正在进行实现钱包连接。
修改 src/main.ts 并添加 connect 命令。我们打算在这个命令处理器中实现一个钱包连接。
import dotenv from 'dotenv';
dotenv.config();
import { bot } from './bot';
import { getWallets } from './ton-connect/wallets';
import TonConnect from '@tonconnect/sdk';
import { TonConnectStorage } from './ton-connect/storage';
import QRCode from 'qrcode';
bot.onText(/\/connect/, async msg => {
  const chatId = msg.chat.id;
  const wallets = await getWallets();
  const connector = new TonConnect({
    storage: new TonConnectStorage(chatId),
    manifestUrl: process.env.MANIFEST_URL
  });
  connector.onStatusChange(wallet => {
    if (wallet) {
      bot.sendMessage(chatId, `${wallet.device.appName} wallet connected!`);
    }
  });
  const tonkeeper = wallets.find(wallet => wallet.appName === 'tonkeeper')!;
  const link = connector.connect({
    bridgeUrl: tonkeeper.bridgeUrl,
    universalLink: tonkeeper.universalLink
  });
  const image = await QRCode.toBuffer(link);
  await bot.sendPhoto(chatId, image);
});
让我们分析一下我们在这里做的事情。首先我们获取钱包列表并创建一个TonConnect实例。
之后,我们订阅钱包变化。当用户连接一个钱包时,机器人会发送信息 ${wallet.device.appName} wallet connected!。
接下来我们找到Tonkeeper钱包并创建连接链接。最后我们生成一个带有链接的二维码,并将其作为照片发送给用户。
现在,你可以运行机器人(接着运行 npm run compile 和 npm run start)并向机器人发送 /connect 信息。机器人应该会回复二维码。用Tonkeeper钱包扫描它。你将在聊天中看到 Tonkeeper wallet connected! 的信息。
我们会在许多地方使用连接器,因此让我们将创建连接器的代码移动到一个单独的文件中:
// src/ton-connect/connector.ts
import TonConnect from '@tonconnect/sdk';
import { TonConnectStorage } from './storage';
import * as process from 'process';
export function getConnector(chatId: number): TonConnect {
    return new TonConnect({
        manifestUrl: process.env.MANIFEST_URL,
        storage: new TonConnectStorage(chatId)
    });
}
并在 src/main.ts 中导入它
// src/main.ts
import dotenv from 'dotenv';
dotenv.config();
import { bot } from './bot';
import { getWallets } from './ton-connect/wallets';
import QRCode from 'qrcode';
import { getConnector } from './ton-connect/connector';
bot.onText(/\/connect/, async msg => {
    const chatId = msg.chat.id;
    const wallets = await getWallets();
    const connector = getConnector(chatId);
    connector.onStatusChange(wallet => {
        if (wallet) {
            bot.sendMessage(chatId, `${wallet.device.appName} wallet connected!`);
        }
    });
    const tonkeeper = wallets.find(wallet => wallet.appName === 'tonkeeper')!;
    const link = connector.connect({
        bridgeUrl: tonkeeper.bridgeUrl,
        universalLink: tonkeeper.universalLink
    });
    const image = await QRCode.toBuffer(link);
    await bot.sendPhoto(chatId, image);
});
目前我们有以下文件结构:
bot-demo
├── src
│   ├── ton-connect
│   │   ├── connector.ts
│   │   ├── wallets.ts
│   │   └── storage.ts
│   ├── bot.ts
│   └── main.ts
├── package.json
├── package-lock.json
├── .env
└── tsconfig.json