Skip to content

Commit 6799df9

Browse files
authored
feat: use Swap API in example app (#243)
* Migrate example app to Swap API * Update token and chain selection * Add support for embedded actions
1 parent b360000 commit 6799df9

File tree

12 files changed

+280
-116
lines changed

12 files changed

+280
-116
lines changed

apps/example/app/viem/components/Bridge.tsx

Lines changed: 27 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,26 @@ import { ChainSelect } from "@/components/ChainSelect";
44
import { Divider } from "@/components/Divider";
55
import { TokenSelect } from "@/components/TokenSelect";
66
import { Button, Label, Skeleton } from "@/components/ui";
7-
import { useAvailableRoutes } from "@/lib/hooks/useAvailableRoutes";
8-
import { useInputTokens } from "@/lib/hooks/useInputTokens";
9-
import { useOutputTokens } from "@/lib/hooks/useOutputTokens";
10-
import { useQuote } from "@/lib/hooks/useQuote";
11-
import { useSupportedAcrossChains } from "@/lib/hooks/useSupportedAcrossChains";
7+
import { useSwapQuote } from "@/lib/hooks/useSwapQuote";
128
import { getExplorerLink, isNativeToken } from "@/lib/utils";
139
import { TokenInfo } from "@across-protocol/app-sdk";
14-
import { useEffect, useState } from "react";
10+
import { useState } from "react";
1511
import { formatUnits, parseUnits } from "viem";
1612
import { useAccount, useBalance, useChains } from "wagmi";
1713
import { useDebounceValue } from "usehooks-ts";
18-
import { useExecuteQuote } from "@/lib/hooks/useExecuteQuote";
14+
import { useExecuteSwapQuote } from "@/lib/hooks/useExecuteSwapQuote";
1915
import { Progress } from "./Progress";
2016
import { TokenInput } from "@/components/TokenInput";
2117
import { ExternalLink } from "@/components/ExternalLink";
2218
import { useAcrossChains } from "@/lib/hooks/useAcrossChains";
19+
import { useSwapTokens } from "@/lib/hooks/useSwapTokens";
20+
import { useSwapChains } from "@/lib/hooks/useSwapChains";
2321

2422
export function Bridge() {
2523
const { address } = useAccount();
2624
const chains = useChains();
2725
// CHAINS
28-
const { supportedChains } = useSupportedAcrossChains({});
26+
const { swapChains: supportedChains } = useSwapChains({});
2927

3028
// use only token data for chains we support
3129
const acrossChains = useAcrossChains();
@@ -37,7 +35,7 @@ export function Bridge() {
3735
);
3836

3937
// FROM TOKEN
40-
const { inputTokens } = useInputTokens(originChainId);
38+
const { tokens: inputTokens } = useSwapTokens(originChainId);
4139

4240
const [fromToken, setFromToken] = useState<TokenInfo | undefined>(
4341
inputTokens?.[0],
@@ -55,74 +53,49 @@ export function Bridge() {
5553
number | undefined
5654
>(chains.find((chain) => chain.id !== originChainId)?.id);
5755

58-
const { availableRoutes } = useAvailableRoutes({
59-
originChainId,
60-
destinationChainId,
61-
originToken: fromToken?.address,
62-
});
63-
64-
const outputTokensForRoute = availableRoutes?.map((route) =>
65-
route.outputToken.toLowerCase(),
66-
);
67-
68-
const { outputTokens: outputTokensForChain } =
69-
useOutputTokens(destinationChainId);
70-
71-
const [outputTokens, setOutputTokens] = useState<TokenInfo[] | undefined>();
72-
73-
useEffect(() => {
74-
const _outputTokens = outputTokensForChain?.filter((token) =>
75-
outputTokensForRoute?.includes(token.address.toLowerCase()),
76-
);
77-
setOutputTokens(_outputTokens);
78-
}, [availableRoutes]);
56+
const { tokens: outputTokens } = useSwapTokens(destinationChainId);
7957

8058
const [toToken, setToToken] = useState<TokenInfo | undefined>(
8159
outputTokens?.[0],
8260
);
8361

84-
useEffect(() => {
85-
if (outputTokens) {
86-
setToToken(
87-
outputTokens.find((token) => token.symbol === fromToken?.symbol) ??
88-
outputTokens?.[0],
89-
);
90-
}
91-
}, [outputTokens]);
92-
9362
const [inputAmount, setInputAmount] = useState<string>("");
9463
const [debouncedInputAmount] = useDebounceValue(inputAmount, 300);
95-
const route = availableRoutes?.find((route) => {
96-
return (
97-
route.outputToken.toLocaleLowerCase() ===
98-
toToken?.address?.toLowerCase() &&
99-
route.outputTokenSymbol === toToken.symbol
100-
);
101-
});
10264

10365
const quoteConfig =
104-
route && debouncedInputAmount && fromToken
66+
debouncedInputAmount &&
67+
fromToken &&
68+
toToken &&
69+
destinationChainId &&
70+
originChainId &&
71+
address
10572
? {
106-
route,
73+
route: {
74+
originChainId: originChainId,
75+
destinationChainId: destinationChainId,
76+
inputToken: fromToken.address,
77+
outputToken: toToken.address,
78+
},
79+
amount: parseUnits(debouncedInputAmount, fromToken?.decimals),
80+
depositor: address,
10781
recipient: address,
108-
inputAmount: parseUnits(debouncedInputAmount, fromToken?.decimals),
10982
}
11083
: undefined;
11184

11285
const {
11386
quote,
11487
isLoading: quoteLoading,
11588
isRefetching,
116-
} = useQuote(quoteConfig);
89+
} = useSwapQuote(quoteConfig);
11790

11891
const {
119-
executeQuote,
92+
executeSwapQuote,
12093
progress,
12194
error,
12295
isPending,
12396
depositReceipt,
12497
fillReceipt,
125-
} = useExecuteQuote(quote);
98+
} = useExecuteSwapQuote(quote ? { swapQuote: quote } : undefined);
12699
const inputBalance = fromTokenBalance
127100
? parseFloat(
128101
formatUnits(fromTokenBalance?.value, fromTokenBalance?.decimals),
@@ -232,12 +205,12 @@ export function Bridge() {
232205
{quote && toToken && (
233206
<p className="text-md font-normal text-text">
234207
{parseFloat(
235-
formatUnits(quote.deposit.outputAmount, toToken.decimals),
208+
formatUnits(BigInt(quote.minOutputAmount), toToken.decimals),
236209
).toFixed(3)}
237210
</p>
238211
)}
239212
<Button
240-
onClick={() => executeQuote()}
213+
onClick={() => executeSwapQuote()}
241214
disabled={!(quote && toToken) || isRefetching || isPending}
242215
className="mt-2"
243216
variant="accent"

apps/example/app/viem/components/Progress.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { LoadingIndicator, Status } from "@/components/LoadingIndicator";
22
import { cn } from "@/lib/utils";
3-
import { ExecutionProgress } from "@across-protocol/app-sdk";
3+
import { ExecutionProgress, SwapExecutionProgress } from "@across-protocol/app-sdk";
44

55
export type ProgressProps = {
6-
progress: ExecutionProgress;
6+
progress: ExecutionProgress | SwapExecutionProgress;
77
error?: Error | null;
88
className?: string;
99
};

apps/example/app/viem/components/Stake.tsx

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { optimism } from "viem/chains";
2323
import { useAccount, useBalance } from "wagmi";
2424
import { useStakeQuote } from "@/lib/hooks/useStakeQuote";
2525
import { useQueryClient } from "@tanstack/react-query";
26+
import { useExecuteSwapQuote } from "@/lib/hooks/useExecuteSwapQuote";
2627

2728
export function Stake() {
2829
const queryClient = useQueryClient();
@@ -44,8 +45,6 @@ export function Stake() {
4445
originChainIds.has(c.chainId),
4546
);
4647

47-
const toToken = stakeToken;
48-
4948
const [originChainId, setOriginChainId] = useState<number | undefined>(
5049
originChains?.[0]?.chainId,
5150
);
@@ -94,14 +93,6 @@ export function Stake() {
9493

9594
const [inputAmount, setInputAmount] = useState<string>("");
9695
const [debouncedInputAmount] = useDebounceValue(inputAmount, 300);
97-
const route = availableRoutes?.find((route) => {
98-
return (
99-
route.outputToken.toLowerCase() === toToken?.address?.toLowerCase() &&
100-
route.inputToken.toLowerCase() === fromToken?.address.toLowerCase() &&
101-
route.originChainId === originChainId &&
102-
route.outputTokenSymbol === toToken.symbol
103-
);
104-
});
10596

10697
const { userStakeFormatted, userStakeQueryKey } = useUserStake();
10798
const {
@@ -114,24 +105,25 @@ export function Stake() {
114105

115106
const {
116107
stakeQuote,
117-
error: stakeQuoteError,
118108
isLoading: stakeQuotePending,
119109
isRefetching: stakeQuoteRefetching,
120110
} = useStakeQuote(
121111
{
122-
route,
123-
inputAmount: parseUnits(
124-
debouncedInputAmount,
125-
STAKE_CONTRACT.token.decimals,
126-
),
112+
route: {
113+
originChainId: originChainId!,
114+
destinationChainId: routeConfig.destinationChainId,
115+
inputToken: fromToken?.address!,
116+
outputToken: stakeToken.address,
117+
},
118+
amount: parseUnits(debouncedInputAmount, STAKE_CONTRACT.token.decimals),
127119
},
128120
{
129121
enabled: Boolean(debouncedInputAmount),
130122
},
131123
);
132124

133-
const { executeQuote, progress, isPending, fillTxLink, depositTxLink } =
134-
useExecuteQuote(stakeQuote);
125+
const { executeSwapQuote, progress, isPending, fillTxLink, depositTxLink } =
126+
useExecuteSwapQuote(stakeQuote ? { swapQuote: stakeQuote } : undefined);
135127

136128
useEffect(() => {
137129
if (withdrawConfirming) {
@@ -250,7 +242,7 @@ export function Stake() {
250242
onChange={(e) => setInputAmount(e.currentTarget.value)}
251243
/>
252244
<Button
253-
onClick={() => executeQuote()}
245+
onClick={() => executeSwapQuote()}
254246
disabled={!stakeQuote || stakeQuoteRefetching || isPending}
255247
className="mt-2"
256248
variant="accent"
@@ -270,7 +262,10 @@ export function Stake() {
270262
{stakeQuote && fromToken && (
271263
<p className="text-md font-normal text-text">
272264
{parseFloat(
273-
formatUnits(stakeQuote.deposit.outputAmount, toToken.decimals),
265+
formatUnits(
266+
BigInt(stakeQuote.minOutputAmount),
267+
stakeToken.decimals,
268+
),
274269
).toFixed(6)}
275270
</p>
276271
)}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import { useMutation } from "@tanstack/react-query";
2+
import { useAcross } from "../across";
3+
import { buildQueryKey, getExplorerLink } from "../utils";
4+
import { AcrossClient, SwapExecutionProgress } from "@across-protocol/app-sdk";
5+
import { useChainId, useChains, useConfig, useSwitchChain } from "wagmi";
6+
import { useState } from "react";
7+
import { TransactionReceipt } from "viem";
8+
import { getWalletClient } from "wagmi/actions";
9+
10+
export type useExecuteSwapQuoteParams =
11+
| Omit<Parameters<AcrossClient["executeSwapQuote"]>[0], "walletClient">
12+
| undefined;
13+
14+
export function useExecuteSwapQuote(params: useExecuteSwapQuoteParams) {
15+
const sdk = useAcross();
16+
const config = useConfig();
17+
const chains = useChains();
18+
const { switchChainAsync } = useSwitchChain();
19+
const chainId = useChainId();
20+
const mutationKey = buildQueryKey("executeSwapQuote", params);
21+
22+
const [progress, setProgress] = useState<SwapExecutionProgress>({
23+
status: "idle",
24+
step: "approve",
25+
});
26+
const [depositReceipt, setDepositReceipt] = useState<TransactionReceipt>();
27+
const [fillReceipt, setFillReceipt] = useState<TransactionReceipt>();
28+
29+
function resetProgress() {
30+
setProgress({
31+
status: "idle",
32+
step: "approve",
33+
});
34+
}
35+
36+
const { mutate: executeSwapQuote, ...rest } = useMutation({
37+
mutationKey,
38+
mutationFn: async () => {
39+
resetProgress();
40+
41+
if (!params) {
42+
return;
43+
}
44+
45+
const inputChainId = params.swapQuote.inputToken.chainId;
46+
47+
if (chainId !== inputChainId) {
48+
await switchChainAsync({ chainId: inputChainId });
49+
}
50+
51+
const walletClient = await getWalletClient(config);
52+
if (!walletClient) {
53+
return;
54+
}
55+
56+
return sdk.executeSwapQuote({
57+
...params,
58+
walletClient,
59+
onProgress: (progress) => {
60+
console.log(progress);
61+
if (progress.status === "txSuccess" && progress.step === "swap") {
62+
setDepositReceipt(progress.txReceipt);
63+
}
64+
if (progress.status === "txSuccess" && progress.step === "fill") {
65+
setFillReceipt(progress.txReceipt);
66+
}
67+
setProgress(progress);
68+
},
69+
});
70+
},
71+
onError: (error) => {
72+
console.log("ERROR", error);
73+
},
74+
});
75+
76+
const originChain = chains.find(
77+
(chain) => chain.id === params?.swapQuote.inputToken.chainId,
78+
);
79+
80+
const destinationChain = chains.find(
81+
(chain) => chain.id === params?.swapQuote.outputToken.chainId,
82+
);
83+
84+
const depositTxLink =
85+
depositReceipt &&
86+
originChain &&
87+
getExplorerLink({
88+
chain: originChain,
89+
type: "transaction",
90+
txHash: depositReceipt.transactionHash,
91+
});
92+
93+
const fillTxLink =
94+
fillReceipt &&
95+
destinationChain &&
96+
getExplorerLink({
97+
chain: destinationChain,
98+
type: "transaction",
99+
txHash: fillReceipt.transactionHash,
100+
});
101+
102+
return {
103+
progress,
104+
executeSwapQuote,
105+
depositReceipt,
106+
fillReceipt,
107+
depositTxLink,
108+
fillTxLink,
109+
...rest,
110+
};
111+
}

0 commit comments

Comments
 (0)