ton-connect/kit/ton-appkit

Claude Code and agent skill by ton-connect/kit

Trust tier 2
  • community
LLM review pending
Source check pending

Checking that the SKILL.md file is still in the repository.

Use this skill whenever the user is building an app with @ton/appkit or @ton/appkit-react as a library consumer: setup, TonConnect wallet connection, balances, sending TON/jettons/NFTs, swaps, staking, signing, real-time WebSocket updates, cache invalidation, mainnet/testnet switching, SSR/Next.js hydration, Telegram Mini App returns, iOS deep links, or debugging stale wallet/network/transaction behavior. Do not use for contributing code to the @ton/kit monorepo itself; use kit-dev for repo-internal development tasks.

Install

npx vskill@latest install ton-connect/kit/ton-appkit

SKILL.md

AppKit Guide

Guide developers consuming @ton/appkit + @ton/appkit-react in their TON apps. Assume React, TypeScript, and TanStack Query basics, but explain TON/AppKit-specific decisions clearly.

For contributing to the @ton/kit monorepo, use kit-dev instead. This skill is for library-consumer answers: code the user's app would write, not package internals.

Response workflow

  1. Classify the task first. If the user wants a complete feature quickly, prefer drop-in components. If they describe custom UX, use hooks. If they report stale data, real-time, network, or SSR symptoms, start with the relevant gotcha before writing code.
  2. Give runnable integration shape. Include provider setup when the bug could come from missing QueryClientProvider, AppKitProvider, connector config, CSS import, or streaming providers. Avoid isolated hook snippets that omit required context.
  3. Use AppKit semantics exactly. Balances are already-formatted decimal strings (TON or jetton units — never raw nano), transfer amounts are human-readable strings, mutations do not automatically invalidate caches, streaming is opt-in, and wallet state is client-only for SSR.
  4. Name the tradeoff. Drop-in components are fastest and safest; hooks are for custom UX and require explicit loading/error/cache handling.

What do you want to do?

TaskPath
Set up a new apppnpm create ton-appkit (scaffolds React+Vite project) or Quick Setup below
Wallet connect/disconnect<TonConnectButton /> or useConnect/useDisconnect
Show TON balanceuseBalance() + optionally useWatchBalance() for live updates
Send TON<SendTonButton /> or useTransferTon()
Send jettons (USDT, etc.)<SendJettonButton /> or useTransferJetton()
Show NFTsuseNfts() + <NftItem />
Swap tokens<SwapWidget /> or useSwapQuote + useBuildSwapTransaction + useSendTransaction
Stake TON<StakingWidget /> or staking hooks
Buy TON/jettons with crypto (onramp)<CryptoOnrampWidget /> or onramp hooks (useCryptoOnrampProviders + useCryptoOnrampQuote + useCreateCryptoOnrampDeposit + useCryptoOnrampStatus)
Use a custom (app-defined) provideruseCustomProvider<T>(id)
Sign messageuseSignText / useSignBinary / useSignCell
Mainnet/testnet supportConfigure both networks + useDefaultNetwork() + explicit network params
Real-time updatesRegister streaming provider + mount useWatchBalance / useWatchTransactions
Fix Next.js hydration'use client' providers + mount gate or dynamic import
Refresh balance after sendqueryClient.invalidateQueries({ queryKey: ['balance'] })
Clear stale data on disconnectqueryClient.removeQueries for wallet-scoped keys

Drop-in components (<TonConnectButton />, <SendTonButton />, <SwapWidget />, etc.) are the fastest path. Use hooks only for custom UX.

For extended swap/staking/jetton recipes, see skill-reference/recipes.md.

Packages

  • `@ton/appkit` — core SDK: AppKit class, actions, connectors (TonConnect), DeFi managers.
  • `@ton/appkit-react` — React hooks, AppKitProvider, UI components.
  • `@tanstack/react-query` — required peer dependency.

Quick Setup

tsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { AppKit, Network, createTonConnectConnector } from '@ton/appkit';
import { AppKitProvider } from '@ton/appkit-react';
import '@ton/appkit-react/styles.css';
const queryClient = new QueryClient({
defaultOptions: { queries: { refetchOnWindowFocus: false } },
});
const appKit = new AppKit({
networks: {
[Network.mainnet().chainId]: {
apiClient: { url: 'https://toncenter.com', key: 'YOUR_API_KEY' },
},
// Keep testnet too so users on testnet wallets aren't silently rejected.
// Drop it once you're production-only.
[Network.testnet().chainId]: {
apiClient: { url: 'https://testnet.toncenter.com', key: 'YOUR_API_KEY' },
},
},
connectors: [
createTonConnectConnector({
tonConnectOptions: { manifestUrl: 'https://your-app.com/tonconnect-manifest.json' },
}),
],
});
// QueryClientProvider MUST wrap AppKitProvider
function App() {
return (
<QueryClientProvider client={queryClient}>
<AppKitProvider appKit={appKit}>{/* your app */}</AppKitProvider>
</QueryClientProvider>
);
}

For Next.js: add 'use client' to the providers file and gate wallet-dependent UI until mount, or use dynamic(() => import(...), { ssr: false }). There is no `ssr` flag on `AppKitConfig` — the SSR boundary is purely a React/Next concern, not an AppKit option. Wallet state (useAddress, balances, etc.) is client-only by construction; render a stable placeholder on the server and swap in wallet-dependent UI after useEffect/mount.

Browser polyfills (Vite / Webpack / Rspack)

@ton/core (transitive dep) uses Node's Buffer and process. Browser bundlers don't include these by default — without polyfills you get a white screen and `ReferenceError: Buffer is not defined` at runtime.

For Vite, install vite-plugin-node-polyfills and add it to vite.config.ts:

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

If the plugin clashes with your project (e.g. nested workspace, alias resolution warnings), use a manual polyfill instead — pnpm add buffer process, then create src/polyfills.ts:

ts
import { Buffer } from 'buffer';
import process from 'process';
(globalThis as { Buffer?: typeof Buffer }).Buffer = Buffer;
(globalThis as { process?: typeof process }).process = process;

Import ./polyfills as the first line of `main.tsx` (before any AppKit code).

Next.js: no polyfill needed — Next's Webpack config handles Node globals automatically.

First-touch send: self-transfer

For a safe end-to-end test of the send flow, use useAddress() as the recipient — the user sends 0.01 TON to themselves. Validates wallet, network, signing, and on-chain confirmation without risk:

tsx
const address = useAddress();
{address && <SendTonButton recipientAddress={address} amount="0.01" />}

For real recipients, paste a TON address from Tonkeeper (UQ… non-bounceable for wallets, EQ… bounceable for contracts). Random/made-up strings without valid checksums make Tonkeeper's simulator say "no changes to your account" and refuse to sign.

Hooks Reference

Hooks come from @ton/appkit-react. Queries return TanStack { data, isLoading, isError, error }. Mutations return { mutate, mutateAsync, isPending, error, data, reset }.

Wallet

useConnectors() · useConnect() · useDisconnect() · useSelectedWallet() · useAddress() · useConnectedWallets()

useConnect() returns a mutation; call it with mutate({ connectorId }), passing the connector's id from useConnectors() (not the connector object itself). On iOS, the deep link that opens the wallet only fires if connect() runs in the same synchronous tick as the user's click — any await before it kills the user-activation token and the wallet won't open.

tsx
const connectors = useConnectors();
const { mutate: connect, isPending } = useConnect();
// ✅ Good: synchronous in click handler
<button onClick={() => connect({ connectorId: connector.id })}>Connect</button>
// ❌ Bad on iOS: awaiting before connect() loses the user gesture
<button onClick={async () => {
await analytics.track('connect_clicked'); // <-- breaks deep link on iOS
connect({ connectorId: connector.id });
}}>Connect</button>

If you need side effects, fire-and-forget them (no await) or run them in mutation: { onSuccess }.

Balance & assets
  • useBalance() / useBalanceByAddress({ address, network? }) — returns a decimal TON string already formatted with 9 decimals (e.g. "0.500000000" for a wallet holding 0.5 TON). It is NOT raw nano and NOT a bigint. Render directly or format with Intl.NumberFormat; do not pass it through formatUnits(_, 9) again — that re-slices on the decimal point and yields garbage like "0..500000000". Pass network explicitly for cross-network reads: useBalanceByAddress({ address, network: Network.testnet() }).
  • useWatchBalance() / useWatchBalanceByAddress(...) — real-time updates (requires streaming provider)
  • useJettons() — returns { jettons: Jetton[], addressBook }. Use data?.jettons, not data directly. Each Jetton.balance is already formatted via the jetton's own decimalsNumber (e.g. "1.5" for 1.5 USDT) — same rule: don't re-format with formatUnits. Image URLs live under info.image.smallUrl / info.image.url (note the Url suffix).
  • useJettonBalanceByAddress({ jettonAddress, ownerAddress, jettonDecimals?, network? }) — note the param is ownerAddress, not address. Returns a pre-formatted decimal string in the jetton's own units (e.g. "1.5" for 1.5 USDT). If jettonDecimals isn't passed, AppKit looks it up from jetton metadata (one extra read). Same rule as useBalance: don't pass the result through formatUnits again.
  • useJettonInfo({ address, network? }) — address is the jetton master contract address. Returns { name, symbol, decimals?, image, … } | null.
  • useJettonWalletAddress({ jettonAddress, ownerAddress, network? }) — resolves the owner's individual jetton-wallet contract address for a given jetton.
  • useWatchJettons() / useWatchJettonsByAddress(...) — real-time jetton list updates (requires streaming provider).
  • useWatchBalanceByAddress({ address, network? }) — real-time balance for an arbitrary address.
  • useNfts() — returns { nfts: NFT[], addressBook? }. Use data?.nfts, not data directly.
  • useNft({ address }) (address is the NFT item's own contract address — NOT tokenAddress)
Sending

useTransferTon() · useTransferJetton() · useTransferNft() · useSendTransaction()

Call with mutate({ recipientAddress, amount: '0.5', comment? }). Amount is a human-readable string — AppKit converts using token decimals. The mutation's data resolves to SendTransactionResponse = { boc, normalizedBoc, normalizedHash } (object, not a bare string).

Param names differ per asset — easy to get wrong from intuition:

  • TON: mutate({ recipientAddress, amount, comment? })
  • Jetton: mutate({ jettonAddress, recipientAddress, amount, comment? }) — jettonAddress is the jetton master contract.
  • NFT: mutate({ nftAddress, recipientAddress, amount?, comment? }) — `nftAddress` (NOT tokenAddress!) is the individual NFT item's contract address (nft.address from useNfts()). Optional amount is the forward TON (gas/notification), defaults are sensible. There is no <SendNftButton /> drop-in — compose from the hook.
Signing

useSignText() · useSignBinary() · useSignCell()

Network

useNetwork() (selected wallet's network, undefined if not connected) · useDefaultNetwork() returns [defaultNetwork, setDefaultNetwork] · useNetworks() · useBlockNumber()

For mainnet/testnet apps, configure both networks and make reads explicit when the UI lets users choose a network:

tsx
const [network, setNetwork] = useDefaultNetwork();
const { data: balance } = useBalanceByAddress({ address, network });
<button onClick={() => setNetwork(Network.mainnet())}>Mainnet</button>
<button onClick={() => setNetwork(Network.testnet())}>Testnet</button>

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 88 of 100Pattern checks on the skill's files.
  • Model reviewNot runA model review is scheduled for the next rescan.

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
51
Repository forks
24
7-day trend score
20

Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/ton-connect/kit/ton-appkit/badge)](https://verified-skill.com/skills/ton-connect/kit/ton-appkit)

Something wrong with this skill or its page? Tell us.