Tonkeeper (TON) Wallet Integration
TON Connect is not simply "connect a wallet". The protocol is fundamentally different from EVM: there's no direct JSON-RPC, the connection works through a relay server (bridge), and the wallet (including Tonkeeper) interacts via deep links or QR codes. If you try to transfer EVM experience to TON without understanding this model, you'll end up with a non-functional integration.
TON Connect 2.0: How Connection Works
The dApp initiates a session: generates a keypair (x25519), publishes its public key on a bridge server (bridge.tonapi.io or self-hosted). The wallet receives an invite through a deep link (ton-connect://...) or QR code. After handshake — an encrypted channel through the bridge. All requests (sendTransaction, signMessage) go through this channel, not through direct RPC to the node.
This means: integration works even if the TON node is unavailable — the bridge maintains a connection with the wallet separately from blockchain reads.
Implementation with @tonconnect/ui-react
The official library covers most use cases:
import { TonConnectUIProvider, TonConnectButton, useTonConnectUI, useTonAddress } from '@tonconnect/ui-react';
// At the root of your application
<TonConnectUIProvider manifestUrl="https://yourapp.com/tonconnect-manifest.json">
<App />
</TonConnectUIProvider>
manifest.json — a mandatory file that the wallet shows to the user when connecting:
{
"url": "https://yourapp.com",
"name": "Your dApp",
"iconUrl": "https://yourapp.com/icon-256.png"
}
The file must be accessible over HTTPS at the specified URL. Without a proper manifest, Tonkeeper will deny the connection.
Sending a transaction:
const [tonConnectUI] = useTonConnectUI();
const userAddress = useTonAddress(); // raw or friendly format
async function sendTon(toAddress: string, amountNano: string) {
await tonConnectUI.sendTransaction({
validUntil: Math.floor(Date.now() / 1000) + 300, // 5 minutes
messages: [
{
address: toAddress,
amount: amountNano, // in nanoTON (1 TON = 1e9 nanoTON)
}
]
});
}
Interacting with Smart Contracts
TON contracts accept messages with body — TL-B cell. To send a call to a Jetton contract (like ERC-20):
import { beginCell, toNano } from '@ton/core';
// Transfer Jetton: op = 0xf8a7ea5
const body = beginCell()
.storeUint(0xf8a7ea5, 32) // op code
.storeUint(0, 64) // query_id
.storeCoins(toNano('10')) // amount
.storeAddress(destinationAddress)
.storeAddress(responseAddress)
.storeBit(0) // no custom payload
.storeCoins(toNano('0.05')) // forward_ton_amount
.storeBit(0)
.endCell();
await tonConnectUI.sendTransaction({
validUntil: Math.floor(Date.now() / 1000) + 300,
messages: [{
address: jettonWalletAddress,
amount: toNano('0.1').toString(), // TON for gas
payload: body.toBoc().toString('base64'),
}]
});
Getting Address and Balance
Reading data — through TonAPI or toncenter.com, not through TON Connect:
import { TonClient, Address } from '@ton/ton';
const client = new TonClient({
endpoint: 'https://toncenter.com/api/v2/jsonRPC',
apiKey: process.env.TONCENTER_API_KEY,
});
const balance = await client.getBalance(Address.parse(userAddress));
useTonAddress() returns the address in two formats: raw (0:abcd...) and friendly (base64url, bounce/non-bounce). For display — friendly. For comparison in code — raw or normalized via Address.parse().toString().
Timeline Guidelines
Basic TON Connect integration (connection + sending TON) — half a day. With Jetton transfers support and balance reads — 1-2 days. Full-featured wallet screen with transaction history via TonAPI — 2-3 days.







