Skip to content

Commit

Permalink
Merge pull request #24 from La-DAO/liquidity
Browse files Browse the repository at this point in the history
Liquidity Prototype
  • Loading branch information
iafhurtado authored Sep 11, 2024
2 parents 528b1a4 + 82eae63 commit 3b1e893
Show file tree
Hide file tree
Showing 9 changed files with 358 additions and 231 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export const vaultABI = [
export const liquidityABI = [
{
inputs: [
{ internalType: "address", name: "_pool", type: "address" },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<number | null>(null);
const { data: totalReserves } = useReadContract({
address: "0xD6DaB267b7C23EdB2ed5605d9f3f37420e88e291",
abi: vaultABI,
abi: liquidityABI,
functionName: "totalSupply",
});

Expand All @@ -33,7 +33,7 @@ const VaultInfo: React.FC = () => {
error: lpTokenError,
} = useReadContract({
address: "0xD6DaB267b7C23EdB2ed5605d9f3f37420e88e291",
abi: vaultABI,
abi: liquidityABI,
functionName: "getTotalAmounts",
});

Expand Down Expand Up @@ -101,4 +101,4 @@ const VaultInfo: React.FC = () => {
);
};

export default VaultInfo;
export default LiquidityInfo;
270 changes: 270 additions & 0 deletions packages/nextjs/app/liquidity/components/LiquidityWidget/index.tsx
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 packages/nextjs/app/liquidity/components/OverviewWidget/index.tsx
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;
Loading

0 comments on commit 3b1e893

Please sign in to comment.