diff --git a/packages/nextjs/app/components/abis/vault.ts b/packages/nextjs/app/components/abis/liquidity.ts similarity index 99% rename from packages/nextjs/app/components/abis/vault.ts rename to packages/nextjs/app/components/abis/liquidity.ts index bf9dd86..17fa024 100644 --- a/packages/nextjs/app/components/abis/vault.ts +++ b/packages/nextjs/app/components/abis/liquidity.ts @@ -1,4 +1,4 @@ -export const vaultABI = [ +export const liquidityABI = [ { inputs: [ { internalType: "address", name: "_pool", type: "address" }, diff --git a/packages/nextjs/app/vault/components/VaultInfo/index.tsx b/packages/nextjs/app/liquidity/components/LiquidityInfo/index.tsx similarity index 95% rename from packages/nextjs/app/vault/components/VaultInfo/index.tsx rename to packages/nextjs/app/liquidity/components/LiquidityInfo/index.tsx index ff54242..145e0c9 100644 --- a/packages/nextjs/app/vault/components/VaultInfo/index.tsx +++ b/packages/nextjs/app/liquidity/components/LiquidityInfo/index.tsx @@ -3,15 +3,15 @@ import React, { useEffect, useState } from "react"; import Image from "next/image"; import { useReadContract } from "wagmi"; -import { vaultABI } from "~~/app/components/abis/vault"; +import { liquidityABI } from "~~/app/components/abis/liquidity"; import { useTranslation } from "~~/app/context/LanguageContext"; -const VaultInfo: React.FC = () => { +const LiquidityInfo: React.FC = () => { const { t } = useTranslation(); const [, setTotalReserves] = useState(null); const { data: totalReserves } = useReadContract({ address: "0xD6DaB267b7C23EdB2ed5605d9f3f37420e88e291", - abi: vaultABI, + abi: liquidityABI, functionName: "totalSupply", }); @@ -33,7 +33,7 @@ const VaultInfo: React.FC = () => { error: lpTokenError, } = useReadContract({ address: "0xD6DaB267b7C23EdB2ed5605d9f3f37420e88e291", - abi: vaultABI, + abi: liquidityABI, functionName: "getTotalAmounts", }); @@ -101,4 +101,4 @@ const VaultInfo: React.FC = () => { ); }; -export default VaultInfo; +export default LiquidityInfo; diff --git a/packages/nextjs/app/liquidity/components/LiquidityWidget/index.tsx b/packages/nextjs/app/liquidity/components/LiquidityWidget/index.tsx new file mode 100644 index 0000000..bd6fa7a --- /dev/null +++ b/packages/nextjs/app/liquidity/components/LiquidityWidget/index.tsx @@ -0,0 +1,270 @@ +"use client"; + +import React, { useEffect, useState } from "react"; +import { spenderAddress, usdcContract, xocContract } from "@/app/constants/contracts"; +import { parseEther, parseUnits } from "viem"; +import { useAccount, useReadContract, useWriteContract } from "wagmi"; +import { ERC20ABI } from "~~/app/components/abis/erc20"; +import { liquidityABI } from "~~/app/components/abis/liquidity"; +import { useTranslation } from "~~/app/context/LanguageContext"; + +// Use parseUnits for USDC + +const LiquidityWidget: React.FC = () => { + const { address: accountAddress } = useAccount(); // Get the address, not the entire account object + const { t } = useTranslation(); + const [action, setAction] = useState<"Deposit" | "Withdraw">("Deposit"); + const [tokenA, setTokenA] = useState(""); // USDC amount + const [tokenB, setTokenB] = useState(""); // XOC amount + const [xocAllowanceState, xocSetAllowanceState] = useState("0"); + const [usdcAllowanceState, usdcSetAllowanceState] = useState("0"); + // State for share withdrawal input + const [shareAmount, setShareAmount] = useState(""); // Share amount + + // State to track if approval is needed + const [requiresApproval, setRequiresApproval] = useState(false); + + const { writeContract: deposit } = useWriteContract(); + + const { writeContract: approveERC20 } = useWriteContract(); + + const { writeContract: withdraw } = useWriteContract(); + + // Hook to read the XOC contract allowance + const { + data: xocAllowance, + isError, + isLoading, + } = useReadContract({ + address: xocContract, + abi: ERC20ABI, + functionName: "allowance", + args: [accountAddress, spenderAddress], // Only pass the address + }); + + useEffect(() => { + if (isError) { + console.error("Error fetching allowance"); + xocSetAllowanceState("0"); + } else if (!isLoading && xocAllowance) { + const allowanceInEther = (Number(xocAllowance) / 1e18).toFixed(7); + xocSetAllowanceState(allowanceInEther); + } + }, [xocAllowance, isError, isLoading]); + + // Hook to read the USDC contract allowance + const { + data: usdcAllowance, + isError: usdcIsError, + isLoading: usdcIsLoading, + } = useReadContract({ + address: usdcContract, + abi: ERC20ABI, + functionName: "allowance", + args: [accountAddress, spenderAddress], + }); + + useEffect(() => { + if (usdcIsError) { + console.error("Error fetching allowance"); + usdcSetAllowanceState("0"); + } else if (!usdcIsLoading && usdcAllowance) { + const allowanceInEther = (Number(usdcAllowance) / 1e6).toFixed(7); // USDC has 6 decimals + usdcSetAllowanceState(allowanceInEther); + } + }, [usdcAllowance, usdcIsError, usdcIsLoading]); + + // Trigger approval check whenever tokenA or tokenB changes + useEffect(() => { + // Function to check if approval is required + const checkIfApprovalNeeded = () => { + const usdcAmount = parseFloat(tokenA) || 0; + const xocAmount = parseFloat(tokenB) || 0; + + // Compare the input values against the allowance states + const needsApproval = xocAmount > parseFloat(xocAllowanceState) || usdcAmount > parseFloat(usdcAllowanceState); + setRequiresApproval(needsApproval); + }; + + checkIfApprovalNeeded(); + }, [tokenA, tokenB, xocAllowanceState, usdcAllowanceState]); + + const handleActionChange = (newAction: "Deposit" | "Withdraw") => { + setAction(newAction); + }; + + // Function to handle the deposit + const handleDeposit = async () => { + if (!accountAddress) { + console.error("Account address not found"); + return; + } + + const usdcAmount = parseFloat(tokenA) || 0; + const xocAmount = parseFloat(tokenB) || 0; + const xocAmountInWei = parseEther(xocAmount.toString()); // XOC uses 18 decimals + const usdcAmountInWei = parseUnits(usdcAmount.toString(), 6); // USDC uses 6 decimals + + try { + const tx = await deposit({ + abi: liquidityABI, + address: "0xD6DaB267b7C23EdB2ed5605d9f3f37420e88e291", // Liquidity contract address + functionName: "deposit", + args: [usdcAmountInWei, xocAmountInWei, accountAddress], + }); + + console.log("Transaction submitted:", tx); + // Optionally wait for the transaction to be mined + // const receipt = await tx.wait(); + // console.log("Transaction confirmed:", receipt); + } catch (err) { + console.error("Error executing contract function:", err); + } + }; + + // Function to handle approval + const handleApproval = async () => { + const usdcAmount = parseFloat(tokenA) || 0; + const xocAmount = parseFloat(tokenB) || 0; + + try { + // Approve XOC + if (usdcAmount > parseFloat(usdcAllowanceState)) { + await approveERC20({ + abi: ERC20ABI, + address: usdcContract, + functionName: "approve", + args: [spenderAddress, usdcAmount * 1e6], // Multiply by 1e6 to convert to USDC decimals + }); + } + + // Approve USDC + if (xocAmount > parseFloat(xocAllowanceState)) { + await approveERC20({ + abi: ERC20ABI, + address: xocContract, + functionName: "approve", + args: [spenderAddress, xocAmount * 1e18], // Multiply by 1e18 to convert to XOC decimals + }); + } + } catch (err) { + console.error("Error approving tokens:", err); + } + }; + + console.log("Xoc Allowance", xocAllowanceState); + console.log("USDC Allowance", usdcAllowanceState); + + // Function to handle the withdrawal + const handleWithdrawal = async () => { + if (!accountAddress) { + console.error("Account address not found"); + return; + } + + const shareAmountInWei = parseEther(shareAmount.toString()); // XOC uses 18 decimals + + try { + const tx = await withdraw({ + abi: liquidityABI, + address: "0xD6DaB267b7C23EdB2ed5605d9f3f37420e88e291", // Liquidity contract address + functionName: "withdraw", + args: [shareAmountInWei, accountAddress], + }); + + console.log("Transaction submitted:", tx); + // Optionally wait for the transaction to be mined + // const receipt = await tx.wait(); + // console.log("Transaction confirmed:", receipt); + } catch (err) { + console.error("Error executing contract function:", err); + } + }; + + return ( +
+
+

{t("XoktleLiquidityTitle")}

+
+
+ +
+
+ + +
+
+ + {/* Conditionally render input fields based on the selected action */} + {action === "Deposit" ? ( +
+
+ + setTokenA(e.target.value)} + className="w-full p-2 border rounded-lg dark:bg-neutral dark:text-neutral-content" + placeholder={t("XoktleUSDCAmount")} + /> +
+ +
+ + setTokenB(e.target.value)} + className="w-full p-2 border rounded-lg dark:bg-neutral dark:text-neutral-content" + placeholder={t("XoktleXOCAmount")} + /> +
+
+ ) : ( +
+
+ + setShareAmount(e.target.value)} + className="w-full p-2 border rounded-lg dark:bg-neutral dark:text-neutral-content" + placeholder={t("XoktleShareAmount")} + /> +
+
+ )} + + +
+ ); +}; + +export default LiquidityWidget; diff --git a/packages/nextjs/app/liquidity/components/OverviewWidget/index.tsx b/packages/nextjs/app/liquidity/components/OverviewWidget/index.tsx new file mode 100644 index 0000000..4569aea --- /dev/null +++ b/packages/nextjs/app/liquidity/components/OverviewWidget/index.tsx @@ -0,0 +1,68 @@ +"use client"; + +import React, { useEffect, useState } from "react"; +import { useAccount, useReadContract } from "wagmi"; +import { liquidityABI } from "~~/app/components/abis/liquidity"; +import { useTranslation } from "~~/app/context/LanguageContext"; + +const OverviewWidget: React.FC = () => { + const { t } = useTranslation(); + const [balance, setBalance] = useState(null); + const { address: accountAddress } = useAccount(); // Get accountAddress using useAccount hook + + // Fetch the balanceOf using the accountAddress + const { + data: balanceData, + isLoading: balanceLoading, + error: balanceError, + } = useReadContract({ + address: "0xD6DaB267b7C23EdB2ed5605d9f3f37420e88e291", // Liquidity contract address + abi: liquidityABI, + functionName: "balanceOf", + args: [accountAddress], // Pass accountAddress as argument to balanceOf + }); + + useEffect(() => { + if (balanceData) { + // Convert the BigInt to a number and format it + const balanceValue = Number(balanceData) / 10 ** 18; // Assuming the token has 18 decimals + setBalance(balanceValue); + } + }, [balanceData]); + + // Function to format the balance as a standard number with commas + const formatNumber = (amount: number) => { + return new Intl.NumberFormat("en-US", { + minimumFractionDigits: 6, + maximumFractionDigits: 6, + }).format(amount); + }; + + const formattedBalance = balance !== null ? formatNumber(balance) : null; + + return ( +
+ {/* Title */} +
+

{t("XoktleOverView")}

+
+
+ + {/* Loading and Error States */} + {balanceLoading &&

Loading balance...

} + {balanceError &&

Error loading balance.

} + + {/* Display Balance */} + {!balanceLoading && !balanceError && balance !== null && ( +
+
+ {t("XoktleAccountShares")}: + {formattedBalance} shares +
+
+ )} +
+ ); +}; + +export default OverviewWidget; diff --git a/packages/nextjs/app/vault/page.tsx b/packages/nextjs/app/liquidity/page.tsx similarity index 74% rename from packages/nextjs/app/vault/page.tsx rename to packages/nextjs/app/liquidity/page.tsx index d91bbae..2af6e55 100644 --- a/packages/nextjs/app/vault/page.tsx +++ b/packages/nextjs/app/liquidity/page.tsx @@ -1,13 +1,13 @@ +import LiquidityInfo from "./components/LiquidityInfo"; import LiquidityWidget from "./components/LiquidityWidget"; import OverviewWidget from "./components/OverviewWidget"; -import VaultInfo from "./components/VaultInfo"; import { NextPage } from "next"; -const VaultPage: NextPage = () => { +const LiquidityPage: NextPage = () => { return (
{/* VaultInfo at 4/5 width */} - + {/* Container for Widgets */}
@@ -18,4 +18,4 @@ const VaultPage: NextPage = () => { ); }; -export default VaultPage; +export default LiquidityPage; diff --git a/packages/nextjs/app/locales/en/common.json b/packages/nextjs/app/locales/en/common.json index ab1cc21..696c85c 100644 --- a/packages/nextjs/app/locales/en/common.json +++ b/packages/nextjs/app/locales/en/common.json @@ -101,5 +101,9 @@ "XoktleXOCIndicate": "Indicate the amount of $XOC to deposit", "XoktleXOCAmount": "Enter amount of $XOC", "XoktleUSDCIndicate": "Indicate the amount of $USDC to deposit", - "XoktleUSDCAmount": "Enter amount of $USDC" + "XoktleUSDCAmount": "Enter amount of $USDC", + "XoktleShareIndicate": "Indicate the amount of shares to withdraw", + "XoktleShareAmount": "Enter amount of shares", + "XoktleOverView": "Overview", + "XoktleAccountShares": "Your Liquidity Shares" } diff --git a/packages/nextjs/app/locales/mx/common.json b/packages/nextjs/app/locales/mx/common.json index 59bbd8b..e22117c 100644 --- a/packages/nextjs/app/locales/mx/common.json +++ b/packages/nextjs/app/locales/mx/common.json @@ -113,7 +113,11 @@ "XoktleXOCIndicate": "Indica la cantidad de $XOC a depositar", "XoktleXOCAmount": "Cantidad de $XOC", "XoktleUSDCIndicate": "Indica la cantidad de $USDC a depositar", - "XoktleUSDCAmount": "Cantidad de $USDC" + "XoktleUSDCAmount": "Cantidad de $USDC", + "XoktleShareIndicate": "Indica la cantidad de acciones a retirar", + "XoktleShareAmount": "Cantidad de acciones", + "XoktleOverView": "Vista General", + "XoktleAccountShares": "Acciones en tu cuenta" diff --git a/packages/nextjs/app/vault/components/LiquidityWidget/index.tsx b/packages/nextjs/app/vault/components/LiquidityWidget/index.tsx deleted file mode 100644 index 8f61329..0000000 --- a/packages/nextjs/app/vault/components/LiquidityWidget/index.tsx +++ /dev/null @@ -1,140 +0,0 @@ -"use client"; - -import React, { useEffect, useState } from "react"; -import { spenderAddress, usdcContract, xocContract } from "@/app/constants/contracts"; -import { useAccount, useReadContract } from "wagmi"; -import { ERC20ABI } from "~~/app/components/abis/erc20"; -import { useTranslation } from "~~/app/context/LanguageContext"; - -const LiquidityWidget: React.FC = () => { - const { address: accountAddress } = useAccount(); // Get the address, not the entire account object - const { t } = useTranslation(); - const [action, setAction] = useState<"Deposit" | "Withdraw">("Deposit"); - const [tokenA, setTokenA] = useState(""); - const [tokenB, setTokenB] = useState(""); - const [xocAllowanceState, xocSetAllowanceState] = useState("0"); - const [usdcAllowanceState, usdcSetAllowanceState] = useState("0"); - - const { - data: xocAllowance, - isError, - isLoading, - } = useReadContract({ - address: xocContract, - abi: ERC20ABI, - functionName: "allowance", - args: [accountAddress, spenderAddress], // Only pass the address - }); - - useEffect(() => { - if (isError) { - console.error("Error fetching allowance"); - xocSetAllowanceState("0"); - } else if (!isLoading && xocAllowance) { - const allowanceInEther = (Number(xocAllowance) / 1e18).toFixed(7); - xocSetAllowanceState(allowanceInEther); - } - }, [xocAllowance, isError, isLoading]); - - console.log("XOCAllowance", xocAllowanceState); - - const { - data: usdcAllowance, - isError: usdcIsError, - isLoading: usdcIsLoading, - } = useReadContract({ - address: usdcContract, - abi: ERC20ABI, - functionName: "allowance", - args: [accountAddress, spenderAddress], - }); - - useEffect(() => { - if (usdcIsError) { - console.error("Error fetching allowance"); - usdcSetAllowanceState("0"); - } else if (!usdcIsLoading && usdcAllowance) { - const allowanceInEther = (Number(usdcAllowance) / 1e6).toFixed(7); - usdcSetAllowanceState(allowanceInEther); - } - }, [usdcAllowance, usdcIsError, usdcIsLoading]); - - console.log("USDCAllowance", usdcAllowanceState); - - const handleActionChange = (newAction: "Deposit" | "Withdraw") => { - setAction(newAction); - }; - - const calculateOutput = () => { - return parseFloat(tokenA) + parseFloat(tokenB); - }; - - return ( -
-
-

{t("XoktleLiquidityTitle")}

-
-
- -
-
- - -
-
- -
-
- - setTokenA(e.target.value)} - className="w-full p-2 border rounded-lg dark:bg-neutral dark:text-neutral-content" - placeholder={t("XoktleXOCAmount")} - /> -
- -
- - setTokenB(e.target.value)} - className="w-full p-2 border rounded-lg dark:bg-neutral dark:text-neutral-content" - placeholder={t("XoktleUSDCAmount")} - /> -
- -
- - -
-
- - -
- ); -}; - -export default LiquidityWidget; diff --git a/packages/nextjs/app/vault/components/OverviewWidget/index.tsx b/packages/nextjs/app/vault/components/OverviewWidget/index.tsx deleted file mode 100644 index 25884e0..0000000 --- a/packages/nextjs/app/vault/components/OverviewWidget/index.tsx +++ /dev/null @@ -1,79 +0,0 @@ -"use client"; - -import React, { useEffect, useState } from "react"; -import { useReadContract } from "wagmi"; -import { vaultABI } from "~~/app/components/abis/vault"; - -const OverviewWidget: React.FC = () => { - const [tokenA, setTokenA] = useState(null); - const [tokenB, setTokenB] = useState(null); - const { - data: lpTokenData, - isLoading: lpTokenLoading, - error: lpTokenError, - } = useReadContract({ - address: "0xD6DaB267b7C23EdB2ed5605d9f3f37420e88e291", - abi: vaultABI, - functionName: "getTotalAmounts", - }); - - useEffect(() => { - if (lpTokenData) { - // Extract and format tokenA and tokenB from lpTokenData - const [tokenAValue, tokenBValue] = (lpTokenData as bigint[]).map((value: bigint) => Number(value) / 10 ** 18); - setTokenA(tokenAValue); - setTokenB(tokenBValue); - } - }, [lpTokenData]); - - // Format as currency: tokenA as USD and tokenB as MXN - const formatCurrency = (amount: number, currency: string) => { - return new Intl.NumberFormat("en-US", { - style: "currency", - currency, - minimumFractionDigits: 2, - maximumFractionDigits: 2, - }).format(amount); - }; - - const formattedTokenA = tokenA !== null ? formatCurrency(tokenA, "USD") : null; - const formattedTokenB = tokenB !== null ? formatCurrency(tokenB, "MXN") : null; - - return ( -
- {/* Title */} -
-

Overview

-
-
- - {/* Loading and Error States */} - {lpTokenLoading &&

Loading data...

} - {lpTokenError &&

Error loading data.

} - - {/* Token Information */} - {!lpTokenLoading && !lpTokenError && tokenA !== null && tokenB !== null && ( -
-
- Total USDC Deposits: - {formattedTokenA} -
- -
- Total XOC Deposits: - {formattedTokenB} -
- -
- Total Value: - - {formattedTokenA && formattedTokenB ? `${formattedTokenA} + ${formattedTokenB}` : ""} - -
-
- )} -
- ); -}; - -export default OverviewWidget;