Skip to main content
AppKit supports on-chain liquid Toncoin staking through pluggable staking providers. For most user-facing apps, the flow is:
  1. Register a staking provider.
  2. Show the terms and quote of the current provider.
  3. Build a transaction from that quote.
  4. Send the transaction from the connected wallet.
  5. Display the user’s staked balance.

Available providers

Tonstakers is the primary liquid staking AppKit provider. It is configured per network, similar to API clients:
// Or @ton/appkit-react for React
import { AppKit, Network } from '@ton/appkit';
import { createTonstakersProvider } from '@ton/appkit/staking/tonstakers';

const kit = new AppKit({
  providers: [
    // Optional configuration options — omit when not known.
    createTonstakersProvider({
      [Network.mainnet().chainId]: {
        // Defaults to a known pool when available.
        contractAddress: 'EQ...POOL_ADDRESS',

        // Optional TonAPI key, get it at https://tonconsole.com/tonapi/api-keys
        // TonAPI is an alternative API client to TON Center.
        tonApiToken: '<TON_API_TOKEN>',
      }
    }),
  ],
});

Set up a staking provider

Before requesting quotes or building transactions, register at least one staking provider:
import {
  AppKit,
  AppKitProvider,
  Network,
  createTonConnectConnector,
} from '@ton/appkit-react';
import { createTonstakersProvider } from '@ton/appkit/staking/tonstakers';
import '@ton/appkit-react/styles.css';

const kit = new AppKit({
  networks: {
    [Network.mainnet().chainId]: {
      apiClient: {
        url: 'https://toncenter.com',
        key: '<MAINNET_API_KEY>',
      },
    },
  },
  connectors: [
    createTonConnectConnector({
      tonConnectOptions: {
        manifestUrl: 'https://tonconnect-sdk-demo-dapp.vercel.app/tonconnect-manifest.json',
      },
    }),
  ],
  providers: [createTonstakersProvider()],
});

export function App() {
  return <AppKitProvider appKit={kit}>{/* ...app... */}</AppKitProvider>;
}

Show provider terms and a quote

Retail staking UIs should display the provider and an up-to-date quote before asking the user to confirm the transaction.
import {
  useAddress,
  useStakingProviderInfo,
  useStakingProviders,
  useStakingQuote,
} from '@ton/appkit-react';

export const StakingQuoteCard = () => {
  const address = useAddress();
  const { data: providers } = useStakingProviders();
  const providerId = providers?.[0];

  const { data: providerInfo } = useStakingProviderInfo({ providerId });
  const { data: quote, isLoading, error } = useStakingQuote({
    providerId,

    // Quote direction: 'stake' or 'unstake'
    direction: 'stake',

    // Staking user's TON wallet address
    userAddress: address ?? '<TON_WALLET_ADDRESS>',

    // Fractional Toncoin amount string.
    // For example, '0.1' or '10' Toncoin.
    amount: '1',
  });

  if (!providerId) {
    return <p>No staking providers configured.</p>;
  }

  if (isLoading) {
    return <p>Loading quote...</p>;
  }

  if (error) {
    return <p>Error: {error.message}</p>;
  }

  return (
    <div>
      <p>Provider: {providerId}</p>
      <p>APY: {providerInfo ? providerInfo.apy / 100 : 0}%</p>
      <p>Stake amount: {quote?.amountIn} Toncoin</p>
      <p>Estimated output: {quote?.amountOut} Toncoin</p>
    </div>
  );
};

Build and send the staking transaction

Build the transaction from the quote, then send it through the connected wallet:
import {
  useAddress,
  useBuildStakeTransaction,
  useSendTransaction,
  useStakingProviders,
  useStakingQuote,
} from '@ton/appkit-react';

export const StakeButton = () => {
  const address = useAddress();
  const { data: providers } = useStakingProviders();
  const providerId = providers?.[0];
  const { data: quote } = useStakingQuote({
    providerId,

    // Quote direction: 'stake' or 'unstake'
    direction: 'stake',

    // Staking user's TON wallet address
    userAddress: address ?? '<TON_WALLET_ADDRESS>',

    // Fractional Toncoin amount string.
    // For example, '0.1' or '10' Toncoin.
    amount: '1',
  });
  const {
    mutateAsync: buildTransaction,
    isPending: isBuilding,
  } = useBuildStakeTransaction();
  const {
    mutateAsync: sendTransaction,
    isPending: isSending,
  } = useSendTransaction();

  const handleStake = async () => {
    if (!quote || !address || !providerId) {
      return;
    }

    const transaction = await buildTransaction({
      providerId,
      quote,
      userAddress: address,
    });

    await sendTransaction(transaction);
  };

  return (
    <button
      onClick={() => void handleStake()}
      disabled={!quote || !address || isBuilding || isSending}
    >
      {isBuilding || isSending ? 'Submitting...' : 'Stake 1 TON'}
    </button>
  );
};

Display the staked balance

After the transaction is sent, query the staking balance for the connected wallet:
import {
  useAddress,
  useStakingProviders,
  useStakedBalance,
} from '@ton/appkit-react';

export const StakedBalanceCard = () => {
  const address = useAddress();
  const { data: providers } = useStakingProviders();
  const providerId = providers?.[0];
  const { data: balance, isLoading, error } = useStakedBalance({
    providerId,

    // Staking user's TON wallet address
    userAddress: address ?? '<TON_WALLET_ADDRESS>',
  });

  if (!providerId) {
    return <p>No staking providers configured.</p>;
  }

  if (isLoading) {
    return <p>Loading staked balance...</p>;
  }

  if (error) {
    return <p>Error: {error.message}</p>;
  }

  return <p>Staked balance: {balance?.stakedBalance ?? '0'}</p>;
};

See also