TON
UKey Wallet TON Connect Provider is used to connect TON accounts, send transactions, request data signatures and monitor wallet status in web applications. The page is accessible via window.$ukey.tonconnect.
UKey Wallet implements the TON Connect 2.0 protocol and can access DApps and SDKs that support TON Connect.
Fast Links
Find Provider
// Detect the UKey Wallet TON provider
const provider = window.$ukey?.tonconnect;
if (!provider) {
throw new Error("UKey Wallet TON provider not detected");
}
// Inspect device and wallet details
console.log("Detected device:", provider.deviceInfo);
console.log("Detected wallet:", provider.walletInfo);
console.log("Protocol revision:", provider.protocolVersion); // Reference value: 2
Get Going
Link Wallet
// Basic connection flow
const connectEvent = await provider.connect();
if (connectEvent.event === "connect") {
const addressItem = connectEvent.payload.items.find(
(item) => item.name === "ton_addr",
);
console.log("Connection established:", addressItem.address);
} else {
console.error("Unable to connect:", connectEvent.payload.message);
}
Connect using Manifest
The production environment should provide a manifest URL that allows the wallet to display the trusted application name, icon, and privacy link:
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("Resolved address:", addressItem.address);
console.log("Returned proof:", proofItem?.proof);
}
Manifest format
Create tonconnect-manifest.json in the application root directory:
{
"url": "https://sampleapp.example",
"name": "Your App Name",
"iconUrl": "https://sampleapp.example/icon.png",
"termsOfUseUrl": "https://sampleapp.example/terms",
"privacyPolicyUrl": "https://sampleapp.example/privacy"
}
Restore connection
After refreshing the page, you can try to restore the previous session:
const connectEvent = await provider.restoreConnection();
if (connectEvent.event === "connect") {
console.log("Previous session restored");
}
Unlink
await provider.disconnect();
Transfers
Send tx
You can initiate a TON transfer by calling sendTransaction via the send method:
const callResult = await provider.send({
method: "sendTransaction",
id: Date.now().toString(),
requestParams: [
JSON.stringify({
valid_until: Math.floor(Date.now() / 1000) + 600, // 10 minutes validity period
messages: [
{
address: "0:8a1d3c5e7f90b2d4c6e8f00123456789abcdeffedcba9876543210fedcba1234", // Recipient address (original format)
amount: "1000000000", // sample amount expressed in nanotons (1 TON = 10^9 nanotons)
},
],
}),
],
});
if ("callResult" in callResult) {
console.log("Transaction BOC payload:", callResult.callResult);
} else {
console.error("Transaction submission failed:", callResult.error.message);
}
Send multiple messages
A single TON transaction can contain up to 4 messages:
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
},
],
}),
],
});
Send transaction with payload
Contract interaction usually requires carrying a payload in Base64 BOC format in the message:
import { beginCell } from "@ton/core";
// Create the comment cell
const comment = beginCell()
.storeUint(0, 32) // Note: 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"),
},
],
}),
],
});
Send transaction with StateInit
When deploying a contract or initializing account status, you can carry stateInit in the message:
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"), // Note: Base64 encoded StateInit
},
],
}),
],
});
Data signature
Sign arbitrary data
Use signData to request a user-signed Base64 encoded cell:
const callResult = await provider.send({
method: "signData",
id: Date.now().toString(),
requestParams: [
JSON.stringify({
schema_crc: 0, // Note: Custom schema identifier
cell: cellBoc.toString("base64"), // Note: Base64 encoded cell to be signed
}),
],
});
if ("callResult" in callResult) {
console.log("Generated signature:", callResult.callResult.signature); // Note: Base64 signature
console.log("Recorded timestamp:", callResult.callResult.timestamp); // Note: UNIX timestamp
}
Proof of signature (authentication)
In login scenarios, request ton_proof when connecting and put the challenge generated by the server into the 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", // Note: nonce used to prevent replay
},
],
});
if (connectEvent.event === "connect") {
const proofItem = connectEvent.payload.items.find(
(i) => i.name === "ton_proof",
);
if (proofItem) {
// Send the proof to your backend for verification
const proof = proofItem.proof;
console.log({
signature: proof.signature,
timestamp: proof.timestamp,
domain: proof.domain,
payload: proof.payload,
});
}
}
Event Flow
Listen for events
provider.listen((event) => {
console.log("Wallet callback event:", event);
});
// Or use a dedicated event listener
provider.on("accountChanged", (address) => {
if (address) {
console.log("Active account changed:", address);
} else {
console.log("Connection closed");
}
});
provider.on("disconnect", () => {
console.log("Wallet session disconnected");
});
API Notes
Methods
| Method | Details |
|---|---|
connect(version?, request?) | Connect wallet |
restoreConnection() | Restore previous session |
disconnect() | Disconnect wallet |
send(message) | Send RPC request |
listen(callback) | Listen for wallet events |
Send method
| Method | Details |
|---|---|
sendTransaction | Send TON or interact with the contract |
signData | Sign any data cell |
disconnect | Disconnect via RPC |
Types
interface AccountInfo {
address: string; // 原始地址格式,例如 0:<hex>
network: string; // 网络编号:-239 主网,-3 测试网
publicKey: string; // hex public key without the 0x prefix
walletStateInit: string; // Note: Base64 encoded state init
}
interface TransactionRequest {
valid_until?: number; // Note: Validity UNIX timestamp
network?: string; // Note: Network ID
from?: string; // Note: Sender address
messages: Message[]; // Note: Max 4 messages
}
interface Message {
address: string; // 接收方的原始地址格式
amount: string; // amount field, measured in nanotons
payload?: string; // Note: Base64 BOC is used for contract calls
stateInit?: string; // Note: Base64 StateInit for deployment
}
interface SignDataRequest {
schema_crc: number; // Note: Schema identifier
cell: string; // Note: Base64 encoded cell
publicKey?: string; // Note: optional specific key
}
interface SignDataResult {
signature: string; // Note: Base64 signature
timestamp: number; // UTC 时间戳(UNIX)
}
interface DeviceInfo {
platform: string; // allowed values: 'iphone' | 'android' | 'windows' | 'mac' | 'linux'
appName: string; // sample app name: 'ukey'
appVersion: string; // Note: wallet version
maxProtocolVersion: number; // Reference value: 2
features: string[]; // Note: Supported features
}
error code
| Code | Description |
|---|---|
| 0 | unknown error |
| 1 | Request format error |
| 100 | Unrecognized application |
| 300 | User rejected the request |
| 400 | Method not available |
Using TON Connect SDK
React projects can use the TON Connect SDK. UKey Wallet will appear as a compatible wallet in the connection process:
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 will be automatically detected by TON Connect SDK.
Send transactions using the 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("Serialized BOC:", callResult.boc);
};
return <button onClick={handleSend}>Send 1 TON</button>;
}
Addr Format
TON addresses are available in raw and user-friendly formats. The original format is often used in Provider requests, and the friendly format is usually used when displayed to users:
import { Address } from "@ton/core";
// Provider 内部使用的原始格式
const raw = "0:8a1d3c5e7f90b2d4c6e8f00123456789abcdeffedcba9876543210fedcba1234";
// 对用户更友好的 bounceable 格式
const friendly = Address.parse(raw).toString();
// For instance 'EQBx7qMv2Lk9sNp4Rz5hTc8Yw1Df6JaQ3'
// Non-bounce format
const nonBounceable = Address.parse(raw).toString({ bounceable: false });
// 参考结果可能像 'UQBm4xPv8nLs2kTr6qWc9Yd1fGh5Jz0Ra'
// Convert from friendly format to raw format
const addr = Address.parse("EQBx7qMv2Lk9sNp4Rz5hTc8Yw1Df6JaQ3");
const rawAddress = `${addr.workChain}:${addr.hash.toString("hex")}`;
Handle Errors
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("The transaction was declined by the user");
break;
case 1:
console.log("Request format is invalid - review the parameters");
break;
case 400:
console.log("Requested method is unsupported");
break;
default:
console.error("Operation error:", callResult.error.message);
}
} else {
console.log("Successful callResult:", callResult.callResult);
}