@@ -6,11 +6,13 @@ import {
66 TabsTrigger ,
77 TabsContent ,
88 ArticleIconCard ,
9+ DocImage
910} from " @doc" ;
1011import {
1112 ReactIcon ,
1213 TypeScriptIcon ,
1314} from " @/icons" ;
15+ import SwapWidgetImage from " ./swap-dark.png" ;
1416
1517export const metadata = createMetadata ({
1618 image: {
@@ -23,7 +25,6 @@ export const metadata = createMetadata({
2325
2426# Swap Tokens
2527
26- Build a custom token swap interface that allows users to exchange tokens across chains using thirdweb Payments.
2728
2829<Tabs defaultValue = " react" >
2930 <TabsList >
@@ -39,308 +40,21 @@ Build a custom token swap interface that allows users to exchange tokens across
3940
4041<TabsContent value = " react" >
4142
42- ### Simple Swap Component
43-
44- Create a basic swap interface with token selection and amount input:
43+ You can use the [ ` SwapWidget ` ] ( /references/typescript/v5/SwapWidget ) component to easily integrate a token swap interface into your app
4544
4645``` tsx
47- import React , { useState , useEffect } from " react" ;
48- import { Bridge } from " thirdweb" ;
49- import { useActiveAccount } from " thirdweb/react" ;
50- import { createThirdwebClient } from " thirdweb" ;
51- import { ethereum , polygon } from " thirdweb/chains" ;
46+ import { SwapWidget } from " thirdweb/react" ;
5247
5348const client = createThirdwebClient ({
5449 clientId: " YOUR_CLIENT_ID" ,
5550});
5651
57- function SwapInterface() {
58- const account = useActiveAccount ();
59- const [fromToken, setFromToken] = useState (" ETH" );
60- const [toToken, setToToken] = useState (" MATIC" );
61- const [amount, setAmount] = useState (" " );
62- const [quote, setQuote] = useState (null );
63- const [isLoading, setIsLoading] = useState (false );
64-
65- const getQuote = async () => {
66- if (! amount || ! account ) return ;
67-
68- setIsLoading (true );
69- try {
70- const preparedQuote = await Bridge .Buy .prepare ({
71- originChainId: ethereum .id ,
72- originTokenAddress: " 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" , // ETH
73- destinationChainId: polygon .id ,
74- destinationTokenAddress: " 0x0000000000000000000000000000000000001010" , // MATIC
75- amount: BigInt (amount * 1e18 ), // Convert to wei
76- sender: account .address ,
77- receiver: account .address ,
78- client ,
79- });
80- setQuote (preparedQuote );
81- } catch (error ) {
82- console .error (" Failed to get quote:" , error );
83- }
84- setIsLoading (false );
85- };
86-
87- useEffect (() => {
88- if (amount && account ) {
89- const timer = setTimeout (getQuote , 500 );
90- return () => clearTimeout (timer );
91- }
92- }, [amount , account ]);
93-
94- const executeSwap = async () => {
95- if (! quote || ! account ) return ;
96-
97- try {
98- for (const step of quote .steps ) {
99- for (const transaction of step .transactions ) {
100- const result = await sendAndConfirmTransaction ({
101- transaction ,
102- account ,
103- });
104-
105- // Wait for cross-chain completion if needed
106- if ([" buy" , " sell" , " transfer" ].includes (transaction .action )) {
107- let swapStatus;
108- do {
109- swapStatus = await Bridge .status ({
110- transactionHash: result .transactionHash ,
111- chainId: transaction .chainId ,
112- client ,
113- });
114- if (swapStatus .status === " PENDING" ) {
115- await new Promise (resolve => setTimeout (resolve , 3000 ));
116- }
117- } while (swapStatus .status === " PENDING" );
118- }
119- }
120- }
121-
122- alert (" Swap completed successfully!" );
123- setQuote (null );
124- setAmount (" " );
125- } catch (error ) {
126- console .error (" Swap failed:" , error );
127- alert (" Swap failed. Please try again." );
128- }
129- };
130-
131- return (
132- <div className = " max-w-md mx-auto p-6 bg-white rounded-lg shadow-lg" >
133- <h2 className = " text-2xl font-bold mb-6 text-center" >Token Swap</h2 >
134-
135- { /* From Token */ }
136- <div className = " mb-4" >
137- <label className = " block text-sm font-medium mb-2" >From</label >
138- <div className = " flex gap-2" >
139- <input
140- type = " number"
141- placeholder = " 0.0"
142- value = { amount }
143- onChange = { (e ) => setAmount (e .target .value )}
144- className = " flex-1 p-3 border rounded-lg focus:ring-2 focus:ring-blue-500"
145- />
146- <select
147- value = { fromToken }
148- onChange = { (e ) => setFromToken (e .target .value )}
149- className = " p-3 border rounded-lg focus:ring-2 focus:ring-blue-500"
150- >
151- <option value = " ETH" >ETH</option >
152- <option value = " USDC" >USDC</option >
153- <option value = " WETH" >WETH</option >
154- </select >
155- </div >
156- <p className = " text-sm text-gray-500 mt-1" >Ethereum</p >
157- </div >
158-
159- { /* Swap Arrow */ }
160- <div className = " flex justify-center mb-4" >
161- <button className = " p-2 rounded-full bg-gray-100 hover:bg-gray-200" >
162- ↓
163- </button >
164- </div >
165-
166- { /* To Token */ }
167- <div className = " mb-6" >
168- <label className = " block text-sm font-medium mb-2" >To</label >
169- <div className = " flex gap-2" >
170- <input
171- type = " text"
172- placeholder = " 0.0"
173- value = { quote ? (Number (quote .destinationAmount ) / 1e18 ).toFixed (6 ) : " " }
174- readOnly
175- className = " flex-1 p-3 border rounded-lg bg-gray-50"
176- />
177- <select
178- value = { toToken }
179- onChange = { (e ) => setToToken (e .target .value )}
180- className = " p-3 border rounded-lg focus:ring-2 focus:ring-blue-500"
181- >
182- <option value = " MATIC" >MATIC</option >
183- <option value = " USDC" >USDC</option >
184- <option value = " WETH" >WETH</option >
185- </select >
186- </div >
187- <p className = " text-sm text-gray-500 mt-1" >Polygon</p >
188- </div >
189-
190- { /* Quote Info */ }
191- { quote && (
192- <div className = " mb-4 p-3 bg-blue-50 rounded-lg" >
193- <p className = " text-sm" >
194- <span className = " font-medium" >Rate:</span > 1 { fromToken } = { (Number (quote .destinationAmount ) / Number (quote .originAmount )).toFixed (6 )} { toToken }
195- </p >
196- <p className = " text-sm" >
197- <span className = " font-medium" >Fee:</span > { ((Number (quote .originAmount ) - Number (amount ) * 1e18 ) / 1e18 ).toFixed (6 )} { fromToken }
198- </p >
199- <p className = " text-sm" >
200- <span className = " font-medium" >Estimated time:</span > { Math .round (quote .estimatedExecutionTimeMs / 1000 )} s
201- </p >
202- </div >
203- )}
204-
205- { /* Swap Button */ }
206- <button
207- onClick = { executeSwap }
208- disabled = { ! quote || isLoading || ! account }
209- className = " w-full py-3 px-4 bg-blue-600 text-white rounded-lg font-medium hover:bg-blue-700 disabled:bg-gray-300 disabled:cursor-not-allowed"
210- >
211- { ! account ? " Connect Wallet" : isLoading ? " Getting Quote..." : " Swap" }
212- </button >
213-
214- { ! account && (
215- <p className = " text-center text-sm text-gray-500 mt-3" >
216- Connect your wallet to start swapping
217- </p >
218- )}
219- </div >
220- );
52+ function Example() {
53+ return <SwapWidget client = { client } />;
22154}
22255```
22356
224- ### Advanced Swap with Route Selection
225-
226- Build a more sophisticated swap interface with multiple route options:
227-
228- ``` tsx
229- import React , { useState , useEffect } from " react" ;
230- import { Bridge } from " thirdweb" ;
231- import { useActiveAccount } from " thirdweb/react" ;
232-
233- function AdvancedSwapInterface() {
234- const account = useActiveAccount ();
235- const [routes, setRoutes] = useState ([]);
236- const [selectedRoute, setSelectedRoute] = useState (null );
237- const [quotes, setQuotes] = useState ([]);
238-
239- // Get available routes
240- const getRoutes = async () => {
241- try {
242- const availableRoutes = await Bridge .routes ({
243- originChainId: 1 , // Ethereum
244- originTokenAddress: " 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" ,
245- client ,
246- });
247- setRoutes (availableRoutes .slice (0 , 5 )); // Show top 5 routes
248- } catch (error ) {
249- console .error (" Failed to get routes:" , error );
250- }
251- };
252-
253- // Get quotes for multiple routes
254- const getQuotes = async (amount ) => {
255- if (! amount || ! account ) return ;
256-
257- const quotePromises = routes .map (async (route ) => {
258- try {
259- const quote = await Bridge .Buy .prepare ({
260- originChainId: route .originToken .chainId ,
261- originTokenAddress: route .originToken .address ,
262- destinationChainId: route .destinationToken .chainId ,
263- destinationTokenAddress: route .destinationToken .address ,
264- amount: BigInt (amount * 1e18 ),
265- sender: account .address ,
266- receiver: account .address ,
267- client ,
268- });
269- return { route , quote };
270- } catch (error ) {
271- return { route , error };
272- }
273- });
274-
275- const results = await Promise .all (quotePromises );
276- setQuotes (results .filter (result => result .quote ));
277- };
278-
279- useEffect (() => {
280- getRoutes ();
281- }, []);
282-
283- return (
284- <div className = " max-w-2xl mx-auto p-6" >
285- <h2 className = " text-2xl font-bold mb-6" >Advanced Token Swap</h2 >
286-
287- { /* Amount Input */ }
288- <div className = " mb-6" >
289- <input
290- type = " number"
291- placeholder = " Enter amount to swap"
292- onChange = { (e ) => getQuotes (e .target .value )}
293- className = " w-full p-3 border rounded-lg focus:ring-2 focus:ring-blue-500"
294- />
295- </div >
296-
297- { /* Route Options */ }
298- <div className = " space-y-3" >
299- <h3 className = " text-lg font-medium" >Available Routes</h3 >
300- { quotes .map (({ route , quote }, index ) => (
301- <div
302- key = { index }
303- className = " border rounded-lg p-4 hover:bg-gray-50 cursor-pointer"
304- onClick = { () => setSelectedRoute ({ route , quote })}
305- >
306- <div className = " flex justify-between items-center" >
307- <div >
308- <p className = " font-medium" >
309- { route .originToken .symbol } → { route .destinationToken .symbol }
310- </p >
311- <p className = " text-sm text-gray-500" >
312- { route .originToken .name } to { route .destinationToken .name }
313- </p >
314- </div >
315- <div className = " text-right" >
316- <p className = " font-medium" >
317- { (Number (quote .destinationAmount ) / Math .pow (10 , route .destinationToken .decimals )).toFixed (6 )}
318- </p >
319- <p className = " text-sm text-gray-500" >
320- ~{ Math .round (quote .estimatedExecutionTimeMs / 1000 )} s
321- </p >
322- </div >
323- </div >
324- </div >
325- ))}
326- </div >
327-
328- { selectedRoute && (
329- <div className = " mt-6 p-4 bg-blue-50 rounded-lg" >
330- <h4 className = " font-medium mb-2" >Selected Route</h4 >
331- <p >{ selectedRoute .route .originToken .symbol } → { selectedRoute .route .destinationToken .symbol } </p >
332- <button
333- onClick = { () => executeSwap (selectedRoute .quote )}
334- className = " mt-3 w-full py-2 px-4 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
335- >
336- Execute Swap
337- </button >
338- </div >
339- )}
340- </div >
341- );
342- }
343- ```
57+ <DocImage src = { SwapWidgetImage } />
34458
34559</TabsContent >
34660
@@ -607,16 +321,20 @@ export class PriceComparator {
607321}
608322```
609323
324+ <Callout variant = " info" >
325+ ** Gas Optimization Tip:** For frequently traded pairs, consider caching route data to reduce API calls and improve UI responsiveness.
326+ </Callout >
327+
610328</TabsContent >
611329</Tabs >
612330
613- ## View it in action:
331+ ## Live Playground
614332
615333<ArticleIconCard
616- title = " Token Swap Playground"
617- description = " Try building and testing swap interfaces with live data "
334+ title = " Swap Widget Playground"
335+ description = " Try out the Swap Widget in our live playground "
618336 icon = { ReactIcon }
619- href = " https://playground.thirdweb.com/connect/pay "
337+ href = " https://playground.thirdweb.com/bridge/swap-widget "
620338/>
621339
622340## Key Features
@@ -627,9 +345,7 @@ export class PriceComparator {
627345- ** Price comparison** - Compare multiple routes to maximize output
628346- ** Status tracking** - Monitor cross-chain transaction progress
629347
630- <Callout variant = " info" >
631- ** Gas Optimization Tip:** For frequently traded pairs, consider caching route data to reduce API calls and improve UI responsiveness.
632- </Callout >
348+
633349
634350## Going Further
635351
0 commit comments