Ton API
Make dApp

Guide to Building a dApp with TON API

This guide provides detailed instructions for building a decentralized application (dApp) using TON API and TonConnect, including step-by-step explanations and code examples. The complete project is available on GitHub (opens in a new tab).

Prerequisites

Before you begin, ensure you have the following tools and accounts set up:

  • Node.js (version >= 20)
  • npm (Node Package Manager)
  • Git installed on your computer
  • TON API Key from TonConsole (opens in a new tab)
  • Basic knowledge of React and TypeScript (optional but recommended)

Creating a Project

We'll create a React project using Vite with the TypeScript template. This setup is optional—you can use a different stack if preferred.

Step 1: Initialize the Project

Run the following commands to create a new project using the TypeScript template:

npm create vite@latest tonapi-dapp -- --template react-ts
cd tonapi-dapp

Add TonConnect

To integrate TonConnect, we need to install the TonConnect UI React library:

npm install @tonconnect/ui-react

Update the src/main.tsx file to include the TonConnectUIProvider:

import { TonConnectUIProvider } from '@tonconnect/ui-react';
import App from './App.tsx';
 
createRoot(document.getElementById('root')!).render(
  <TonConnectUIProvider
    manifestUrl="https://ton-connect.github.io/demo-dapp-with-react-ui/tonconnect-manifest.json"
  >
    <App />
  </TonConnectUIProvider>
);

For correct operation, specify the correct manifestUrl in the TonConnectUIProvider.


Integrating TON API

To use TON API, install the necessary libraries:

npm install @ton-api/client @ton/core
🔵

For using @ton/core in a browser environment, you need to include the Buffer polyfill.

Add Buffer Polyfill

To ensure @ton/core works correctly in the browser, install the vite-plugin-node-polyfills:

npm install vite-plugin-node-polyfills

Then configure Vite by updating vite.config.ts:

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { nodePolyfills } from 'vite-plugin-node-polyfills';
 
export default defineConfig({
  plugins: [nodePolyfills(), react()],
  base: '/tonapi-dapp-example/',
});

Create the TON API Client

Next, set up a client to interact with the TON API. Create a new file src/tonapi.ts with the following content:

import { TonApiClient } from '@ton-api/client';
 
const ta = new TonApiClient({
  baseUrl: 'https://tonapi.io',
  apiKey: import.meta.env.VITE_TONAPI_KEY || undefined,
});
 
export default ta;

Note: Ensure you add your TON API key to the .env file:

VITE_TONAPI_KEY=your-api-key

Fetching Jetton Balances

Now, we’ll fetch and display the user’s jetton balances. Update the src/App.tsx file:

import { useEffect, useState } from "react";
import { TonConnectButton, useTonAddress } from "@tonconnect/ui-react";
import { JettonBalance } from "@ton-api/client";
import ta from "./tonapi";
 
function App() {
  const [jettons, setJettons] = useState<JettonBalance[] | null>(null);
  const [error, setError] = useState<string | null>(null);
 
  const connectedAddressString = useTonAddress();
 
  useEffect(() => {
    if (!connectedAddressString) {
      setJettons(null);
      return;
    }
 
    ta.accounts
      .getAccountJettonsBalances(connectedAddressString)
      .then(res => setJettons(res.balances))
      .catch(err => setError(err.message || "Failed to fetch jettons"));
  }, [connectedAddressString]);
 
  return (
    <div>
      <TonConnectButton />
 
      <h1>Jetton Balances</h1>
      {jettons ? (
        jettons.map(j => (
          <div key={j.jetton.id}>
            {j.jetton.name}: {j.balance}
          </div>
        ))
      ) : (
        <p>No jettons found</p>
      )}
      {error && <p>{error}</p>}
    </div>
  );
}
 
export default App;

Implementing Jetton Transfer

Now that we have jetton balances displayed, let's add a transfer functionality. We’ll implement a modal to send jettons.

Step 1: Create SendJettonModal

Create a new file src/components/SendJettonModal.tsx with the following code:

import { useState } from "react";
import { JettonBalance } from "@ton-api/client";
import { useTonConnectUI } from "@tonconnect/ui-react";
import { getJettonSendTransactionRequest } from "../utils/jetton-transfer";
 
export const SendJettonModal = ({ jetton, senderAddress, onClose }) => {
  const [recipientAddress, setRecipientAddress] = useState("");
  const [amount, setAmount] = useState("");
  const [error, setError] = useState(null);
 
  const [tonConnectUI] = useTonConnectUI();
 
  const handleSubmit = () => {
    try {
      const transaction = getJettonSendTransactionRequest(
        jetton, amount, recipientAddress, senderAddress
      );
      
      tonConnectUI.sendTransaction(transaction)
        .then(() => {
          setError(null);
          onClose();
        })
        .catch(e => setError(e.message || "Transaction failed"));
    } catch (e) {
      setError(e instanceof Error ? e.message : "Unexpected error occurred");
    }
  };
 
  return (
    <div className="modal">
      <div className="modal-content">
        <h2>Send {jetton.jetton.name}</h2>
        {error && <p>{error}</p>}
        <label>
          Recipient Address:
          <input value={recipientAddress} onChange={e => setRecipientAddress(e.target.value)} />
        </label>
        <label>
          Amount:
          <input value={amount} onChange={e => setAmount(e.target.value)} />
        </label>
        <button onClick={handleSubmit}>Send</button>
        <button onClick={onClose}>Cancel</button>
      </div>
    </div>
  );
};

Step 2: Utility for Transactions

Create src/utils/jetton-transfer.ts to handle transaction creation:

import { JettonBalance } from '@ton-api/client';
import { beginCell, Address, toNano } from '@ton/core';
import { SendTransactionRequest } from '@tonconnect/ui-react';
import { fromDecimals } from './decimals';
 
export const getJettonSendTransactionRequest = (
  jetton: JettonBalance, amountStr: string, recipientAddressStr: string, senderAddress: Address
): SendTransactionRequest => {
  const amount = fromDecimals(amountStr, jetton.jetton.decimals);
 
  const recipient = Address.parse(recipientAddressStr);
 
  const body = beginCell()
    .storeUint(0xf8a7ea5, 32) // jetton transfer operation
    .storeUint(0, 64) // query ID
    .storeCoins(amount) // jetton amount
    .storeAddress(recipient)
    .storeAddress(senderAddress)
    .storeUint(0, 1) // empty payload
    .storeCoins(1n) // forward TON amount
    .endCell();
 
  return {
    validUntil: Math.floor(Date.now() / 1000) + 360,
    messages: [{
      address: jetton.walletAddress.address.toRawString(),
      amount: toNano('0.05').toString(), // estimated fee
      payload: body.toBoc().toString('base64'),
    }]
  };
};

Final Steps

Launch the Development Server

Run the following command to start the development server:

npm run dev

Your dApp will be accessible at http://localhost:3000 (opens in a new tab).

Complete Source Code

The full source code for this project is available on GitHub (opens in a new tab). You can also view the working example on GitHub Pages (opens in a new tab).


Additional Resources


Happy building your dApp!