跳到主要内容

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 i @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>

Open example on GitHub

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

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

Latest 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.