diff --git a/apps/portal/src/app/bridge/swap/page.mdx b/apps/portal/src/app/bridge/swap/page.mdx index 42c65173ec5..41ee17c68ce 100644 --- a/apps/portal/src/app/bridge/swap/page.mdx +++ b/apps/portal/src/app/bridge/swap/page.mdx @@ -6,11 +6,13 @@ import { TabsTrigger, TabsContent, ArticleIconCard, + DocImage } from "@doc"; import { ReactIcon, TypeScriptIcon, } from "@/icons"; +import SwapWidgetImage from "./swap-dark.png"; export const metadata = createMetadata({ image: { @@ -23,7 +25,6 @@ export const metadata = createMetadata({ # Swap Tokens -Build a custom token swap interface that allows users to exchange tokens across chains using thirdweb Payments. @@ -39,309 +40,97 @@ Build a custom token swap interface that allows users to exchange tokens across -### Simple Swap Component +You can use the [`SwapWidget`](/references/typescript/v5/SwapWidget) component to easily integrate a token swap interface into your app -Create a basic swap interface with token selection and amount input: ```tsx -import React, { useState, useEffect } from "react"; -import { Bridge } from "thirdweb"; -import { useActiveAccount } from "thirdweb/react"; -import { createThirdwebClient } from "thirdweb"; -import { ethereum, polygon } from "thirdweb/chains"; +import { SwapWidget } from "thirdweb/react"; const client = createThirdwebClient({ clientId: "YOUR_CLIENT_ID", }); -function SwapInterface() { - const account = useActiveAccount(); - const [fromToken, setFromToken] = useState("ETH"); - const [toToken, setToToken] = useState("MATIC"); - const [amount, setAmount] = useState(""); - const [quote, setQuote] = useState(null); - const [isLoading, setIsLoading] = useState(false); +function Example() { + return ; +} +``` - const getQuote = async () => { - if (!amount || !account) return; + - setIsLoading(true); - try { - const preparedQuote = await Bridge.Buy.prepare({ - originChainId: ethereum.id, - originTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", // ETH - destinationChainId: polygon.id, - destinationTokenAddress: "0x0000000000000000000000000000000000001010", // MATIC - amount: BigInt(amount * 1e18), // Convert to wei - sender: account.address, - receiver: account.address, - client, - }); - setQuote(preparedQuote); - } catch (error) { - console.error("Failed to get quote:", error); - } - setIsLoading(false); - }; +## Live Playground - useEffect(() => { - if (amount && account) { - const timer = setTimeout(getQuote, 500); - return () => clearTimeout(timer); - } - }, [amount, account]); + - const executeSwap = async () => { - if (!quote || !account) return; - try { - for (const step of quote.steps) { - for (const transaction of step.transactions) { - const result = await sendAndConfirmTransaction({ - transaction, - account, - }); +### Configuring default token selection - // Wait for cross-chain completion if needed - if (["buy", "sell", "transfer"].includes(transaction.action)) { - let swapStatus; - do { - swapStatus = await Bridge.status({ - transactionHash: result.transactionHash, - chainId: transaction.chainId, - client, - }); - if (swapStatus.status === "PENDING") { - await new Promise(resolve => setTimeout(resolve, 3000)); - } - } while (swapStatus.status === "PENDING"); - } - } - } +By default, no tokens are selected in the widget UI. You can configure the default token selection for the swap widget by passing in the `prefill` prop. +it only sets the default token selection, User can change these selections in the widget UI - alert("Swap completed successfully!"); - setQuote(null); - setAmount(""); - } catch (error) { - console.error("Swap failed:", error); - alert("Swap failed. Please try again."); - } - }; - - return ( -
-

Token Swap

- - {/* From Token */} -
- -
- setAmount(e.target.value)} - className="flex-1 p-3 border rounded-lg focus:ring-2 focus:ring-blue-500" - /> - -
-

Ethereum

-
- - {/* Swap Arrow */} -
- -
- - {/* To Token */} -
- -
- - -
-

Polygon

-
- - {/* Quote Info */} - {quote && ( -
-

- Rate: 1 {fromToken} = {(Number(quote.destinationAmount) / Number(quote.originAmount)).toFixed(6)} {toToken} -

-

- Fee: {((Number(quote.originAmount) - Number(amount) * 1e18) / 1e18).toFixed(6)} {fromToken} -

-

- Estimated time: {Math.round(quote.estimatedExecutionTimeMs / 1000)}s -

-
- )} - - {/* Swap Button */} - - - {!account && ( -

- Connect your wallet to start swapping -

- )} -
- ); -} + +#### Set an ERC20 token to Buy by default + +```tsx + ``` -### Advanced Swap with Route Selection +#### Set a native token to Sell by default -Build a more sophisticated swap interface with multiple route options: +To configure a native token to sell by default, you can omit the `tokenAddress` property. ```tsx -import React, { useState, useEffect } from "react"; -import { Bridge } from "thirdweb"; -import { useActiveAccount } from "thirdweb/react"; - -function AdvancedSwapInterface() { - const account = useActiveAccount(); - const [routes, setRoutes] = useState([]); - const [selectedRoute, setSelectedRoute] = useState(null); - const [quotes, setQuotes] = useState([]); - - // Get available routes - const getRoutes = async () => { - try { - const availableRoutes = await Bridge.routes({ - originChainId: 1, // Ethereum - originTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", - client, - }); - setRoutes(availableRoutes.slice(0, 5)); // Show top 5 routes - } catch (error) { - console.error("Failed to get routes:", error); - } - }; + +``` - // Get quotes for multiple routes - const getQuotes = async (amount) => { - if (!amount || !account) return; +#### Set amount and token to Buy by default - const quotePromises = routes.map(async (route) => { - try { - const quote = await Bridge.Buy.prepare({ - originChainId: route.originToken.chainId, - originTokenAddress: route.originToken.address, - destinationChainId: route.destinationToken.chainId, - destinationTokenAddress: route.destinationToken.address, - amount: BigInt(amount * 1e18), - sender: account.address, - receiver: account.address, - client, - }); - return { route, quote }; - } catch (error) { - return { route, error }; - } - }); +```tsx + +``` - const results = await Promise.all(quotePromises); - setQuotes(results.filter(result => result.quote)); - }; - - useEffect(() => { - getRoutes(); - }, []); - - return ( -
-

Advanced Token Swap

- - {/* Amount Input */} -
- getQuotes(e.target.value)} - className="w-full p-3 border rounded-lg focus:ring-2 focus:ring-blue-500" - /> -
- - {/* Route Options */} -
-

Available Routes

- {quotes.map(({ route, quote }, index) => ( -
setSelectedRoute({ route, quote })} - > -
-
-

- {route.originToken.symbol} → {route.destinationToken.symbol} -

-

- {route.originToken.name} to {route.destinationToken.name} -

-
-
-

- {(Number(quote.destinationAmount) / Math.pow(10, route.destinationToken.decimals)).toFixed(6)} -

-

- ~{Math.round(quote.estimatedExecutionTimeMs / 1000)}s -

-
-
-
- ))} -
- - {selectedRoute && ( -
-

Selected Route

-

{selectedRoute.route.originToken.symbol} → {selectedRoute.route.destinationToken.symbol}

- -
- )} -
- ); -} +#### Set both buy and sell tokens by default + +```tsx + ``` + + + + +
@@ -607,17 +396,13 @@ export class PriceComparator { } ``` + +**Gas Optimization Tip:** For frequently traded pairs, consider caching route data to reduce API calls and improve UI responsiveness. + +
-## View it in action: - - ## Key Features @@ -627,9 +412,7 @@ export class PriceComparator { - **Price comparison** - Compare multiple routes to maximize output - **Status tracking** - Monitor cross-chain transaction progress - -**Gas Optimization Tip:** For frequently traded pairs, consider caching route data to reduce API calls and improve UI responsiveness. - + ## Going Further diff --git a/apps/portal/src/app/bridge/swap/swap-dark.png b/apps/portal/src/app/bridge/swap/swap-dark.png new file mode 100644 index 00000000000..48ca746d6f8 Binary files /dev/null and b/apps/portal/src/app/bridge/swap/swap-dark.png differ