Skip to content

Commit 84e9985

Browse files
committed
Portal: Add SwapWidget in bridge/swap page
1 parent d9f6028 commit 84e9985

File tree

2 files changed

+16
-300
lines changed

2 files changed

+16
-300
lines changed

apps/portal/src/app/bridge/swap/page.mdx

Lines changed: 16 additions & 300 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ import {
66
TabsTrigger,
77
TabsContent,
88
ArticleIconCard,
9+
DocImage
910
} from "@doc";
1011
import {
1112
ReactIcon,
1213
TypeScriptIcon,
1314
} from "@/icons";
15+
import SwapWidgetImage from "./swap-dark.png";
1416

1517
export 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

5348
const 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

118 KB
Loading

0 commit comments

Comments
 (0)