跳到主要内容

TON

UKey Wallet TON Connect Provider 用于在 Web 应用中连接 TON 账户、发送交易、请求数据签名和监听钱包状态。页面中可通过 window.$ukey.tonconnect 访问。

ℹ️

UKey Wallet 实现 TON Connect 2.0 协议,可接入支持 TON Connect 的 DApp 和 SDK。


快捷链接


找 Provider

// 检查 UKey Wallet TON provider
const provider = window.$ukey?.tonconnect;

if (!provider) {
throw new Error("未检测到 UKey Wallet TON provider");
}

// 确认设备与钱包信息
console.log("识别到的设备信息:", provider.deviceInfo);
console.log("识别到的钱包信息:", provider.walletInfo);
console.log("协议版本号:", provider.protocolVersion); // 数值参考片段:2

快上手

连接账户

// 基础连接方式
const connectEvent = await provider.connect();

if (connectEvent.event === "connect") {
const addressItem = connectEvent.payload.items.find(
(item) => item.name === "ton_addr",
);
console.log("连接结果:", addressItem.address);
} else {
console.error("连接未成功:", connectEvent.payload.message);
}

使用 Manifest 连接

生产环境应提供 manifest URL,让钱包展示可信的应用名称、图标和隐私链接:

const connectRequest = {
manifestUrl: "https://sampleapp.example/tonconnect-manifest.json",
items: [
{ name: "ton_addr" },
{ name: "ton_proof", payload: "demo-proof-check" },
],
};

const connectEvent = await provider.connect(2, connectRequest);

if (connectEvent.event === "connect") {
const addressItem = connectEvent.payload.items.find(
(i) => i.name === "ton_addr",
);
const proofItem = connectEvent.payload.items.find(
(i) => i.name === "ton_proof",
);

console.log("解析出的地址:", addressItem.address);
console.log("返回的证明数据:", proofItem?.proof);
}

Manifest 格式

在应用根目录创建 tonconnect-manifest.json

{
"url": "https://sampleapp.example",
"name": "Your App Name",
"iconUrl": "https://sampleapp.example/icon.png",
"termsOfUseUrl": "https://sampleapp.example/terms",
"privacyPolicyUrl": "https://sampleapp.example/privacy"
}

恢复连接

页面刷新后可以尝试恢复之前的会话:

const connectEvent = await provider.restoreConnection();

if (connectEvent.event === "connect") {
console.log("上一次会话已恢复");
}

断连

await provider.disconnect();

交易项

发交易

通过 send 方法调用 sendTransaction 可以发起 TON 转账:

const callResult = await provider.send({
method: "sendTransaction",
id: Date.now().toString(),
requestParams: [
JSON.stringify({
valid_until: Math.floor(Date.now() / 1000) + 600, // 10 分钟有效期
messages: [
{
address: "0:8a1d3c5e7f90b2d4c6e8f00123456789abcdeffedcba9876543210fedcba1234", // 接收方地址(原始格式)
amount: "1000000000", // 金额参考值,单位 nanotons(1 TON = 10^9 nanotons)
},
],
}),
],
});

if ("callResult" in callResult) {
console.log("返回的交易 BOC:", callResult.callResult);
} else {
console.error("交易提交未成功:", callResult.error.message);
}

发送多条消息

TON 单笔交易最多包含 4 条消息:

const callResult = await provider.send({
method: "sendTransaction",
id: Date.now().toString(),
requestParams: [
JSON.stringify({
valid_until: Math.floor(Date.now() / 1000) + 600,
messages: [
{
address: "0:8a1d3c5e7f90b2d4c6e8f00123456789abcdeffedcba9876543210fedcba1234",
amount: "500000000", // half TON
},
{
address: "0:8e4d3c2b1a098765f4e3d2c1b0a9876543210fedcba9876543210fedcba98765",
amount: "300000000", // 0.3 TON amount
},
],
}),
],
});

发送带 Payload 的交易

合约交互通常需要在消息中携带 Base64 BOC 格式的 payload:

import { beginCell } from "@ton/core";

// 构建评论 cell
const comment = beginCell()
.storeUint(0, 32) // comment opcode
.storeStringTail("Hello from dApp!")
.endCell();

const callResult = await provider.send({
method: "sendTransaction",
id: Date.now().toString(),
requestParams: [
JSON.stringify({
valid_until: Math.floor(Date.now() / 1000) + 600,
messages: [
{
address: "0:8a1d3c5e7f90b2d4c6e8f00123456789abcdeffedcba9876543210fedcba1234",
amount: "100000000",
payload: comment.toBoc().toString("base64"),
},
],
}),
],
});

发送带 StateInit 的交易

部署合约或初始化账户状态时,可在消息中携带 stateInit

const callResult = await provider.send({
method: "sendTransaction",
id: Date.now().toString(),
requestParams: [
JSON.stringify({
valid_until: Math.floor(Date.now() / 1000) + 600,
messages: [
{
address: "0:9d8c7b6a5948372615049382716f5e4d3c2b1a09876543210fedcba987654321",
amount: "50000000",
stateInit: stateInitBoc.toString("base64"), // Base64 编码的 StateInit 数据
},
],
}),
],
});

数据签名

签名任意数据

使用 signData 可以请求用户签名 Base64 编码的 cell:

const callResult = await provider.send({
method: "signData",
id: Date.now().toString(),
requestParams: [
JSON.stringify({
schema_crc: 0, // 自定义 schema ID
cell: cellBoc.toString("base64"), // Base64 编码的待签名 cell 数据
}),
],
});

if ("callResult" in callResult) {
console.log("生成的签名:", callResult.callResult.signature); // Base64 格式签名
console.log("记录时间戳:", callResult.callResult.timestamp); // UNIX 时间戳
}

签名证明(身份验证)

登录类场景建议在连接时请求 ton_proof,并把服务端生成的 challenge 放入 payload:

const connectEvent = await provider.connect(2, {
manifestUrl: "https://sampleapp.example/tonconnect-manifest.json",
items: [
{ name: "ton_addr" },
{
name: "ton_proof",
payload: "server-issued-demo-challenge", // 用于防重放的 nonce
},
],
});

if (connectEvent.event === "connect") {
const proofItem = connectEvent.payload.items.find(
(i) => i.name === "ton_proof",
);

if (proofItem) {
// 把 proof 发送到后端校验
const proof = proofItem.proof;
console.log({
signature: proof.signature,
timestamp: proof.timestamp,
domain: proof.domain,
payload: proof.payload,
});
}
}

事件流

监听事件

provider.listen((event) => {
console.log("钱包回调事件:", event);
});

// 或者使用专门的事件监听器
provider.on("accountChanged", (address) => {
if (address) {
console.log("当前账户已切换为:", address);
} else {
console.log("连接已关闭");
}
});

provider.on("disconnect", () => {
console.log("钱包会话已断开");
});

API说明

方法集

方法说明
connect(version?, request?)连接钱包
restoreConnection()恢复之前的会话
disconnect()断开钱包连接
send(message)发送 RPC 请求
listen(callback)监听钱包事件

Send 方法

方法说明
sendTransaction发送 TON 或与合约交互
signData签名任意数据 cell
disconnect通过 RPC 断开连接

类型集

interface AccountInfo {
address: string; // 原始地址格式 (0:<hex>)
network: string; // 网络 ID(-239 主网,-3 测试网)
publicKey: string; // 不带 0x 的十六进制公钥
walletStateInit: string; // Base64 编码后的 state init
}

interface TransactionRequest {
valid_until?: number; // 过期时间的 UNIX 时间戳
network?: string; // 网络标识
from?: string; // 发起方地址
messages: Message[]; // 最多可带 4 条消息
}

interface Message {
address: string; // 接收方原始地址格式
amount: string; // 金额字段,单位为 nanotons
payload?: string; // 合约调用要用到的 Base64 BOC
stateInit?: string; // 部署时要带的 Base64 StateInit
}

interface SignDataRequest {
schema_crc: number; // Schema ID
cell: string; // Base64 编码的 cell 数据
publicKey?: string; // 可选指定公钥
}

interface SignDataResult {
signature: string; // Base64 格式签名
timestamp: number; // UTC UNIX 时间戳
}

interface DeviceInfo {
platform: string; // allowed values: 'iphone' | 'android' | 'windows' | 'mac' | 'linux'
appName: string; // sample app name: 'ukey'
appVersion: string; // 钱包版本号
maxProtocolVersion: number; // 数值参考片段:2
features: string[]; // 支持能力列表
}

错误码

错误码说明
0未归类错误
1请求格式不正确
100应用来源未知
300用户取消了本次请求
400当前方法不可用

使用 TON Connect SDK

React 项目可以使用 TON Connect SDK。UKey Wallet 会作为兼容钱包出现在连接流程中:

npm install @tonconnect/ui-react
import { TonConnectUIProvider, TonConnectButton } from "@tonconnect/ui-react";

function TonConnectShell() {
return (
<TonConnectUIProvider manifestUrl="https://sampleapp.example/tonconnect-manifest.json">
<TonConnectButton />
<YourApp />
</TonConnectUIProvider>
);
}

UKey Wallet 会被 TON Connect SDK 自动检测到。

使用 SDK 发送交易

import { useTonConnectUI } from "@tonconnect/ui-react";

function TonTransferButton() {
const [tonConnectUI] = useTonConnectUI();

const handleSend = async () => {
const callResult = await tonConnectUI.sendTransaction({
validUntil: Math.floor(Date.now() / 1000) + 600,
messages: [
{
address: "EQA...",
amount: "1000000000",
},
],
});
console.log("序列化后的 BOC:", callResult.boc);
};

return <button onClick={handleSend}>发送 1 TON</button>;
}

地址样式

TON 地址有原始格式和用户友好格式。Provider 请求中常使用原始格式,面向用户展示时通常使用友好格式:

import { Address } from "@ton/core";

// 原始格式(provider 内部使用)
const raw = "0:8a1d3c5e7f90b2d4c6e8f00123456789abcdeffedcba9876543210fedcba1234";

// 友好格式(可 bounce)
const friendly = Address.parse(raw).toString();
// 例如 'EQBx7qMv2Lk9sNp4Rz5hTc8Yw1Df6JaQ3'

// 不可 bounce 格式
const nonBounceable = Address.parse(raw).toString({ bounceable: false });
// 例如:'UQBm4xPv8nLs2kTr6qWc9Yd1fGh5Jz0Ra'

// 把友好格式转回原始格式
const addr = Address.parse("EQBx7qMv2Lk9sNp4Rz5hTc8Yw1Df6JaQ3");
const rawAddress = `${addr.workChain}:${addr.hash.toString("hex")}`;

处理异常

const callResult = await provider.send({
method: "sendTransaction",
id: Date.now().toString(),
requestParams: [
/* remaining fields omitted */
],
});

if ("error" in callResult) {
switch (callResult.error.code) {
case 300:
console.log("用户拒绝了这笔交易");
break;
case 1:
console.log("请求格式不正确,请检查参数");
break;
case 400:
console.log("当前方法不受支持");
break;
default:
console.error("执行报错:", callResult.error.message);
}
} else {
console.log("成功结果:", callResult.callResult);
}