TypeScript SDK
使用 TypeScript 构建 TON 应用的完整指南
本节重点
- 如何安装和配置 TON TypeScript SDK?
- 如何连接到 TON 网络?
- 如何查询链上数据?
- 如何构建和发送交易?
- 如何与智能合约交互?
- 如何使用 TON Connect 连接钱包?
SDK 概述
TON 生态提供了多个 TypeScript/JavaScript 库:
- @ton/ton - 核心 SDK,提供完整功能
- @ton/core - 底层数据结构和工具
- @ton/crypto - 加密相关功能
- @tonconnect/sdk - 钱包连接标准
安装和配置
环境要求
- Node.js: >= 18.0
- TypeScript: >= 4.5(可选)
安装依赖
bash
# 核心库
npm install @ton/ton @ton/core @ton/crypto
# 钱包连接
npm install @tonconnect/sdk @tonconnect/ui
# 开发工具
npm install -D typescript @types/node快速开始
创建项目
bash
# 创建新项目
mkdir ton-app
cd ton-app
npm init -y
# 安装依赖
npm install @ton/ton @ton/core @ton/crypto
# 创建 TypeScript 配置
npx tsc --init基础示例
创建 src/index.ts:
typescript
import { TonClient, Address } from '@ton/ton';
async function main() {
// 连接到测试网
const client = new TonClient({
endpoint: 'https://testnet.toncenter.com/api/v2/jsonRPC',
});
// 查询余额
const address = Address.parse('EQ...');
const balance = await client.getBalance(address);
console.log('余额:', balance);
}
main();运行:
bash
npx ts-node src/index.ts连接到 TON 网络
创建客户端
typescript
import { TonClient } from '@ton/ton';
// 连接到测试网
const testnetClient = new TonClient({
endpoint: 'https://testnet.toncenter.com/api/v2/jsonRPC',
apiKey: 'your-api-key', // 可选,但推荐使用
});
// 连接到主网
const mainnetClient = new TonClient({
endpoint: 'https://toncenter.com/api/v2/jsonRPC',
apiKey: 'your-api-key',
});
// 使用自定义 RPC
const customClient = new TonClient({
endpoint: 'https://your-custom-rpc-url',
});配置选项
typescript
import { TonClient, HttpApi } from '@ton/ton';
const client = new TonClient({
endpoint: 'https://testnet.toncenter.com/api/v2/jsonRPC',
apiKey: 'your-api-key',
timeout: 30000, // 30 秒超时
});地址和密钥管理
地址操作
typescript
import { Address } from '@ton/core';
// 解析地址
const address = Address.parse('EQ...');
// 地址格式转换
console.log(address.toString()); // 用户友好格式
console.log(address.toRawString()); // 原始格式
console.log(address.toString({ bounceable: false })); // 不可弹回格式
// 检查地址相等
const isSame = address.equals(anotherAddress);
// 获取工作链
const workchain = address.workChain; // 0 或 -1生成密钥对
typescript
import { mnemonicNew, mnemonicToPrivateKey } from '@ton/crypto';
import { WalletContractV4 } from '@ton/ton';
// 生成新助记词
const mnemonic = await mnemonicNew(24);
console.log('助记词:', mnemonic.join(' '));
// 从助记词派生密钥对
const keyPair = await mnemonicToPrivateKey(mnemonic);
console.log('公钥:', keyPair.publicKey.toString('hex'));
console.log('私钥:', keyPair.secretKey.toString('hex'));
// 创建钱包
const wallet = WalletContractV4.create({
workchain: 0,
publicKey: keyPair.publicKey,
});
console.log('钱包地址:', wallet.address.toString());导入密钥
typescript
import { mnemonicToPrivateKey, mnemonicValidate } from '@ton/crypto';
// 验证助记词
const mnemonic = 'your 24 word mnemonic phrase...'.split(' ');
const isValid = await mnemonicValidate(mnemonic);
if (isValid) {
const keyPair = await mnemonicToPrivateKey(mnemonic);
// 使用密钥对...
}查询链上数据
查询余额
typescript
import { TonClient, Address, fromNano } from '@ton/ton';
const client = new TonClient({
endpoint: 'https://testnet.toncenter.com/api/v2/jsonRPC',
});
const address = Address.parse('EQ...');
// 查询余额(返回 bigint,单位为 nanoton)
const balance = await client.getBalance(address);
console.log('余额:', fromNano(balance), 'TON');查询账户状态
typescript
// 查询账户状态
const state = await client.getContractState(address);
console.log('状态:', state.state); // 'active', 'uninitialized', 'frozen'
console.log('余额:', state.balance);
console.log('最后交易:', state.lastTransaction);查询交易历史
typescript
// 查询交易历史
const transactions = await client.getTransactions(address, {
limit: 10,
});
for (const tx of transactions) {
console.log('交易哈希:', tx.hash().toString('hex'));
console.log('时间:', new Date(tx.now * 1000));
console.log('---');
}调用 Get 方法
typescript
import { TonClient, Address } from '@ton/ton';
const client = new TonClient({
endpoint: 'https://testnet.toncenter.com/api/v2/jsonRPC',
});
const contractAddress = Address.parse('EQ...');
// 调用 get 方法
const result = await client.runMethod(contractAddress, 'get_counter');
// 读取返回值
const counter = result.stack.readNumber();
console.log('计数器值:', counter);解析复杂返回值
typescript
// 调用返回多个值的方法
const result = await client.runMethod(contractAddress, 'get_data');
// 读取多个返回值
const id = result.stack.readNumber();
const owner = result.stack.readAddress();
const active = result.stack.readBoolean();
console.log('ID:', id);
console.log('所有者:', owner.toString());
console.log('激活:', active);构建和发送交易
基础转账
typescript
import { TonClient, WalletContractV4, internal } from '@ton/ton';
import { mnemonicToPrivateKey } from '@ton/crypto';
import { toNano } from '@ton/core';
async function sendTON() {
const client = new TonClient({
endpoint: 'https://testnet.toncenter.com/api/v2/jsonRPC',
});
// 从助记词创建钱包
const mnemonic = 'your 24 word mnemonic...'.split(' ');
const keyPair = await mnemonicToPrivateKey(mnemonic);
const wallet = WalletContractV4.create({
workchain: 0,
publicKey: keyPair.publicKey,
});
const contract = client.open(wallet);
// 发送 TON
await contract.sendTransfer({
seqno: await contract.getSeqno(),
secretKey: keyPair.secretKey,
messages: [
internal({
to: 'EQ...',
value: toNano('1'),
body: 'Hello TON!',
}),
],
});
console.log('交易已发送');
}
sendTON();发送多笔交易
typescript
// 批量转账
await contract.sendTransfer({
seqno: await contract.getSeqno(),
secretKey: keyPair.secretKey,
messages: [
internal({
to: 'EQ...1',
value: toNano('1'),
}),
internal({
to: 'EQ...2',
value: toNano('2'),
}),
internal({
to: 'EQ...3',
value: toNano('3'),
}),
],
});调用智能合约
typescript
import { beginCell, toNano } from '@ton/core';
// 构建消息体
const body = beginCell()
.storeUint(1, 32) // op code
.storeUint(0, 64) // query id
.storeUint(100, 32) // amount
.endCell();
// 发送到合约
await contract.sendTransfer({
seqno: await contract.getSeqno(),
secretKey: keyPair.secretKey,
messages: [
internal({
to: 'EQ...contract',
value: toNano('0.05'),
body: body,
}),
],
});TON Connect 钱包连接
安装 TON Connect
bash
npm install @tonconnect/sdk @tonconnect/ui创建 Manifest
创建 public/tonconnect-manifest.json:
json
{
"url": "https://your-app.com",
"name": "Your App Name",
"iconUrl": "https://your-app.com/icon.png",
"termsOfUseUrl": "https://your-app.com/terms",
"privacyPolicyUrl": "https://your-app.com/privacy"
}基础使用
typescript
import { TonConnectUI } from '@tonconnect/ui';
// 初始化 TON Connect
const tonConnectUI = new TonConnectUI({
manifestUrl: 'https://your-app.com/tonconnect-manifest.json',
});
// 连接钱包
await tonConnectUI.connectWallet();
// 获取钱包信息
const wallet = tonConnectUI.wallet;
if (wallet) {
console.log('钱包地址:', wallet.account.address);
console.log('链:', wallet.account.chain);
}
// 监听连接状态
tonConnectUI.onStatusChange((wallet) => {
if (wallet) {
console.log('钱包已连接:', wallet.account.address);
} else {
console.log('钱包已断开');
}
});发送交易
typescript
import { toNano } from '@ton/core';
// 发送交易
const transaction = {
validUntil: Math.floor(Date.now() / 1000) + 600, // 10 分钟有效期
messages: [
{
address: 'EQ...',
amount: toNano('1').toString(),
payload: 'Hello TON!',
},
],
};
try {
const result = await tonConnectUI.sendTransaction(transaction);
console.log('交易成功:', result);
} catch (error) {
console.error('交易失败:', error);
}React 集成
bash
npm install @tonconnect/ui-reacttsx
import { TonConnectUIProvider, TonConnectButton, useTonConnectUI, useTonWallet } from '@tonconnect/ui-react';
// 在应用根组件包裹 Provider
function App() {
return (
<TonConnectUIProvider manifestUrl="https://your-app.com/tonconnect-manifest.json">
<YourApp />
</TonConnectUIProvider>
);
}
// 使用连接按钮
function ConnectWallet() {
return <TonConnectButton />;
}
// 获取钱包信息
function WalletInfo() {
const wallet = useTonWallet();
if (!wallet) {
return <div>未连接钱包</div>;
}
return (
<div>
<p>地址: {wallet.account.address}</p>
<p>链: {wallet.account.chain}</p>
</div>
);
}
// 发送交易
function SendTransaction() {
const [tonConnectUI] = useTonConnectUI();
const handleSend = async () => {
await tonConnectUI.sendTransaction({
validUntil: Math.floor(Date.now() / 1000) + 600,
messages: [
{
address: 'EQ...',
amount: toNano('1').toString(),
},
],
});
};
return <button onClick={handleSend}>发送 1 TON</button>;
}Cell 操作
构建 Cell
typescript
import { beginCell, Cell } from '@ton/core';
// 构建简单 Cell
const cell = beginCell()
.storeUint(0x12345678, 32) // 存储 32 位无符号整数
.storeInt(-100, 32) // 存储 32 位有符号整数
.storeCoins(toNano('1')) // 存储金额
.storeAddress(address) // 存储地址
.storeStringTail('Hello') // 存储字符串
.endCell();
// 构建嵌套 Cell
const innerCell = beginCell()
.storeUint(1, 8)
.endCell();
const outerCell = beginCell()
.storeRef(innerCell) // 存储引用
.endCell();解析 Cell
typescript
// 解析 Cell
const slice = cell.beginParse();
const op = slice.loadUint(32);
const amount = slice.loadInt(32);
const coins = slice.loadCoins();
const address = slice.loadAddress();
const text = slice.loadStringTail();
console.log('Op:', op);
console.log('Amount:', amount);
console.log('Coins:', fromNano(coins));
console.log('Address:', address.toString());
console.log('Text:', text);Cell 序列化
typescript
// 序列化为 BOC(Bag of Cells)
const boc = cell.toBoc();
console.log('BOC (base64):', boc.toString('base64'));
console.log('BOC (hex):', boc.toString('hex'));
// 从 BOC 反序列化
const cellFromBoc = Cell.fromBoc(boc)[0];实用工具
单位转换
typescript
import { toNano, fromNano } from '@ton/core';
// TON 转 nanoton
const nanotons = toNano('1.5'); // 1500000000n
const nanotons2 = toNano(1.5); // 1500000000n
// nanoton 转 TON
const tons = fromNano(1500000000n); // "1.5"
const tons2 = fromNano('1500000000'); // "1.5"地址验证
typescript
import { Address } from '@ton/core';
function isValidAddress(addressString: string): boolean {
try {
Address.parse(addressString);
return true;
} catch {
return false;
}
}
console.log(isValidAddress('EQ...')); // true
console.log(isValidAddress('invalid')); // false等待交易确认
typescript
async function waitForTransaction(
client: TonClient,
address: Address,
previousLt: bigint,
timeout = 60000
): Promise<boolean> {
const start = Date.now();
while (Date.now() - start < timeout) {
const state = await client.getContractState(address);
if (state.lastTransaction && state.lastTransaction.lt > previousLt) {
return true;
}
await new Promise(resolve => setTimeout(resolve, 2000));
}
return false;
}完整示例
Jetton 管理器
typescript
import { TonClient, Address, toNano, fromNano, beginCell } from '@ton/ton';
import { WalletContractV4, internal } from '@ton/ton';
import { mnemonicToPrivateKey } from '@ton/crypto';
class JettonManager {
private client: TonClient;
private wallet: WalletContractV4;
private keyPair: any;
private jettonMasterAddress: Address;
constructor(
endpoint: string,
mnemonic: string[],
jettonMasterAddress: string
) {
this.client = new TonClient({ endpoint });
this.jettonMasterAddress = Address.parse(jettonMasterAddress);
}
async init(mnemonic: string[]) {
this.keyPair = await mnemonicToPrivateKey(mnemonic);
this.wallet = WalletContractV4.create({
workchain: 0,
publicKey: this.keyPair.publicKey,
});
}
// 获取 Jetton 钱包地址
async getJettonWalletAddress(ownerAddress: Address): Promise<Address> {
const result = await this.client.runMethod(
this.jettonMasterAddress,
'get_wallet_address',
[{ type: 'slice', cell: beginCell().storeAddress(ownerAddress).endCell() }]
);
return result.stack.readAddress();
}
// 查询余额
async getBalance(ownerAddress: Address): Promise<bigint> {
try {
const jettonWalletAddress = await this.getJettonWalletAddress(ownerAddress);
const result = await this.client.runMethod(
jettonWalletAddress,
'get_wallet_data'
);
return result.stack.readBigNumber();
} catch {
return 0n;
}
}
// 转账 Jetton
async transfer(
toAddress: Address,
amount: bigint,
forwardPayload?: string
): Promise<void> {
const contract = this.client.open(this.wallet);
const jettonWalletAddress = await this.getJettonWalletAddress(this.wallet.address);
// 构建转账消息
let body = beginCell()
.storeUint(0xf8a7ea5, 32) // op: transfer
.storeUint(0, 64) // query_id
.storeCoins(amount) // amount
.storeAddress(toAddress) // destination
.storeAddress(this.wallet.address) // response_destination
.storeBit(0) // no custom_payload
.storeCoins(toNano('0.01')); // forward_ton_amount
if (forwardPayload) {
body = body
.storeBit(1)
.storeRef(beginCell().storeStringTail(forwardPayload).endCell());
} else {
body = body.storeBit(0);
}
// 发送交易
await contract.sendTransfer({
seqno: await contract.getSeqno(),
secretKey: this.keyPair.secretKey,
messages: [
internal({
to: jettonWalletAddress,
value: toNano('0.1'),
body: body.endCell(),
}),
],
});
}
// 销毁 Jetton
async burn(amount: bigint): Promise<void> {
const contract = this.client.open(this.wallet);
const jettonWalletAddress = await this.getJettonWalletAddress(this.wallet.address);
const body = beginCell()
.storeUint(0x595f07bc, 32) // op: burn
.storeUint(0, 64) // query_id
.storeCoins(amount) // amount
.storeAddress(this.wallet.address) // response_destination
.endCell();
await contract.sendTransfer({
seqno: await contract.getSeqno(),
secretKey: this.keyPair.secretKey,
messages: [
internal({
to: jettonWalletAddress,
value: toNano('0.05'),
body: body,
}),
],
});
}
}
// 使用示例
async function main() {
const manager = new JettonManager(
'https://testnet.toncenter.com/api/v2/jsonRPC',
'your 24 word mnemonic...'.split(' '),
'EQ...jetton_master_address'
);
await manager.init('your 24 word mnemonic...'.split(' '));
// 查询余额
const balance = await manager.getBalance(Address.parse('EQ...'));
console.log('余额:', fromNano(balance));
// 转账
await manager.transfer(
Address.parse('EQ...recipient'),
toNano('100'),
'Hello from TON!'
);
console.log('转账成功');
}
main();最佳实践
1. 错误处理
typescript
async function safeTransaction() {
try {
await contract.sendTransfer({
seqno: await contract.getSeqno(),
secretKey: keyPair.secretKey,
messages: [/* ... */],
});
} catch (error) {
if (error instanceof Error) {
console.error('交易失败:', error.message);
}
throw error;
}
}2. 重试机制
typescript
async function retryOperation<T>(
operation: () => Promise<T>,
maxRetries = 3,
delay = 1000
): Promise<T> {
for (let i = 0; i < maxRetries; i++) {
try {
return await operation();
} catch (error) {
if (i === maxRetries - 1) throw error;
await new Promise(resolve => setTimeout(resolve, delay * (i + 1)));
}
}
throw new Error('Max retries reached');
}3. 配置管理
typescript
interface Config {
network: 'testnet' | 'mainnet';
apiKey?: string;
mnemonic: string;
}
function getClient(config: Config): TonClient {
const endpoints = {
testnet: 'https://testnet.toncenter.com/api/v2/jsonRPC',
mainnet: 'https://toncenter.com/api/v2/jsonRPC',
};
return new TonClient({
endpoint: endpoints[config.network],
apiKey: config.apiKey,
});
}