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);
}