diff --git a/docs.json b/docs.json index 132e21c8b..3dcf78c45 100644 --- a/docs.json +++ b/docs.json @@ -175,7 +175,8 @@ "ecosystem/appkit/init", "ecosystem/appkit/toncoin", "ecosystem/appkit/jettons", - "ecosystem/appkit/nfts" + "ecosystem/appkit/nfts", + "ecosystem/appkit/stake" ] }, { diff --git a/ecosystem/appkit/init.mdx b/ecosystem/appkit/init.mdx index ee41bd034..27a3e6537 100644 --- a/ecosystem/appkit/init.mdx +++ b/ecosystem/appkit/init.mdx @@ -133,13 +133,13 @@ For local development and testing, use the [manifest file from this demo dApp](h AppKit, AppKitProvider, // Enables TON wallet connections - TonConnectConnector, + createTonConnectConnector, } from '@ton/appkit-react'; import '@ton/appkit-react/styles.css'; const kit = new AppKit({ connectors: [ - new TonConnectConnector({ + createTonConnectConnector({ tonConnectOptions: { // Public link to the application manifest JSON file. // For local development and testing, use the one from a demo dApp: @@ -159,12 +159,12 @@ For local development and testing, use the [manifest file from this demo dApp](h import { AppKit, // Enables TON wallet connections - TonConnectConnector, + createTonConnectConnector, } from '@ton/appkit'; const kit = new AppKit({ connectors: [ - new TonConnectConnector({ + createTonConnectConnector({ tonConnectOptions: { // Public link to the application manifest JSON file. // For local development and testing, use the one from a demo dApp: @@ -178,34 +178,56 @@ For local development and testing, use the [manifest file from this demo dApp](h ### Providers -The [setup with connectors](#connectors) connects to TON wallets via services such as [Tonkeeper](/ecosystem/wallet-apps/tonkeeper). To enable advanced DeFi operations like asset swaps, configure DeFi providers. +The [setup with connectors](#connectors) connects to TON wallets via services such as [Tonkeeper](/ecosystem/wallet-apps/tonkeeper). To enable advanced DeFi operations like asset swaps or staking, configure DeFi providers. -[Omniston](https://ston.fi/omniston) is the primary swap provider. To set it up: +Supported swap providers: + +- [Omniston](https://ston.fi/omniston) is the primary swap provider. + +Supported staking providers: + +- [Tonstakers](https://tonstakers.com) is the primary staking provider. + +To set up all DeFi providers: + Swaps with Omniston require its SDK to be installed: + ```shell npm i @ston-fi/omniston-sdk ``` ```ts // Or @ton/appkit-react for React - import { AppKit, Network } from '@ton/appkit'; + import { AppKit } from '@ton/appkit'; // Omniston as a swap provider import { OmnistonSwapProvider } from '@ton/appkit/swap/omniston'; + // Tonstakers as a staking provider + import { createTonstakersProvider } from '@ton/appkit/staking/tonstakers'; + const kit = new AppKit({ - // Set up a swap provider - providers: [new OmnistonSwapProvider({ - /* custom configuration options */ - })], + // DeFi providers setup + providers: [ + // Swaps + new OmnistonSwapProvider({ + /* custom configuration options, all optional */ + }), + // Staking + createTonstakersProvider({ + /* custom configuration options, all optional */ + }), + ], }); ``` @@ -257,14 +279,15 @@ The following initialization example sets up everything at once: AppKitProvider, Network, // Wallet connector - TonConnectConnector, + createTonConnectConnector, } from '@ton/appkit-react'; // Styles import '@ton/appkit-react/styles.css'; - // DeFi provider - import { OmnistonSwapProvider } from '@ston-fi/omniston-sdk'; + // DeFi providers + import { OmnistonSwapProvider } from '@ton/appkit/swap/omniston'; + import { createTonstakersProvider } from '@ton/appkit/staking/tonstakers'; // TanStack Query import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; @@ -298,7 +321,7 @@ The following initialization example sets up everything at once: // 2. Configure connectors. connectors: [ // Enables connections with TON wallet services, such as Tonkeeper or MyTonWallet. - new TonConnectConnector({ + createTonConnectConnector({ tonConnectOptions: { // Public link to the application manifest JSON file. // For local development and testing, use the one from a demo dApp: @@ -307,7 +330,10 @@ The following initialization example sets up everything at once: }), ], // 3. Configure DeFi providers. - providers: [new OmnistonSwapProvider()], + providers: [ + new OmnistonSwapProvider(), + createTonstakersProvider(), + ], }); // 4. Wrap the rest of the app in a QueryClientProvider and an AppKitProvider. @@ -328,11 +354,12 @@ The following initialization example sets up everything at once: AppKit, Network, // Wallet connector - TonConnectConnector, + createTonConnectConnector, } from '@ton/appkit'; - // DeFi provider - import { OmnistonSwapProvider } from '@ston-fi/omniston-sdk'; + // DeFi providers + import { OmnistonSwapProvider } from '@ton/appkit/swap/omniston'; + import { createTonstakersProvider } from '@ton/appkit/staking/tonstakers'; // Initialization const kit = new AppKit({ @@ -353,7 +380,7 @@ The following initialization example sets up everything at once: // 2. Configure connectors. connectors: [ // Enables connections with TON wallet services, such as Tonkeeper or MyTonWallet. - new TonConnectConnector({ + createTonConnectConnector({ tonConnectOptions: { // Public link to the application manifest JSON file. // For local development and testing, use the one from a demo dApp: @@ -362,7 +389,10 @@ The following initialization example sets up everything at once: }), ], // 3. Configure DeFi providers. - providers: [new OmnistonSwapProvider()], + providers: [ + new OmnistonSwapProvider(), + createTonstakersProvider(), + ], }); ``` @@ -455,7 +485,7 @@ kit.swapManager.registerProvider(swapProvider); path="connectors" type="Connector[] | undefined" > - Array of connectors that enable wallet connections. The primary connector is `TonConnectConnector`. + Array of connectors that enable wallet connections. The primary connector is `TonConnectConnector`, which is created by dedicated function. Each connector must expose the following methods: @@ -522,13 +552,13 @@ Before migrating, [install AppKit and peer packages](#installation) and add nece // Replaces AppKitProvider, // Wallet connector - TonConnectConnector, + createTonConnectConnector, } from '@ton/appkit-react'; import '@ton/appkit-react/styles.css'; const kit = new AppKit({ connectors: [ - new TonConnectConnector({ + createTonConnectConnector({ // In place of props on the TonConnectUIProvider tonConnectOptions: { // Public link to the application manifest JSON file. @@ -580,12 +610,12 @@ Before migrating, [install AppKit and peer packages](#installation) and add nece ```ts import { AppKit, - TonConnectConnector, + createTonConnectConnector, } from '@ton/appkit'; const kit = new AppKit({ connectors: [ - new TonConnectConnector({ + createTonConnectConnector({ // Pass the existing TonConnectUI instance object tonConnectUI }), @@ -597,7 +627,7 @@ Before migrating, [install AppKit and peer packages](#installation) and add nece ```ts // Passing the existing TonConnectUI instance object - kit.addConnector(new TonConnectConnector({ tonConnectUI })); + kit.addConnector(createTonConnectConnector({ tonConnectUI })); ``` Refer to the [complete initialization setup](#complete-setup) for all the possible AppKit configuration options. diff --git a/ecosystem/appkit/overview.mdx b/ecosystem/appkit/overview.mdx index b591232ac..adf7b26f7 100644 --- a/ecosystem/appkit/overview.mdx +++ b/ecosystem/appkit/overview.mdx @@ -57,6 +57,13 @@ TON Connect's **AppKit** is an open-source SDK that integrates web2 and web3 app horizontal="true" href="/ecosystem/appkit/nfts" /> + + ## See also diff --git a/ecosystem/appkit/stake.mdx b/ecosystem/appkit/stake.mdx new file mode 100644 index 000000000..36aa59827 --- /dev/null +++ b/ecosystem/appkit/stake.mdx @@ -0,0 +1,411 @@ +--- +title: "How to stake Toncoin using AppKit" +sidebarTitle: "Stake Toncoin" +--- + +import { Aside } from '/snippets/aside.jsx'; + + + + + +AppKit supports on-chain liquid Toncoin staking through [pluggable staking providers](#available-providers). For most user-facing apps, the flow is: + +1. Register a staking provider. +1. Show the terms and quote of the current provider. +1. Build a transaction from that quote. +1. Send the transaction from the connected wallet. +1. Display the user's staked balance. + +## Available providers + +[Tonstakers](https://tonstakers.com) is the primary liquid staking [AppKit provider](/ecosystem/appkit/init#providers). It is configured per network, similar to API clients: + +```ts +// 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: '', + } + }), + ], +}); +``` + +## Set up a staking provider + +Before requesting quotes or building transactions, register at least one staking provider: + + + ```tsx title="React" icon="react" wrap lines + 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: '', + }, + }, + }, + connectors: [ + createTonConnectConnector({ + tonConnectOptions: { + manifestUrl: 'https://tonconnect-sdk-demo-dapp.vercel.app/tonconnect-manifest.json', + }, + }), + ], + providers: [createTonstakersProvider()], + }); + + export function App() { + return {/* ...app... */}; + } + ``` + + ```ts title="TypeScript" icon="globe" wrap lines + import { + AppKit, + Network, + createTonConnectConnector, + } from '@ton/appkit'; + import { createTonstakersProvider } from '@ton/appkit/staking/tonstakers'; + + const kit = new AppKit({ + networks: { + [Network.mainnet().chainId]: { + apiClient: { + url: 'https://toncenter.com', + key: '', + }, + }, + }, + connectors: [ + createTonConnectConnector({ + tonConnectOptions: { + manifestUrl: 'https://tonconnect-sdk-demo-dapp.vercel.app/tonconnect-manifest.json', + }, + }), + ], + providers: [createTonstakersProvider()], + }); + ``` + + +## 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. + + + + + ```tsx title="React" icon="react" + 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 ?? '', + + // Fractional Toncoin amount string. + // For example, '0.1' or '10' Toncoin. + amount: '1', + }); + + if (!providerId) { + return

No staking providers configured.

; + } + + if (isLoading) { + return

Loading quote...

; + } + + if (error) { + return

Error: {error.message}

; + } + + return ( +
+

Provider: {providerId}

+

APY: {providerInfo ? providerInfo.apy / 100 : 0}%

+

Stake amount: {quote?.amountIn} Toncoin

+

Estimated output: {quote?.amountOut} Toncoin

+
+ ); + }; + ``` + + ```ts title="TypeScript" icon="globe" + import { + type AppKit, + getSelectedWallet, + getStakingProviderInfo, + getStakingProviders, + getStakingQuote, + } from '@ton/appkit'; + + async function getStakeQuote( + /** Initialized AppKit instance */ + kit: AppKit, + /** Fractional Toncoin amount string */ + amount: string, + ) { + const providerId = getStakingProviders(kit)[0]; + const userAddress = getSelectedWallet(kit)?.getAddress(); + if (!providerId || !userAddress) { + return null; + } + + const providerInfo = await getStakingProviderInfo(kit, { providerId }); + const quote = await getStakingQuote(kit, { + providerId, + + // Quote direction: 'stake' or 'unstake' + direction: 'stake', + + // Staking user's TON wallet address + userAddress, + + // Fractional Toncoin amount string. + // For example, '0.1' or '10' Toncoin. + amount, + }); + + console.log('Provider:', providerId); + console.log('APY:', providerInfo.apy / 100, '%'); + console.log('Stake amount:', quote.amountIn, 'Toncoin'); + console.log('Estimated output:', quote.amountOut, 'Toncoin'); + + return quote; + } + ``` +
+ +## Build and send the staking transaction + +Build the transaction from the quote, then send it through the connected wallet: + + + ```tsx title="React" icon="react" + 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 ?? '', + + // 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 ( + + ); + }; + ``` + + ```ts title="TypeScript" icon="globe" + import { + type AppKit, + getSelectedWallet, + getStakingProviders, + getStakingQuote, + buildStakeTransaction, + sendTransaction, + } from '@ton/appkit'; + + async function stakeTon( + /** Initialized AppKit instance */ + kit: AppKit, + /** Fractional Toncoin amount string */ + amount: string, + ) { + const providerId = getStakingProviders(kit)[0]; + const userAddress = getSelectedWallet(kit)?.getAddress(); + if (!providerId || !userAddress) { + return null; + } + const quote = await getStakingQuote(kit, { + providerId, + + // Quote direction: 'stake' or 'unstake' + direction: 'stake', + + // Staking user's TON wallet address + userAddress, + + // Fractional Toncoin amount string. + // For example, '0.1' or '10' Toncoin. + amount, + }); + + const transaction = await buildStakeTransaction(kit, { + providerId, quote, userAddress, + }); + + const result = await sendTransaction(kit, transaction); + console.log('Transaction sent:', result.boc); + } + ``` + + +## Display the staked balance + +After the transaction is sent, query the staking balance for the connected wallet: + + + ```tsx title="React" icon="react" + 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 ?? '', + }); + + if (!providerId) { + return

No staking providers configured.

; + } + + if (isLoading) { + return

Loading staked balance...

; + } + + if (error) { + return

Error: {error.message}

; + } + + return

Staked balance: {balance?.stakedBalance ?? '0'}

; + }; + ``` + + ```ts title="TypeScript" icon="globe" + import { + type AppKit, + getSelectedWallet, + getStakingProviders, + getStakedBalance, + } from '@ton/appkit'; + + async function getUserStakedBalance( + /** Initialized AppKit instance */ + kit: AppKit, + ) { + const providerId = getStakingProviders(kit)[0]; + const userAddress = getSelectedWallet(kit)?.getAddress(); + if (!providerId || !userAddress) { + return null; + } + + const balance = await getStakedBalance(kit, { + providerId, + + // Staking user's TON wallet address + userAddress, + }); + + console.log('Staked balance:', balance.stakedBalance); + return balance; + } + ``` +
+ +## See also + +- [Tonstakers documentation, external](https://docs.tonstakers.com) +- [Transaction fees](/foundations/fees) +- [AppKit overview](/ecosystem/appkit/overview) +- [TON Connect overview](/ecosystem/ton-connect/overview)