# Get started with TON Connect (https://docs-rbcpr9qys-ton-core-docs.vercel.app/llms/applications/ton-connect/get-started/content.md)



This page walks you through every supported integration path.

Start with [What you need](#what-you-need) and then select the section for your stack:

* [What you need](#what-you-need)
* [Build a dApp with React](#build-a-dapp-with-react)
* [Build a dApp with Next.js](#build-a-dapp-with-nextjs)
* [Build a dApp with vanilla JS](#build-a-dapp-with-vanilla-js)
* [Build without a UI (`@tonconnect/sdk`)](#build-without-a-ui-tonconnectsdk)

## What you need [#what-you-need]

Before you start integrating, prepare the manifest file and pick a TON Connect SDK.

<div className="fd-steps">
  <div className="fd-step">
    ### Prepare the manifest [#1-prepare-the-manifest]

    Prepare `tonconnect-manifest.json` — the JSON file the wallet fetches to learn your app's name, icon, and policy URLs. The wallet shows this metadata to the user before approving the connection.

    The minimum:

    ```json
    {
      "url": "https://yourapp.com",
      "name": "Your App",
      "iconUrl": "https://yourapp.com/icon-180.png"
    }
    ```

    Optional:

    ```json
    {
      "termsOfUseUrl": "https://yourapp.com/terms",
      "privacyPolicyUrl": "https://yourapp.com/privacy"
    }
    ```

    #### Hosting requirements: [#hosting-requirements]

    The manifest must be publicly accessible by the time wallets connect to your app:

    * The file must be reachable with a `GET` from any origin, without CORS restrictions, without auth and without a Cloudflare-style proxy challenge.
    * The icon at the URL listed in `iconUrl` must be PNG or ICO. SVG is not supported. Use a 180×180 px PNG.
    * The manifest must be served over HTTPS. Wallets do not guarantee they will fetch a manifest served over plain HTTP.
    * Any reachable HTTPS URL is valid. Hosting the manifest at the root of your domain (e.g. `https://yourapp.com/tonconnect-manifest.json`) keeps access simple.

    If the wallet cannot fetch the manifest, the connect flow returns `MANIFEST_NOT_FOUND_ERROR` (code 2) or `MANIFEST_CONTENT_ERROR` (code 3). See [Manifest 404 and CORS](https://docs-rbcpr9qys-ton-core-docs.vercel.app/llms/applications/ton-connect/troubleshooting/content.md).

    For the full field reference, see [Manifest](https://docs-rbcpr9qys-ton-core-docs.vercel.app/llms/applications/ton-connect/core-concepts/content.md).
  </div>

  <div className="fd-step">
    ### Pick an SDK [#2-pick-an-sdk]

    There are three dApp-facing packages, all published from [`ton-connect/sdk`](https://github.com/ton-connect/sdk):

    | Package                | When to use                                                                  |
    | ---------------------- | ---------------------------------------------------------------------------- |
    | `@tonconnect/ui-react` | React or Next.js dApps. &#x2A;*Recommended.** Hooks, prebuilt button, modal. |
    | `@tonconnect/ui`       | Vanilla JS or non-React frameworks. Same UI components, no React bindings.   |
    | `@tonconnect/sdk`      | Headless integrations — server-side flows, custom UI from scratch.           |

    Pick `ui-react` if your app is React. Pick `ui` if it is not. Reach for `sdk` only when you need low-level control.
  </div>
</div>

## Build a dApp with React [#build-a-dapp-with-react]

Install the UI kit, host the manifest, mount the provider, add a connect button, and send a test transaction.

For Next.js-specific notes (App Router, `'use client'`, SSR), see [Build a dApp with Next.js](#build-a-dapp-with-nextjs).

<div className="fd-steps">
  <div className="fd-step">
    ### Install [#1-install]

    ```bash
    npm i @tonconnect/ui-react
    ```
  </div>

  <div className="fd-step">
    ### Host the manifest [#2-host-the-manifest]

    Place `tonconnect-manifest.json` at the root of your app's domain. See [What you need](#1-prepare-the-manifest) for the full requirements.
  </div>

  <div className="fd-step">
    ### Wrap the app in `TonConnectUIProvider` [#3-wrap-the-app-in-tonconnectuiprovider]

    ```tsx
    import { TonConnectUIProvider } from '@tonconnect/ui-react';

    export function App() {
        return (
            <TonConnectUIProvider manifestUrl="https://yourapp.com/tonconnect-manifest.json">
                <YourApp />
            </TonConnectUIProvider>
        );
    }
    ```
  </div>

  <div className="fd-step">
    ### Add the connect button [#4-add-the-connect-button]

    ```tsx
    import { TonConnectButton } from '@tonconnect/ui-react';

    export function Header() {
        return (
            <header>
                <TonConnectButton />
            </header>
        );
    }
    ```

    The button toggles between "Connect Wallet" and the connected account state automatically.

    You can pass a `className` or `style` prop:

    ```tsx
    <TonConnectButton className="my-class" style={{ float: 'right' }} />
    ```
  </div>

  <div className="fd-step">
    ### Read connection state [#5-read-connection-state]

    ```tsx
    import { useTonAddress, useTonWallet } from '@tonconnect/ui-react';

    function Status() {
        const address = useTonAddress();
        const wallet = useTonWallet();

        if (!wallet) return <span>Not connected</span>;
        return <span>Connected as {address}</span>;
    }
    ```
  </div>

  <div className="fd-step">
    ### Send a transaction [#6-send-a-transaction]

    The destination here is the connected wallet's own address, returned by `useTonAddress()` in user-friendly form. Replace it with your recipient.

    ```tsx
    import { useTonAddress, useTonConnectUI, useTonWallet } from '@tonconnect/ui-react';

    function PayButton() {
        const [tonConnectUi] = useTonConnectUI();
        const wallet = useTonWallet();
        const address = useTonAddress();

        const handlePay = async () => {
            if (!address) return;
            try {
                await tonConnectUi.sendTransaction({
                    validUntil: Math.floor(Date.now() / 1000) + 300,
                    network: '-239', // mainnet
                    messages: [
                        { address, amount: '100000000' }, // 0.1 Gram
                    ],
                });
            } catch (e) {
                console.error(e);
            }
        };

        return (
            <button onClick={handlePay} disabled={!wallet}>
                Send 0.1 Gram
            </button>
        );
    }
    ```

    `address` must be in user-friendly format (base64url with the bounce flag — `EQ…` for bounceable, `UQ…` for non-bounceable). Wallets reject raw `0:<hex>` addresses. To convert one, use `toUserFriendlyAddress` from `@tonconnect/ui-react`. Each `message` also accepts optional `payload`, `stateInit`, and `extraCurrency` fields. Amounts use **nanograms**: `1 Gram = 10⁹ nanogram`. Send `100000000` for 0.1 Gram.
  </div>
</div>

### Open the modal manually [#open-the-modal-manually]

```tsx
const [tonConnectUi] = useTonConnectUI();

<button onClick={() => tonConnectUi.openModal()}>
    Connect Wallet
</button>
```

### Customize the UI [#customize-the-ui]

```tsx
import { useTonConnectUI, THEME } from '@tonconnect/ui-react';

const [tonConnectUi] = useTonConnectUI();

tonConnectUi.uiOptions = {
    language: 'ru',
    uiPreferences: { theme: THEME.DARK },
};
```

`uiOptions` is a setter, not a plain object. Assigning to it runs the merge, theme switch, and re-render logic; mutating a nested property (e.g. `tonConnectUi.uiOptions.uiPreferences.theme = ...`) bypasses the setter and has no effect. Always reassign the whole object.

## Build a dApp with Next.js [#build-a-dapp-with-nextjs]

Next.js needs two adjustments on top of the [React tutorial](#build-a-dapp-with-react): `TonConnectUIProvider` must run on the client, and the manifest is served from `public/`. The hooks behave the same as in plain React.

<div className="fd-steps">
  <div className="fd-step">
    ### Install [#1-install-1]

    ```bash
    npm i @tonconnect/ui-react
    ```
  </div>

  <div className="fd-step">
    ### Host the manifest [#2-host-the-manifest-1]

    Drop `tonconnect-manifest.json` into your project's `public/` directory:

    ```text
    public/tonconnect-manifest.json
    ```

    Next.js serves the file at `https://yourapp.com/tonconnect-manifest.json`. Make sure your hosting layer does not add CORS or auth gates — see [Manifest 404 and CORS](https://docs-rbcpr9qys-ton-core-docs.vercel.app/llms/applications/ton-connect/troubleshooting/content.md).
  </div>

  <div className="fd-step">
    ### Set up App Router [#3-set-up-app-router]

    `TonConnectUIProvider` reads from local storage and opens modals — both browser-only. Mount it in a client component:

    ```tsx
    // app/providers.tsx
    'use client';

    import { TonConnectUIProvider } from '@tonconnect/ui-react';

    export function Providers({ children }: { children: React.ReactNode }) {
        return (
            <TonConnectUIProvider manifestUrl="https://yourapp.com/tonconnect-manifest.json">
                {children}
            </TonConnectUIProvider>
        );
    }
    ```

    Then wrap the root layout:

    ```tsx
    // app/layout.tsx
    import { Providers } from './providers';

    export default function RootLayout({ children }: { children: React.ReactNode }) {
        return (
            <html>
                <body>
                    <Providers>{children}</Providers>
                </body>
            </html>
        );
    }
    ```

    Components that use `useTonWallet`, `useTonConnectUI`, etc. must also start with `'use client'`.
  </div>

  <div className="fd-step">
    ### Set up Pages Router [#4-set-up-pages-router]

    Dynamic-import the provider with SSR off so it does not run on the server:

    ```tsx
    import dynamic from 'next/dynamic';
    import type { AppProps } from 'next/app';

    const TonConnectUIProvider = dynamic(
        () => import('@tonconnect/ui-react').then(m => m.TonConnectUIProvider),
        { ssr: false }
    );

    function MyApp({ Component, pageProps }: AppProps) {
        return (
            <TonConnectUIProvider manifestUrl="https://yourapp.com/tonconnect-manifest.json">
                <Component {...pageProps} />
            </TonConnectUIProvider>
        );
    }

    export default MyApp;
    ```
  </div>
</div>

### SSR pitfalls [#ssr-pitfalls]

* **Local storage.** TON Connect persists the session in `localStorage`. Code that reads the wallet state during SSR will see "not connected" until hydration. Render the wallet-dependent UI inside a client component so the hooks run after hydration, or return a skeleton while `useTonWallet()` is still `null`.
* **Browser-only APIs.** Anything from `@tonconnect/ui-react` that touches `window`, modals, or storage must run in a client component or be lazy-loaded with `ssr: false`.

The hooks, button, and transaction-sending API are identical to plain React — see [Build a dApp with React](#build-a-dapp-with-react) for the rest.

## Build a dApp with vanilla JS [#build-a-dapp-with-vanilla-js]

Same flow as the [React tutorial](#build-a-dapp-with-react) — connect button, status subscription, transaction — built with `@tonconnect/ui` for plain HTML / JavaScript.

<div className="fd-steps">
  <div className="fd-step">
    ### Install [#1-install-2]

    Via npm:

    ```bash
    npm i @tonconnect/ui
    ```

    Or via CDN:

    ```html
    <script src="https://unpkg.com/@tonconnect/ui@latest/dist/tonconnect-ui.min.js"></script>
    ```

    The CDN bundle exposes the API on `window.TON_CONNECT_UI`.
  </div>

  <div className="fd-step">
    ### Host the manifest [#2-host-the-manifest-2]

    Place `tonconnect-manifest.json` at the root of your domain. Hosting rules in [What you need](#1-prepare-the-manifest).
  </div>

  <div className="fd-step">
    ### Mark up a connect-button container [#3-mark-up-a-connect-button-container]

    ```html
    <div id="ton-connect"></div>
    <button id="send" disabled>Send 0.1 Gram</button>
    ```
  </div>

  <div className="fd-step">
    ### Initialize the UI [#4-initialize-the-ui]

    ```javascript
    const ui = new TON_CONNECT_UI.TonConnectUI({
        manifestUrl: 'https://yourapp.com/tonconnect-manifest.json',
        buttonRootId: 'ton-connect',
    });
    ```

    The library renders the connect button into `#ton-connect`. Tapping it opens the wallet picker.

    If you imported via npm:

    ```javascript
    import { TonConnectUI } from '@tonconnect/ui';
    const ui = new TonConnectUI({ /* same options */ });
    ```
  </div>

  <div className="fd-step">
    ### Subscribe to connection status [#5-subscribe-to-connection-status]

    ```javascript
    ui.onStatusChange(wallet => {
        document.getElementById('send').disabled = !wallet;
    });
    ```

    `wallet` is `null` when disconnected, or a connected `Wallet` (with `device`, `account`, and optional `connectItems`) otherwise.
  </div>

  <div className="fd-step">
    ### Send a transaction [#6-send-a-transaction-1]

    ```javascript
    import { toUserFriendlyAddress } from '@tonconnect/ui';

    document.getElementById('send').onclick = async () => {
        const wallet = ui.wallet;
        if (!wallet) return;
        const address = toUserFriendlyAddress(wallet.account.address);  // or window.TON_CONNECT_UI.toUserFriendlyAddress
        try {
            await ui.sendTransaction({
                validUntil: Math.floor(Date.now() / 1000) + 300,
                network: '-239',
                messages: [
                    { address, amount: '100000000' }, // 0.1 Gram
                ],
            });
        } catch (error) {
            console.error('Transaction failed:', error);
        }
    };
    ```

    The destination `address` must be in user-friendly format; see the note in the [React example](#6-send-a-transaction). Amounts are in **nanograms**: `1 Gram = 10⁹ nanograms`.
  </div>
</div>

### Restore on reload [#restore-on-reload]

`onStatusChange` fires whenever the wallet state changes — connect, disconnect, and a successful session restore.

## Build without a UI (`@tonconnect/sdk`) [#build-without-a-ui-tonconnectsdk]

`@tonconnect/sdk` is the headless TON Connect implementation — the same protocol layer that ships inside `@tonconnect/ui-react` and `@tonconnect/ui`, with no wallet picker, no modal, and no DOM dependencies. Use it on a server, in a custom UI you render yourself, or as a reference when porting TON Connect to another language.

For a regular browser dApp, prefer [`@tonconnect/ui-react`](#build-a-dapp-with-react) (React, Next.js) or [`@tonconnect/ui`](#build-a-dapp-with-vanilla-js) (vanilla JS). They wrap this SDK and add the wallet picker, connect button, and notifications.

This section covers the headless API end-to-end: install, connector setup, custom storage, wallet discovery, the connect handshake, status events, and `sendTransaction`. The same shape applies whether you call it from a server or a custom in-browser UI.

<div className="fd-steps">
  <div className="fd-step">
    ### Install [#1-install-3]

    ```bash
    npm i @tonconnect/sdk
    ```
  </div>

  <div className="fd-step">
    ### Create a connector [#2-create-a-connector]

    ```ts
    import TonConnect from '@tonconnect/sdk';

    const connector = new TonConnect({
        manifestUrl: 'https://yourapp.com/tonconnect-manifest.json',
        storage: myStorage,
    });

    await connector.restoreConnection();
    ```

    `manifestUrl` is the public URL of your `tonconnect-manifest.json`. The wallet fetches it during connect and shows the metadata to the user. See [What you need](#1-prepare-the-manifest) for hosting rules.

    `storage` is an `IStorage` implementation. In a browser, the SDK falls back to `window.localStorage` if you omit it. Anywhere else — Node.js, a worker — supply your own. `restoreConnection()` reads from storage and wires the connector back to the bridge if a session is already there. Call it once per instance, not on every request.
  </div>

  <div className="fd-step">
    ### Custom storage [#3-custom-storage]

    ```ts
    interface IStorage {
        setItem(key: string, value: string): Promise<void>;
        getItem(key: string): Promise<string | null>;
        removeItem(key: string): Promise<void>;
    }
    ```

    A trivial in-memory implementation suits one-shot flows:

    ```ts
    class MemoryStorage implements IStorage {
        private data = new Map<string, string>();
        async setItem(key: string, value: string) { this.data.set(key, value); }
        async getItem(key: string) { return this.data.get(key) ?? null; }
        async removeItem(key: string) { this.data.delete(key); }
    }
    ```

    For long-running servers, back `IStorage` with a per-user record in your database (Postgres, Redis, etc.) keyed by your user ID. See [Long-lived servers](#long-lived-servers).
  </div>

  <div className="fd-step">
    ### Discover wallets [#4-discover-wallets]

    ```ts
    const wallets = await connector.getWallets();
    ```

    Each entry is a `WalletInfo` with the fields a custom UI needs to render a picker and start a connect:

    ```ts
    interface WalletInfoBase {
        name: string;        // human-readable display name
        appName: string;     // stable identifier
        imageUrl: string;
        aboutUrl: string;
        tondns?: string;
        platforms: ('ios' | 'android' | 'macos' | 'windows'
                  | 'linux' | 'chrome' | 'firefox' | 'safari')[];
        features?: Feature[];
    }

    interface WalletInfoRemote extends WalletInfoBase {
        universalLink: string;
        bridgeUrl: string;
        deepLink?: string;
    }

    interface WalletInfoInjectable extends WalletInfoBase {
        jsBridgeKey: string;
        injected: boolean;
        embedded: boolean;
    }

    type WalletInfo =
        | WalletInfoRemote
        | WalletInfoInjectable
        | (WalletInfoRemote & WalletInfoInjectable);
    ```

    A wallet that supports both transports satisfies the intersection. Narrow with `'universalLink' in wallet` for HTTP wallets and `'jsBridgeKey' in wallet` for injected ones.
  </div>

  <div className="fd-step">
    ### Connect [#5-connect]

    For an HTTP wallet, `connect()` returns a universal link. Show it to the user as a clickable URL, a deep link, or a QR code:

    ```ts
    const link = connector.connect({
        universalLink: 'https://connect.mytonwallet.org',
        bridgeUrl: 'https://tonconnectbridge.mytonwallet.org/bridge/',
    });
    ```

    For a JS-injected wallet (browser extension or in-wallet browser), pass the bridge key. The wallet handles the handoff in-page, so `connect()` returns `void`:

    ```ts
    connector.connect({ jsBridgeKey: 'mytonwallet' });
    ```

    To request `ton_proof` alongside the address, pass it under `request`:

    ```ts
    connector.connect(
        { universalLink, bridgeUrl },
        { request: { tonProof: nonce } },
    );
    ```

    See [Connect a wallet](https://docs-rbcpr9qys-ton-core-docs.vercel.app/llms/applications/ton-connect/how-to/connect/content.md) for the proof-verification flow.
  </div>

  <div className="fd-step">
    ### Listen for status changes [#6-listen-for-status-changes]

    ```ts
    const unsubscribe = connector.onStatusChange(wallet => {
        if (wallet) {
            // wallet.account.address — raw 0:<hex> (convert to user-friendly before passing to sendTransaction)
            // wallet.account.publicKey — hex string without 0x, optional (some wallets omit it)
            // wallet.connectItems?.tonProof — TonProofItemReply, may carry proof or error
        } else {
            // disconnected — by the user or by the wallet
        }
    });
    ```

    The same callback fires for connects, restores, and wallet-initiated disconnects. Call `unsubscribe()` when you no longer need it.
  </div>

  <div className="fd-step">
    ### Send a transaction [#7-send-a-transaction]

    ```ts
    const result = await connector.sendTransaction({
        validUntil: Math.floor(Date.now() / 1000) + 300,
        network: '-239', // mainnet (use '-3' for testnet)
        messages: [
            { address: 'UQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJKZ', // burn address
              amount: '20000000' },
        ],
    });

    console.log(result.boc); // base64 BoC of the signed external message
    ```

    Each wallet advertises its own per-call limit on the `SendTransaction` feature entry: `wallet.device.features.find(f => typeof f === 'object' && f.name === 'SendTransaction')?.maxMessages`.
  </div>
</div>

### Long-lived servers [#long-lived-servers]

* One `TonConnect` instance per signed-in user, with an `IStorage` keyed by user ID, and an in-process cache so the same instance is reused across requests.
* The HTTP bridge stays open over SSE for as long as the connector is live. Call `pauseConnection()` when a user goes idle and `unPauseConnection()` when they return.
* React to wallet-initiated disconnects through `onStatusChange`. When the callback fires with `null`, evict the cached connector and clear any session token you issued.
* Persist the session per user, not globally. Two users sharing one `TonConnect` will leak addresses and overwrite each other's session keypairs.

## Next steps [#next-steps]

* [Connect a wallet and authenticate the user with `ton_proof`](https://docs-rbcpr9qys-ton-core-docs.vercel.app/llms/applications/ton-connect/how-to/connect/content.md)
* [Send a structured transaction](https://docs-rbcpr9qys-ton-core-docs.vercel.app/llms/applications/ton-connect/how-to/send-transaction/content.md)
* [Sign data](https://docs-rbcpr9qys-ton-core-docs.vercel.app/llms/applications/ton-connect/how-to/sign-data/content.md)
* [Sign and relay a gasless message](https://docs-rbcpr9qys-ton-core-docs.vercel.app/llms/applications/ton-connect/how-to/sign-message-gasless/content.md)
* [Connect-and-act in one tap](https://docs-rbcpr9qys-ton-core-docs.vercel.app/llms/applications/ton-connect/how-to/embedded-request/content.md)
* [Handle wallet-initiated disconnects](https://docs-rbcpr9qys-ton-core-docs.vercel.app/llms/applications/ton-connect/how-to/disconnect/content.md)
* [Filter wallets by required features](https://docs-rbcpr9qys-ton-core-docs.vercel.app/llms/applications/ton-connect/how-to/filter-wallets/content.md)
* [Enable WalletConnect support](https://docs-rbcpr9qys-ton-core-docs.vercel.app/llms/applications/ton-connect/how-to/walletconnect-support/content.md)
