-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #24 from La-DAO/liquidity
Liquidity Prototype
- Loading branch information
Showing
9 changed files
with
358 additions
and
231 deletions.
There are no files selected for viewing
2 changes: 1 addition & 1 deletion
2
packages/nextjs/app/components/abis/vault.ts → ...s/nextjs/app/components/abis/liquidity.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
270 changes: 270 additions & 0 deletions
270
packages/nextjs/app/liquidity/components/LiquidityWidget/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<string>("0"); | ||
const [usdcAllowanceState, usdcSetAllowanceState] = useState<string>("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 ( | ||
<div className="w-full bg-white p-6 rounded-lg shadow-md mt-6"> | ||
<div className="mb-4"> | ||
<h2 className="text-xl font-semibold text-gray-800 mb-2">{t("XoktleLiquidityTitle")}</h2> | ||
<hr className="border-t-2 border-gray-300 rounded-t-full" /> | ||
</div> | ||
|
||
<div className="mb-6 flex justify-center"> | ||
<div className="flex"> | ||
<button | ||
onClick={() => handleActionChange("Deposit")} | ||
className={`px-6 py-2 rounded-l-full ${ | ||
action === "Deposit" ? "bg-base-300 text-xl text-white" : "bg-gray-200 text-gray-800" | ||
}`} | ||
> | ||
{t("XoktleDepositSwitcher")} | ||
</button> | ||
<button | ||
onClick={() => handleActionChange("Withdraw")} | ||
className={`px-6 py-2 rounded-r-full ${ | ||
action === "Withdraw" ? "bg-base-300 text-xl text-white" : "bg-gray-200 text-gray-800" | ||
}`} | ||
> | ||
{t("XoktleWithdrawSwitcher")} | ||
</button> | ||
</div> | ||
</div> | ||
|
||
{/* Conditionally render input fields based on the selected action */} | ||
{action === "Deposit" ? ( | ||
<div className="space-y-4 mb-6"> | ||
<div> | ||
<label className="block text-gray-700">{t("XoktleUSDCIndicate")}</label> | ||
<input | ||
type="number" | ||
value={tokenA} | ||
onChange={e => setTokenA(e.target.value)} | ||
className="w-full p-2 border rounded-lg dark:bg-neutral dark:text-neutral-content" | ||
placeholder={t("XoktleUSDCAmount")} | ||
/> | ||
</div> | ||
|
||
<div> | ||
<label className="block text-gray-700">{t("XoktleXOCIndicate")}</label> | ||
<input | ||
type="number" | ||
value={tokenB} | ||
onChange={e => setTokenB(e.target.value)} | ||
className="w-full p-2 border rounded-lg dark:bg-neutral dark:text-neutral-content" | ||
placeholder={t("XoktleXOCAmount")} | ||
/> | ||
</div> | ||
</div> | ||
) : ( | ||
<div className="space-y-4 mb-6"> | ||
<div> | ||
<label className="block text-gray-700">{t("XoktleShareIndicate")}</label> | ||
<input | ||
type="number" | ||
value={shareAmount} // This will represent the share amount to withdraw | ||
onChange={e => setShareAmount(e.target.value)} | ||
className="w-full p-2 border rounded-lg dark:bg-neutral dark:text-neutral-content" | ||
placeholder={t("XoktleShareAmount")} | ||
/> | ||
</div> | ||
</div> | ||
)} | ||
|
||
<button | ||
className="w-full py-3 bg-base-300 text-2xl text-white font-semibold rounded-lg" | ||
onClick={() => { | ||
if (requiresApproval) { | ||
handleApproval(); // Call handleApproval when approval is needed | ||
} else if (action === "Deposit") { | ||
handleDeposit(); // Call handleDeposit if deposit is selected | ||
} else if (action === "Withdraw") { | ||
handleWithdrawal(); // Call handleWithdrawal if withdraw is selected | ||
} | ||
}} | ||
> | ||
{requiresApproval ? t("Approve") : action} | ||
</button> | ||
</div> | ||
); | ||
}; | ||
|
||
export default LiquidityWidget; |
68 changes: 68 additions & 0 deletions
68
packages/nextjs/app/liquidity/components/OverviewWidget/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<number | null>(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 ( | ||
<div className="w-full bg-white p-6 rounded-lg shadow-md mt-6"> | ||
{/* Title */} | ||
<div className="mb-4"> | ||
<h2 className="text-xl font-semibold text-gray-800 mb-2">{t("XoktleOverView")}</h2> | ||
<hr className="border-t-2 border-gray-300 rounded-t-full" /> | ||
</div> | ||
|
||
{/* Loading and Error States */} | ||
{balanceLoading && <p className="text-gray-500">Loading balance...</p>} | ||
{balanceError && <p className="text-red-500">Error loading balance.</p>} | ||
|
||
{/* Display Balance */} | ||
{!balanceLoading && !balanceError && balance !== null && ( | ||
<div className="space-y-4"> | ||
<div className="flex justify-between"> | ||
<span className="text-gray-700">{t("XoktleAccountShares")}:</span> | ||
<span className="text-gray-900 font-semibold">{formattedBalance} shares</span> | ||
</div> | ||
</div> | ||
)} | ||
</div> | ||
); | ||
}; | ||
|
||
export default OverviewWidget; |
Oops, something went wrong.