Skip to main content

TON Connect manifest

Before installing and setting up the TON Pay SDK, the application must provide a TON Connect manifest, which is a JSON file that defines application metadata. Wallets use the TON Connect manifest to discover the application.

First payment

1

Install necessary libraries

# API
npm i @ton-pay/api

# UI (install separately from API)
npm i @ton-pay/ui-react @tonconnect/ui-react
2

Add TON Connect provider

TON Pay UI uses TON Connect UI for wallet communication.The application must be wrapped with TonConnectUIProvider and configured with an absolute URL to the TON Connect manifest. Add TonConnectUIProvider at the root of the application.
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

Add a TonPayButton and provide a handler. The handler uses useTonPay to connect a wallet if needed, send a transaction through TON Connect, and return tracking data for the next step.
  • TonPayButton wraps wallet connect and disconnect UX and invokes the provided handler.
  • useTonPay accepts an async message factory that receives senderAddr and returns { message } along with any tracking fields to propagate.
  • Return reference from createTonPayTransfer so it can be used later with getTonPayTransferByReference.
The returned { message } is a TON Connect transaction message. useTonPay forwards it to the wallet through TON Connect and initiates the transaction send; direct calls to the wallet SDK are not required.In the examples below, replace <WALLET_ADDRESS> with the recipient wallet address, <TONPAY_API_KEY> with an optional dashboard API key, and <ORDER_REFERENCE> with an order label or ID.
import { TonPayButton, useTonPay } from "@ton-pay/ui-react";
import { createTonPayTransfer } from "@ton-pay/api";

const recipientAddr = "<WALLET_ADDRESS>";
const orderReference = "<ORDER_REFERENCE>";

// Set chain to "mainnet" in production.
const options = {
  chain: "testnet",

  // Pass an API key from the dashboard when available.
  apiKey: "<TONPAY_API_KEY>",
} as const;

export default function PayButton() {
  const { pay } = useTonPay();

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

  return (
    <TonPayButton handlePay={() => pay(createMessage)} />
  );
}
// Backend: POST /api/create-payment
import { createTonPayTransfer, TON } from "@ton-pay/api";

const recipientAddr = "<WALLET_ADDRESS>";

// Set chain to "mainnet" in production.
const options = {
  chain: "testnet",

  // Pass an API key from the dashboard when available.
  apiKey: "<TONPAY_API_KEY>",
} as const;

app.post("/api/create-payment", async (req, res) => {
  const { productId, senderAddr } = req.body;

  // Create an order and calculate the amount from the product price.
  const amount = 12.23;
  const orderId = 1;

  // Create the transfer and get tracking identifiers.
  const { message, reference, bodyBase64Hash } = await createTonPayTransfer(
    { amount, asset: TON, recipientAddr, senderAddr },
    options
  );

  // Persist identifiers in the database immediately.

  // Return only the message to the client.
  res.json({ message });
});
// Frontend
import { TonPayButton, useTonPay } from "@ton-pay/ui-react";

export function PayOrder({ productId }: { productId: string }) {
  const { pay } = useTonPay();

  async function createMessage(senderAddr: string) {
    const response = await fetch("/api/create-payment", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ productId, senderAddr }),
    });
    const { message } = await response.json();
    return { message };
  }

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

Handle loading state and results

Wallet approval does not mean the payment is finalized yet. After pay(createMessage) resolves, use the reference returned by createTonPayTransfer to query TON Pay until the transfer leaves the pending state.The example below uses React. The same flow applies in other clients: keep the reference, poll status, and update the UI when the transfer leaves pending.Use the SDK flow below:
  1. Return reference from the createMessage function together with message.
  2. Call pay(createMessage) and, once it resolves, read the propagated reference from its return value.
  3. Call getTonPayTransferByReference(reference, options) with the same chain and optional apiKey used during transfer creation.
  4. While the SDK returns status: "pending", keep the UI in a loading or “confirming payment” state.
  5. When the status becomes success, show the confirmation UI and persist any result fields the application needs, such as txHash or traceId.
  6. When the status becomes error, show the failure state and capture errorCode or errorMessage for diagnostics.
In a client-only flow, persist the reference after pay(createMessage) resolves and returns it. This is enough to resume status checks after a reload during polling, but it does not cover the period while the wallet approval screen is open. To avoid that gap, create the transfer on the server and persist the reference before opening the wallet.
Result handling example
import { useState } from "react";
import { TonPayButton, useTonPay } from "@ton-pay/ui-react";
import {
  createTonPayTransfer,
  getTonPayTransferByReference,
  type CompletedTonPayTransferInfo,
} from "@ton-pay/api";

type PaymentState = "idle" | "sending" | "pending" | "success" | "error";

const amount = 12.34;
const recipientAddr = "<WALLET_ADDRESS>";

// Set chain to "mainnet" in production.
const options = {
  chain: "testnet",

  // Pass an API key from the dashboard when available.
  apiKey: "<TONPAY_API_KEY>",
} as const;

export default function Checkout() {
  const { pay } = useTonPay();
  const [paymentState, setPaymentState] = useState<PaymentState>("idle");
  const [reference, setReference] = useState<string | null>(null);
  const [result, setResult] = useState<CompletedTonPayTransferInfo | null>(null);
  const [errorMessage, setErrorMessage] = useState<string | null>(null);

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

    setReference(reference);
    return { message, reference };
  }

  // Polls TON Pay until the transfer gets a final status.
  async function waitForTransferResult(reference: string) {
    for (;;) {
      const transfer = await getTonPayTransferByReference(reference, options);

      if (transfer.status === "pending") {
        await new Promise((resolve) => setTimeout(resolve, 1000));
        continue;
      }

      return transfer;
    }
  }

  async function handlePay() {
    setPaymentState("sending");
    setErrorMessage(null);
    setReference(null);
    setResult(null);

    try {
      const { reference } = await pay(createMessage);
      setPaymentState("pending");

      const transfer = await waitForTransferResult(reference);

      if (transfer.status === "success") {
        setResult(transfer);
        setPaymentState("success");
        return;
      }

      setPaymentState("error");
      setErrorMessage(transfer.errorMessage ?? "Payment failed");
    } catch (error) {
      setPaymentState("error");
      setErrorMessage(error instanceof Error ? error.message : "Payment failed");
    }
  }

  return (
    <>
      <TonPayButton
        handlePay={handlePay}
        isLoading={paymentState === "sending" || paymentState === "pending"}
      />

      {paymentState === "pending" && reference && (
        <div>
          Payment submitted. Waiting for blockchain confirmation. Reference: {reference}
        </div>
      )}

      {paymentState === "success" && result && (
        <div>
          Payment confirmed. Tx hash: {result.txHash}
        </div>
      )}

      {paymentState === "error" && errorMessage && (
        <div>
          Payment failed: {errorMessage}
        </div>
      )}
    </>
  );
}
The status lookup guide describes the response fields and lookup methods used in this step.

Full example

This minimal example scaffolds a React app, installs TON Pay dependencies, and renders a working button wired to TON Connect. Replace the manifest URL and recipientAddr with the necessary 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";

const recipientAddr = "<WALLET_ADDRESS>";
const commentToSender = "Order #123";

// Set chain to "mainnet" in production.
const options = {
  chain: "testnet",

  // Pass an API key from the dashboard when available.
  apiKey: "<TONPAY_API_KEY>",
} as const;

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

  async function createMessage(senderAddr: string) {
    const { message, reference } = await createTonPayTransfer(
      {
        amount: 12.34,
        asset: "TON",
        recipientAddr,
        senderAddr,
        commentToSender,
      },
      options
    );
    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