TON Connect для React
Эта страница переведена сообществом на русский язык, но нуждается в улучшениях. Если вы хотите принять участие в переводе свяжитесь с @alexgton.
Рекомендуемый SDK для приложений на React — это UI React SDK. Это компонент React, который обеспечивает высокоуровневый способ взаимодействия с TON Connect.
Реализация
Установка
Чтобы начать интеграцию TON Connect в ваш DApp, вам необходимо установить пакет @tonconnect/ui-react
. Вы можете использовать npm или yarn для этого:
- npm
- Yarn
- pnpm
npm i @tonconnect/ui-react
yarn add @tonconnect/ui-react
pnpm add @tonconnect/ui-react
Инициализация TON Connect
После установки пакета вам следует создать файл manifest.json
для вашего приложения. Более подробную информацию о том, как создать файл manifest.json, можно найти здесь.
После создания файла манифеста, импортируйте TonConnectUIProvider в корень вашего мини-приложения и передайте URL файла manifest:
import { TonConnectUIProvider } from '@tonconnect/ui-react';
export function App() {
return (
<TonConnectUIProvider manifestUrl="https://<YOUR_APP_URL>/tonconnect-manifest.json">
{ /* Your app */ }
</TonConnectUIProvider>
);
}
Подключение кошелька
Добавьте TonConnectButton
. Кнопка TonConnect — это универсальный компонент пользовательского интерфейса для инициализации соединения. После подключения кошелька она преобразуется в меню кошелька. Рекомендуется разместить ее в правом верхнем углу вашего приложения.
export const Header = () => {
return (
<header>
<span>My App with React UI</span>
<TonConnectButton />
</header>
);
};
Вы также можете добавить className и style props к кнопке. Обратите внимание, что вы не можете передать child в TonConnectButton:
<TonConnectButton className="my-button-class" style={{ float: "right" }}/>
Более того, вы всегда можете инициировать соединение вручную с помощью хука useTonConnectUI
и метода openModal.
export const Header = () => {
const [tonConnectUI, setOptions] = useTonConnectUI();
return (
<header>
<span>My App with React UI</span>
<button onClick={() => tonConnectUI.openModal()}>
Connect Wallet
</button>
</header>
);
};
Подключение к определенному кошельку
Чтобы открыть модальное окно для определенного кошелька, используйте метод openSingleWalletModal()
. Этот метод принимает app_name
кошелька в качестве параметра (см. файл wallets-list.json) и открывает соответствующее модальное окно кошелька. Он возвращает промис, который разрешается, когда модальное окно успешно откроется.
<button onClick={() => tonConnectUI.openSingleWalletModal('tonwallet')}>
Connect Wallet
</button>
Перенаправления
Если вы хотите перенаправить пользователя на определенную страницу после подключения кошелька, вы можете использовать хук useTonConnectUI
и настроить стратегию возврата.
Мини-приложения Telegram
Если вы хотите перенаправить пользователя на Мини-приложение Telegram после подключения кошелька, вы можете настроить элемент TonConnectUIProvider
:
<TonConnectUIProvider
// ... other parameters
actionsConfiguration={{
twaReturnUrl: 'https://t.me/<YOUR_APP_NAME>'
}}
>
</TonConnectUIProvider>
Настройка интерфейса пользователя
Чтобы настроить интерфейс модального окна, вы можете использовать хук useTonConnectUI
и функцию setOptions
. Подробнее о хуке useTonConnectUI можно почитать в разделе Хуки.
Хуки
Если вы хотите использовать некоторые низкоуровневые функции TON Connect UI SDK в своем приложении React, вы можете использовать хуки из пакета @tonconnect/ui-react
.
useTonAddress
Используйте его для получения текущего адреса кошелька пользователя Ton. Передайте логический параметр isUserFriendly
(по умолчанию true
), чтобы выбрать формат адреса. Если кошелек не подключен, хук вернет пустую строку.
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
Используйте этот хук для доступа к функциям открытия и закрытия модального окна. Хук возвращает объект с текущим состоянием модального окна и методами для его открытия и закрытия.
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
Используйте этот хук для извлечения текущего кошелька TON пользователя. Если кошелек не подключен, хук вернет null
. Объект wallet
предоставляет общие данные, такие как адрес пользователя, провайдер, TON proof и другие атрибуты (см. интерфейс Wallet).
Кроме того, вы можете получить более конкретную информацию о подключенном кошельке, такую как его имя, изображение и другие атрибуты (см. интерфейс WalletInfo).
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
Используйте его для получения доступа к экземпляру TonConnectUI
и функции обновления параметров пользовательского интерфейса.
Подробнее о методах экземпляра TonConnectUI
Подробнее о функции setOptions
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
Показывает текущий статус процесса восстановления подключения. Вы можете использовать его для обнаружения завершения процесса восстановления подключения.
import { useIsConnectionRestored } from '@tonconnect/ui-react';
export const EntrypointPage = () => {
const connectionRestored = useIsConnectionRestored();
if (!connectionRestored) {
return <Loader>Please wait...</Loader>;
}
return <MainPage />;
};
Использование
Давайте рассмотрим, как использовать React UI SDK на практике.
Отправка транзакций
Отправьте монеты TON (в nanotons) на конкретный адрес:
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>
);
};
- Больше примеров здесь: Подготовка сообщений
Понимание статуса транзакции по хэшу
Основная идея находится в обработке платежей (используя tonweb). См. подробнее
Дополнительная проверка (ton_proof) на Backend
Узнайте, как подписывать и проверять сообщения: Подпись и проверка
Чтобы убедиться, что пользователь действительно владеет заявленным адресом, вы можете использовать ton_proof
.
Используйте функцию tonConnectUI.setConnectRequestParameters
для настройки параметров вашего запроса на подключение. Вы можете использовать его для:
- Состояние загрузки: Показывайте состояние загрузки, ожидая ответа от вашей серверной части.
- Состояние готовности с tonProof: Установите состояние в 'ready' и включите значение tonProof.
- Если возникла ошибка, удалите загрузчик и создайте запрос на подключение без дополнительных параметров.
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);
}
Обработка результата ton_proof
Результат ton_proof
можно найти в объекте wallet
, когда кошелек подключен:
useEffect(() => {
tonConnectUI.onStatusChange((wallet) => {
if (
wallet.connectItems?.tonProof &&
"proof" in wallet.connectItems.tonProof
) {
checkProofInYourBackend(
wallet.connectItems.tonProof.proof,
wallet.account.address
);
}
});
}, [tonConnectUI]);
Структура 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
}
}
Пример аутентификации можно найти на этой странице
Отключение кошелька
Вызов для отключения кошелька:
const [tonConnectUI] = useTonConnectUI();
await tonConnectUI.disconnect();
Развертывание контракта
Развернуть контракт с использованием TonConnect довольно просто. Вам просто нужно получить код контракта и состояние init, сохранить его как ячейку и отправить транзакцию с предоставленным полем stateInit
.
Обратите внимание, что CONTRACT_CODE
и CONTRACT_INIT_DATA
могут быть найдены в обертках.
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')
}
]
});
Обертки
Обертки - это классы, которые упрощают взаимодействие с контрактом, позволяя вам работать без необходимости беспокоиться о деталях реализации.
- При разработке контракта на FunC вам необходимо написать обертку самостоятельно.
- При использовании языка Tact обертки автоматически генерирую тся для вас.
Ознакомьтесь с документацией blueprint о том, как разрабатывать и развертывать контракты
Давайте рассмотрим пример стандартной обертки Blueprint
Counter и как мы можем ее использовать:
Details
Использование обертки
Класс обертки счетчика: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();
}
}
Теперь вы можете использовать этот класс в своем компоненте React:
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>
);
};
Обертки для жетонов и NFT
Для взаимодействия с жетонами или NFT вы можете использовать assets-sdk. Этот SDK предоставляет обертки, которые упрощают взаимодействие с этими ассетами. Для практических примеров, пожалуйста, проверьте наш раздел примеров.