Skip to content

Commit

Permalink
support deposit/withdraw when swapping to/from the gas token
Browse files Browse the repository at this point in the history
  • Loading branch information
eli-d committed Nov 28, 2024
1 parent d636d7c commit 6a1e8e9
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 17 deletions.
139 changes: 129 additions & 10 deletions web/src/components/ConfirmSwap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { Button } from "@/components/ui/button";
import { redirect, useRouter } from "next/navigation";
import { cn } from "@/lib/utils";
import { cn, getAmountFromMaybeTransfer, TransferTopic } from "@/lib/utils";
import { useSwapStore } from "@/stores/useSwapStore";
import { motion } from "framer-motion";
import {
Expand Down Expand Up @@ -129,6 +129,21 @@ export const ConfirmSwap = () => {
isPending: isSwapPending,
reset: resetSwap,
} = useWriteContract();
const {
writeContractAsync: writeContractDepositGasToken,
data: depositGasTokenData,
error: depositGasTokenError,
isPending: isDepositGasTokenPending,
reset: resetDepositGasToken,
} = useWriteContract();

const {
writeContractAsync: writeContractWithdrawGasToken,
data: withdrawGasTokenData,
error: withdrawGasTokenError,
isPending: isWithdrawGasTokenPending,
reset: resetWithdrawGasToken,
} = useWriteContract();

const token0Price = token0SqrtPriceX96
? sqrtPriceX96ToPrice(token0SqrtPriceX96.result, token0.decimals)
Expand Down Expand Up @@ -199,6 +214,26 @@ export const ConfirmSwap = () => {
});

const onSubmit = () => {
if (token0.isGasToken && token0.abi) {
writeContractDepositGasToken({
address: token0.address,
abi: token0.abi,
functionName: "deposit",
// TODO our type for wrapping writeContract incorrectly narrows this to undefined, when it should be bigint | undefined
// @ts-ignore
value: BigInt(token0AmountRaw ?? 0n),
});
} else approve();
};

const performSwap = useCallback(() => {
writeContractSwap({
...swapOptions,
args: swapOptions.args,
});
}, [swapOptions, writeContractSwap]);

const approve = useCallback(() => {
if (
token0.abi &&
(!allowanceData?.result ||
Expand All @@ -213,25 +248,79 @@ export const ConfirmSwap = () => {
} else {
performSwap();
}
};
}, [
token0,
allowanceData?.result,
token0AmountRaw,
ammContract.address,
performSwap,
writeContractApproval,
]);

const performSwap = useCallback(() => {
writeContractSwap({
...swapOptions,
args: swapOptions.args,
});
}, [swapOptions, writeContractSwap]);
const withdrawGasToken = useCallback(
(amount: bigint) => {
if (token1.isGasToken && token1.abi) {
writeContractWithdrawGasToken({
address: token1.address,
abi: token1.abi,
functionName: "withdraw",
args: [amount],
});
}
},
[writeContractWithdrawGasToken, token1],
);

const swapResult = useWaitForTransactionReceipt({
hash: swapData,
});
const swapAmountReceived = getAmountFromMaybeTransfer(
swapResult.data?.logs,
token1.address,
);

const depositGasTokenResult = useWaitForTransactionReceipt({
hash: depositGasTokenData,
});
const withdrawGasTokenResult = useWaitForTransactionReceipt({
hash: withdrawGasTokenData,
});

useEffect(() => {
if (!depositGasTokenResult.data) return;
approve();
}, [depositGasTokenResult.data, approve]);

// once we have the result, initiate the swap
useEffect(() => {
if (!approvalResult.data) return;
performSwap();
}, [approvalResult.data, performSwap]);

// if we swapped the gas token, unwrap it now
useEffect(() => {
if (!swapResult.data) return;
withdrawGasToken(swapAmountReceived);
}, [swapResult.data, withdrawGasToken, swapAmountReceived]);

if (
isDepositGasTokenPending ||
(depositGasTokenData && !depositGasTokenResult.data)
) {
return (
<Confirm
text={"Wrap"}
// remove "W" from wrapping token
fromAsset={{
symbol: token0.symbol.substring(1),
amount: token0Amount ?? "0",
}}
toAsset={{ symbol: token1.symbol, amount: token1Amount ?? "0" }}
transactionHash={depositGasTokenData}
/>
);
}

if (isApprovalPending || (approvalData && !approvalResult.data)) {
return (
<EnableSpending
Expand All @@ -252,6 +341,23 @@ export const ConfirmSwap = () => {
);
}

if (
isWithdrawGasTokenPending ||
(withdrawGasTokenData && !withdrawGasTokenResult.data)
)
return (
<Confirm
text={"Unwrap"}
fromAsset={{ symbol: token0.symbol, amount: token0Amount ?? "0" }}
// remove "W" from wrapping token
toAsset={{
symbol: token1.symbol.substring(1),
amount: token1Amount ?? "0",
}}
transactionHash={withdrawGasTokenData}
/>
);

// success
if (swapResult.data) {
return (
Expand All @@ -261,6 +367,8 @@ export const ConfirmSwap = () => {
setToken1Amount("0");
resetApproval();
resetSwap();
resetDepositGasToken();
resetWithdrawGasToken();
swapResult.refetch();
router.push("/");
}}
Expand All @@ -270,14 +378,25 @@ export const ConfirmSwap = () => {
}

// error
if (swapError || approvalError) {
const error = swapError || approvalError;
if (
swapError ||
approvalError ||
depositGasTokenError ||
withdrawGasTokenError
) {
const error =
swapError ||
approvalError ||
depositGasTokenError ||
withdrawGasTokenError;
return (
<Fail
text={(error as any)?.shortMessage}
onDone={() => {
resetApproval();
resetSwap();
resetDepositGasToken();
resetWithdrawGasToken();
swapResult.refetch();
router.push("/");
}}
Expand Down
6 changes: 4 additions & 2 deletions web/src/components/SwapForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -182,12 +182,14 @@ export const SwapForm = () => {

const { data: token0Balance } = useBalance({
address,
token: token0.address,
// display gas token balance for WETH
token: token0.isGasToken ? undefined : token0.address,
});

const { data: token1Balance } = useBalance({
address,
token: token1.address,
// display gas token balance for WETH
token: token1.isGasToken ? undefined : token1.address,
});

const { error: quote1Error, isLoading: quote1IsLoading } =
Expand Down
34 changes: 30 additions & 4 deletions web/src/config/tokens.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { allChains } from "./chains";
import LightweightERC20 from "./abi/LightweightERC20";
import WETH10 from "./abi/WETH10";
import { useGraphqlGlobal } from "@/hooks/useGraphql";
import { graphql, useFragment } from "@/gql";
import { useCallback, useEffect, useMemo } from "react";
import { useSwapStore } from "@/stores/useSwapStore";
import { EmptyToken } from "@/lib/utils";
import { useStakeStore } from "@/stores/useStakeStore";
import { useChainId } from "wagmi";

export type ChainIdTypes = (typeof allChains)[number]["id"];

Expand All @@ -14,9 +16,17 @@ export type Token = {
symbol: string;
name: string;
decimals: number;
abi?: typeof LightweightERC20;
icon?: string;
};
} & (
| {
abi?: typeof LightweightERC20;
isGasToken?: false | never;
}
| {
abi?: typeof WETH10;
isGasToken: true;
}
);

const TokensFragment = graphql(`
fragment TokensFragment on SeawaterPool {
Expand Down Expand Up @@ -61,6 +71,14 @@ export function useTokens(token?: "default" | string) {
setToken1: setStakeToken1,
} = useStakeStore();

const chainId = useChainId();
const gasToken = allChains.find((c) => c.id === chainId)!.nativeCurrency;
// TODO we should resolve this from the backend, or have a stronger check
const isGasToken = useCallback(
(s: string) => s.toLowerCase() === "w" + gasToken.symbol.toLowerCase(),
[gasToken],
);

const { data } = useGraphqlGlobal();
const tokensData = useFragment(TokensFragment, data?.pools);
const fusdcData_ = useFragment(FusdcFragment, data?.fusdc);
Expand All @@ -73,13 +91,21 @@ export function useTokens(token?: "default" | string) {
[t.token.symbol.toLowerCase()]: {
...t.token,
address: t.token.address as `0x${string}`,
abi: LightweightERC20,
icon: t.token.image,
...(isGasToken(t.token.symbol)
? {
abi: WETH10,
isGasToken: true as true,
}
: {
abi: LightweightERC20,
isGasToken: false as false,
}),
},
}),
{} as { [symbol: string]: Token },
);
}, [tokensData, fusdcData_]);
}, [tokensData, fusdcData_, isGasToken]);
const isTokens = fusdcData_ && tokensData;

const DefaultToken = isTokens
Expand Down
15 changes: 14 additions & 1 deletion web/src/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import LightweightERC20 from "@/config/abi/LightweightERC20";
import { Token } from "@/config/tokens";
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
import { Log } from "viem";

export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
Expand All @@ -13,3 +13,16 @@ export const EmptyToken: Token = {
address: "0x",
decimals: 0,
};

export const TransferTopic =
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" as const;

// getAmountFromMaybeTransfer to search logs for a transfer event, maybe filtering by an address, then returning the amount as found in the log's data
export const getAmountFromMaybeTransfer = (logs?: Log[], address?: string) => {
const log = logs?.find(
(l) =>
l.topics.at(0) === TransferTopic &&
(address ? l.address === address : true),
);
return BigInt(log?.data ?? 0n);
};

0 comments on commit 6a1e8e9

Please sign in to comment.