diff --git a/docs.json b/docs.json index 132e21c8b..cb542e4a8 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/swap" ] }, { diff --git a/ecosystem/appkit/init.mdx b/ecosystem/appkit/init.mdx index ee41bd034..322c79318 100644 --- a/ecosystem/appkit/init.mdx +++ b/ecosystem/appkit/init.mdx @@ -180,32 +180,51 @@ For local development and testing, use the [manifest file from this demo dApp](h 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. -[Omniston](https://ston.fi/omniston) is the primary swap provider. To set it up: +Supported swap providers: + +- [Omniston](https://ston.fi/omniston). Requires the `@ston-fi/omniston-sdk` package. +- [DeDust](https://dedust.io). Has no additional dependencies. + +AppKit uses the first registered swap provider by default. Pass `providerId` when requesting a quote to target a specific provider. + 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 + // Swap providers import { OmnistonSwapProvider } from '@ton/appkit/swap/omniston'; + import { DeDustSwapProvider } from '@ton/appkit/swap/dedust'; + + // 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: [ + // AppKit uses the first registered swap provider by default. + new OmnistonSwapProvider({ + /* custom configuration options, none are required */ + }), + new DeDustSwapProvider({ + /* custom configuration options, none are required */ + }), + ], }); ``` @@ -263,8 +282,9 @@ The following initialization example sets up everything at once: // 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 { DeDustSwapProvider } from '@ton/appkit/swap/dedust'; // TanStack Query import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; @@ -307,7 +327,11 @@ The following initialization example sets up everything at once: }), ], // 3. Configure DeFi providers. - providers: [new OmnistonSwapProvider()], + providers: [ + // AppKit uses the first registered swap provider by default. + new OmnistonSwapProvider(), + new DeDustSwapProvider(), + ], }); // 4. Wrap the rest of the app in a QueryClientProvider and an AppKitProvider. @@ -331,8 +355,9 @@ The following initialization example sets up everything at once: TonConnectConnector, } from '@ton/appkit'; - // DeFi provider - import { OmnistonSwapProvider } from '@ston-fi/omniston-sdk'; + // DeFi providers + import { OmnistonSwapProvider } from '@ton/appkit/swap/omniston'; + import { DeDustSwapProvider } from '@ton/appkit/swap/dedust'; // Initialization const kit = new AppKit({ @@ -362,7 +387,11 @@ The following initialization example sets up everything at once: }), ], // 3. Configure DeFi providers. - providers: [new OmnistonSwapProvider()], + providers: [ + // AppKit uses the first registered swap provider by default. + new OmnistonSwapProvider(), + new DeDustSwapProvider(), + ], }); ``` @@ -503,6 +532,7 @@ Before migrating, [install AppKit and peer packages](#installation) and add nece Ensure that TON Connect packages are of the latest version: @@ -513,6 +543,7 @@ Before migrating, [install AppKit and peer packages](#installation) and add nece Use `AppKitProvider` in place of the `TonConnectUIProvider`: @@ -554,6 +585,7 @@ Before migrating, [install AppKit and peer packages](#installation) and add nece The `AppKitProvider` bridges to TON Connect under the hood. Existing few `@tonconnect/ui-react` hooks, such as `useTonAddress()`, `useTonWallet()`, and others, will continue to work inside the `AppKitProvider` automatically. @@ -566,6 +598,7 @@ Before migrating, [install AppKit and peer packages](#installation) and add nece ```shell npm up @tonconnect/ui --save @@ -574,6 +607,7 @@ Before migrating, [install AppKit and peer packages](#installation) and add nece It is possible to reuse the existing `TonConnectUI` object when configuring AppKit [connectors](#connectors): @@ -605,6 +639,7 @@ Before migrating, [install AppKit and peer packages](#installation) and add nece Instead of consolidating everything within the instantiated `TonConnectUI` object, AppKit offers several tree-shakable functions that enhance and expand the capabilities of the existing `TonConnectUI` functionality. Refer to [relevant how-to pages](/ecosystem/appkit/overview#quick-start) for concrete examples. diff --git a/ecosystem/appkit/overview.mdx b/ecosystem/appkit/overview.mdx index b591232ac..5d19782bc 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/swap.mdx b/ecosystem/appkit/swap.mdx new file mode 100644 index 000000000..da3c54282 --- /dev/null +++ b/ecosystem/appkit/swap.mdx @@ -0,0 +1,382 @@ +--- +title: "How to swap tokens using AppKit" +sidebarTitle: "Swap tokens" +--- + +import { Aside } from '/snippets/aside.jsx'; + + + +AppKit supports on-chain token swaps through [pluggable swap providers](#create-a-custom-swap-provider). Supported swap kinds are Toncoin to jetton and jetton to jetton. + +The swap flow has two steps: [get a quote](#get-a-swap-quote) for the desired trade, then [build and send the swap transaction](#build-and-send-the-swap-transaction). + + + +## Available providers + +AppKit supports two swap providers: + +- `OmnistonSwapProvider` integrates the [STON.fi](https://ston.fi) DEX aggregator through the Omniston SDK: `@ston-fi/omniston-sdk`. Requires the `@ston-fi/omniston-sdk` package. +- `DeDustSwapProvider` integrates the [DeDust](https://dedust.io) Router v2 aggregator. Has no additional dependencies. + +Register one or more providers during [AppKit initialization](/ecosystem/appkit/init#providers). AppKit uses the first registered swap provider by default. Pass `providerId` when requesting a quote to target a specific provider. + +## Get a swap quote + +A swap quote estimates how many tokens are received for a given input amount. The quote requires the source token, destination token, and the amount to swap. + + + + + ```tsx title="React" icon="react" + import { Network } from '@ton/appkit'; + import { useSwapQuote } from '@ton/appkit-react'; + + export const SwapQuoteCard = ({ + fromAddress = 'ton', + fromDecimals = 9, + toAddress = 'EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs', + toDecimals = 6, + amount = '1', + }) => { + const { + data: quote, + isLoading, + error, + } = useSwapQuote({ + // Source token: what to swap + from: { + // Either a source jetton master (minter) contract address + // or a Toncoin identifier in its stead + address: fromAddress ?? 'ton', + + // Decimal precision of the token to calculate raw unit amounts + // For example, Toncoin = 9, USDT (jetton) = 6 + decimals: fromDecimals, + }, + + // Target token: what to receive + to: { + address: toAddress, + decimals: toDecimals, + }, + + // Amount to swap in fractional units + // For example, '0.1' or '1' + amount, + + // Set the target network explicitly. + // Jettons such as USDT are available on mainnet only. + network: Network.mainnet(), + + // Optional slippage tolerance in basis points + slippageBps: 100, // 1% + + // (optional) Direction of the swap + // If true, `amount` sets the target amount of tokens to receive (buy) + // If false, `amount` sets the source amount of tokens to spend (sell) + // Defaults to false + isReverseSwap: false, + }); + + if (isLoading) { + return
Loading quote...
; + } + + if (error) { + return
Error: {error.message}
; + } + + return ( +
+

Swap quote

+ {quote && ( +
+

From: {quote.fromToken.address}

+

To: {quote.toToken.address}

+

Input amount: {quote.fromAmount}

+

Expected output: {quote.toAmount}

+

Minimum received: {quote.minReceived}

+

Price impact: {quote.priceImpact ? `${quote.priceImpact / 100}%` : 'n/a'}

+
+ )} +
+ ); + }; + ``` + + ```ts title="TypeScript" icon="globe" + import { Network, type AppKit, getSwapQuote } from '@ton/appkit'; + + async function fetchSwapQuote( + /** Initialized AppKit instance */ + kit: AppKit, + /** + * Source jetton master (minter) contract address + * or Toncoin 'ton' identifier + */ + fromAddress: string, + /** + * Decimal precision of the source token to calculate raw amounts + * For example, Toncoin = 9, USDT (jetton) = 6 + */ + fromDecimals: number, + /** + * Target jetton master (minter) contract address + * or Toncoin 'ton' identifier + */ + toAddress: string, + /** + * Decimal precision of the target token to calculate raw amounts + * For example, Toncoin = 9, USDT (jetton) = 6 + */ + toDecimals: number, + /** + * Amount to swap in fractional units + * For example, '0.1' or '1' + */ + amount: string, + /** + * Optional direction of the swap + * If true, `amount` sets the target amount of tokens to receive (buy) + * If false, `amount` sets the source amount of tokens to spend (sell) + * Defaults to false + */ + isReverseSwap?: boolean, + ) { + const quote = await getSwapQuote(kit, { + from: { address: fromAddress, decimals: fromDecimals }, + to: { address: toAddress, decimals: toDecimals }, + amount, + // Set the target network explicitly. + // Jettons such as USDT are available on mainnet only. + network: Network.mainnet(), + slippageBps: 100, // 1% + ...(isReverseSwap && { isReverseSwap }), + }); + console.log('Swap quote:', quote); + return quote; + } + ``` +
+ +## Build and send the swap transaction + +Building a swap transaction requires a quote and the sender wallet address. Some parameters are optional: + +- `slippageBps` overrides the provider default slippage for a single swap. +- `destinationAddress` sets a different recipient for the output tokens. +- `deadline` sets a UNIX timestamp after which the transaction becomes invalid. + + + ```tsx title="React" icon="react" + import { + Network, + useSwapQuote, + useBuildSwapTransaction, + useSendTransaction, + useAddress, + } from '@ton/appkit-react'; + + export const SwapForm = ({ fromToken, toToken, amount }) => { + const address = useAddress(); + const { data: quote } = useSwapQuote({ + from: fromToken, + to: toToken, + amount, + network: Network.mainnet(), + }); + const { mutateAsync: buildTransaction, isPending: isBuilding } = + useBuildSwapTransaction(); + const { mutateAsync: sendTransaction, isPending: isSending } = + useSendTransaction(); + + const handleSwap = async () => { + if (!quote || !wallet) return; + + // Build the swap transaction from the quote + const transaction = await buildTransaction({ + quote, + userAddress: address ?? '', + slippageBps: 100, // 1% + deadline: Math.floor(Date.now() / 1000) + 600, // 10 minutes + }); + + // Sign and send via TON Connect + const result = await sendTransaction(transaction); + console.log('Swap transaction sent:', result); + }; + + const isPending = isBuilding || isSending; + + return ( +
+ {quote && ( +
+

Output amount: {quote.toAmount}

+ +
+ )} +
+ ); + }; + ``` + + ```ts title="TypeScript" icon="globe" + import { + Network, + type AppKit, + getSwapQuote, + buildSwapTransaction, + sendTransaction, + getSelectedWallet, + } from '@ton/appkit'; + + async function executeSwap( + /** Initialized AppKit instance */ + kit: AppKit, + /** Source token address */ + fromAddress: string, + /** Source token decimals */ + fromDecimals: number, + /** Destination token address */ + toAddress: string, + /** Destination token decimals */ + toDecimals: number, + /** Amount to swap in fractional units */ + amount: string, + ) { + // Step 1: Get a quote + const quote = await getSwapQuote(kit, { + from: { address: fromAddress, decimals: fromDecimals }, + to: { address: toAddress, decimals: toDecimals }, + amount, + network: Network.mainnet(), + }); + + // Step 2: Build the swap transaction + const selectedWallet = getSelectedWallet(kit); + const transaction = await buildSwapTransaction(kit, { + quote, + userAddress: selectedWallet?.getAddress() ?? '', + slippageBps: 100, // 1% + deadline: Math.floor(Date.now() / 1000) + 600, // 10 minutes + }); + + // Step 3: Sign and send via TON Connect + const result = await sendTransaction(kit, transaction); + console.log('Swap transaction sent:', result); + } + ``` +
+ +## Create a custom swap provider + +Custom providers can be added to integrate other DEXes or aggregators. A swap provider implements the `SwapProvider` interface — two methods for quoting and transaction building: `getQuote()` and `buildSwapTransaction()`. + +```ts title="TypeScript" icon="globe" +import type { + SwapProvider, + SwapQuote, + SwapQuoteParams, + SwapParams, + TransactionRequest, +} from '@ton/appkit'; + +class CustomSwapProvider implements SwapProvider { + readonly type = 'swap'; + + // Unique identifier for this provider. + // It must not collide with existing identifiers, + // such as 'omniston' and 'dedust'. + readonly providerId = 'custom-dex'; + + async getQuote( + params: SwapQuoteParams, + ): Promise { + // Fetch a quote from the DEX API + const response = await fetch( + `https://api.example-dex.com/quote?from=${params.from}&to=${params.to}&amount=${params.amount}`, + ); + const data = await response.json(); + return { + fromToken: params.from, + toToken: params.to, + rawFromAmount: data.rawFromAmount, + rawToAmount: data.rawToAmount, + fromAmount: params.amount, + toAmount: data.outputAmount, + rawMinReceived: data.rawMinReceived, + minReceived: data.minReceived, + network: params.network, + providerId: this.providerId, + metadata: data, + }; + } + + async buildSwapTransaction( + params: SwapParams, + ): Promise { + // Build the transaction payload using the quote data + const response = await fetch('https://api.example-dex.com/build', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + quote: params.quote, + userAddress: params.userAddress, + destinationAddress: params.destinationAddress, + slippageBps: params.slippageBps, + deadline: params.deadline, + }), + }); + return response.json(); + } +} +``` + +### Register the provider + +Register the custom provider during [AppKit initialization](/ecosystem/appkit/init#providers) or [dynamically](/ecosystem/appkit/init#add-new-providers) at runtime: + + + ```ts title="At initialization" + import { AppKit } from '@ton/appkit'; + + const kit = new AppKit({ + providers: [new CustomSwapProvider()], + }); + ``` + + ```ts title="At runtime" + kit.swapManager.registerProvider(new CustomSwapProvider()); + ``` + + +### Specify the provider + +Once registered, the provider is available through `getSwapQuote` and `buildSwapTransaction`. Target it by passing `providerId: 'custom-dex'` when fetching quotes. + +AppKit uses the first registered swap provider by default. To change the default later, call `kit.swapManager.setDefaultProvider('custom-dex')`. + +Inspect registered provider IDs with `kit.swapManager.getRegisteredProviders()`. Check whether an ID is already in use with `kit.swapManager.hasProvider('custom-dex')` before registering a new provider. + +## See also + +- [AppKit overview](/ecosystem/appkit/overview) +- [TON Connect overview](/ecosystem/ton-connect) diff --git a/ecosystem/appkit/toncoin.mdx b/ecosystem/appkit/toncoin.mdx index 9f0611048..66a06b44b 100644 --- a/ecosystem/appkit/toncoin.mdx +++ b/ecosystem/appkit/toncoin.mdx @@ -25,7 +25,7 @@ Balances are returned as strings representing fractional Toncoin units. For exam