Skip to main content
TON Pay SDK helps you accept on-chain payments (TON coin and Jettons) from web apps and Telegram Mini Apps with minimal effort. It ships with:
  • API utilities to build transfer messages and query transfer status.
  • React UI: a wallet-aware button and a hook that orchestrates TonConnect handoff, sending, and post-send tracking.
  • Non‑React apps: use @ton-pay/ui(see TonPay Button (pure JS)).

How TON Pay works

TON Pay connects your app (prepares transfer messages and tracks status) with user wallets (approve and sign) via TonConnect. Apps keep business logic and UI; wallets keep keys and approval flow. This separation delivers secure, smooth payment UX.

TonConnect manifest (required)

Wallets discover your app via a TonConnect manifest. Create tonconnect-manifest.json and host it at a public, absolute URL:
{
  "url": "https://myapp.com",
  "name": "My App",
  "iconUrl": "https://myapp.com/icon-180.png"
}
Best practices: serve it at https://myapp.com/tonconnect-manifest.json and make it accessible from any origin (no CORS restrictions). Learn more in TonConnect docs: Creating the TON Connect manifest for DApp.

Steps: First Payment

1) Install

  • React
  • Vanilla JS
# API
npm i @ton-pay/api

# UI (install separately from API)
npm i @ton-pay/ui-react @tonconnect/ui-react
Non‑React apps: use @ton-pay/ui instead of @ton-pay/ui-react.
You can install only the API package @ton-pay/api if you don’t need React UI. The UI package does not depend on the API; install @ton-pay/api separately only when you need the API helpers alongside UI.

2) Add TonConnect provider

TON Pay UI relies on the official TonConnect UI to communicate with wallets. Wrap your app with TonConnectUIProvider and pass your TonConnect manifest URL (absolute URL). See the manifest section above; full details in TonConnect docs: How TON Connect works. TonConnectUIProvider should be added to the root of the app.
import { TonConnectUIProvider } from '@tonconnect/ui-react';
import AppContent from "./AppContent";

export default function App() {
  return (
    <TonConnectUIProvider
      manifestUrl="https://myapp.com/tonconnect-manifest.json">
      <AppContent />
    </TonConnectUIProvider>
  );
}

3) Add a payment button

Drop in a TonPayButton and give it a handler. The handler uses useTonPay to connect a wallet (if needed), send a transaction via TonConnect, and track the result. Keep the message factory simple and readable.
import { TonPayButton, useTonPay } from "@ton-pay/ui-react";
import { useTonConnectUI } from "@tonconnect/ui-react";
import { createTonPayTransfer } from "@ton-pay/api";

export default function PayButton() {
  const { pay } = useTonPay();
  const [tonConnectUI] = useTonConnectUI();

  async function createMessage(senderAddr: string) {
    const { message, reference } = await createTonPayTransfer(
      {
        amount: 12.34,
        asset: "TON",
        recipientAddr: "EQC...yourWalletAddress",
        senderAddr,
        commentToSender: "Order #123",
      },
      { 
        chain: "testnet", // use "mainnet" for production
        apiKey: "yourTonPayApiKey" // optional
      }
    );
    return { message, reference };
  }

  return (
    <TonPayButton handlePay={() => pay(createMessage)} />
  );
}
The { message } you return is a TonConnect transaction message. useTonPay passes it to the wallet (via TonConnect) and initiates the send; you don’t call the wallet SDK yourself.
Client-only processing is fragile: users can close the tab before you persist results. Prefer server-side message building and tracking for reliability. See the recommended flow in Send Payments.
Optional: If you have an API key (available in your dashboard), pass it to createTonPayTransfer options to enable automatic tracking and webhook notifications.
  • TonPayButton wraps wallet connect/disconnect UX and invokes your handler.
  • useTonPay automates wallet connection and transaction sending via TonConnect. Pass an async factory that receives senderAddr and returns { message } plus any tracking fields you want to propagate back.
This example shows a minimal loading state and how to surface the reference. If you also have a transaction hash, render an explorer link (see snippet below).
import { useState } from "react";
import { TonPayButton, useTonPay } from "@ton-pay/ui-react";
import { createTonPayTransfer, getTonPayTransferByReference, type CompletedTonPayTransferInfo } from "@ton-pay/api";

export default function Checkout() {
  const { pay } = useTonPay();
  const [loading, setLoading] = useState(false);
  const [reference, setReference] = useState<string | null>(null);
  const [result, setResult] = useState<CompletedTonPayTransferInfo | null>(null);

  const options = { chain: "testnet", apiKey: "yourTonPayApiKey" } as const;

  async function createMessage(senderAddr: string) {
    const { message, reference } = await createTonPayTransfer(
      { amount: 12.34, asset: "TON", recipientAddr: "EQC...yourWalletAddress", senderAddr },
      options
    );
    setReference(reference);
    return { message, reference };
  }

  async function handlePay() {
    setLoading(true);
    try {
      const { reference } = await pay(createMessage);
      // Poll status until success
      for (;;) {
        try {
          const t = await getTonPayTransferByReference(reference, options);
          if (t.status === "success") {
            setResult(t);
            break;
          }
        } catch {}
        await new Promise(r => setTimeout(r, 1000));
      }
    } finally {
      setLoading(false);
    }
  }

  return (
    <>
      <TonPayButton handlePay={handlePay} isLoading={loading} />

      {reference && !result && <div>Payment sent. Reference: {reference}</div>}
      {result && <div>Payment successful! Tx hash: {result.txHash}</div>}
      {/* If you have a transaction hash, render an explorer link:
          <ExplorerLink txHash={result?.txHash!} />
       */}
    </>
  );
}
import { useTonWallet } from "@tonconnect/ui-react";
import { CHAIN } from "@tonconnect/ui";

function ExplorerLink({ txHash }: { txHash: string }) {
  const wallet = useTonWallet();
  const isTestnet = wallet?.account?.chain === CHAIN.TESTNET;
  const tonviewer = isTestnet
    ? `https://testnet.tonviewer.com/transaction/${txHash}`
    : `https://tonviewer.com/transaction/${txHash}`;
  const tonscan = isTestnet
    ? `https://testnet.tonscan.org/tx/${txHash}`
    : `https://tonscan.org/tx/${txHash}`;

  return (
    <div>
      <a href={tonviewer} target="_blank" rel="noreferrer">TonViewer</a>
      {" • "}
      <a href={tonscan} target="_blank" rel="noreferrer">Tonscan</a>
    </div>
  );
}
This minimal example scaffolds a React app, installs TON Pay dependencies, and renders a working button wired to TonConnect. Replace the manifest URL and recipientAddr with your values.
npx create-react-app my-app --template typescript
cd my-app
npm i @ton-pay/api @ton-pay/ui-react @tonconnect/ui-react
// src/App.tsx
import { TonConnectUIProvider } from "@tonconnect/ui-react";
import { TonPayButton, useTonPay } from "@ton-pay/ui-react";
import { createTonPayTransfer } from "@ton-pay/api";

function AppContent() {
  const { pay } = useTonPay();

  async function createMessage(senderAddr: string) {
    const { message, reference } = await createTonPayTransfer(
      {
        amount: 12.34,
        asset: "TON",
        recipientAddr: "EQC...yourWalletAddress",
        senderAddr,
        commentToSender: "Order #123",
      },
      { 
        chain: "testnet",
        apiKey: "yourTonPayApiKey"
      }
    );
    return { message, reference };
  }

  return (
    <TonPayButton handlePay={() => pay(createMessage)} />
  );
}

export default function App() {
  return (
    <TonConnectUIProvider 
      manifestUrl="https://ton-connect.github.io/demo-dapp-with-wallet/tonconnect-manifest.json"
    >
      <AppContent />
    </TonConnectUIProvider>
  );
}

See also