TON Connect for React
The recommended SDK for React Apps is a UI React SDK. It is a React component that provides a high-level way to interact with TON Connect.
Implementation
Installation
To start integrating TON Connect into your DApp, you need to install the @tonconnect/ui-react
package. You can use npm or yarn for this purpose:
- npm
- Yarn
- pnpm
npm i @tonconnect/ui-react
yarn add @tonconnect/ui-react
pnpm add @tonconnect/ui-react
TON Connect Initiation
After installing the package, you should create a manifest.json
file for your application. More information on how to create a manifest.json file can be found here.
After creating the manifest file, import TonConnectUIProvider to the root of your Mini App and pass the manifest URL:
import { TonConnectUIProvider } from '@tonconnect/ui-react';
export function App() {
return (
<TonConnectUIProvider manifestUrl="https://<YOUR_APP_URL>/tonconnect-manifest.json">
{ /* Your app */ }
</TonConnectUIProvider>
);
}
Connect to the Wallet
Add the TonConnectButton
. The TonConnect Button is a universal UI component for initializing a connection. After the wallet is connected, it transforms into a wallet menu. It is recommended to place it in the top right corner of your app.
export const Header = () => {
return (
<header>
<span>My App with React UI</span>
<TonConnectButton />
</header>
);
};
You can add className and style props to the button as well. Note that you cannot pass child to the TonConnectButton:
<TonConnectButton className="my-button-class" style={{ float: "right" }}/>
Moreover, you can always initiate the connection manually using the useTonConnectUI
hook and openModal method.
export const Header = () => {
const [tonConnectUI, setOptions] = useTonConnectUI();
return (
<header>
<span>My App with React UI</span>
<button onClick={() => tonConnectUI.openModal()}>
Connect Wallet
</button>
</header>
);
};
Connect with a specific wallet
To open a modal window for a specific wallet, use the openSingleWalletModal()
method. This method takes the wallet's app_name
as a parameter (refer to the wallets-list.json file) and opens the corresponding wallet modal. It returns a promise that resolves once the modal window has successfully opened.
<button onClick={() => tonConnectUI.openSingleWalletModal('tonwallet')}>
Connect Wallet
</button>
Redirects
If you want to redirect the user to a specific page after wallet connection, you can use useTonConnectUI
hook and customize your return strategy.
Telegram Mini Apps
If you want to redirect the user to a Telegram Mini App after wallet connection, you can customize the TonConnectUIProvider
element:
<TonConnectUIProvider
// ... other parameters
actionsConfiguration={{
twaReturnUrl: 'https://t.me/<YOUR_APP_NAME>'
}}
>
</TonConnectUIProvider>
UI customization
To customize UI of the modal you can use the useTonConnectUI
hook and the setOptions
function. See more about useTonConnectUI hook in Hooks section.
Hooks
If you want to use some low-level TON Connect UI SDK features in your React app, you can use hooks from the @tonconnect/ui-react
package.
useTonAddress
Use it to get user's current ton wallet address. Pass the boolean parameter isUserFriendly
(default is true
) to choose the format of the address. If the wallet is not connected, the hook will return an empty string.
import { useTonAddress } from '@tonconnect/ui-react';
export const Address = () => {
const userFriendlyAddress = useTonAddress();
const rawAddress = useTonAddress(false);
return (
userFriendlyAddress && (
<div>
<span>User-friendly address: {userFriendlyAddress}</span>
<span>Raw address: {rawAddress}</span>
</div>
)
);
};
useTonConnectModal
Use this hook to access the functions for opening and closing the modal window. The hook returns an object with the current modal state and methods to open and close the modal.
import { useTonConnectModal } from '@tonconnect/ui-react';
export const ModalControl = () => {
const { state, open, close } = useTonConnectModal();
return (
<div>
<div>Modal state: {state?.status}</div>
<button onClick={open}>Open modal</button>
<button onClick={close}>Close modal</button>
</div>
);
};
useTonWallet
Use this hook to retrieve the user's current TON wallet.
If the wallet is not connected, the hook will return null
. The wallet
object provides common data such as the user's address, provider, TON proof, and other attributes (see the Wallet interface).
Additionally, you can access more specific details about the connected wallet, such as its name, image, and other attributes (refer to the WalletInfo interface).
import { useTonWallet } from '@tonconnect/ui-react';
export const Wallet = () => {
const wallet = useTonWallet();
return (
wallet && (
<div>
<span>Connected wallet address: {wallet.account.address}</span>
<span>Device: {wallet.device.appName}</span>
<span>Connected via: {wallet.provider}</span>
{wallet.connectItems?.tonProof?.proof && <span>Ton proof: {wallet.connectItems.tonProof.proof}</span>}
<div>Connected wallet info:</div>
<div>
{wallet.name} <img src={wallet.imageUrl} />
</div>
</div>
)
);
};
useTonConnectUI
Use it to get access to the TonConnectUI
instance and UI options updating function.
See more about TonConnectUI instance methods
See more about setOptions function
import { Locales, useTonConnectUI } from '@tonconnect/ui-react';
export const Settings = () => {
const [tonConnectUI, setOptions] = useTonConnectUI();
const onLanguageChange = (language: Locales) => {
setOptions({ language });
};
return (
<div>
<label>language</label>
<select onChange={(e) => onLanguageChange(e.target.value as Locales)}>
<option value="en">en</option>
<option value="ru">ru</option>
</select>
</div>
);
};
useIsConnectionRestored
Indicates current status of the connection restoring process. You can use it to detect when connection restoring process if finished.
import { useIsConnectionRestored } from '@tonconnect/ui-react';
export const EntrypointPage = () => {
const connectionRestored = useIsConnectionRestored();
if (!connectionRestored) {
return <Loader>Please wait...</Loader>;
}
return <MainPage />;
};
Usage
Let's take a look at how to use the React UI SDK on practice.
Sending transactions
Send TON coins (in nanotons) to a specific address:
import { useTonConnectUI } from '@tonconnect/ui-react';
const transaction: SendTransactionRequest = {
validUntil: Date.now() + 5 * 60 * 1000, // 5 minutes
messages: [
{
address:
"0QD-SuoCHsCL2pIZfE8IAKsjc0aDpDUQAoo-ALHl2mje04A-", // message destination in user-friendly format
amount: "20000000", // Toncoin in nanotons
},
],
};
export const Settings = () => {
const [tonConnectUI, setOptions] = useTonConnectUI();
return (
<div>
<button onClick={() => tonConnectUI.sendTransaction(transaction)}>
Send transaction
</button>
</div>
);
};
- Get more examples here: Preparing Messages
Understanding Transaction Status by Hash
The principle is located in Payment Processing (using tonweb). See more
Optional Check (ton_proof) on the Backend
Understand how to sign and verify messages: Signing and Verification
To ensure that the user truly owns the declared address, you can use ton_proof
.
Use the tonConnectUI.setConnectRequestParameters
function to set up your connection request parameters. You can use it for:
- Loading State: Show a loading state while waiting for a response from your backend.
- Ready State with tonProof: Set the state to 'ready' and include the tonProof value.
- If an error occurs, remove the loader and create the connect request without additional parameters.
const [tonConnectUI] = useTonConnectUI();
// Set loading state
tonConnectUI.setConnectRequestParameters({ state: "loading" });
// Fetch tonProofPayload from backend
const tonProofPayload: string | null =
await fetchTonProofPayloadFromBackend();
if (tonProofPayload) {
// Set ready state with tonProof
tonConnectUI.setConnectRequestParameters({
state: "ready",
value: { tonProof: tonProofPayload },
});
} else {
// Remove loader
tonConnectUI.setConnectRequestParameters(null);
}
Handling ton_proof Result
You can find the ton_proof
result in the wallet
object when the wallet is connected:
useEffect(() => {
tonConnectUI.onStatusChange((wallet) => {
if (
wallet.connectItems?.tonProof &&
"proof" in wallet.connectItems.tonProof
) {
checkProofInYourBackend(
wallet.connectItems.tonProof.proof,
wallet.account.address
);
}
});
}, [tonConnectUI]);
Structure of ton_proof
type TonProofItemReplySuccess = {
name: "ton_proof";
proof: {
timestamp: string; // Unix epoch time (seconds)
domain: {
lengthBytes: number; // Domain length
value: string; // Domain name
};
signature: string; // Base64-encoded signature
payload: string; // Payload from the request
}
}
You can find an example of authentication on this page
Wallet Disconnection
Call to disconnect the wallet:
const [tonConnectUI] = useTonConnectUI();
await tonConnectUI.disconnect();
Deploying contract
Deploying contract using TonConnect is pretty straightforward. You just need to obtain contract code and state init, store it as a cell and send transaction with stateInit
field provided.
Note, that CONTRACT_CODE
and CONTRACT_INIT_DATA
may be found in wrappers.
import { beginCell, Cell, contractAddress, StateInit, storeStateInit } from '@ton/core';
const [tonConnectUI] = useTonConnectUI();
const init = {
code: Cell.fromBase64('<CONTRACT_CODE>'),
data: Cell.fromBase64('<CONTRACT_INIT_DATA>')
} satisfies StateInit;
const stateInit = beginCell()
.store(storeStateInit(init))
.endCell();
const address = contractAddress(0, init);
await tonConnectUI.sendTransaction({
validUntil: Date.now() + 5 * 60 * 1000, // 5 minutes
messages: [
{
address: address.toRawString(),
amount: '5000000',
stateInit: stateInit.toBoc().toString('base64')
}
]
});
Wrappers
Wrappers are classes that simplify interaction with the contract, allowing you to work without concerning yourself with the underlying details.
- When developing a contract in FunC, you need to write the wrapper yourself.
- When using the Tact language, wrappers are automatically generated for you.
Check out the blueprint documentation how to develop and deploy contracts
Let's take a look at the default Blueprint
Counter wrapper example and how we can use it:
Details
Wrapper usage
Counter wrapper class:import { Address, beginCell, Cell, Contract, contractAddress, ContractProvider, Sender, SendMode } from '@ton/core';
export type CounterConfig = {
id: number;
counter: number;
};
export function counterConfigToCell(config: CounterConfig): Cell {
return beginCell().storeUint(config.id, 32).storeUint(config.counter, 32).endCell();
}
export const Opcodes = {
increase: 0x7e8764ef,
};
export class Counter implements Contract {
constructor(
readonly address: Address,
readonly init?: { code: Cell; data: Cell },
) {}
static createFromAddress(address: Address) {
return new Counter(address);
}
static createFromConfig(config: CounterConfig, code: Cell, workchain = 0) {
const data = counterConfigToCell(config);
const init = { code, data };
return new Counter(contractAddress(workchain, init), init);
}
async sendDeploy(provider: ContractProvider, via: Sender, value: bigint) {
await provider.internal(via, {
value,
sendMode: SendMode.PAY_GAS_SEPARATELY,
body: beginCell().endCell(),
});
}
async sendIncrease(
provider: ContractProvider,
via: Sender,
opts: {
increaseBy: number;
value: bigint;
queryID?: number;
},
) {
await provider.internal(via, {
value: opts.value,
sendMode: SendMode.PAY_GAS_SEPARATELY,
body: beginCell()
.storeUint(Opcodes.increase, 32)
.storeUint(opts.queryID ?? 0, 64)
.storeUint(opts.increaseBy, 32)
.endCell(),
});
}
async getCounter(provider: ContractProvider) {
const result = await provider.get('get_counter', []);
return result.stack.readNumber();
}
async getID(provider: ContractProvider) {
const result = await provider.get('get_id', []);
return result.stack.readNumber();
}
}
Then you can use this class in your react component:
import "buffer";
import {
TonConnectUI,
useTonConnectUI,
useTonWallet,
} from "@tonconnect/ui-react";
import {
Address,
beginCell,
Sender,
SenderArguments,
storeStateInit,
toNano,
TonClient,
} from "@ton/ton";
class TonConnectProvider implements Sender {
/**
* The TonConnect UI instance.
* @private
*/
private readonly provider: TonConnectUI;
/**
* The address of the current account.
*/
public get address(): Address | undefined {
const address = this.provider.account?.address;
return address ? Address.parse(address) : undefined;
}
/**
* Creates a new TonConnectProvider.
* @param provider
*/
public constructor(provider: TonConnectUI) {
this.provider = provider;
}
/**
* Sends a message using the TonConnect UI.
* @param args
*/
public async send(args: SenderArguments): Promise<void> {
// The transaction is valid for 10 minutes.
const validUntil = Math.floor(Date.now() / 1000) + 600;
// The address of the recipient, should be in bounceable format for all smart contracts.
const address = args.to.toString({ urlSafe: true, bounceable: true });
// The address of the sender, if available.
const from = this.address?.toRawString();
// The amount to send in nano tokens.
const amount = args.value.toString();
// The state init cell for the contract.
let stateInit: string | undefined;
if (args.init) {
// State init cell for the contract.
const stateInitCell = beginCell()
.store(storeStateInit(args.init))
.endCell();
// Convert the state init cell to boc base64.
stateInit = stateInitCell.toBoc().toString("base64");
}
// The payload for the message.
let payload: string | undefined;
if (args.body) {
// Convert the message body to boc base64.
payload = args.body.toBoc().toString("base64");
}
// Send the message using the TonConnect UI and wait for the message to be sent.
await this.provider.sendTransaction({
validUntil: validUntil,
from: from,
messages: [
{
address: address,
amount: amount,
stateInit: stateInit,
payload: payload,
},
],
});
}
}
const CONTRACT_ADDRESS = "EQAYLhGmznkBlPxpnOaGXda41eEkliJCTPF6BHtz8KXieLSc";
const getCounterInstance = async () => {
const client = new TonClient({
endpoint: "https://testnet.toncenter.com/api/v2/jsonRPC",
});
// OR you can use createApi from @ton-community/assets-sdk
// import {
// createApi,
// } from "@ton-community/assets-sdk";
// const NETWORK = "testnet";
// const client = await createApi(NETWORK);
const address = Address.parse(CONTRACT_ADDRESS);
const counterInstance = client.open(Counter.createFromAddress(address));
return counterInstance;
};
export const Header = () => {
const [tonConnectUI, setOptions] = useTonConnectUI();
const wallet = useTonWallet();
const increaseCount = async () => {
const counterInstance = await getCounterInstance();
const sender = new TonConnectProvider(tonConnectUI);
await counterInstance.sendIncrease(sender, {
increaseBy: 1,
value: toNano("0.05"),
});
};
const getCount = async () => {
const counterInstance = await getCounterInstance();
const count = await counterInstance.getCounter();
console.log("count", count);
};
return (
<main>
{!wallet && (
<button onClick={() => tonConnectUI.openModal()}>Connect Wallet</button>
)}
{wallet && (
<>
<button onClick={increaseCount}>Increase count</button>
<button onClick={getCount}>Get count</button>
</>
)}
</main>
);
};
Wrappers for Jettons and NFTs
To interact with jettons or NFTs you can use assets-sdk. This SDK provides wrappers that simplify interaction with these assets. For practical examples, please check our examples section.
API Documentation
Examples
- Step-by-step TON Hello World guide to create a simple DApp with React UI.
- Demo dApp - Example of a DApp with
@tonconnect/ui-react
. - ton.vote - Example of React website with TON Connect implementation.