Skip to main content

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.



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

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

MethodDetails
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

MethodDetails
sendTransactionSend TON or interact with the contract
signDataSign any data cell
disconnectDisconnect 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

CodeDescription
0unknown error
1Request format error
100Unrecognized application
300User rejected the request
400Method 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);
}