From 896cfb595ddb0cea4d112464557417b421da4311 Mon Sep 17 00:00:00 2001 From: Kraznik <103571066+Kraznik@users.noreply.github.com> Date: Thu, 29 May 2025 15:20:47 -0500 Subject: [PATCH 01/22] init --- src/components/deposit-view.tsx | 321 +++++++++++++++++++++++++++----- 1 file changed, 270 insertions(+), 51 deletions(-) diff --git a/src/components/deposit-view.tsx b/src/components/deposit-view.tsx index 7760186..27741af 100644 --- a/src/components/deposit-view.tsx +++ b/src/components/deposit-view.tsx @@ -140,6 +140,33 @@ const VAULT_ABI = [ ], outputs: [{ name: "shares", type: "uint256" }], }, + { + name: "depositAndBridge", + type: "function", + stateMutability: "payable", + inputs: [ + { name: "depositAsset", type: "address" }, + { name: "depositAmount", type: "uint256" }, + { name: "minimumMint", type: "uint256" }, + { name: "to", type: "address" }, + { name: "bridgeWildCard", type: "bytes" }, + { name: "feeToken", type: "address" }, + { name: "maxFee", type: "uint256" } + ], + outputs: [{ name: "shares", type: "uint256" }], + }, + { + name: "previewFee", + type: "function", + stateMutability: "view", + inputs: [ + { name: "shareAmount", type: "uint96" }, + { name: "to", type: "address" }, + { name: "bridgeWildCard", type: "bytes" }, + { name: "feeToken", type: "address" } + ], + outputs: [{ name: "fee", type: "uint256" }], + }, { name: "totalAssets", type: "function", @@ -220,6 +247,10 @@ const DepositView: React.FC = ({ | "depositing" | "idle" >("idle"); + const [isMultiChain, setIsMultiChain] = useState(false); + const [bridgeFee, setBridgeFee] = useState("0"); + const [isLoadingFee, setIsLoadingFee] = useState(false); + const [targetChain, setTargetChain] = useState("arbitrum"); // Default target chain // Get strategy config based on asset type const strategyConfigs = { @@ -475,6 +506,81 @@ const DepositView: React.FC = ({ console.log("Status changed:", status); }, [status]); + // Add preview fee function + const previewBridgeFee = async (amount: bigint) => { + if (!address || !amount || !isMultiChain) return; + + setIsLoadingFee(true); + try { + const client = createPublicClient({ + transport: http(strategyConfig.rpc || "https://base.llamarpc.com"), + chain: { + id: 8453, + name: "Base", + network: "base", + nativeCurrency: { + decimals: 18, + name: "Ethereum", + symbol: "ETH", + }, + rpcUrls: { + default: { http: [strategyConfig.rpc || "https://base.llamarpc.com"] }, + public: { http: [strategyConfig.rpc || "https://base.llamarpc.com"] }, + }, + }, + }); + + // Get bridge wildcard based on target chain + const bridgeWildCard = getBridgeWildCard(targetChain); + + // Convert amount to uint96 for previewFee + const shareAmount = amount as unknown as bigint; + + // Call previewFee function with exact parameters from your example + const fee = await client.readContract({ + address: vaultContractAddress as Address, + abi: VAULT_ABI, + functionName: "previewFee", + args: [ + shareAmount, // shareAmount (uint96) + address as Address, // to address + bridgeWildCard, // bridgeWildCard bytes + "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" as Address // feeToken (ETH address) + ], + }); + + console.log("Bridge fee calculation:", { + shareAmount: shareAmount.toString(), + to: address, + bridgeWildCard, + feeToken: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", + calculatedFee: fee.toString() + }); + + setBridgeFee(formatUnits(fee as bigint, 18)); + } catch (error) { + console.error("Error previewing bridge fee:", error); + setBridgeFee("0"); + } finally { + setIsLoadingFee(false); + } + }; + + // Helper function to get bridge wildcard + const getBridgeWildCard = (chain: string): `0x${string}` => { + switch (chain) { + case "arbitrum": + return "0x000000000000000000000000000000000000000000000000000000000000759e"; + case "optimism": + return "0x000000000000000000000000000000000000000000000000000000000000759f"; + case "ethereum": + return "0x000000000000000000000000000000000000000000000000000000000000759d"; + default: + return "0x000000000000000000000000000000000000000000000000000000000000759e"; + } + }; + + // Modify handleDeposit function const handleDeposit = async () => { console.log("Deposit clicked", { address, @@ -485,6 +591,8 @@ const DepositView: React.FC = ({ duration, currentAllowance: allowance?.toString(), isApproved, + isMultiChain, + targetChain, }); if (!address || !amount || !approve || !deposit) { @@ -517,14 +625,6 @@ const DepositView: React.FC = ({ const currentAllowance = allowance ? BigInt(allowance.toString()) : BigInt(0); const needsApproval = currentAllowance < amountInWei; - console.log("Approval check:", { - currentAllowance: currentAllowance.toString(), - amountInWei: amountInWei.toString(), - needsApproval, - isApproved, - isApproving - }); - if (allowance === undefined) { setErrorMessage('Unable to fetch allowance. Please check your network and try again.'); setIsWaitingForSignature(false); @@ -534,16 +634,6 @@ const DepositView: React.FC = ({ // Step 1: Approve USDS for boring vault if needed if (needsApproval && !isApproved && !isApproving) { console.log('Calling approve function...'); - console.log("Sending approve transaction for boring vault...", { - tokenContract: tokenContractAddress, - spender: boringVaultAddress, - amount: amountInWei.toString(), - functionName: "approve", - abi: ERC20_ABI, - chainId: 8453, - account: address - }); - setIsApproving(true); try { const approveTx = await approve({ @@ -555,23 +645,11 @@ const DepositView: React.FC = ({ account: address as Address, }); - console.log("Approval transaction sent:", { - hash: approveTx, - tokenContract: tokenContractAddress, - spender: boringVaultAddress, - amount: amountInWei.toString() - }); - if (typeof approveTx === "string" && approveTx.startsWith("0x")) { setApprovalHash(approveTx as `0x${string}`); } } catch (error: any) { - console.error("Approval transaction failed:", { - error: error.message, - tokenContract: tokenContractAddress, - spender: boringVaultAddress, - amount: amountInWei.toString() - }); + console.error("Approval transaction failed:", error); setIsApproving(false); setErrorMessage(error.message || "Approval failed"); } @@ -587,34 +665,74 @@ const DepositView: React.FC = ({ // Only proceed with deposit if we have sufficient allowance if (!needsApproval || isApproved) { - // Step 2: Proceed with deposit to LayerZeroTeller - console.log("Proceeding with deposit to LayerZeroTeller"); setIsDepositing(true); // Calculate minimum mint amount based on slippage const slippageAmount = amountInWei * BigInt(Math.floor(parseFloat(slippage) * 10000)) / BigInt(10000); const minimumMint = amountInWei - slippageAmount; - // Proceed with deposit using the LayerZeroTeller contract - console.log("Sending deposit transaction to LayerZeroTeller:", { - contract: vaultContractAddress, - token: tokenContractAddress, - amount: amountInWei.toString(), - minimumMint: minimumMint.toString(), - }); + if (isMultiChain) { + // Preview bridge fee before proceeding + await previewBridgeFee(amountInWei); + + // Get bridge wildcard + const bridgeWildCard = getBridgeWildCard(targetChain); + + // Convert bridge fee to wei + const bridgeFeeWei = parseEther(bridgeFee); + + // Proceed with multi-chain deposit + console.log("Sending multi-chain deposit transaction:", { + contract: vaultContractAddress, + token: tokenContractAddress, + amount: amountInWei.toString(), + minimumMint: "0", // Set minimumMint to 0 for multi-chain + bridgeWildCard, + bridgeFee: bridgeFeeWei.toString(), + }); - const tx = await deposit({ - address: vaultContractAddress as Address, - abi: VAULT_ABI, - functionName: "deposit", - args: [tokenContractAddress as Address, amountInWei, minimumMint], - chainId: 8453, - account: address as Address, - }); + const tx = await deposit({ + address: vaultContractAddress as Address, + abi: VAULT_ABI, + functionName: "depositAndBridge", + args: [ + tokenContractAddress as Address, + amountInWei, + BigInt(0), // Set minimumMint to 0 for multi-chain + address as Address, + bridgeWildCard, + "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" as Address, // ETH address + bridgeFeeWei // Use the calculated bridge fee + ], + chainId: 8453, + account: address as Address, + value: bridgeFeeWei, // Include the calculated bridge fee in ETH + }); + + if (tx && typeof tx === "string" && tx.startsWith("0x")) { + setTransactionHash(tx as `0x${string}`); + } + } else { + // Regular single-chain deposit + console.log("Sending deposit transaction:", { + contract: vaultContractAddress, + token: tokenContractAddress, + amount: amountInWei.toString(), + minimumMint: minimumMint.toString(), + }); + + const tx = await deposit({ + address: vaultContractAddress as Address, + abi: VAULT_ABI, + functionName: "deposit", + args: [tokenContractAddress as Address, amountInWei, minimumMint], + chainId: 8453, + account: address as Address, + }); - if (tx && typeof tx === "string" && tx.startsWith("0x")) { - setTransactionHash(tx as `0x${string}`); - setIsDepositing(true); + if (tx && typeof tx === "string" && tx.startsWith("0x")) { + setTransactionHash(tx as `0x${string}`); + } } } else { console.log("Insufficient allowance, approval needed first"); @@ -630,6 +748,14 @@ const DepositView: React.FC = ({ } }; + // Add effect to preview fee when amount changes + useEffect(() => { + if (isMultiChain && amount) { + const amountInWei = parseUnits(amount, depositTokenDecimals); + previewBridgeFee(amountInWei); + } + }, [amount, isMultiChain, targetChain]); + const fetchBalance = async () => { if (!address || selectedAsset !== "USD") return; @@ -865,6 +991,41 @@ const DepositView: React.FC = ({ + {/* Multi-chain Toggle */} +
+ Multi-chain Deposit + +
+ + {/* Target Chain Selection - Only shown when multi-chain is enabled */} + {isMultiChain && ( +
+ + +
+ )} +
= ({ )}
+ {/* Bridge Fee Display */} + {isMultiChain && ( +
+ + Bridge Fee:{" "} + {isLoadingFee ? ( + + + + + + Loading... + + ) : ( + {bridgeFee} ETH + )} + +
+
+ + + + + You need to have enough ETH in your wallet to cover the bridge fee. The fee will be paid in ETH along with your deposit. + +
+
+
+ )}
From 34c144484786a788216bf24ccd3181784b8f6e53 Mon Sep 17 00:00:00 2001 From: Kraznik <103571066+Kraznik@users.noreply.github.com> Date: Thu, 29 May 2025 15:26:44 -0500 Subject: [PATCH 02/22] feat: coming soon hover on invalid duration strategy --- src/components/ui/card.tsx | 223 +++++++++++++++++++++++-------------- src/config/env.ts | 54 +-------- src/pages/earn-subpage.tsx | 1 + 3 files changed, 139 insertions(+), 139 deletions(-) diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx index 3a96d3c..9c3425b 100644 --- a/src/components/ui/card.tsx +++ b/src/components/ui/card.tsx @@ -6,6 +6,7 @@ import { TooltipTrigger, TooltipProvider, } from "@/components/ui/tooltip"; +import { InfoIcon } from "lucide-react"; type DurationType = "30_DAYS" | "60_DAYS" | "180_DAYS" | "PERPETUAL_DURATION"; @@ -26,26 +27,9 @@ interface CustomCardProps { disableHover?: boolean; onReset?: () => void; isComingSoon?: boolean; + availableDurations?: DurationType[]; } -const InfoIcon = () => ( - - - -); - const formatDuration = (duration: string) => { if (duration === "PERPETUAL_DURATION") return "Perpetual"; const [number, period] = duration.split("_"); @@ -66,6 +50,7 @@ const CustomCard: React.FC = ({ disableHover, onReset, isComingSoon, + availableDurations, ...props }) => { const handleDurationClick = (duration: DurationType) => { @@ -78,6 +63,13 @@ const CustomCard: React.FC = ({ e.stopPropagation(); // Stop event from bubbling up to parent card }; + // Helper function to check if a duration is available + const isDurationAvailable = (duration: DurationType) => { + // If availableDurations is not provided, assume all are available + if (!availableDurations) return true; + return availableDurations.includes(duration); + }; + return (
= ({ Select Duration

- - - - + {/* 30 Days Button */} + + + + + + {!isDurationAvailable("30_DAYS") && ( + +

Coming Soon

+
+ )} +
+
+ + {/* 60 Days Button */} + + + + + + {!isDurationAvailable("60_DAYS") && ( + +

Coming Soon

+
+ )} +
+
+ + {/* 180 Days Button */} + + + + + + {!isDurationAvailable("180_DAYS") && ( + +

Coming Soon

+
+ )} +
+
+ + {/* Perpetual Button */} + + + + + + {!isDurationAvailable("PERPETUAL_DURATION") && ( + +

Coming Soon

+
+ )} +
+
)} diff --git a/src/config/env.ts b/src/config/env.ts index ed99893..5204f33 100644 --- a/src/config/env.ts +++ b/src/config/env.ts @@ -6,7 +6,7 @@ export const USD_STRATEGIES = { boringVaultAddress: "0xB964ca354A074fBf05765DaC640e01799c9E7f9E", deposit_token: "USDS", deposit_token_contract: "0x820C137fa70C8691f0e44Dc420a5e53c168921Dc", - deposit_token_decimal:18, + deposit_token_decimal: 18, deposit_token_image: "/images/icons/usdc.svg", deposit_token_2: "USDS", deposit_token_contract_2: "0x820C137fa70C8691f0e44Dc420a5e53c168921Dc", @@ -36,58 +36,6 @@ export const USD_STRATEGIES = { rpc: "https://rpc.soniclabs.com", }, }, - "60_DAYS": { - STABLE: { - network: "Sonic", - contract: "0x2F7397FD2D49E5b636eF44503771B17EDEd67621", - boringVaultAddress: "0xB964ca354A074fBf05765DaC640e01799c9E7f9E", - deposit_token: "USDS", - deposit_token_contract: "0x29219dd400f2bf60e5a23d13be72b486d4038894", - description: "60-day stable USD strategy on Sonic network", - apy: "4.2%", - incentives: "-", - tvl: "4000000", - rpc: "https://rpc.soniclabs.com", - }, - INCENTIVE: { - network: "Sonic", - contract: "0x2F7397FD2D49E5b636eF44503771B17EDEd67120", - boringVaultAddress: "0xB964ca354A074fBf05765DaC640e01799c9E7f9E", - deposit_token: "USDS", - deposit_token_contract: "0x29219dd400f2bf60e5a23d13be72b486d4038894", - description: "60-day incentivized USD strategy on Sonic network", - apy: "7.0%", - incentives: "1.8% in SONIC tokens", - tvl: "5000000", - rpc: "https://rpc.soniclabs.com", - }, - }, - "180_DAYS": { - STABLE: { - network: "Sonic", - contract: "0x2F7397FD2D49E5b636eF44500771B17EDEd67620", - boringVaultAddress: "0xB964ca354A074fBf05765DaC640e01799c9E7f9E", - deposit_token: "USDS", - deposit_token_contract: "0x29219dd400f2bf60e5a23d13be72b486d4038894", - description: "180-day stable USD strategy on Sonic network", - apy: "4.8%", - incentives: "-", - tvl: "4500000", - rpc: "https://rpc.soniclabs.com", - }, - INCENTIVE: { - network: "Sonic", - contract: "0x2F7397FD2D49E5b636eF14503771B17EDEd67620", - boringVaultAddress: "0xB964ca354A074fBf05765DaC640e01799c9E7f9E", - deposit_token: "USDS", - deposit_token_contract: "0x29219dd400f2bf60e5a23d13be72b486d4038894", - description: "180-day incentivized USD strategy on Sonic network", - apy: "8.0%", - incentives: "2.2% in SONIC tokens", - tvl: "5500000", - rpc: "https://rpc.soniclabs.com", - }, - }, }; export const BTC_STRATEGIES = {}; diff --git a/src/pages/earn-subpage.tsx b/src/pages/earn-subpage.tsx index 8f29656..587181e 100644 --- a/src/pages/earn-subpage.tsx +++ b/src/pages/earn-subpage.tsx @@ -299,6 +299,7 @@ const YieldSubpage: React.FC = ({ depositParams }) => { onDurationSelect={(duration: DurationType) => handleDurationSelect("USD", duration) } + availableDurations={Object.keys(USD_STRATEGIES) as DurationType[]} /> Date: Thu, 29 May 2025 20:23:18 -0500 Subject: [PATCH 03/22] feat: add withdraw from base using oslver --- src/config/abi/erc20.ts | 4 +- src/config/abi/solver.ts | 612 ++++++++++++++++++++++++++++++++ src/config/env.ts | 2 + src/pages/portfolio-subpage.tsx | 250 ++++++++----- 4 files changed, 778 insertions(+), 90 deletions(-) create mode 100644 src/config/abi/solver.ts diff --git a/src/config/abi/erc20.ts b/src/config/abi/erc20.ts index bf0ab54..f04204b 100644 --- a/src/config/abi/erc20.ts +++ b/src/config/abi/erc20.ts @@ -1,4 +1,4 @@ -[ +export const ERC20_ABI = [ { inputs: [ { @@ -159,4 +159,4 @@ ], outputs: [{ name: "", type: "bool" }], }, -]; +] as const; diff --git a/src/config/abi/solver.ts b/src/config/abi/solver.ts new file mode 100644 index 0000000..be88a89 --- /dev/null +++ b/src/config/abi/solver.ts @@ -0,0 +1,612 @@ +export const SOLVER_ABI = [ + { + inputs: [ + { internalType: "address", name: "_owner", type: "address" }, + { internalType: "address", name: "_auth", type: "address" }, + { + internalType: "address payable", + name: "_boringVault", + type: "address", + }, + { internalType: "address", name: "_accountant", type: "address" }, + ], + stateMutability: "nonpayable", + type: "constructor", + }, + { inputs: [], name: "BoringOnChainQueue__BadDeadline", type: "error" }, + { inputs: [], name: "BoringOnChainQueue__BadDiscount", type: "error" }, + { inputs: [], name: "BoringOnChainQueue__BadInput", type: "error" }, + { inputs: [], name: "BoringOnChainQueue__BadShareAmount", type: "error" }, + { inputs: [], name: "BoringOnChainQueue__BadUser", type: "error" }, + { inputs: [], name: "BoringOnChainQueue__DeadlinePassed", type: "error" }, + { inputs: [], name: "BoringOnChainQueue__Keccak256Collision", type: "error" }, + { + inputs: [], + name: "BoringOnChainQueue__MAXIMUM_MINIMUM_SECONDS_TO_DEADLINE", + type: "error", + }, + { + inputs: [], + name: "BoringOnChainQueue__MAXIMUM_SECONDS_TO_MATURITY", + type: "error", + }, + { inputs: [], name: "BoringOnChainQueue__MAX_DISCOUNT", type: "error" }, + { inputs: [], name: "BoringOnChainQueue__NotMatured", type: "error" }, + { inputs: [], name: "BoringOnChainQueue__Overflow", type: "error" }, + { inputs: [], name: "BoringOnChainQueue__Paused", type: "error" }, + { + inputs: [], + name: "BoringOnChainQueue__PermitFailedAndAllowanceTooLow", + type: "error", + }, + { inputs: [], name: "BoringOnChainQueue__RequestNotFound", type: "error" }, + { + inputs: [], + name: "BoringOnChainQueue__RescueCannotTakeSharesFromActiveRequests", + type: "error", + }, + { inputs: [], name: "BoringOnChainQueue__SolveAssetMismatch", type: "error" }, + { + inputs: [], + name: "BoringOnChainQueue__WithdrawsNotAllowedForAsset", + type: "error", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "address", name: "user", type: "address" }, + { + indexed: true, + internalType: "contract Authority", + name: "newAuthority", + type: "address", + }, + ], + name: "AuthorityUpdated", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "bytes32", + name: "requestId", + type: "bytes32", + }, + { indexed: true, internalType: "address", name: "user", type: "address" }, + { + indexed: false, + internalType: "uint256", + name: "timestamp", + type: "uint256", + }, + ], + name: "OnChainWithdrawCancelled", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "bytes32", + name: "requestId", + type: "bytes32", + }, + { indexed: true, internalType: "address", name: "user", type: "address" }, + { + indexed: true, + internalType: "address", + name: "assetOut", + type: "address", + }, + { indexed: false, internalType: "uint96", name: "nonce", type: "uint96" }, + { + indexed: false, + internalType: "uint128", + name: "amountOfShares", + type: "uint128", + }, + { + indexed: false, + internalType: "uint128", + name: "amountOfAssets", + type: "uint128", + }, + { + indexed: false, + internalType: "uint40", + name: "creationTime", + type: "uint40", + }, + { + indexed: false, + internalType: "uint24", + name: "secondsToMaturity", + type: "uint24", + }, + { + indexed: false, + internalType: "uint24", + name: "secondsToDeadline", + type: "uint24", + }, + ], + name: "OnChainWithdrawRequested", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "bytes32", + name: "requestId", + type: "bytes32", + }, + { indexed: true, internalType: "address", name: "user", type: "address" }, + { + indexed: false, + internalType: "uint256", + name: "timestamp", + type: "uint256", + }, + ], + name: "OnChainWithdrawSolved", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "address", name: "user", type: "address" }, + { + indexed: true, + internalType: "address", + name: "newOwner", + type: "address", + }, + ], + name: "OwnershipTransferred", + type: "event", + }, + { anonymous: false, inputs: [], name: "Paused", type: "event" }, + { anonymous: false, inputs: [], name: "Unpaused", type: "event" }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "assetOut", + type: "address", + }, + { + indexed: false, + internalType: "uint24", + name: "secondsToMaturity", + type: "uint24", + }, + { + indexed: false, + internalType: "uint24", + name: "minimumSecondsToDeadline", + type: "uint24", + }, + { + indexed: false, + internalType: "uint16", + name: "minDiscount", + type: "uint16", + }, + { + indexed: false, + internalType: "uint16", + name: "maxDiscount", + type: "uint16", + }, + { + indexed: false, + internalType: "uint96", + name: "minimumShares", + type: "uint96", + }, + ], + name: "WithdrawAssetSetup", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "assetOut", + type: "address", + }, + ], + name: "WithdrawAssetStopped", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "assetOut", + type: "address", + }, + { + indexed: false, + internalType: "uint24", + name: "minimumSecondsToDeadline", + type: "uint24", + }, + { + indexed: false, + internalType: "uint24", + name: "secondsToMaturity", + type: "uint24", + }, + { + indexed: false, + internalType: "uint16", + name: "minDiscount", + type: "uint16", + }, + { + indexed: false, + internalType: "uint16", + name: "maxDiscount", + type: "uint16", + }, + { + indexed: false, + internalType: "uint96", + name: "minimumShares", + type: "uint96", + }, + ], + name: "WithdrawAssetUpdated", + type: "event", + }, + { + inputs: [], + name: "ONE_SHARE", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "accountant", + outputs: [ + { + internalType: "contract AccountantWithRateProviders", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "authority", + outputs: [ + { internalType: "contract Authority", name: "", type: "address" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "boringVault", + outputs: [ + { internalType: "contract BoringVault", name: "", type: "address" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + components: [ + { internalType: "uint96", name: "nonce", type: "uint96" }, + { internalType: "address", name: "user", type: "address" }, + { internalType: "address", name: "assetOut", type: "address" }, + { internalType: "uint128", name: "amountOfShares", type: "uint128" }, + { internalType: "uint128", name: "amountOfAssets", type: "uint128" }, + { internalType: "uint40", name: "creationTime", type: "uint40" }, + { internalType: "uint24", name: "secondsToMaturity", type: "uint24" }, + { internalType: "uint24", name: "secondsToDeadline", type: "uint24" }, + ], + internalType: "struct BoringOnChainQueue.OnChainWithdraw", + name: "request", + type: "tuple", + }, + ], + name: "cancelOnChainWithdraw", + outputs: [{ internalType: "bytes32", name: "requestId", type: "bytes32" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + components: [ + { internalType: "uint96", name: "nonce", type: "uint96" }, + { internalType: "address", name: "user", type: "address" }, + { internalType: "address", name: "assetOut", type: "address" }, + { internalType: "uint128", name: "amountOfShares", type: "uint128" }, + { internalType: "uint128", name: "amountOfAssets", type: "uint128" }, + { internalType: "uint40", name: "creationTime", type: "uint40" }, + { internalType: "uint24", name: "secondsToMaturity", type: "uint24" }, + { internalType: "uint24", name: "secondsToDeadline", type: "uint24" }, + ], + internalType: "struct BoringOnChainQueue.OnChainWithdraw[]", + name: "requests", + type: "tuple[]", + }, + ], + name: "cancelUserWithdraws", + outputs: [ + { + internalType: "bytes32[]", + name: "canceledRequestIds", + type: "bytes32[]", + }, + ], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + components: [ + { internalType: "uint96", name: "nonce", type: "uint96" }, + { internalType: "address", name: "user", type: "address" }, + { internalType: "address", name: "assetOut", type: "address" }, + { internalType: "uint128", name: "amountOfShares", type: "uint128" }, + { internalType: "uint128", name: "amountOfAssets", type: "uint128" }, + { internalType: "uint40", name: "creationTime", type: "uint40" }, + { internalType: "uint24", name: "secondsToMaturity", type: "uint24" }, + { internalType: "uint24", name: "secondsToDeadline", type: "uint24" }, + ], + internalType: "struct BoringOnChainQueue.OnChainWithdraw", + name: "request", + type: "tuple", + }, + ], + name: "getRequestId", + outputs: [{ internalType: "bytes32", name: "requestId", type: "bytes32" }], + stateMutability: "pure", + type: "function", + }, + { + inputs: [], + name: "getRequestIds", + outputs: [{ internalType: "bytes32[]", name: "", type: "bytes32[]" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "isPaused", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "nonce", + outputs: [{ internalType: "uint96", name: "", type: "uint96" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "owner", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "pause", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "assetOut", type: "address" }, + { internalType: "uint128", name: "amountOfShares", type: "uint128" }, + { internalType: "uint16", name: "discount", type: "uint16" }, + ], + name: "previewAssetsOut", + outputs: [ + { internalType: "uint128", name: "amountOfAssets128", type: "uint128" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + components: [ + { internalType: "uint96", name: "nonce", type: "uint96" }, + { internalType: "address", name: "user", type: "address" }, + { internalType: "address", name: "assetOut", type: "address" }, + { internalType: "uint128", name: "amountOfShares", type: "uint128" }, + { internalType: "uint128", name: "amountOfAssets", type: "uint128" }, + { internalType: "uint40", name: "creationTime", type: "uint40" }, + { internalType: "uint24", name: "secondsToMaturity", type: "uint24" }, + { internalType: "uint24", name: "secondsToDeadline", type: "uint24" }, + ], + internalType: "struct BoringOnChainQueue.OnChainWithdraw", + name: "oldRequest", + type: "tuple", + }, + { internalType: "uint16", name: "discount", type: "uint16" }, + { internalType: "uint24", name: "secondsToDeadline", type: "uint24" }, + ], + name: "replaceOnChainWithdraw", + outputs: [ + { internalType: "bytes32", name: "oldRequestId", type: "bytes32" }, + { internalType: "bytes32", name: "newRequestId", type: "bytes32" }, + ], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "assetOut", type: "address" }, + { internalType: "uint128", name: "amountOfShares", type: "uint128" }, + { internalType: "uint16", name: "discount", type: "uint16" }, + { internalType: "uint24", name: "secondsToDeadline", type: "uint24" }, + ], + name: "requestOnChainWithdraw", + outputs: [{ internalType: "bytes32", name: "requestId", type: "bytes32" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "assetOut", type: "address" }, + { internalType: "uint128", name: "amountOfShares", type: "uint128" }, + { internalType: "uint16", name: "discount", type: "uint16" }, + { internalType: "uint24", name: "secondsToDeadline", type: "uint24" }, + { internalType: "uint256", name: "permitDeadline", type: "uint256" }, + { internalType: "uint8", name: "v", type: "uint8" }, + { internalType: "bytes32", name: "r", type: "bytes32" }, + { internalType: "bytes32", name: "s", type: "bytes32" }, + ], + name: "requestOnChainWithdrawWithPermit", + outputs: [{ internalType: "bytes32", name: "requestId", type: "bytes32" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "contract ERC20", name: "token", type: "address" }, + { internalType: "uint256", name: "amount", type: "uint256" }, + { internalType: "address", name: "to", type: "address" }, + { + components: [ + { internalType: "uint96", name: "nonce", type: "uint96" }, + { internalType: "address", name: "user", type: "address" }, + { internalType: "address", name: "assetOut", type: "address" }, + { internalType: "uint128", name: "amountOfShares", type: "uint128" }, + { internalType: "uint128", name: "amountOfAssets", type: "uint128" }, + { internalType: "uint40", name: "creationTime", type: "uint40" }, + { internalType: "uint24", name: "secondsToMaturity", type: "uint24" }, + { internalType: "uint24", name: "secondsToDeadline", type: "uint24" }, + ], + internalType: "struct BoringOnChainQueue.OnChainWithdraw[]", + name: "activeRequests", + type: "tuple[]", + }, + ], + name: "rescueTokens", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "contract Authority", + name: "newAuthority", + type: "address", + }, + ], + name: "setAuthority", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + components: [ + { internalType: "uint96", name: "nonce", type: "uint96" }, + { internalType: "address", name: "user", type: "address" }, + { internalType: "address", name: "assetOut", type: "address" }, + { internalType: "uint128", name: "amountOfShares", type: "uint128" }, + { internalType: "uint128", name: "amountOfAssets", type: "uint128" }, + { internalType: "uint40", name: "creationTime", type: "uint40" }, + { internalType: "uint24", name: "secondsToMaturity", type: "uint24" }, + { internalType: "uint24", name: "secondsToDeadline", type: "uint24" }, + ], + internalType: "struct BoringOnChainQueue.OnChainWithdraw[]", + name: "requests", + type: "tuple[]", + }, + { internalType: "bytes", name: "solveData", type: "bytes" }, + { internalType: "address", name: "solver", type: "address" }, + ], + name: "solveOnChainWithdraws", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "assetOut", type: "address" }], + name: "stopWithdrawsInAsset", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "newOwner", type: "address" }], + name: "transferOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "unpause", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "assetOut", type: "address" }, + { internalType: "uint24", name: "secondsToMaturity", type: "uint24" }, + { + internalType: "uint24", + name: "minimumSecondsToDeadline", + type: "uint24", + }, + { internalType: "uint16", name: "minDiscount", type: "uint16" }, + { internalType: "uint16", name: "maxDiscount", type: "uint16" }, + { internalType: "uint96", name: "minimumShares", type: "uint96" }, + ], + name: "updateWithdrawAsset", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "", type: "address" }], + name: "withdrawAssets", + outputs: [ + { internalType: "bool", name: "allowWithdraws", type: "bool" }, + { internalType: "uint24", name: "secondsToMaturity", type: "uint24" }, + { + internalType: "uint24", + name: "minimumSecondsToDeadline", + type: "uint24", + }, + { internalType: "uint16", name: "minDiscount", type: "uint16" }, + { internalType: "uint16", name: "maxDiscount", type: "uint16" }, + { internalType: "uint96", name: "minimumShares", type: "uint96" }, + ], + stateMutability: "view", + type: "function", + }, +] as const; diff --git a/src/config/env.ts b/src/config/env.ts index 5204f33..a89105d 100644 --- a/src/config/env.ts +++ b/src/config/env.ts @@ -4,6 +4,8 @@ export const USD_STRATEGIES = { network: "base", contract: "0xeee7db89be35ee3da3b68928a1e3720ff434fb48", boringVaultAddress: "0xB964ca354A074fBf05765DaC640e01799c9E7f9E", + solverAddress:"0x580cBbC61D0dD1235E6Bb71fEB5aba3b70bCf750", + shareAddress:"0xB964ca354A074fBf05765DaC640e01799c9E7f9E", deposit_token: "USDS", deposit_token_contract: "0x820C137fa70C8691f0e44Dc420a5e53c168921Dc", deposit_token_decimal: 18, diff --git a/src/pages/portfolio-subpage.tsx b/src/pages/portfolio-subpage.tsx index 6aa65f9..a740e91 100644 --- a/src/pages/portfolio-subpage.tsx +++ b/src/pages/portfolio-subpage.tsx @@ -7,6 +7,8 @@ import { useWriteContract, } from "wagmi"; import { USD_STRATEGIES, BTC_STRATEGIES, ETH_STRATEGIES } from "../config/env"; +import { ERC20_ABI } from "../config/abi/erc20"; +import { SOLVER_ABI } from "../config/abi/solver"; import { type Address, createPublicClient, @@ -38,51 +40,6 @@ type StrategyAsset = { [K in DurationType]?: StrategyDuration; } -const ERC20_ABI = [ - { - name: "balanceOf", - type: "function", - stateMutability: "view", - inputs: [{ name: "owner", type: "address" }], - outputs: [{ name: "", type: "uint256" }], - }, - { - name: "decimals", - type: "function", - stateMutability: "view", - inputs: [], - outputs: [{ name: "", type: "uint8" }], - }, -] as const; - -const VAULT_ABI = [ - { - stateMutability: "nonpayable", - type: "function", - name: "redeem", - inputs: [ - { name: "shares", type: "uint256" }, - { name: "receiver", type: "address" }, - { name: "owner", type: "address" }, - ], - outputs: [{ name: "", type: "uint256" }], - }, - { - name: "balanceOf", - type: "function", - stateMutability: "view", - inputs: [{ name: "addr", type: "address" }], - outputs: [{ name: "", type: "uint256" }], - }, - { - name: "decimals", - type: "function", - stateMutability: "view", - inputs: [], - outputs: [{ name: "", type: "uint8" }], - }, -] as const; - const PortfolioSubpage: React.FC = () => { const { address, isConnected } = useAccount(); const [depositSuccess, setDepositSuccess] = useState(false); @@ -91,8 +48,8 @@ const PortfolioSubpage: React.FC = () => { ); const [isDepositing, setIsDepositing] = useState(false); const [isApproved, setIsApproved] = useState(false); - const [approvalHash, setApprovalHash] = useState<`0x${string}` | null>(null); const [isApproving, setIsApproving] = useState(false); + const [approvalHash, setApprovalHash] = useState<`0x${string}` | null>(null); const [strategiesWithBalance, setStrategiesWithBalance] = useState([]); const [selectedStrategy, setSelectedStrategy] = useState(null); const [withdrawAmount, setWithdrawAmount] = useState(""); @@ -205,13 +162,13 @@ const PortfolioSubpage: React.FC = () => { const [balance, decimals] = await Promise.all([ client.readContract({ address: strategy.boringVaultAddress as Address, - abi: VAULT_ABI, + abi: ERC20_ABI, functionName: "balanceOf", args: [address as Address], }), client.readContract({ address: strategy.boringVaultAddress as Address, - abi: VAULT_ABI, + abi: ERC20_ABI, functionName: "decimals", }), ]); @@ -311,17 +268,22 @@ const PortfolioSubpage: React.FC = () => { // Use wagmi's useWriteContract hook const { writeContractAsync: writeContract } = useWriteContract(); - const handleWithdraw = async () => { + const handleApprove = async () => { if (!selectedStrategy || !withdrawAmount || !address) return; try { - setIsWithdrawing(true); + setIsApproving(true); setErrorMessage(null); - // Get the contract address from the selected strategy - const contractAddress = selectedStrategy.contract as Address; + const solverAddress = selectedStrategy.solverAddress as Address; + const vaultAddress = selectedStrategy.boringVaultAddress as Address; + + console.log("Approval details:", { + solverAddress, + vaultAddress, + address + }); - // Create a client to interact with the contract const client = createPublicClient({ transport: http(selectedStrategy.rpc), chain: { @@ -340,52 +302,137 @@ const PortfolioSubpage: React.FC = () => { }, }); - // Get the decimals from the contract + // Get decimals from vault const decimals = (await client.readContract({ - address: contractAddress, - abi: VAULT_ABI, + address: vaultAddress, + abi: ERC20_ABI, functionName: "decimals", })) as number; - console.log(`Contract decimals: ${decimals}`); - - // Parse the withdraw amount with proper decimals const sharesAmount = parseUnits(withdrawAmount, decimals); - console.log("Withdrawing from contract:", { - contract: contractAddress, - shares: sharesAmount.toString(), - receiver: address, - owner: address, - decimals, + console.log("Requesting approval for amount:", sharesAmount.toString()); + + // Approve the solver to spend the vault tokens + const approveTx = await writeContract({ + address: vaultAddress, + abi: ERC20_ABI, + functionName: "approve", + args: [solverAddress, sharesAmount], + chainId: 8453, + account: address, }); - try { - // Call the redeem function on the contract - const tx = await writeContract({ - address: contractAddress, - abi: VAULT_ABI, - functionName: "redeem", - args: [sharesAmount, address, address], - chainId: 8453, // Base chain ID - account: address, + if (approveTx && typeof approveTx === "string" && approveTx.startsWith("0x")) { + console.log("Approval transaction submitted:", approveTx); + setApprovalHash(approveTx as `0x${string}`); + + // Wait for approval transaction to complete + const { isSuccess: isApprovalSuccess } = await useTransaction({ + hash: approveTx as `0x${string}`, }); - if (tx && typeof tx === "string" && tx.startsWith("0x")) { - console.log("Withdrawal transaction submitted:", tx); - setWithdrawTxHash(tx as `0x${string}`); + if (isApprovalSuccess) { + setIsApproved(true); + console.log("Approval successful"); } else { - throw new Error("Failed to get transaction hash"); + throw new Error("Approval failed"); } - } catch (error) { - console.error("Contract call failed:", error); - setIsWithdrawing(false); - setErrorMessage("Transaction failed. Please try again."); + } else { + throw new Error("Failed to get approval transaction hash"); } } catch (error) { - console.error("Error withdrawing:", error); + console.error("Approval failed:", error); + setErrorMessage("Approval failed. Please try again."); + } finally { + setIsApproving(false); + } + }; + + const handleWithdraw = async () => { + if (!selectedStrategy || !withdrawAmount || !address || !isApproved) return; + + try { + setIsWithdrawing(true); + setErrorMessage(null); + + const solverAddress = selectedStrategy.solverAddress as Address; + const vaultAddress = selectedStrategy.boringVaultAddress as Address; + const assetOutAddress = "0x820C137fa70C8691f0e44Dc420a5e53c168921Dc" as Address; + + const client = createPublicClient({ + transport: http(selectedStrategy.rpc), + chain: { + id: 8453, + name: "Base", + network: "base", + nativeCurrency: { + decimals: 18, + name: "Ether", + symbol: "ETH", + }, + rpcUrls: { + default: { http: ["https://mainnet.base.org"] }, + public: { http: ["https://mainnet.base.org"] }, + }, + }, + }); + + // Get decimals from vault contract + const decimals = (await client.readContract({ + address: vaultAddress, + abi: ERC20_ABI, + functionName: "decimals", + })) as number; + + const sharesAmount = parseUnits(withdrawAmount, decimals); + // Convert to uint128 + const amountOfShares = BigInt(sharesAmount.toString()); + const discount = 100; // uint16 - hardcoded + const secondsToDeadline = 3600; // uint24 - hardcoded (1 hour) + + console.log("Debug - Contract call parameters:", { + functionName: "requestOnChainWithdraw", + contractAddress: solverAddress, + args: { + assetOut: assetOutAddress, + amountOfShares: amountOfShares.toString(), + discount: discount.toString(), + secondsToDeadline: secondsToDeadline.toString() + }, + types: { + assetOut: typeof assetOutAddress, + amountOfShares: typeof amountOfShares, + discount: typeof discount, + secondsToDeadline: typeof secondsToDeadline + } + }); + + const tx = await writeContract({ + address: solverAddress, + abi: SOLVER_ABI, + functionName: "requestOnChainWithdraw", + args: [ + assetOutAddress, + amountOfShares, + discount, + secondsToDeadline + ], + chainId: 8453, + account: address, + }); + + if (tx && typeof tx === "string" && tx.startsWith("0x")) { + console.log("Withdrawal transaction submitted:", tx); + setWithdrawTxHash(tx as `0x${string}`); + } else { + throw new Error("Failed to get transaction hash"); + } + } catch (error) { + console.error("Contract call failed:", error); + setErrorMessage("Transaction failed. Please try again."); + } finally { setIsWithdrawing(false); - setErrorMessage("Error preparing withdrawal. Please try again."); } }; @@ -772,19 +819,44 @@ const PortfolioSubpage: React.FC = () => { {errorMessage && ( From 82f031a290824e26f6d3ef2cfa89c3cb1f33bdad Mon Sep 17 00:00:00 2001 From: Kraznik <103571066+Kraznik@users.noreply.github.com> Date: Thu, 29 May 2025 20:42:12 -0500 Subject: [PATCH 04/22] font on browser --- src/styles/globals.css | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/styles/globals.css b/src/styles/globals.css index 2334816..136f4ac 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -2,6 +2,9 @@ @tailwind components; @tailwind utilities; +/* Import Inter font */ +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap'); + /* Your custom global styles */ html, body { @@ -9,8 +12,10 @@ body { padding: 0; margin: 0; background-color: #080b17; - font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, - Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, + Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } a { @@ -24,7 +29,7 @@ a { h1 { color: #d7e3ef; - font-family: Inter; + font-family: 'Inter', sans-serif; font-size: 40px; font-style: normal; font-weight: 600; @@ -34,7 +39,7 @@ h1 { h2 { color: #d7e3ef; - font-family: Inter; + font-family: 'Inter', sans-serif; font-size: 32px; font-style: normal; font-weight: 500; @@ -43,6 +48,7 @@ h2 { h3 { @apply text-lg font-medium; + font-family: 'Inter', sans-serif; } @layer components { From 9bade09c8686528bd233617ff304c467cdbf740f Mon Sep 17 00:00:00 2001 From: Kraznik <103571066+Kraznik@users.noreply.github.com> Date: Thu, 29 May 2025 20:46:05 -0500 Subject: [PATCH 05/22] favicon --- public/images/logo/logo.svg | 3 +++ src/app/layout.tsx | 17 ++++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 public/images/logo/logo.svg diff --git a/public/images/logo/logo.svg b/public/images/logo/logo.svg new file mode 100644 index 0000000..063daa6 --- /dev/null +++ b/public/images/logo/logo.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/app/layout.tsx b/src/app/layout.tsx index b73b7d5..5b3fa6b 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,9 +1,24 @@ import { TooltipProvider } from "@/components/ui/tooltip" import { ReactNode } from "react" +export const metadata = { + title: 'Advanced Yield Platform', + description: 'Advanced Yield Platform for DeFi', + icons: { + icon: '/images/logo/logo.svg', + shortcut: '/images/logo/logo.svg', + apple: '/images/logo/logo.svg', + }, +} + export default function RootLayout({ children }: { children: ReactNode }) { return ( - + + + + + + {children} From 71274991a714a4670a0954a8b64df581f265895d Mon Sep 17 00:00:00 2001 From: Kraznik <103571066+Kraznik@users.noreply.github.com> Date: Sun, 1 Jun 2025 19:36:17 -0500 Subject: [PATCH 06/22] prod --- src/components/deposit-view.tsx | 81 +++++++++++++++++++++++++++++---- src/config/env.ts | 10 ++-- 2 files changed, 76 insertions(+), 15 deletions(-) diff --git a/src/components/deposit-view.tsx b/src/components/deposit-view.tsx index 27741af..825c9f7 100644 --- a/src/components/deposit-view.tsx +++ b/src/components/deposit-view.tsx @@ -36,6 +36,15 @@ interface StrategyConfig { deposit_token_contract?: string; // Optional field for backward compatibility deposit_token_image?: string; // Optional field for deposit token image deposit_token_decimal?: number; // Optional field for deposit token decimal + // Add support for multiple deposit tokens + deposit_token_2?: string; + deposit_token_contract_2?: string; + deposit_token_image_2?: string; + deposit_token_decimal_2?: number; + deposit_token_3?: string; + deposit_token_contract_3?: string; + deposit_token_image_3?: string; + deposit_token_decimal_3?: number; description: string; apy: string; incentives: string; @@ -669,7 +678,8 @@ const DepositView: React.FC = ({ // Calculate minimum mint amount based on slippage const slippageAmount = amountInWei * BigInt(Math.floor(parseFloat(slippage) * 10000)) / BigInt(10000); - const minimumMint = amountInWei - slippageAmount; + // const minimumMint = amountInWei - slippageAmount; + const minimumMint = "2000000"; if (isMultiChain) { // Preview bridge fee before proceeding @@ -879,6 +889,40 @@ const DepositView: React.FC = ({ } }; + // --- Asset selection state --- + // Parse all available deposit assets from strategyConfig + const assetOptions = useMemo(() => { + const options = []; + if (strategyConfig.deposit_token && strategyConfig.deposit_token_contract) { + options.push({ + name: strategyConfig.deposit_token, + contract: strategyConfig.deposit_token_contract, + image: strategyConfig.deposit_token_image, + decimals: strategyConfig.deposit_token_decimal || 6, + }); + } + if (strategyConfig.deposit_token_2 && strategyConfig.deposit_token_contract_2) { + options.push({ + name: strategyConfig.deposit_token_2, + contract: strategyConfig.deposit_token_contract_2, + image: strategyConfig.deposit_token_image_2, + decimals: strategyConfig.deposit_token_decimal_2 || 6, + }); + } + if (strategyConfig.deposit_token_3 && strategyConfig.deposit_token_contract_3) { + options.push({ + name: strategyConfig.deposit_token_3, + contract: strategyConfig.deposit_token_contract_3, + image: strategyConfig.deposit_token_image_3, + decimals: strategyConfig.deposit_token_decimal_3 || 6, + }); + } + return options; + }, [strategyConfig]); + + const [selectedAssetIdx, setSelectedAssetIdx] = useState(0); + const selectedAssetOption = assetOptions[selectedAssetIdx] || assetOptions[0]; + return (
{depositSuccess ? ( @@ -955,7 +999,7 @@ const DepositView: React.FC = ({
Amount - {amount} {depositToken} + {amount} {selectedAssetOption.name}
@@ -975,23 +1019,41 @@ const DepositView: React.FC = ({
- {depositTokenImage && ( + {selectedAssetOption.image && ( {depositToken} )} - Deposit {depositToken} + Deposit {selectedAssetOption.name} +0.00 in 1 year
- - {/* Multi-chain Toggle */} + {/* --- Asset Dropdown & Multi-chain Toggle --- */} + {assetOptions.length > 1 && ( +
+ + +
+ )} + {/* Multi-chain Toggle (always shown) */}
Multi-chain Deposit
- {/* Target Chain Selection - Only shown when multi-chain is enabled */} {isMultiChain && (
@@ -1025,7 +1086,7 @@ const DepositView: React.FC = ({
)} - + {/* --- End Asset Dropdown & Multi-chain Toggle --- */}
Date: Sun, 1 Jun 2025 19:40:13 -0500 Subject: [PATCH 07/22] remove minAMount hard coded for testing --- src/components/deposit-view.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/deposit-view.tsx b/src/components/deposit-view.tsx index 825c9f7..3ad26a9 100644 --- a/src/components/deposit-view.tsx +++ b/src/components/deposit-view.tsx @@ -679,7 +679,7 @@ const DepositView: React.FC = ({ // Calculate minimum mint amount based on slippage const slippageAmount = amountInWei * BigInt(Math.floor(parseFloat(slippage) * 10000)) / BigInt(10000); // const minimumMint = amountInWei - slippageAmount; - const minimumMint = "2000000"; + const minimumMint = amountInWei - slippageAmount; if (isMultiChain) { // Preview bridge fee before proceeding From 57dfde2a0483c9a8ea29f4f96d9c8ad93fbd1acd Mon Sep 17 00:00:00 2001 From: Kraznik <103571066+Kraznik@users.noreply.github.com> Date: Sun, 1 Jun 2025 19:48:18 -0500 Subject: [PATCH 08/22] feat: inActive incentive Card for usd strategy --- src/components/ui/card.tsx | 8 +++++++- src/config/env.ts | 20 ++++++++++---------- src/pages/earn-subpage.tsx | 30 +++++++++++++++++++++++++----- 3 files changed, 42 insertions(+), 16 deletions(-) diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx index 9c3425b..5f8bd0d 100644 --- a/src/components/ui/card.tsx +++ b/src/components/ui/card.tsx @@ -85,7 +85,13 @@ const CustomCard: React.FC = ({ {...props} > {isStrategyCard ? ( -
+
+ {/* Coming Soon Overlay */} + {isComingSoon && ( +
+ Coming Soon +
+ )} {/* Image */}
{ value: "0%", info: "-", }, + comingSoon: true, }, }; } @@ -110,6 +112,7 @@ const getStrategyInfo = (duration: DurationType): StrategyData => { value: strategy.INCENTIVE.apy, info: strategy.INCENTIVE.incentives, }, + comingSoon: (strategy.INCENTIVE as any).comingSoon === true, }, }; }; @@ -256,13 +259,25 @@ const YieldSubpage: React.FC = ({ depositParams }) => { />
- handleStrategySelect( - "incentive", + {...(getStrategyInfo(selectedAsset.duration).incentives[ + selectedAsset.asset as AssetType + ].comingSoon + ? {} + : { + onClick: () => + handleStrategySelect( + "incentive", + selectedAsset.asset as AssetType + ), + })} + className={ + "group " + + (getStrategyInfo(selectedAsset.duration).incentives[ selectedAsset.asset as AssetType - ) + ].comingSoon + ? "pointer-events-none opacity-60" + : "cursor-pointer") } - className="cursor-pointer" > = ({ depositParams }) => { } isStrategyCard={true} disableHover={true} + isComingSoon={ + getStrategyInfo(selectedAsset.duration).incentives[ + selectedAsset.asset as AssetType + ].comingSoon === true + } />
From d60da3f300783246f1bb525ae06ff513f85e312c Mon Sep 17 00:00:00 2001 From: Kraznik <103571066+Kraznik@users.noreply.github.com> Date: Sun, 1 Jun 2025 19:53:07 -0500 Subject: [PATCH 09/22] feat: update connected network using target chain dropdown --- src/components/deposit-view.tsx | 246 +++++++++++++++++++++----------- 1 file changed, 163 insertions(+), 83 deletions(-) diff --git a/src/components/deposit-view.tsx b/src/components/deposit-view.tsx index 3ad26a9..661ab13 100644 --- a/src/components/deposit-view.tsx +++ b/src/components/deposit-view.tsx @@ -14,6 +14,7 @@ import { useWatchContractEvent, useTransaction, useReadContracts, + useSwitchChain, } from "wagmi"; import { USD_STRATEGIES, BTC_STRATEGIES, ETH_STRATEGIES } from "../config/env"; import { @@ -145,7 +146,7 @@ const VAULT_ABI = [ inputs: [ { name: "depositAsset", type: "address" }, { name: "depositAmount", type: "uint256" }, - { name: "minimumMint", type: "uint256" } + { name: "minimumMint", type: "uint256" }, ], outputs: [{ name: "shares", type: "uint256" }], }, @@ -160,7 +161,7 @@ const VAULT_ABI = [ { name: "to", type: "address" }, { name: "bridgeWildCard", type: "bytes" }, { name: "feeToken", type: "address" }, - { name: "maxFee", type: "uint256" } + { name: "maxFee", type: "uint256" }, ], outputs: [{ name: "shares", type: "uint256" }], }, @@ -172,7 +173,7 @@ const VAULT_ABI = [ { name: "shareAmount", type: "uint96" }, { name: "to", type: "address" }, { name: "bridgeWildCard", type: "bytes" }, - { name: "feeToken", type: "address" } + { name: "feeToken", type: "address" }, ], outputs: [{ name: "fee", type: "uint256" }], }, @@ -260,6 +261,7 @@ const DepositView: React.FC = ({ const [bridgeFee, setBridgeFee] = useState("0"); const [isLoadingFee, setIsLoadingFee] = useState(false); const [targetChain, setTargetChain] = useState("arbitrum"); // Default target chain + const { switchChain } = useSwitchChain(); // Get strategy config based on asset type const strategyConfigs = { @@ -269,7 +271,8 @@ const DepositView: React.FC = ({ }; // Explicitly access the strategies for the selected asset first - const assetStrategies = strategyConfigs[selectedAsset as keyof typeof strategyConfigs]; + const assetStrategies = + strategyConfigs[selectedAsset as keyof typeof strategyConfigs]; // Now access the specific duration and strategy type const strategyConfig = (assetStrategies as any)[duration][ // Cast assetStrategies to any for indexing @@ -337,9 +340,14 @@ const DepositView: React.FC = ({ allowance: allowance?.toString(), hasAllowance: !!allowance, amount: amount ? parseUnits(amount, depositTokenDecimals).toString() : "0", - needsApproval: amount ? (BigInt(allowance?.toString() || "0") < parseUnits(amount, depositTokenDecimals)) : false, - currentAllowanceFormatted: allowance ? formatUnits(BigInt(allowance.toString()), depositTokenDecimals) : "0", - requestedAmountFormatted: amount || "0" + needsApproval: amount + ? BigInt(allowance?.toString() || "0") < + parseUnits(amount, depositTokenDecimals) + : false, + currentAllowanceFormatted: allowance + ? formatUnits(BigInt(allowance.toString()), depositTokenDecimals) + : "0", + requestedAmountFormatted: amount || "0", }); // Watch approve transaction @@ -364,7 +372,7 @@ const DepositView: React.FC = ({ useEffect(() => { const checkTokenContract = async () => { if (!tokenContractAddress || !address) return; - + try { const client = createPublicClient({ transport: http(strategyConfig.rpc || "https://base.llamarpc.com"), @@ -378,29 +386,39 @@ const DepositView: React.FC = ({ symbol: "ETH", }, rpcUrls: { - default: { http: [strategyConfig.rpc || "https://base.llamarpc.com"] }, - public: { http: [strategyConfig.rpc || "https://base.llamarpc.com"] }, + default: { + http: [strategyConfig.rpc || "https://base.llamarpc.com"], + }, + public: { + http: [strategyConfig.rpc || "https://base.llamarpc.com"], + }, }, }, }); // Try to read basic token info const [name, symbol, decimals] = await Promise.all([ - client.readContract({ - address: tokenContractAddress as Address, - abi: ERC20_ABI, - functionName: "name", - }).catch(() => "Error reading name"), - client.readContract({ - address: tokenContractAddress as Address, - abi: ERC20_ABI, - functionName: "symbol", - }).catch(() => "Error reading symbol"), - client.readContract({ - address: tokenContractAddress as Address, - abi: ERC20_ABI, - functionName: "decimals", - }).catch(() => "Error reading decimals"), + client + .readContract({ + address: tokenContractAddress as Address, + abi: ERC20_ABI, + functionName: "name", + }) + .catch(() => "Error reading name"), + client + .readContract({ + address: tokenContractAddress as Address, + abi: ERC20_ABI, + functionName: "symbol", + }) + .catch(() => "Error reading symbol"), + client + .readContract({ + address: tokenContractAddress as Address, + abi: ERC20_ABI, + functionName: "decimals", + }) + .catch(() => "Error reading decimals"), ]); console.log("Token contract debug info:", { @@ -533,18 +551,22 @@ const DepositView: React.FC = ({ symbol: "ETH", }, rpcUrls: { - default: { http: [strategyConfig.rpc || "https://base.llamarpc.com"] }, - public: { http: [strategyConfig.rpc || "https://base.llamarpc.com"] }, + default: { + http: [strategyConfig.rpc || "https://base.llamarpc.com"], + }, + public: { + http: [strategyConfig.rpc || "https://base.llamarpc.com"], + }, }, }, }); // Get bridge wildcard based on target chain const bridgeWildCard = getBridgeWildCard(targetChain); - + // Convert amount to uint96 for previewFee const shareAmount = amount as unknown as bigint; - + // Call previewFee function with exact parameters from your example const fee = await client.readContract({ address: vaultContractAddress as Address, @@ -554,7 +576,7 @@ const DepositView: React.FC = ({ shareAmount, // shareAmount (uint96) address as Address, // to address bridgeWildCard, // bridgeWildCard bytes - "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" as Address // feeToken (ETH address) + "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" as Address, // feeToken (ETH address) ], }); @@ -563,7 +585,7 @@ const DepositView: React.FC = ({ to: address, bridgeWildCard, feeToken: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", - calculatedFee: fee.toString() + calculatedFee: fee.toString(), }); setBridgeFee(formatUnits(fee as bigint, 18)); @@ -621,8 +643,13 @@ const DepositView: React.FC = ({ throw new Error("Invalid amount"); } - const roundedAmount = Math.round(amountFloat * Math.pow(10, depositTokenDecimals)) / Math.pow(10, depositTokenDecimals); - const amountInWei = parseUnits(roundedAmount.toFixed(depositTokenDecimals), depositTokenDecimals); + const roundedAmount = + Math.round(amountFloat * Math.pow(10, depositTokenDecimals)) / + Math.pow(10, depositTokenDecimals); + const amountInWei = parseUnits( + roundedAmount.toFixed(depositTokenDecimals), + depositTokenDecimals + ); // First approve USDS for the boring vault const boringVaultAddress = strategyConfig.boringVaultAddress; @@ -631,18 +658,22 @@ const DepositView: React.FC = ({ } // Check if we need approval - const currentAllowance = allowance ? BigInt(allowance.toString()) : BigInt(0); + const currentAllowance = allowance + ? BigInt(allowance.toString()) + : BigInt(0); const needsApproval = currentAllowance < amountInWei; if (allowance === undefined) { - setErrorMessage('Unable to fetch allowance. Please check your network and try again.'); + setErrorMessage( + "Unable to fetch allowance. Please check your network and try again." + ); setIsWaitingForSignature(false); return; } // Step 1: Approve USDS for boring vault if needed if (needsApproval && !isApproved && !isApproving) { - console.log('Calling approve function...'); + console.log("Calling approve function..."); setIsApproving(true); try { const approveTx = await approve({ @@ -677,20 +708,22 @@ const DepositView: React.FC = ({ setIsDepositing(true); // Calculate minimum mint amount based on slippage - const slippageAmount = amountInWei * BigInt(Math.floor(parseFloat(slippage) * 10000)) / BigInt(10000); + const slippageAmount = + (amountInWei * BigInt(Math.floor(parseFloat(slippage) * 10000))) / + BigInt(10000); // const minimumMint = amountInWei - slippageAmount; const minimumMint = amountInWei - slippageAmount; if (isMultiChain) { // Preview bridge fee before proceeding await previewBridgeFee(amountInWei); - + // Get bridge wildcard const bridgeWildCard = getBridgeWildCard(targetChain); - + // Convert bridge fee to wei const bridgeFeeWei = parseEther(bridgeFee); - + // Proceed with multi-chain deposit console.log("Sending multi-chain deposit transaction:", { contract: vaultContractAddress, @@ -712,7 +745,7 @@ const DepositView: React.FC = ({ address as Address, bridgeWildCard, "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" as Address, // ETH address - bridgeFeeWei // Use the calculated bridge fee + bridgeFeeWei, // Use the calculated bridge fee ], chainId: 8453, account: address as Address, @@ -772,14 +805,15 @@ const DepositView: React.FC = ({ setIsLoadingBalance(true); try { // Use correct USDS token contract address on Base - const tokenContractAddress = "0x820C137fa70C8691f0e44Dc420a5e53c168921Dc" as Address; + const tokenContractAddress = + "0x820C137fa70C8691f0e44Dc420a5e53c168921Dc" as Address; const rpcUrl = "https://base.llamarpc.com"; console.log("Fetching balance for:", { tokenContract: tokenContractAddress, userAddress: address, network: "base", - rpc: rpcUrl + rpc: rpcUrl, }); const client = createPublicClient({ @@ -803,21 +837,27 @@ const DepositView: React.FC = ({ // First try to get token info to verify contract try { const [name, symbol, decimals] = await Promise.all([ - client.readContract({ - address: tokenContractAddress, - abi: ERC20_ABI, - functionName: "name", - }).catch(() => "Error reading name"), - client.readContract({ - address: tokenContractAddress, - abi: ERC20_ABI, - functionName: "symbol", - }).catch(() => "Error reading symbol"), - client.readContract({ - address: tokenContractAddress, - abi: ERC20_ABI, - functionName: "decimals", - }).catch(() => "Error reading decimals"), + client + .readContract({ + address: tokenContractAddress, + abi: ERC20_ABI, + functionName: "name", + }) + .catch(() => "Error reading name"), + client + .readContract({ + address: tokenContractAddress, + abi: ERC20_ABI, + functionName: "symbol", + }) + .catch(() => "Error reading symbol"), + client + .readContract({ + address: tokenContractAddress, + abi: ERC20_ABI, + functionName: "decimals", + }) + .catch(() => "Error reading decimals"), ]); console.log("Token contract info:", { @@ -832,23 +872,27 @@ const DepositView: React.FC = ({ // Then try to get balance const [balanceResult, decimalsResult] = await Promise.all([ - client.readContract({ - address: tokenContractAddress, - abi: ERC20_ABI, - functionName: "balanceOf", - args: [address as Address], - }).catch(error => { - console.error("Error reading balance:", error); - return BigInt(0); - }), - client.readContract({ - address: tokenContractAddress, - abi: ERC20_ABI, - functionName: "decimals", - }).catch(error => { - console.error("Error reading decimals:", error); - return 6; // Default to 6 decimals for USDS - }), + client + .readContract({ + address: tokenContractAddress, + abi: ERC20_ABI, + functionName: "balanceOf", + args: [address as Address], + }) + .catch((error) => { + console.error("Error reading balance:", error); + return BigInt(0); + }), + client + .readContract({ + address: tokenContractAddress, + abi: ERC20_ABI, + functionName: "decimals", + }) + .catch((error) => { + console.error("Error reading decimals:", error); + return 6; // Default to 6 decimals for USDS + }), ]); console.log("Balance results:", { @@ -901,7 +945,10 @@ const DepositView: React.FC = ({ decimals: strategyConfig.deposit_token_decimal || 6, }); } - if (strategyConfig.deposit_token_2 && strategyConfig.deposit_token_contract_2) { + if ( + strategyConfig.deposit_token_2 && + strategyConfig.deposit_token_contract_2 + ) { options.push({ name: strategyConfig.deposit_token_2, contract: strategyConfig.deposit_token_contract_2, @@ -909,7 +956,10 @@ const DepositView: React.FC = ({ decimals: strategyConfig.deposit_token_decimal_2 || 6, }); } - if (strategyConfig.deposit_token_3 && strategyConfig.deposit_token_contract_3) { + if ( + strategyConfig.deposit_token_3 && + strategyConfig.deposit_token_contract_3 + ) { options.push({ name: strategyConfig.deposit_token_3, contract: strategyConfig.deposit_token_contract_3, @@ -923,6 +973,30 @@ const DepositView: React.FC = ({ const [selectedAssetIdx, setSelectedAssetIdx] = useState(0); const selectedAssetOption = assetOptions[selectedAssetIdx] || assetOptions[0]; + // Add effect to switch network when target chain changes + useEffect(() => { + if (isMultiChain && targetChain && switchChain) { + const chainId = getChainId(targetChain); + if (chainId) { + switchChain({ chainId }); + } + } + }, [targetChain, isMultiChain, switchChain]); + + // Helper function to get chain ID + const getChainId = (chain: string): number | undefined => { + switch (chain) { + case "arbitrum": + return 42161; + case "optimism": + return 10; + case "ethereum": + return 1; + default: + return undefined; + } + }; + return (
{depositSuccess ? ( @@ -1042,7 +1116,9 @@ const DepositView: React.FC = ({
@@ -1189,7 +1267,9 @@ const DepositView: React.FC = ({ /> - You need to have enough ETH in your wallet to cover the bridge fee. The fee will be paid in ETH along with your deposit. + You need to have enough ETH in your wallet to cover + the bridge fee. The fee will be paid in ETH along + with your deposit.
From ec6f236890f607a6d83b9674faa10816d563449d Mon Sep 17 00:00:00 2001 From: Kraznik <103571066+Kraznik@users.noreply.github.com> Date: Sun, 1 Jun 2025 19:54:53 -0500 Subject: [PATCH 10/22] fix: multiChain toggle off switch back to chain id 8453 --- src/components/deposit-view.tsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/components/deposit-view.tsx b/src/components/deposit-view.tsx index 661ab13..0a98b40 100644 --- a/src/components/deposit-view.tsx +++ b/src/components/deposit-view.tsx @@ -975,10 +975,15 @@ const DepositView: React.FC = ({ // Add effect to switch network when target chain changes useEffect(() => { - if (isMultiChain && targetChain && switchChain) { - const chainId = getChainId(targetChain); - if (chainId) { - switchChain({ chainId }); + if (switchChain) { + if (isMultiChain && targetChain) { + const chainId = getChainId(targetChain); + if (chainId) { + switchChain({ chainId }); + } + } else if (!isMultiChain) { + // Always switch to Base when multi-chain is off + switchChain({ chainId: 8453 }); } } }, [targetChain, isMultiChain, switchChain]); From b73fc22ef4f74330c206849f78f5baf889eb3b9f Mon Sep 17 00:00:00 2001 From: Kraznik <103571066+Kraznik@users.noreply.github.com> Date: Sun, 1 Jun 2025 22:03:07 -0500 Subject: [PATCH 11/22] fix: balance fetch for deposit assest and images --- public/images/icons/sUSDS.svg | 10 +++ public/images/icons/usds.svg | 26 ++++++ src/components/deposit-view.tsx | 136 ++++++++------------------------ src/config/env.ts | 19 +++-- 4 files changed, 79 insertions(+), 112 deletions(-) create mode 100644 public/images/icons/sUSDS.svg create mode 100644 public/images/icons/usds.svg diff --git a/public/images/icons/sUSDS.svg b/public/images/icons/sUSDS.svg new file mode 100644 index 0000000..66835d0 --- /dev/null +++ b/public/images/icons/sUSDS.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/public/images/icons/usds.svg b/public/images/icons/usds.svg new file mode 100644 index 0000000..d3d5fca --- /dev/null +++ b/public/images/icons/usds.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/components/deposit-view.tsx b/src/components/deposit-view.tsx index 0a98b40..c0224e8 100644 --- a/src/components/deposit-view.tsx +++ b/src/components/deposit-view.tsx @@ -41,11 +41,11 @@ interface StrategyConfig { deposit_token_2?: string; deposit_token_contract_2?: string; deposit_token_image_2?: string; - deposit_token_decimal_2?: number; + deposit_token_2_decimal?: number; deposit_token_3?: string; deposit_token_contract_3?: string; deposit_token_image_3?: string; - deposit_token_decimal_3?: number; + deposit_token_3_decimal?: number; description: string; apy: string; incentives: string; @@ -800,22 +800,14 @@ const DepositView: React.FC = ({ }, [amount, isMultiChain, targetChain]); const fetchBalance = async () => { - if (!address || selectedAsset !== "USD") return; + if (!address) return; setIsLoadingBalance(true); try { - // Use correct USDS token contract address on Base - const tokenContractAddress = - "0x820C137fa70C8691f0e44Dc420a5e53c168921Dc" as Address; + const tokenContractAddress = selectedAssetOption.contract as Address; + const decimals = Number(selectedAssetOption.decimals); const rpcUrl = "https://base.llamarpc.com"; - console.log("Fetching balance for:", { - tokenContract: tokenContractAddress, - userAddress: address, - network: "base", - rpc: rpcUrl, - }); - const client = createPublicClient({ transport: http(rpcUrl), chain: { @@ -834,77 +826,14 @@ const DepositView: React.FC = ({ }, }); - // First try to get token info to verify contract - try { - const [name, symbol, decimals] = await Promise.all([ - client - .readContract({ - address: tokenContractAddress, - abi: ERC20_ABI, - functionName: "name", - }) - .catch(() => "Error reading name"), - client - .readContract({ - address: tokenContractAddress, - abi: ERC20_ABI, - functionName: "symbol", - }) - .catch(() => "Error reading symbol"), - client - .readContract({ - address: tokenContractAddress, - abi: ERC20_ABI, - functionName: "decimals", - }) - .catch(() => "Error reading decimals"), - ]); - - console.log("Token contract info:", { - address: tokenContractAddress, - name, - symbol, - decimals, - }); - } catch (error) { - console.error("Error reading token info:", error); - } - - // Then try to get balance - const [balanceResult, decimalsResult] = await Promise.all([ - client - .readContract({ - address: tokenContractAddress, - abi: ERC20_ABI, - functionName: "balanceOf", - args: [address as Address], - }) - .catch((error) => { - console.error("Error reading balance:", error); - return BigInt(0); - }), - client - .readContract({ - address: tokenContractAddress, - abi: ERC20_ABI, - functionName: "decimals", - }) - .catch((error) => { - console.error("Error reading decimals:", error); - return 6; // Default to 6 decimals for USDS - }), - ]); - - console.log("Balance results:", { - rawBalance: balanceResult?.toString(), - decimals: decimalsResult, + const balanceResult = await client.readContract({ + address: tokenContractAddress, + abi: ERC20_ABI, + functionName: "balanceOf", + args: [address as Address], }); - const formattedBalance = formatUnits( - balanceResult as bigint, - decimalsResult as number - ); - console.log("Formatted balance:", formattedBalance); + const formattedBalance = Number(formatUnits(balanceResult as bigint, decimals)).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 }); setBalance(formattedBalance); } catch (error) { console.error("Error fetching balance:", error); @@ -914,25 +843,6 @@ const DepositView: React.FC = ({ } }; - // Initial balance fetch - useEffect(() => { - if (address && selectedAsset === "USD") { - fetchBalance(); - } - }, [address, selectedAsset, duration, strategy]); - - const handleMaxClick = () => { - setAmount(balance); - }; - - const handleAmountChange = (e: React.ChangeEvent) => { - const value = e.target.value; - if (/^\d*\.?\d*$/.test(value)) { - // Only allow numbers and one decimal point - setAmount(value); - } - }; - // --- Asset selection state --- // Parse all available deposit assets from strategyConfig const assetOptions = useMemo(() => { @@ -942,7 +852,7 @@ const DepositView: React.FC = ({ name: strategyConfig.deposit_token, contract: strategyConfig.deposit_token_contract, image: strategyConfig.deposit_token_image, - decimals: strategyConfig.deposit_token_decimal || 6, + decimals: strategyConfig.deposit_token_decimal, }); } if ( @@ -953,7 +863,7 @@ const DepositView: React.FC = ({ name: strategyConfig.deposit_token_2, contract: strategyConfig.deposit_token_contract_2, image: strategyConfig.deposit_token_image_2, - decimals: strategyConfig.deposit_token_decimal_2 || 6, + decimals: strategyConfig.deposit_token_2_decimal, }); } if ( @@ -964,7 +874,7 @@ const DepositView: React.FC = ({ name: strategyConfig.deposit_token_3, contract: strategyConfig.deposit_token_contract_3, image: strategyConfig.deposit_token_image_3, - decimals: strategyConfig.deposit_token_decimal_3 || 6, + decimals: strategyConfig.deposit_token_3_decimal, }); } return options; @@ -1002,6 +912,24 @@ const DepositView: React.FC = ({ } }; + useEffect(() => { + if (address) { + fetchBalance(); + } + }, [address, selectedAssetIdx, selectedAsset, duration, strategy]); + + const handleMaxClick = () => { + setAmount(balance); + }; + + const handleAmountChange = (e: React.ChangeEvent) => { + const value = e.target.value; + if (/^\d*\.?\d*$/.test(value)) { + // Only allow numbers and one decimal point + setAmount(value); + } + }; + return (
{depositSuccess ? ( diff --git a/src/config/env.ts b/src/config/env.ts index 5acc681..880a715 100644 --- a/src/config/env.ts +++ b/src/config/env.ts @@ -4,18 +4,21 @@ export const USD_STRATEGIES = { network: "base", contract: "0xaefc11908fF97c335D16bdf9F2Bf720817423825", boringVaultAddress: "0x279CAD277447965AF3d24a78197aad1B02a2c589", - solverAddress:"0x1d82e9bCc8F325caBBca6E6A3B287fE586536805", - shareAddress:"0x279CAD277447965AF3d24a78197aad1B02a2c589", - deposit_token: "USDS", - deposit_token_contract: "0x820C137fa70C8691f0e44Dc420a5e53c168921Dc", - deposit_token_decimal: 18, + solverAddress: "0xF632c10b19f2a0451cD4A653fC9ca0c15eA1040b", + shareAddress: "0x279CAD277447965AF3d24a78197aad1B02a2c589", + shareAddress_token_decimal: 6, + deposit_token: "USDC", + deposit_token_contract: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", + deposit_token_decimal: 6, deposit_token_image: "/images/icons/usdc.svg", deposit_token_2: "USDS", + deposit_token_2_decimal: 18, deposit_token_contract_2: "0x820C137fa70C8691f0e44Dc420a5e53c168921Dc", - deposit_token_image_2: "/images/icons/usdc.svg", + deposit_token_image_2: "/images/icons/usds.svg", deposit_token_3: "sUSDS", - deposit_token_contract_3: "0x820C137fa70C8691f0e44Dc420a5e53c168921Dc", - deposit_token_image_3: "/images/icons/usdc.svg", + deposit_token_3_decimal: 18, + deposit_token_contract_3: "0x5875eEE11Cf8398102FdAd704C9E96607675467a", + deposit_token_image_3: "/images/icons/sUSDS.svg", description: "Perpetual stable USD strategy on Sonic network", apy: "4.5%", incentives: "None", From 1e49f5e727acf294b6a3f6daf552136903e1b98e Mon Sep 17 00:00:00 2001 From: Kraznik <103571066+Kraznik@users.noreply.github.com> Date: Sun, 1 Jun 2025 22:11:32 -0500 Subject: [PATCH 12/22] add config deposit assets for multiple chains --- src/components/deposit-view.tsx | 124 +++++++++++++++----------------- src/config/env.ts | 79 ++++++++++++++++---- src/pages/earn-subpage.tsx | 100 ++++++++++++++++---------- 3 files changed, 186 insertions(+), 117 deletions(-) diff --git a/src/components/deposit-view.tsx b/src/components/deposit-view.tsx index c0224e8..4f89a1b 100644 --- a/src/components/deposit-view.tsx +++ b/src/components/deposit-view.tsx @@ -32,29 +32,39 @@ type StrategyType = "STABLE" | "INCENTIVE"; interface StrategyConfig { network: string; contract: string; - deposit_token: string; - deposit_contract: string; - deposit_token_contract?: string; // Optional field for backward compatibility - deposit_token_image?: string; // Optional field for deposit token image - deposit_token_decimal?: number; // Optional field for deposit token decimal - // Add support for multiple deposit tokens - deposit_token_2?: string; - deposit_token_contract_2?: string; - deposit_token_image_2?: string; - deposit_token_2_decimal?: number; - deposit_token_3?: string; - deposit_token_contract_3?: string; - deposit_token_image_3?: string; - deposit_token_3_decimal?: number; + base: { + tokens: Array<{ + name: string; + contract: string; + decimal: number; + image: string; + }>; + }; + ethereum: { + tokens: Array<{ + name: string; + contract: string; + decimal: number; + image: string; + }>; + }; + arbitrum: { + tokens: Array<{ + name: string; + contract: string; + decimal: number; + image: string; + }>; + }; description: string; apy: string; incentives: string; tvl: string; - rpc?: string; // Optional field for backward compatibility + rpc?: string; show_cap: boolean; filled_cap: string; cap_limit: string; - boringVaultAddress?: string; // Optional field for boring vault address + boringVaultAddress?: string; } // ERC20 ABI for token operations @@ -275,10 +285,36 @@ const DepositView: React.FC = ({ strategyConfigs[selectedAsset as keyof typeof strategyConfigs]; // Now access the specific duration and strategy type - const strategyConfig = (assetStrategies as any)[duration][ // Cast assetStrategies to any for indexing + const strategyConfig = (assetStrategies as any)[duration][ strategy === "stable" ? "STABLE" : "INCENTIVE" ] as StrategyConfig; + // Get the appropriate network tokens based on the current network + const getNetworkTokens = () => { + switch (strategyConfig.network) { + case "base": + return strategyConfig.base.tokens; + case "ethereum": + return strategyConfig.ethereum.tokens; + case "arbitrum": + return strategyConfig.arbitrum.tokens; + default: + return strategyConfig.base.tokens; + } + }; + + // Parse all available deposit assets from strategyConfig + const assetOptions = useMemo(() => { + return getNetworkTokens(); + }, [strategyConfig]); + + const [selectedAssetIdx, setSelectedAssetIdx] = useState(0); + const selectedAssetOption = assetOptions[selectedAssetIdx] || assetOptions[0]; + + // Update token contract address and decimals + const tokenContractAddress = selectedAssetOption.contract; + const depositTokenDecimals = selectedAssetOption.decimal; + // Calculate deposit cap values from env config const showDepositCap = strategyConfig.show_cap; const depositCap = useMemo( @@ -305,14 +341,6 @@ const DepositView: React.FC = ({ const { address } = useAccount(); - // Get deposit token name, image, and decimals (only if defined in config) - const depositToken = strategyConfig.deposit_token; - const depositTokenImage = strategyConfig.deposit_token_image; - const depositTokenDecimals = strategyConfig.deposit_token_decimal || 6; - - // USDC token contract for approvals - const tokenContractAddress = - strategyConfig.deposit_token_contract || strategyConfig.deposit_contract; // Vault contract for deposits const vaultContractAddress = strategyConfig.contract; @@ -476,10 +504,10 @@ const DepositView: React.FC = ({ console.log("Deposit successful!", { hash: transactionHash, amount, - token: depositToken, + token: selectedAssetOption.name, }); } - }, [isDepositSuccess, transactionHash, amount, depositToken]); + }, [isDepositSuccess, transactionHash, amount, selectedAssetOption.name]); useEffect(() => { const checkApproval = async () => { @@ -805,7 +833,7 @@ const DepositView: React.FC = ({ setIsLoadingBalance(true); try { const tokenContractAddress = selectedAssetOption.contract as Address; - const decimals = Number(selectedAssetOption.decimals); + const decimals = Number(selectedAssetOption.decimal); const rpcUrl = "https://base.llamarpc.com"; const client = createPublicClient({ @@ -843,46 +871,6 @@ const DepositView: React.FC = ({ } }; - // --- Asset selection state --- - // Parse all available deposit assets from strategyConfig - const assetOptions = useMemo(() => { - const options = []; - if (strategyConfig.deposit_token && strategyConfig.deposit_token_contract) { - options.push({ - name: strategyConfig.deposit_token, - contract: strategyConfig.deposit_token_contract, - image: strategyConfig.deposit_token_image, - decimals: strategyConfig.deposit_token_decimal, - }); - } - if ( - strategyConfig.deposit_token_2 && - strategyConfig.deposit_token_contract_2 - ) { - options.push({ - name: strategyConfig.deposit_token_2, - contract: strategyConfig.deposit_token_contract_2, - image: strategyConfig.deposit_token_image_2, - decimals: strategyConfig.deposit_token_2_decimal, - }); - } - if ( - strategyConfig.deposit_token_3 && - strategyConfig.deposit_token_contract_3 - ) { - options.push({ - name: strategyConfig.deposit_token_3, - contract: strategyConfig.deposit_token_contract_3, - image: strategyConfig.deposit_token_image_3, - decimals: strategyConfig.deposit_token_3_decimal, - }); - } - return options; - }, [strategyConfig]); - - const [selectedAssetIdx, setSelectedAssetIdx] = useState(0); - const selectedAssetOption = assetOptions[selectedAssetIdx] || assetOptions[0]; - // Add effect to switch network when target chain changes useEffect(() => { if (switchChain) { diff --git a/src/config/env.ts b/src/config/env.ts index 880a715..f220d8d 100644 --- a/src/config/env.ts +++ b/src/config/env.ts @@ -7,18 +7,73 @@ export const USD_STRATEGIES = { solverAddress: "0xF632c10b19f2a0451cD4A653fC9ca0c15eA1040b", shareAddress: "0x279CAD277447965AF3d24a78197aad1B02a2c589", shareAddress_token_decimal: 6, - deposit_token: "USDC", - deposit_token_contract: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", - deposit_token_decimal: 6, - deposit_token_image: "/images/icons/usdc.svg", - deposit_token_2: "USDS", - deposit_token_2_decimal: 18, - deposit_token_contract_2: "0x820C137fa70C8691f0e44Dc420a5e53c168921Dc", - deposit_token_image_2: "/images/icons/usds.svg", - deposit_token_3: "sUSDS", - deposit_token_3_decimal: 18, - deposit_token_contract_3: "0x5875eEE11Cf8398102FdAd704C9E96607675467a", - deposit_token_image_3: "/images/icons/sUSDS.svg", + + // Base Network Configuration + base: { + tokens: [ + { + name: "USDC", + contract: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", + decimal: 6, + image: "/images/icons/usdc.svg" + }, + { + name: "USDS", + contract: "0x820C137fa70C8691f0e44Dc420a5e53c168921Dc", + decimal: 18, + image: "/images/icons/usds.svg" + }, + { + name: "sUSDS", + contract: "0x5875eEE11Cf8398102FdAd704C9E96607675467a", + decimal: 18, + image: "/images/icons/sUSDS.svg" + } + ] + }, + + // Ethereum Mainnet Configuration + ethereum: { + tokens: [ + { + name: "USDC", + contract: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + decimal: 6, + image: "/images/icons/usdc.svg" + }, + { + name: "USDT", + contract: "0xdAC17F958D2ee523a2206206994597C13D831ec7", + decimal: 6, + image: "/images/icons/usdt.svg" + }, + { + name: "USDS", + contract: "0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD", + decimal: 18, + image: "/images/icons/usds.svg" + } + ] + }, + + // Arbitrum Configuration + arbitrum: { + tokens: [ + { + name: "USDC", + contract: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", + decimal: 6, + image: "/images/icons/usdc.svg" + }, + { + name: "USDS", + contract: "0x6491c05A82219b8D1479057361ff1654749b876b", + decimal: 18, + image: "/images/icons/USDS.svg" + } + ] + }, + description: "Perpetual stable USD strategy on Sonic network", apy: "4.5%", incentives: "None", diff --git a/src/pages/earn-subpage.tsx b/src/pages/earn-subpage.tsx index 7071bf6..933a78b 100644 --- a/src/pages/earn-subpage.tsx +++ b/src/pages/earn-subpage.tsx @@ -6,21 +6,53 @@ import { USD_STRATEGIES, BTC_STRATEGIES, ETH_STRATEGIES } from "../config/env"; type DurationType = "30_DAYS" | "60_DAYS" | "180_DAYS" | "PERPETUAL_DURATION"; type StrategyType = "STABLE" | "INCENTIVE"; -interface StrategyConfig { +interface TokenConfig { + name: string; + contract: string; + decimal: number; + image: string; +} + +interface NetworkConfig { + tokens: TokenConfig[]; +} + +interface BaseStrategyConfig { network: string; contract: string; - deposit_token: string; - deposit_token_contract: string; + boringVaultAddress: string; + solverAddress: string; + shareAddress: string; + shareAddress_token_decimal: number; + base: NetworkConfig; + ethereum: NetworkConfig; + arbitrum: NetworkConfig; description: string; apy: string; incentives: string; tvl: string; rpc: string; + show_cap: boolean; + filled_cap: string; + cap_limit: string; +} + +interface IncentiveStrategyConfig { + network: string; + comingSoon: boolean; + contract: string; + deposit_token: string; + deposit_token_contract: string; + tvl: string; + rpc: string; + description: string; + apy: string; + incentives: string; } interface StrategyDuration { - STABLE: StrategyConfig; - INCENTIVE: StrategyConfig; + STABLE: BaseStrategyConfig; + INCENTIVE: IncentiveStrategyConfig; } interface StrategyAsset { @@ -65,19 +97,17 @@ interface YieldSubpageProps { const getStrategyInfo = (duration: DurationType): StrategyData => { const getAssetStrategies = (asset: AssetType) => { - const strategies: StrategyAsset = { - USD: USD_STRATEGIES, - BTC: BTC_STRATEGIES, - ETH: ETH_STRATEGIES, - }[asset]; + const strategies: Record> = { + USD: USD_STRATEGIES as Record, + BTC: BTC_STRATEGIES as Record, + ETH: ETH_STRATEGIES as Record, + }; - // Use the duration as is since it matches the keys in env.ts - const durationKey = duration; - const strategy = strategies[durationKey]; + const strategy = strategies[asset][duration]; if (!strategy) { console.error( - `No strategy found for ${asset} with duration ${durationKey}` + `No strategy found for ${asset} with duration ${duration}` ); return { stable: { @@ -112,7 +142,7 @@ const getStrategyInfo = (duration: DurationType): StrategyData => { value: strategy.INCENTIVE.apy, info: strategy.INCENTIVE.incentives, }, - comingSoon: (strategy.INCENTIVE as any).comingSoon === true, + comingSoon: strategy.INCENTIVE.comingSoon, }, }; }; @@ -120,45 +150,41 @@ const getStrategyInfo = (duration: DurationType): StrategyData => { return { stable: { USD: getAssetStrategies("USD").stable, - BTC: getAssetStrategies("BTC").stable, ETH: getAssetStrategies("ETH").stable, + BTC: getAssetStrategies("BTC").stable, }, incentives: { USD: getAssetStrategies("USD").incentives, - BTC: getAssetStrategies("BTC").incentives, ETH: getAssetStrategies("ETH").incentives, + BTC: getAssetStrategies("BTC").incentives, }, }; }; const YieldSubpage: React.FC = ({ depositParams }) => { const [selectedAsset, setSelectedAsset] = useState(null); - const [selectedStrategy, setSelectedStrategy] = useState(null); + const [selectedStrategy, setSelectedStrategy] = useState( + null + ); - // Add effect to handle URL parameters or parent navigation useEffect(() => { - if (depositParams?.asset && depositParams?.duration) { + if (depositParams) { + const apy = + getStrategyInfo(depositParams.duration as DurationType)[ + depositParams.strategy === "stable" ? "stable" : "incentives" + ][depositParams.asset as AssetType].apy.value; + setSelectedAsset({ asset: depositParams.asset, duration: depositParams.duration as DurationType, }); - if (depositParams.strategy) { - const strategyInfo = getStrategyInfo( - depositParams.duration as DurationType - ); - const apy = - strategyInfo[ - depositParams.strategy === "stable" ? "stable" : "incentives" - ][depositParams.asset as AssetType].apy.value; - - setSelectedStrategy({ - type: depositParams.strategy as "stable" | "incentive", - asset: depositParams.asset, - duration: depositParams.duration as DurationType, - apy, - }); - } + setSelectedStrategy({ + type: depositParams.strategy as "stable" | "incentive", + asset: depositParams.asset, + duration: depositParams.duration as DurationType, + apy, + }); } }, [depositParams]); @@ -319,7 +345,7 @@ const YieldSubpage: React.FC = ({ depositParams }) => { onDurationSelect={(duration: DurationType) => handleDurationSelect("USD", duration) } - availableDurations={Object.keys(USD_STRATEGIES) as DurationType[]} + availableDurations={["PERPETUAL_DURATION"]} /> Date: Sun, 1 Jun 2025 22:14:06 -0500 Subject: [PATCH 13/22] fix assest dropdown based on chain selected --- src/components/deposit-view.tsx | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/components/deposit-view.tsx b/src/components/deposit-view.tsx index 4f89a1b..4690b87 100644 --- a/src/components/deposit-view.tsx +++ b/src/components/deposit-view.tsx @@ -289,24 +289,23 @@ const DepositView: React.FC = ({ strategy === "stable" ? "STABLE" : "INCENTIVE" ] as StrategyConfig; - // Get the appropriate network tokens based on the current network + // Get the appropriate network tokens based on the selected target chain const getNetworkTokens = () => { - switch (strategyConfig.network) { - case "base": - return strategyConfig.base.tokens; - case "ethereum": - return strategyConfig.ethereum.tokens; + switch (targetChain) { case "arbitrum": return strategyConfig.arbitrum.tokens; + case "ethereum": + return strategyConfig.ethereum.tokens; + case "base": default: return strategyConfig.base.tokens; } }; - // Parse all available deposit assets from strategyConfig + // Parse all available deposit assets from strategyConfig, filtered by targetChain const assetOptions = useMemo(() => { return getNetworkTokens(); - }, [strategyConfig]); + }, [strategyConfig, targetChain]); const [selectedAssetIdx, setSelectedAssetIdx] = useState(0); const selectedAssetOption = assetOptions[selectedAssetIdx] || assetOptions[0]; From 7b34a31291c6dc95e667d77057411cbaa0c388d3 Mon Sep 17 00:00:00 2001 From: Kraznik <103571066+Kraznik@users.noreply.github.com> Date: Sun, 1 Jun 2025 22:21:45 -0500 Subject: [PATCH 14/22] fetch token balace when selected diff chain to deposit --- public/images/icons/usdt.svg | 7 +++ src/components/deposit-view.tsx | 82 ++++++++++++++++++++++++++------- 2 files changed, 72 insertions(+), 17 deletions(-) create mode 100644 public/images/icons/usdt.svg diff --git a/public/images/icons/usdt.svg b/public/images/icons/usdt.svg new file mode 100644 index 0000000..4174696 --- /dev/null +++ b/public/images/icons/usdt.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/components/deposit-view.tsx b/src/components/deposit-view.tsx index 4690b87..66c128e 100644 --- a/src/components/deposit-view.tsx +++ b/src/components/deposit-view.tsx @@ -826,6 +826,55 @@ const DepositView: React.FC = ({ } }, [amount, isMultiChain, targetChain]); + // Helper to get correct RPC and chain config for each chain + const getChainConfig = (chain: string) => { + switch (chain) { + case "arbitrum": + return { + rpcUrl: "https://arbitrum.drpc.org", + chain: { + id: 42161, + name: "Arbitrum", + network: "arbitrum", + nativeCurrency: { decimals: 18, name: "Ether", symbol: "ETH" }, + rpcUrls: { + default: { http: ["https://arbitrum.drpc.org"] }, + public: { http: ["https://arbitrum.drpc.org"] }, + }, + }, + }; + case "ethereum": + return { + rpcUrl: "https://eth.llamarpc.com", + chain: { + id: 1, + name: "Ethereum", + network: "ethereum", + nativeCurrency: { decimals: 18, name: "Ether", symbol: "ETH" }, + rpcUrls: { + default: { http: ["https://eth.llamarpc.com"] }, + public: { http: ["https://eth.llamarpc.com"] }, + }, + }, + }; + case "base": + default: + return { + rpcUrl: "https://base.llamarpc.com", + chain: { + id: 8453, + name: "Base", + network: "base", + nativeCurrency: { decimals: 18, name: "Ethereum", symbol: "ETH" }, + rpcUrls: { + default: { http: ["https://base.llamarpc.com"] }, + public: { http: ["https://base.llamarpc.com"] }, + }, + }, + }; + } + }; + const fetchBalance = async () => { if (!address) return; @@ -833,24 +882,11 @@ const DepositView: React.FC = ({ try { const tokenContractAddress = selectedAssetOption.contract as Address; const decimals = Number(selectedAssetOption.decimal); - const rpcUrl = "https://base.llamarpc.com"; + const { rpcUrl, chain } = getChainConfig(targetChain); const client = createPublicClient({ transport: http(rpcUrl), - chain: { - id: 8453, - name: "Base", - network: "base", - nativeCurrency: { - decimals: 18, - name: "Ethereum", - symbol: "ETH", - }, - rpcUrls: { - default: { http: [rpcUrl] }, - public: { http: [rpcUrl] }, - }, - }, + chain, }); const balanceResult = await client.readContract({ @@ -860,7 +896,12 @@ const DepositView: React.FC = ({ args: [address as Address], }); - const formattedBalance = Number(formatUnits(balanceResult as bigint, decimals)).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 }); + const formattedBalance = Number( + formatUnits(balanceResult as bigint, decimals) + ).toLocaleString(undefined, { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }); setBalance(formattedBalance); } catch (error) { console.error("Error fetching balance:", error); @@ -903,7 +944,14 @@ const DepositView: React.FC = ({ if (address) { fetchBalance(); } - }, [address, selectedAssetIdx, selectedAsset, duration, strategy]); + }, [ + address, + selectedAssetIdx, + selectedAsset, + duration, + strategy, + targetChain, + ]); const handleMaxClick = () => { setAmount(balance); From 4ae41428ee9ac39cd67408265dff8cd2c2eec893 Mon Sep 17 00:00:00 2001 From: Kraznik <103571066+Kraznik@users.noreply.github.com> Date: Sun, 1 Jun 2025 22:31:41 -0500 Subject: [PATCH 15/22] remove errror.message when trx fails --- src/components/deposit-view.tsx | 13 ++- src/utils/balanceCalculator.ts | 182 ++++++++++++++++++++++++++++++++ 2 files changed, 193 insertions(+), 2 deletions(-) create mode 100644 src/utils/balanceCalculator.ts diff --git a/src/components/deposit-view.tsx b/src/components/deposit-view.tsx index 66c128e..6d52daf 100644 --- a/src/components/deposit-view.tsx +++ b/src/components/deposit-view.tsx @@ -15,6 +15,7 @@ import { useTransaction, useReadContracts, useSwitchChain, + useChainId } from "wagmi"; import { USD_STRATEGIES, BTC_STRATEGIES, ETH_STRATEGIES } from "../config/env"; import { @@ -272,6 +273,7 @@ const DepositView: React.FC = ({ const [isLoadingFee, setIsLoadingFee] = useState(false); const [targetChain, setTargetChain] = useState("arbitrum"); // Default target chain const { switchChain } = useSwitchChain(); + const { chain } = useAccount(); // Get connected chain info // Get strategy config based on asset type const strategyConfigs = { @@ -809,10 +811,10 @@ const DepositView: React.FC = ({ setErrorMessage("Please approve the token spending first"); } } catch (error: any) { - console.error("Transaction failed:", error); + console.error("TRX failed:", transactionHash); setIsApproving(false); setIsDepositing(false); - setErrorMessage(error.message || "Transaction failed"); + setErrorMessage(`TRX failed${transactionHash ? ': ' + transactionHash : ''}`); } finally { setIsWaitingForSignature(false); } @@ -953,6 +955,13 @@ const DepositView: React.FC = ({ targetChain, ]); + // Update targetChain when connected chain changes + useEffect(() => { + if (chain) { + setTargetChain(chain.name.toLowerCase()); + } + }, [chain]); + const handleMaxClick = () => { setAmount(balance); }; diff --git a/src/utils/balanceCalculator.ts b/src/utils/balanceCalculator.ts new file mode 100644 index 0000000..13e7ef9 --- /dev/null +++ b/src/utils/balanceCalculator.ts @@ -0,0 +1,182 @@ +import { ethers } from 'ethers'; +import { USD_STRATEGIES } from '../config/env'; + +interface TokenBalance { + chain: string; + token: string; + balance: number; + usdValue: number; +} + +interface ChainConfig { + tokens: { + name: string; + contract: string; + decimal: number; + image: string; + }[]; + rpc: string; +} + +interface StrategyConfig { + network: string; + contract: string; + boringVaultAddress: string; + solverAddress: string; + shareAddress: string; + shareAddress_token_decimal: number; + base: ChainConfig; + ethereum: ChainConfig; + arbitrum: ChainConfig; + description: string; + apy: string; + incentives: string; + cap_limit: string; + filled_cap: string; + show_cap: boolean; + tvl: string; + rpc: string; +} + +// Cache for providers to avoid creating new ones for each request +const providerCache: { [key: string]: ethers.JsonRpcProvider } = {}; + +function getProvider(rpcUrl: string): ethers.JsonRpcProvider { + if (!providerCache[rpcUrl]) { + providerCache[rpcUrl] = new ethers.JsonRpcProvider(rpcUrl); + } + return providerCache[rpcUrl]; +} + +export async function calculateTotalBalanceInUSD( + address: string, + provider: ethers.Provider +): Promise { + const balances: TokenBalance[] = []; + const strategy = USD_STRATEGIES.PERPETUAL_DURATION.STABLE as StrategyConfig; + + // Check each chain configuration + const chains = ['base', 'ethereum', 'arbitrum'] as const; + + for (const chain of chains) { + const chainConfig = strategy[chain]; + if (!chainConfig?.tokens) continue; + + const chainProvider = getProvider(chainConfig.rpc); + + for (const token of chainConfig.tokens) { + try { + const tokenContract = new ethers.Contract( + token.contract, + ['function balanceOf(address) view returns (uint256)'], + chainProvider + ); + + const balance = await tokenContract.balanceOf(address); + const formattedBalance = Number(ethers.formatUnits(balance, token.decimal)); + + // For stablecoins, we assume 1:1 USD value + const usdValue = formattedBalance; + + balances.push({ + chain, + token: token.name, + balance: formattedBalance, + usdValue + }); + } catch (error) { + console.error(`Error fetching balance for ${token.name} on ${chain}:`, error); + } + } + } + + // Calculate total USD value + const totalUSDValue = balances.reduce((sum, token) => sum + token.usdValue, 0); + + return totalUSDValue; +} + +// Function to update filled_cap in env.ts +export async function updateFilledCap( + address: string, + provider: ethers.Provider +): Promise { + const totalBalance = await calculateTotalBalanceInUSD(address, provider); + const formattedBalance = totalBalance.toLocaleString('en-US', { + minimumFractionDigits: 0, + maximumFractionDigits: 0 + }); + + // Update the filled_cap in the configuration + USD_STRATEGIES.PERPETUAL_DURATION.STABLE.filled_cap = formattedBalance; +} + +// Function to get real-time filled cap value +export async function getRealTimeFilledCap( + address: string, + provider: ethers.Provider +): Promise { + const totalBalance = await calculateTotalBalanceInUSD(address, provider); + return totalBalance.toLocaleString('en-US', { + minimumFractionDigits: 0, + maximumFractionDigits: 0 + }); +} + +// Function to get real-time filled cap value with caching +let lastFetchedTime = 0; +let cachedFilledCap: string | null = null; +const CACHE_DURATION = 30000; // 30 seconds cache + +export async function getCachedFilledCap( + address: string, + provider: ethers.Provider +): Promise { + const now = Date.now(); + + // Return cached value if it's still valid + if (cachedFilledCap && (now - lastFetchedTime) < CACHE_DURATION) { + return cachedFilledCap; + } + + // Fetch new value + const newValue = await getRealTimeFilledCap(address, provider); + cachedFilledCap = newValue; + lastFetchedTime = now; + + return newValue; +} + +let updateInterval: NodeJS.Timeout | null = null; + +// Function to start periodic updates of filled_cap +export function startFilledCapUpdates( + address: string, + provider: ethers.Provider, + intervalMs: number = 30000 // Default to 30 seconds +): void { + // Clear any existing interval + if (updateInterval) { + clearInterval(updateInterval); + } + + // Initial update + updateFilledCap(address, provider).catch(console.error); + + // Set up periodic updates + updateInterval = setInterval(async () => { + try { + await updateFilledCap(address, provider); + } catch (error) { + console.error('Error updating filled_cap:', error); + } + }, intervalMs); +} + +// Function to stop periodic updates +export function stopFilledCapUpdates(): void { + if (updateInterval) { + clearInterval(updateInterval); + updateInterval = null; + } +} \ No newline at end of file From b69bb8791becb8d630741f65458d601e715759d1 Mon Sep 17 00:00:00 2001 From: Kraznik <103571066+Kraznik@users.noreply.github.com> Date: Sun, 1 Jun 2025 22:58:22 -0500 Subject: [PATCH 16/22] feat add auth session key --- .env.local | 4 +- package-lock.json | 123 +++++++++++++++++++++++++++++++++ package.json | 3 + src/pages/api/check-session.ts | 27 ++++++++ src/pages/api/verify-code.ts | 34 +++++++++ src/pages/page.tsx | 57 +++++++++++++-- 6 files changed, 240 insertions(+), 8 deletions(-) create mode 100644 src/pages/api/check-session.ts create mode 100644 src/pages/api/verify-code.ts diff --git a/.env.local b/.env.local index 008bb05..ef0531d 100644 --- a/.env.local +++ b/.env.local @@ -1,3 +1,5 @@ NEXT_PUBLIC_WC_PROJECT_ID=your_project_id_here NEXT_PUBLIC_ENABLE_TESTNETS=false -NEXT_PUBLIC_BETA_ACCESS_CODE=your_code \ No newline at end of file +NEXT_PUBLIC_BETA_ACCESS_CODE=your_code +JWT_SECRET=your_super_secret_jwt_key +SERVER_SECRET_ACCESS_CODE=code__ \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 99374db..b697638 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,9 @@ "@tanstack/react-query": "^5.55.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "cookie": "^1.0.2", "ethers": "^6.13.5", + "jsonwebtoken": "^9.0.2", "lucide-react": "^0.484.0", "next": "^15.1.4", "react": "^19.0.0", @@ -28,6 +30,7 @@ }, "devDependencies": { "@shadcn/ui": "^0.0.4", + "@types/jsonwebtoken": "^9.0.9", "@types/node": "^20.14.8", "@types/react": "^19.0.6", "autoprefixer": "^10.4.21", @@ -2268,6 +2271,17 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.9.tgz", + "integrity": "sha512-uoe+GxEuHbvy12OUQct2X9JenKM3qAscquYymuQN4fMWG9DBQtykrQEFcAbVACF7qaLw9BePSodUL0kquqBJpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*", + "@types/node": "*" + } + }, "node_modules/@types/ms": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", @@ -3815,6 +3829,12 @@ "ieee754": "^1.2.1" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, "node_modules/bufferutil": { "version": "4.0.9", "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.9.tgz", @@ -4131,6 +4151,15 @@ "dev": true, "license": "MIT" }, + "node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/cookie-es": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-1.2.2.tgz", @@ -4521,6 +4550,15 @@ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "license": "MIT" }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/eciesjs": { "version": "0.4.14", "resolved": "https://registry.npmjs.org/eciesjs/-/eciesjs-0.4.14.tgz", @@ -6989,6 +7027,28 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "license": "MIT", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", @@ -7005,6 +7065,27 @@ "node": ">=4.0" } }, + "node_modules/jwa": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", + "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "license": "MIT", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/keccak": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.4.tgz", @@ -7148,6 +7229,42 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -7156,6 +7273,12 @@ "license": "MIT", "peer": true }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, "node_modules/log-symbols": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-5.1.0.tgz", diff --git a/package.json b/package.json index 370e2c6..05c30f2 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,9 @@ "@tanstack/react-query": "^5.55.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "cookie": "^1.0.2", "ethers": "^6.13.5", + "jsonwebtoken": "^9.0.2", "lucide-react": "^0.484.0", "next": "^15.1.4", "react": "^19.0.0", @@ -29,6 +31,7 @@ }, "devDependencies": { "@shadcn/ui": "^0.0.4", + "@types/jsonwebtoken": "^9.0.9", "@types/node": "^20.14.8", "@types/react": "^19.0.6", "autoprefixer": "^10.4.21", diff --git a/src/pages/api/check-session.ts b/src/pages/api/check-session.ts new file mode 100644 index 0000000..a14a1c2 --- /dev/null +++ b/src/pages/api/check-session.ts @@ -0,0 +1,27 @@ +import { NextApiRequest, NextApiResponse } from 'next'; +import jwt from 'jsonwebtoken'; +import { parse } from 'cookie'; + +const JWT_SECRET = process.env.JWT_SECRET || 'YOUR_DEFAULT_SECRET'; // Use the same secret as verify-code.ts + +export default function handler(req: NextApiRequest, res: NextApiResponse) { + if (req.method !== 'GET') { + return res.status(405).json({ message: 'Method Not Allowed' }); + } + + const cookies = parse(req.headers.cookie || ''); + const token = cookies.auth_token; + + if (!token) { + return res.status(200).json({ isValid: false }); + } + + try { + jwt.verify(token, JWT_SECRET); + // If verification is successful, the token is valid + return res.status(200).json({ isValid: true }); + } catch (error) { + // If verification fails, the token is invalid (expired, tampered, etc.) + return res.status(200).json({ isValid: false }); + } +} \ No newline at end of file diff --git a/src/pages/api/verify-code.ts b/src/pages/api/verify-code.ts new file mode 100644 index 0000000..d9b99cd --- /dev/null +++ b/src/pages/api/verify-code.ts @@ -0,0 +1,34 @@ +import { NextApiRequest, NextApiResponse } from 'next'; +import jwt from 'jsonwebtoken'; + +// Replace with a strong, unique secret key stored securely in environment variables +const JWT_SECRET = process.env.JWT_SECRET || 'YOUR_DEFAULT_SECRET'; // Use a secure secret! + +export default function handler(req: NextApiRequest, res: NextApiResponse) { + if (req.method !== 'POST') { + return res.status(405).json({ message: 'Method Not Allowed' }); + } + + const { accessCode } = req.body; + + // Replace with your actual server-side secret access code env variable + const SERVER_SECRET_ACCESS_CODE = process.env.SERVER_SECRET_ACCESS_CODE; + + if (!SERVER_SECRET_ACCESS_CODE) { + console.error("SERVER_SECRET_ACCESS_CODE is not defined!"); + return res.status(500).json({ message: 'Server configuration error' }); + } + + if (accessCode === SERVER_SECRET_ACCESS_CODE) { + // Code is correct, generate JWT + const token = jwt.sign({ verified: true }, JWT_SECRET, { expiresIn: '1h' }); // Token expires in 1 hour + + // Set JWT as an HTTP-only cookie + res.setHeader('Set-Cookie', `auth_token=${token}; HttpOnly; Path=/; Max-Age=${60 * 60}`); // Max-Age in seconds (1 hour) + + return res.status(200).json({ message: 'Verification successful' }); + } else { + // Code is incorrect + return res.status(401).json({ message: 'Invalid access code' }); + } +} \ No newline at end of file diff --git a/src/pages/page.tsx b/src/pages/page.tsx index 55bd853..108da72 100644 --- a/src/pages/page.tsx +++ b/src/pages/page.tsx @@ -28,13 +28,56 @@ export default function Page() { const [isCodePopupOpen, setIsCodePopupOpen] = useState(true); const [verificationError, setVerificationError] = useState(""); - const handleVerifyCode = (code: string) => { - if (code === process.env.NEXT_PUBLIC_BETA_ACCESS_PUBLIC_CODE) { - setIsVerified(true); - setIsCodePopupOpen(false); - setVerificationError(""); - } else { - setVerificationError("Incorrect code. Please try again."); + // Function to check for an existing session (will need a backend API to read the cookie) + const checkSession = async () => { + try { + const response = await fetch('/api/check-session'); // We will need to create this API route + if (response.ok) { + const data = await response.json(); + if (data.isValid) { + setIsVerified(true); + setIsCodePopupOpen(false); + return true; + } + } + } catch (error) { + console.error("Error checking session:", error); + } + setIsVerified(false); + setIsCodePopupOpen(true); + return false; + }; + + // useEffect to check session on component mount + useEffect(() => { + checkSession(); + }, []); // Run only once on mount + + const handleVerifyCode = async (code: string) => { + try { + const response = await fetch('/api/verify-code', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ accessCode: code }), + }); + + if (response.ok) { + setIsVerified(true); + setIsCodePopupOpen(false); + setVerificationError(""); // Clear any previous errors + } else { + const data = await response.json(); + setVerificationError(data.message || "Incorrect code. Please try again."); + setIsVerified(false); + setIsCodePopupOpen(true); + } + } catch (error) { + console.error("Error verifying code:", error); + setVerificationError("An error occurred during verification."); + setIsVerified(false); + setIsCodePopupOpen(true); } }; From f81fe952710c3c595c911bad8c5bfd60cdaac303 Mon Sep 17 00:00:00 2001 From: Kraznik <103571066+Kraznik@users.noreply.github.com> Date: Mon, 2 Jun 2025 00:06:28 -0500 Subject: [PATCH 17/22] feat: add rateProvider for minAMount and auth key for 6 hours --- src/components/deposit-view.tsx | 139 +++++--- src/config/abi/rateProvider.ts | 552 ++++++++++++++++++++++++++++++++ src/config/env.ts | 31 +- src/pages/api/verify-code.ts | 2 +- 4 files changed, 668 insertions(+), 56 deletions(-) create mode 100644 src/config/abi/rateProvider.ts diff --git a/src/components/deposit-view.tsx b/src/components/deposit-view.tsx index 6d52daf..4c47a55 100644 --- a/src/components/deposit-view.tsx +++ b/src/components/deposit-view.tsx @@ -26,6 +26,7 @@ import { http, parseUnits, } from "viem"; +import { RATE_PROVIDER_ABI } from "../config/abi/rateProvider"; type DurationType = "30_DAYS" | "60_DAYS" | "180_DAYS" | "PERPETUAL_DURATION"; type StrategyType = "STABLE" | "INCENTIVE"; @@ -66,6 +67,8 @@ interface StrategyConfig { filled_cap: string; cap_limit: string; boringVaultAddress?: string; + rateProvider: string; + shareAddress: string; } // ERC20 ABI for token operations @@ -680,6 +683,49 @@ const DepositView: React.FC = ({ depositTokenDecimals ); + // Get rate from rate provider + const rateProviderAddress = strategyConfig.rateProvider; + + const client = createPublicClient({ + transport: http(strategyConfig.rpc || "https://base.llamarpc.com"), + chain: { + id: 8453, + name: "Base", + network: "base", + nativeCurrency: { + decimals: 18, + name: "Ethereum", + symbol: "ETH", + }, + rpcUrls: { + default: { + http: [strategyConfig.rpc || "https://base.llamarpc.com"], + }, + public: { + http: [strategyConfig.rpc || "https://base.llamarpc.com"], + }, + }, + }, + }); + + // Get rate from rate provider using the deposit token address + const rate = await client.readContract({ + address: rateProviderAddress as Address, + abi: RATE_PROVIDER_ABI, + functionName: "getRateInQuote", + args: [selectedAssetOption.contract as Address], + }); + + console.log("Rate calculation:", { + rateProvider: rateProviderAddress, + depositToken: selectedAssetOption.contract, + rate: rate.toString(), + amountInWei: amountInWei.toString(), + }); + + // Calculate minimum mint amount based on rate - no slippage + const minimumMint = (amountInWei * BigInt(rate)) / BigInt(1e18); + // First approve USDS for the boring vault const boringVaultAddress = strategyConfig.boringVaultAddress; if (!boringVaultAddress) { @@ -736,13 +782,6 @@ const DepositView: React.FC = ({ if (!needsApproval || isApproved) { setIsDepositing(true); - // Calculate minimum mint amount based on slippage - const slippageAmount = - (amountInWei * BigInt(Math.floor(parseFloat(slippage) * 10000))) / - BigInt(10000); - // const minimumMint = amountInWei - slippageAmount; - const minimumMint = amountInWei - slippageAmount; - if (isMultiChain) { // Preview bridge fee before proceeding await previewBridgeFee(amountInWei); @@ -763,26 +802,36 @@ const DepositView: React.FC = ({ bridgeFee: bridgeFeeWei.toString(), }); - const tx = await deposit({ - address: vaultContractAddress as Address, - abi: VAULT_ABI, - functionName: "depositAndBridge", - args: [ - tokenContractAddress as Address, - amountInWei, - BigInt(0), // Set minimumMint to 0 for multi-chain - address as Address, - bridgeWildCard, - "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" as Address, // ETH address - bridgeFeeWei, // Use the calculated bridge fee - ], - chainId: 8453, - account: address as Address, - value: bridgeFeeWei, // Include the calculated bridge fee in ETH - }); - - if (tx && typeof tx === "string" && tx.startsWith("0x")) { - setTransactionHash(tx as `0x${string}`); + try { + const tx = await deposit({ + address: vaultContractAddress as Address, + abi: VAULT_ABI, + functionName: "depositAndBridge", + args: [ + tokenContractAddress as Address, + amountInWei, + BigInt(0), // Set minimumMint to 0 for multi-chain + address as Address, + bridgeWildCard, + "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" as Address, // ETH address + bridgeFeeWei, // Use the calculated bridge fee + ], + chainId: 8453, + account: address as Address, + value: bridgeFeeWei, // Include the calculated bridge fee in ETH + }); + + if (tx && typeof tx === "string" && tx.startsWith("0x")) { + setTransactionHash(tx as `0x${string}`); + console.log("Multi-chain deposit transaction sent:", tx); + } else { + throw new Error("Invalid transaction response"); + } + } catch (error: any) { + console.error("Multi-chain deposit failed:", error); + setErrorMessage(error.message || "Multi-chain deposit failed"); + setIsDepositing(false); + return; } } else { // Regular single-chain deposit @@ -793,17 +842,27 @@ const DepositView: React.FC = ({ minimumMint: minimumMint.toString(), }); - const tx = await deposit({ - address: vaultContractAddress as Address, - abi: VAULT_ABI, - functionName: "deposit", - args: [tokenContractAddress as Address, amountInWei, minimumMint], - chainId: 8453, - account: address as Address, - }); - - if (tx && typeof tx === "string" && tx.startsWith("0x")) { - setTransactionHash(tx as `0x${string}`); + try { + const tx = await deposit({ + address: vaultContractAddress as Address, + abi: VAULT_ABI, + functionName: "deposit", + args: [tokenContractAddress as Address, amountInWei, minimumMint], + chainId: 8453, + account: address as Address, + }); + + if (tx && typeof tx === "string" && tx.startsWith("0x")) { + setTransactionHash(tx as `0x${string}`); + console.log("Deposit transaction sent:", tx); + } else { + throw new Error("Invalid transaction response"); + } + } catch (error: any) { + console.error("Deposit failed:", error); + setErrorMessage(error.message || "Deposit failed"); + setIsDepositing(false); + return; } } } else { @@ -811,10 +870,10 @@ const DepositView: React.FC = ({ setErrorMessage("Please approve the token spending first"); } } catch (error: any) { - console.error("TRX failed:", transactionHash); + console.error("Transaction failed:", error); setIsApproving(false); setIsDepositing(false); - setErrorMessage(`TRX failed${transactionHash ? ': ' + transactionHash : ''}`); + setErrorMessage(error.message || "Transaction failed"); } finally { setIsWaitingForSignature(false); } diff --git a/src/config/abi/rateProvider.ts b/src/config/abi/rateProvider.ts new file mode 100644 index 0000000..3ddd2d7 --- /dev/null +++ b/src/config/abi/rateProvider.ts @@ -0,0 +1,552 @@ +export const RATE_PROVIDER_ABI = [ + { + inputs: [ + { internalType: "address", name: "_owner", type: "address" }, + { internalType: "address", name: "_vault", type: "address" }, + { internalType: "address", name: "payoutAddress", type: "address" }, + { internalType: "uint96", name: "startingExchangeRate", type: "uint96" }, + { internalType: "address", name: "_base", type: "address" }, + { + internalType: "uint16", + name: "allowedExchangeRateChangeUpper", + type: "uint16", + }, + { + internalType: "uint16", + name: "allowedExchangeRateChangeLower", + type: "uint16", + }, + { + internalType: "uint24", + name: "minimumUpdateDelayInSeconds", + type: "uint24", + }, + { internalType: "uint16", name: "platformFee", type: "uint16" }, + { internalType: "uint16", name: "performanceFee", type: "uint16" }, + ], + stateMutability: "nonpayable", + type: "constructor", + }, + { + inputs: [], + name: "AccountantWithRateProviders__ExchangeRateAboveHighwaterMark", + type: "error", + }, + { + inputs: [], + name: "AccountantWithRateProviders__LowerBoundTooLarge", + type: "error", + }, + { + inputs: [], + name: "AccountantWithRateProviders__OnlyCallableByBoringVault", + type: "error", + }, + { inputs: [], name: "AccountantWithRateProviders__Paused", type: "error" }, + { + inputs: [], + name: "AccountantWithRateProviders__PerformanceFeeTooLarge", + type: "error", + }, + { + inputs: [], + name: "AccountantWithRateProviders__PlatformFeeTooLarge", + type: "error", + }, + { + inputs: [], + name: "AccountantWithRateProviders__UpdateDelayTooLarge", + type: "error", + }, + { + inputs: [], + name: "AccountantWithRateProviders__UpperBoundTooSmall", + type: "error", + }, + { + inputs: [], + name: "AccountantWithRateProviders__ZeroFeesOwed", + type: "error", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "address", name: "user", type: "address" }, + { + indexed: true, + internalType: "contract Authority", + name: "newAuthority", + type: "address", + }, + ], + name: "AuthorityUpdated", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "uint24", + name: "oldDelay", + type: "uint24", + }, + { + indexed: false, + internalType: "uint24", + name: "newDelay", + type: "uint24", + }, + ], + name: "DelayInSecondsUpdated", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "uint96", + name: "oldRate", + type: "uint96", + }, + { + indexed: false, + internalType: "uint96", + name: "newRate", + type: "uint96", + }, + { + indexed: false, + internalType: "uint64", + name: "currentTime", + type: "uint64", + }, + ], + name: "ExchangeRateUpdated", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "feeAsset", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "amount", + type: "uint256", + }, + ], + name: "FeesClaimed", + type: "event", + }, + { anonymous: false, inputs: [], name: "HighwaterMarkReset", type: "event" }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "uint16", + name: "oldBound", + type: "uint16", + }, + { + indexed: false, + internalType: "uint16", + name: "newBound", + type: "uint16", + }, + ], + name: "LowerBoundUpdated", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "address", name: "user", type: "address" }, + { + indexed: true, + internalType: "address", + name: "newOwner", + type: "address", + }, + ], + name: "OwnershipTransferred", + type: "event", + }, + { anonymous: false, inputs: [], name: "Paused", type: "event" }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "oldPayout", + type: "address", + }, + { + indexed: false, + internalType: "address", + name: "newPayout", + type: "address", + }, + ], + name: "PayoutAddressUpdated", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "uint16", + name: "oldFee", + type: "uint16", + }, + { + indexed: false, + internalType: "uint16", + name: "newFee", + type: "uint16", + }, + ], + name: "PerformanceFeeUpdated", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "uint16", + name: "oldFee", + type: "uint16", + }, + { + indexed: false, + internalType: "uint16", + name: "newFee", + type: "uint16", + }, + ], + name: "PlatformFeeUpdated", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "asset", + type: "address", + }, + { indexed: false, internalType: "bool", name: "isPegged", type: "bool" }, + { + indexed: false, + internalType: "address", + name: "rateProvider", + type: "address", + }, + ], + name: "RateProviderUpdated", + type: "event", + }, + { anonymous: false, inputs: [], name: "Unpaused", type: "event" }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "uint16", + name: "oldBound", + type: "uint16", + }, + { + indexed: false, + internalType: "uint16", + name: "newBound", + type: "uint16", + }, + ], + name: "UpperBoundUpdated", + type: "event", + }, + { + inputs: [], + name: "accountantState", + outputs: [ + { internalType: "address", name: "payoutAddress", type: "address" }, + { internalType: "uint96", name: "highwaterMark", type: "uint96" }, + { internalType: "uint128", name: "feesOwedInBase", type: "uint128" }, + { + internalType: "uint128", + name: "totalSharesLastUpdate", + type: "uint128", + }, + { internalType: "uint96", name: "exchangeRate", type: "uint96" }, + { + internalType: "uint16", + name: "allowedExchangeRateChangeUpper", + type: "uint16", + }, + { + internalType: "uint16", + name: "allowedExchangeRateChangeLower", + type: "uint16", + }, + { internalType: "uint64", name: "lastUpdateTimestamp", type: "uint64" }, + { internalType: "bool", name: "isPaused", type: "bool" }, + { + internalType: "uint24", + name: "minimumUpdateDelayInSeconds", + type: "uint24", + }, + { internalType: "uint16", name: "platformFee", type: "uint16" }, + { internalType: "uint16", name: "performanceFee", type: "uint16" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "authority", + outputs: [ + { internalType: "contract Authority", name: "", type: "address" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "base", + outputs: [{ internalType: "contract ERC20", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "contract ERC20", name: "feeAsset", type: "address" }, + ], + name: "claimFees", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "decimals", + outputs: [{ internalType: "uint8", name: "", type: "uint8" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getRate", + outputs: [{ internalType: "uint256", name: "rate", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "contract ERC20", name: "quote", type: "address" }, + ], + name: "getRateInQuote", + outputs: [ + { internalType: "uint256", name: "rateInQuote", type: "uint256" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "contract ERC20", name: "quote", type: "address" }, + ], + name: "getRateInQuoteSafe", + outputs: [ + { internalType: "uint256", name: "rateInQuote", type: "uint256" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getRateSafe", + outputs: [{ internalType: "uint256", name: "rate", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "owner", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "pause", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "uint96", name: "newExchangeRate", type: "uint96" }, + ], + name: "previewUpdateExchangeRate", + outputs: [ + { internalType: "bool", name: "updateWillPause", type: "bool" }, + { internalType: "uint256", name: "newFeesOwedInBase", type: "uint256" }, + { internalType: "uint256", name: "totalFeesOwedInBase", type: "uint256" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "contract ERC20", name: "", type: "address" }], + name: "rateProviderData", + outputs: [ + { internalType: "bool", name: "isPeggedToBase", type: "bool" }, + { + internalType: "contract IRateProvider", + name: "rateProvider", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "resetHighwaterMark", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "contract Authority", + name: "newAuthority", + type: "address", + }, + ], + name: "setAuthority", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "contract ERC20", name: "asset", type: "address" }, + { internalType: "bool", name: "isPeggedToBase", type: "bool" }, + { internalType: "address", name: "rateProvider", type: "address" }, + ], + name: "setRateProviderData", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "newOwner", type: "address" }], + name: "transferOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "unpause", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint24", + name: "minimumUpdateDelayInSeconds", + type: "uint24", + }, + ], + name: "updateDelay", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "uint96", name: "newExchangeRate", type: "uint96" }, + ], + name: "updateExchangeRate", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "allowedExchangeRateChangeLower", + type: "uint16", + }, + ], + name: "updateLower", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "payoutAddress", type: "address" }, + ], + name: "updatePayoutAddress", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "uint16", name: "performanceFee", type: "uint16" }, + ], + name: "updatePerformanceFee", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "uint16", name: "platformFee", type: "uint16" }], + name: "updatePlatformFee", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint16", + name: "allowedExchangeRateChangeUpper", + type: "uint16", + }, + ], + name: "updateUpper", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "vault", + outputs: [ + { internalType: "contract BoringVault", name: "", type: "address" }, + ], + stateMutability: "view", + type: "function", + }, +] as const; diff --git a/src/config/env.ts b/src/config/env.ts index f220d8d..da1e056 100644 --- a/src/config/env.ts +++ b/src/config/env.ts @@ -7,7 +7,8 @@ export const USD_STRATEGIES = { solverAddress: "0xF632c10b19f2a0451cD4A653fC9ca0c15eA1040b", shareAddress: "0x279CAD277447965AF3d24a78197aad1B02a2c589", shareAddress_token_decimal: 6, - + rateProvider: "0x03D9a9cE13D16C7cFCE564f41bd7E85E5cde8Da6", + // Base Network Configuration base: { tokens: [ @@ -15,21 +16,21 @@ export const USD_STRATEGIES = { name: "USDC", contract: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", decimal: 6, - image: "/images/icons/usdc.svg" + image: "/images/icons/usdc.svg", }, { name: "USDS", contract: "0x820C137fa70C8691f0e44Dc420a5e53c168921Dc", decimal: 18, - image: "/images/icons/usds.svg" + image: "/images/icons/usds.svg", }, { name: "sUSDS", contract: "0x5875eEE11Cf8398102FdAd704C9E96607675467a", decimal: 18, - image: "/images/icons/sUSDS.svg" - } - ] + image: "/images/icons/sUSDS.svg", + }, + ], }, // Ethereum Mainnet Configuration @@ -39,21 +40,21 @@ export const USD_STRATEGIES = { name: "USDC", contract: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", decimal: 6, - image: "/images/icons/usdc.svg" + image: "/images/icons/usdc.svg", }, { name: "USDT", contract: "0xdAC17F958D2ee523a2206206994597C13D831ec7", decimal: 6, - image: "/images/icons/usdt.svg" + image: "/images/icons/usdt.svg", }, { name: "USDS", contract: "0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD", decimal: 18, - image: "/images/icons/usds.svg" - } - ] + image: "/images/icons/usds.svg", + }, + ], }, // Arbitrum Configuration @@ -63,15 +64,15 @@ export const USD_STRATEGIES = { name: "USDC", contract: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", decimal: 6, - image: "/images/icons/usdc.svg" + image: "/images/icons/usdc.svg", }, { name: "USDS", contract: "0x6491c05A82219b8D1479057361ff1654749b876b", decimal: 18, - image: "/images/icons/USDS.svg" - } - ] + image: "/images/icons/USDS.svg", + }, + ], }, description: "Perpetual stable USD strategy on Sonic network", diff --git a/src/pages/api/verify-code.ts b/src/pages/api/verify-code.ts index d9b99cd..c450693 100644 --- a/src/pages/api/verify-code.ts +++ b/src/pages/api/verify-code.ts @@ -24,7 +24,7 @@ export default function handler(req: NextApiRequest, res: NextApiResponse) { const token = jwt.sign({ verified: true }, JWT_SECRET, { expiresIn: '1h' }); // Token expires in 1 hour // Set JWT as an HTTP-only cookie - res.setHeader('Set-Cookie', `auth_token=${token}; HttpOnly; Path=/; Max-Age=${60 * 60}`); // Max-Age in seconds (1 hour) + res.setHeader('Set-Cookie', `auth_token=${token}; HttpOnly; Path=/; Max-Age=${6 * 60 * 60}`); // Max-Age in seconds (6 hours) return res.status(200).json({ message: 'Verification successful' }); } else { From 706a17aee8b90a87fbafd349a9e388ed1515ad13 Mon Sep 17 00:00:00 2001 From: Kraznik <103571066+Kraznik@users.noreply.github.com> Date: Mon, 2 Jun 2025 00:22:44 -0500 Subject: [PATCH 18/22] fix: deposit minAMount convert to 6 decimal values --- src/components/deposit-view.tsx | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/components/deposit-view.tsx b/src/components/deposit-view.tsx index 4c47a55..e74c0b1 100644 --- a/src/components/deposit-view.tsx +++ b/src/components/deposit-view.tsx @@ -716,16 +716,23 @@ const DepositView: React.FC = ({ args: [selectedAssetOption.contract as Address], }); - console.log("Rate calculation:", { - rateProvider: rateProviderAddress, - depositToken: selectedAssetOption.contract, - rate: rate.toString(), - amountInWei: amountInWei.toString(), - }); + console.log("Raw rate from contract:", rate.toString()); - // Calculate minimum mint amount based on rate - no slippage + // Calculate minimum mint amount in 6 decimals + // First multiply by rate, then divide by 1e18 to get 6 decimals const minimumMint = (amountInWei * BigInt(rate)) / BigInt(1e18); + // Convert to exactly 6 decimals by multiplying by 1e6 and dividing by 1e18 + const minimumMintIn6Decimals = (minimumMint * BigInt(1e6)) / BigInt(1e18); + + console.log("Minimum mint calculation details:", { + amountInWei: amountInWei.toString(), + rate: rate.toString(), + minimumMint: minimumMint.toString(), + minimumMintIn6Decimals: minimumMintIn6Decimals.toString(), + minimumMintLength: minimumMintIn6Decimals.toString().length, + }); + // First approve USDS for the boring vault const boringVaultAddress = strategyConfig.boringVaultAddress; if (!boringVaultAddress) { @@ -839,7 +846,7 @@ const DepositView: React.FC = ({ contract: vaultContractAddress, token: tokenContractAddress, amount: amountInWei.toString(), - minimumMint: minimumMint.toString(), + minimumMint: minimumMintIn6Decimals.toString(), }); try { @@ -847,7 +854,7 @@ const DepositView: React.FC = ({ address: vaultContractAddress as Address, abi: VAULT_ABI, functionName: "deposit", - args: [tokenContractAddress as Address, amountInWei, minimumMint], + args: [tokenContractAddress as Address, amountInWei, minimumMintIn6Decimals], chainId: 8453, account: address as Address, }); From 58701bf33a066e3493d0166792006fbf36836e14 Mon Sep 17 00:00:00 2001 From: Gyanshu Pathak Date: Wed, 4 Jun 2025 01:27:53 +0530 Subject: [PATCH 19/22] earn section responsiveness added --- .env.local | 4 +- src/components/deposit-view.tsx | 14 +-- src/components/ui/card.tsx | 2 +- src/components/ui/header.tsx | 4 +- src/pages/earn-subpage.tsx | 14 +-- src/pages/page.tsx | 201 +++++++++++++++++++++----------- 6 files changed, 154 insertions(+), 85 deletions(-) diff --git a/.env.local b/.env.local index ef0531d..f20b6c7 100644 --- a/.env.local +++ b/.env.local @@ -1,5 +1,5 @@ -NEXT_PUBLIC_WC_PROJECT_ID=your_project_id_here +NEXT_PUBLIC_WC_PROJECT_ID=1ac26acf6371d43254aa7c9d285252e2 NEXT_PUBLIC_ENABLE_TESTNETS=false NEXT_PUBLIC_BETA_ACCESS_CODE=your_code JWT_SECRET=your_super_secret_jwt_key -SERVER_SECRET_ACCESS_CODE=code__ \ No newline at end of file +SERVER_SECRET_ACCESS_CODE=yieldsogood \ No newline at end of file diff --git a/src/components/deposit-view.tsx b/src/components/deposit-view.tsx index e74c0b1..2f3f23b 100644 --- a/src/components/deposit-view.tsx +++ b/src/components/deposit-view.tsx @@ -1041,7 +1041,7 @@ const DepositView: React.FC = ({ }; return ( -
+
{depositSuccess ? (
@@ -1130,10 +1130,10 @@ const DepositView: React.FC = ({
) : (
-
-
+
+
{/* Left Card - Deposit Input */} -
+
{selectedAssetOption.image && ( @@ -1322,7 +1322,7 @@ const DepositView: React.FC = ({
{/* Right Card - Strategy Info */} -
+
{/* Background gradient effect - top */}
@@ -1382,7 +1382,7 @@ const DepositView: React.FC = ({ {/* Deposit Cap Progress Bar - Only shown if show_cap is true */} {showDepositCap && ( -
+
${remainingSpace} Remaining @@ -1439,7 +1439,7 @@ const DepositView: React.FC = ({ diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx index 5f8bd0d..888fbda 100644 --- a/src/components/ui/card.tsx +++ b/src/components/ui/card.tsx @@ -173,7 +173,7 @@ const CustomCard: React.FC = ({ style={{ backgroundColor: hoverColor }} />
-
+

{shouldShowBanner && (
@@ -112,7 +112,7 @@ export function Header({
)} -
+
{children}
diff --git a/src/pages/earn-subpage.tsx b/src/pages/earn-subpage.tsx index 933a78b..0913a14 100644 --- a/src/pages/earn-subpage.tsx +++ b/src/pages/earn-subpage.tsx @@ -216,7 +216,7 @@ const YieldSubpage: React.FC = ({ depositParams }) => { // Always render the main content, assuming verification is handled by parent return (
= ({ depositParams }) => { /> ) : selectedAsset ? (
-

Select a Yield Source

-
+

Select a Yield Source

+
= ({ depositParams }) => { selectedDuration={selectedAsset.duration} onReset={handleReset} disableHover={true} - className="h-[311px]" + className="h-[311px] w-full" /> -
+
handleStrategySelect( @@ -334,10 +334,10 @@ const YieldSubpage: React.FC = ({ depositParams }) => {
) : (
-

+

Select a asset you want yield on

-
+
{ @@ -116,79 +117,147 @@ export default function Page() { return (
-
-
-
{ - setSelectedSubPage(SubPage.Yield); - setDepositParams(null); - }} - > - Lucidity Logo -
-
-
-
- +
+ +
+
+ + +
- + +
+ {isMobileMenuOpen && ( +
+ + + + + +
+ )} +
+
{renderSubPage()}
From f5c7a3580493cf1da4aeacce845cd412c8469e28 Mon Sep 17 00:00:00 2001 From: Gyanshu Pathak Date: Fri, 6 Jun 2025 12:08:35 +0530 Subject: [PATCH 20/22] portfolio responsive added --- package-lock.json | 299 +++++++++++++++++++++++++- package.json | 1 + src/components/ui/card.tsx | 2 +- src/components/ui/markets-table.tsx | 4 +- src/components/yield-details-view.tsx | 44 ++-- src/pages/markets-subpage.tsx | 57 ++--- src/pages/portfolio-subpage.tsx | 151 +++++++++++-- src/pages/yield/[id].tsx | 28 +++ 8 files changed, 521 insertions(+), 65 deletions(-) create mode 100644 src/pages/yield/[id].tsx diff --git a/package-lock.json b/package-lock.json index b697638..31d7dbb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "react-dom": "^19.0.0", "react-error-boundary": "^5.0.0", "react-icons": "^5.5.0", + "recharts": "^2.15.3", "tailwind-merge": "^3.0.2", "tailwindcss-animate": "^1.0.7", "viem": "2.23.3", @@ -2239,6 +2240,60 @@ "tslib": "^2.4.0" } }, + "node_modules/@types/d3-array": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", + "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz", + "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==" + }, "node_modules/@types/debug": { "version": "4.1.12", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", @@ -4246,6 +4301,116 @@ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "license": "MIT" }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "engines": { + "node": ">=12" + } + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -4359,6 +4524,11 @@ "node": ">=0.10.0" } }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==" + }, "node_modules/decode-uri-component": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", @@ -4518,6 +4688,15 @@ "node": ">=0.10.0" } }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -5650,6 +5829,14 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "license": "MIT" }, + "node_modules/fast-equals": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.2.2.tgz", + "integrity": "sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw==", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/fast-glob": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", @@ -6390,6 +6577,14 @@ "node": ">= 0.4" } }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "engines": { + "node": ">=12" + } + }, "node_modules/iron-webcrypto": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz", @@ -6935,7 +7130,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, "license": "MIT" }, "node_modules/js-yaml": { @@ -7229,6 +7423,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -7313,7 +7512,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, "license": "MIT", "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" @@ -8491,7 +8689,6 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.4.0", @@ -8640,7 +8837,6 @@ "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true, "license": "MIT" }, "node_modules/react-remove-scroll": { @@ -8690,6 +8886,20 @@ } } }, + "node_modules/react-smooth": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz", + "integrity": "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==", + "dependencies": { + "fast-equals": "^5.0.1", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/react-style-singleton": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", @@ -8712,6 +8922,21 @@ } } }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -8766,6 +8991,46 @@ "node": ">= 12.13.0" } }, + "node_modules/recharts": { + "version": "2.15.3", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.3.tgz", + "integrity": "sha512-EdOPzTwcFSuqtvkDoaM5ws/Km1+WTAO2eizL7rqiG0V2UVhTnz0m7J2i0CjVPUCdEkZImaWvXLbZDS2H5t6GFQ==", + "dependencies": { + "clsx": "^2.0.0", + "eventemitter3": "^4.0.1", + "lodash": "^4.17.21", + "react-is": "^18.3.1", + "react-smooth": "^4.0.4", + "recharts-scale": "^0.4.4", + "tiny-invariant": "^1.3.1", + "victory-vendor": "^36.6.8" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/recharts-scale": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz", + "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==", + "dependencies": { + "decimal.js-light": "^2.4.1" + } + }, + "node_modules/recharts/node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, + "node_modules/recharts/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -9923,6 +10188,11 @@ "real-require": "^0.1.0" } }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==" + }, "node_modules/tinyglobby": { "version": "0.2.12", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.12.tgz", @@ -10473,6 +10743,27 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/victory-vendor": { + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz", + "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, "node_modules/viem": { "version": "2.23.3", "resolved": "https://registry.npmjs.org/viem/-/viem-2.23.3.tgz", diff --git a/package.json b/package.json index 05c30f2..438be8a 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "react-dom": "^19.0.0", "react-error-boundary": "^5.0.0", "react-icons": "^5.5.0", + "recharts": "^2.15.3", "tailwind-merge": "^3.0.2", "tailwindcss-animate": "^1.0.7", "viem": "2.23.3", diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx index 888fbda..5f8bd0d 100644 --- a/src/components/ui/card.tsx +++ b/src/components/ui/card.tsx @@ -173,7 +173,7 @@ const CustomCard: React.FC = ({ style={{ backgroundColor: hoverColor }} />
-
+

= ({ return (
{/* Table Header */} -
+
= ({
onRowClick && onRowClick(item)} - className="cursor-pointer pl-[32px]" + className="cursor-pointer px-[16px] sm:pl-[32px]" >
= ({

{/* Toggle buttons */} -
+
@@ -250,16 +250,16 @@ const YieldDetailsView: React.FC = ({ ); return ( -
+
-
-

+
+

{name}

-
+
@@ -267,30 +267,38 @@ const YieldDetailsView: React.FC = ({
{/* Stats */} -
-
-
TVL
-
{tvl}
+
+ {/* TVL */} +
+
TVL
+
{tvl}
-
-
Base APY
-
{baseApy}
+ + {/* Base APY */} +
+
Base APY
+
{baseApy}
-
-
Contract Address
-
+ + {/* Contract Address */} +
+
Contract Address
+
{contractAddress}
-
-
Network
-
{network}
+ + {/* Network */} +
+
Network
+
{network}
+ {/* Tabs */}
typeof window !== "undefined" && window.innerWidth < 640; type AssetType = "ALL" | "USD" | "ETH" | "BTC"; @@ -108,6 +111,7 @@ const MarketsSubpage: React.FC = () => { } ], }; + const router = useRouter(); // Fill the "ALL" category marketData.ALL = [...marketData.ETH, ...marketData.BTC, ...marketData.USD]; @@ -124,7 +128,20 @@ const MarketsSubpage: React.FC = () => { // Handler for row clicks const handleRowClick = (item: MarketItem) => { - setSelectedItem(item); + if (isMobile()) { + router.push({ + pathname: `/yield/${item.id}`, + query: { + name: item.name, + tvl: item.tvl, + baseApy: item.baseYield, + contractAddress: item.contractAddress || "", + network: item.network || "" + }, + }); + } else { + setSelectedItem(item); + } }; // Get sorted data @@ -172,10 +189,10 @@ const MarketsSubpage: React.FC = () => { }; return ( -
+
{/* Left side - 50% */} -
-
+
+
{
{/* Asset Selection */} -
-
- - - - +
+
+ + + +
@@ -251,7 +252,7 @@ const MarketsSubpage: React.FC = () => {
{/* Right side - 50% */} -
+
{selectedItem ? ( { const { address, isConnected } = useAccount(); const [depositSuccess, setDepositSuccess] = useState(false); @@ -461,10 +540,23 @@ const PortfolioSubpage: React.FC = () => { } }; + const CustomXAxisTick = ({ x, y, payload, index, data }) => { + const currentLabel = payload.value; + const prevLabel = index > 0 ? data[index - 1]?.date : null; + + const showLabel = currentLabel !== prevLabel; + + return showLabel ? ( + + {currentLabel} + + ) : null; + }; + return (
{/* Top Section - Portfolio Value, PNL, and Wallet */} -
+
@@ -534,11 +626,11 @@ const PortfolioSubpage: React.FC = () => {
-
+
Wallet Address
-
+
{isConnected ? address : "Not connected"}
@@ -547,19 +639,54 @@ const PortfolioSubpage: React.FC = () => { {/* Main Content - Split View */}
{/* Left Side - Assets Table */} -
+
Total Portfolio Value
- {/* Column Headers */} -
-
+ {/* Graph */} +
+ + + + } + /> + `$${value}`} + domain={[0, 100]} + fontSize={12} + axisLine={false} + /> + [`$${value.toFixed(2)}`, "Value"]} + /> + + + + +
+ +
+
Available Yields
-
+
Expiry {
-
+
Base APY {
-
+
Current Balance @@ -722,7 +849,7 @@ const PortfolioSubpage: React.FC = () => {
{/* Right Side - Withdraw Form or Info */} -
+
{selectedStrategy ? (

diff --git a/src/pages/yield/[id].tsx b/src/pages/yield/[id].tsx new file mode 100644 index 0000000..de5733e --- /dev/null +++ b/src/pages/yield/[id].tsx @@ -0,0 +1,28 @@ +import { useRouter } from "next/router"; +import { YieldDetailsView } from "@/components/yield-details-view"; +import React from "react"; +import { ArrowLeft } from "lucide-react"; + +const YieldDetailPage = () => { + const router = useRouter(); + const { name, tvl, baseApy, contractAddress, network } = router.query; + + if (!name) return
Loading...
; + + return ( +
+ + +
+ ); +}; + +export default YieldDetailPage; \ No newline at end of file From cb54e69d67a7556a8f477a09092268797fa3eb19 Mon Sep 17 00:00:00 2001 From: Gyanshu Pathak Date: Sat, 14 Jun 2025 13:28:26 +0530 Subject: [PATCH 21/22] feat:withdraw flow mobile view added --- src/pages/markets-subpage.tsx | 1 + src/pages/portfolio-subpage.tsx | 73 +- src/pages/portfolio/[contract].tsx | 1116 ++++++++++++++++++++++++++++ 3 files changed, 1167 insertions(+), 23 deletions(-) create mode 100644 src/pages/portfolio/[contract].tsx diff --git a/src/pages/markets-subpage.tsx b/src/pages/markets-subpage.tsx index a7c47ca..73295a0 100644 --- a/src/pages/markets-subpage.tsx +++ b/src/pages/markets-subpage.tsx @@ -128,6 +128,7 @@ const MarketsSubpage: React.FC = () => { // Handler for row clicks const handleRowClick = (item: MarketItem) => { + console.log("Row clicked:", item); if (isMobile()) { router.push({ pathname: `/yield/${item.id}`, diff --git a/src/pages/portfolio-subpage.tsx b/src/pages/portfolio-subpage.tsx index 302fa71..0bdf21d 100644 --- a/src/pages/portfolio-subpage.tsx +++ b/src/pages/portfolio-subpage.tsx @@ -16,15 +16,18 @@ import { formatUnits, parseUnits, } from "viem"; -import { - BarChart, - Bar, - XAxis, - YAxis, - Tooltip, - ResponsiveContainer, - CartesianGrid, -} from "recharts"; +import { useRouter } from "next/router"; + +const isMobile = () => typeof window !== "undefined" && window.innerWidth < 640; +// import { +// BarChart, +// Bar, +// XAxis, +// YAxis, +// Tooltip, +// ResponsiveContainer, +// CartesianGrid, +// } from "recharts"; interface StrategyConfig { network: string; @@ -140,6 +143,8 @@ const PortfolioSubpage: React.FC = () => { const [isRefreshingBalance, setIsRefreshingBalance] = useState(false); const [errorMessage, setErrorMessage] = useState(null); + const router = useRouter(); + // Watch deposit transaction const { isLoading: isWaitingForDeposit, isSuccess: isDepositSuccess } = useTransaction({ @@ -515,9 +520,31 @@ const PortfolioSubpage: React.FC = () => { } }; + // Handler for row clicks const handleStrategySelect = (strategy: any) => { - setSelectedStrategy(strategy); - setWithdrawAmount(strategy.balance.toString()); + console.log("strategy",strategy) + if (isMobile()) { + router.push({ + pathname: `/portfolio/${strategy.contract}`, + query: { + strategy: strategy.contract, + asset: strategy.asset, + balance: strategy.balance, + duration: strategy.duration, + type: strategy.type, + apy: strategy.apy, + SolverAddress: strategy.solverAddress, + boringVaultAddress: strategy.boringVaultAddress, + // tvl: strategy.tvl, + // baseApy: strategy.baseYield, + // contractAddress: strategy.contractAddress || "", + // network: strategy.network || "" + }, + }); + } else { + setSelectedStrategy(strategy); + setWithdrawAmount(strategy.balance.toString()); + } }; const handleAmountChange = (e: React.ChangeEvent) => { @@ -540,18 +567,18 @@ const PortfolioSubpage: React.FC = () => { } }; - const CustomXAxisTick = ({ x, y, payload, index, data }) => { - const currentLabel = payload.value; - const prevLabel = index > 0 ? data[index - 1]?.date : null; + // const CustomXAxisTick = ({ x, y, payload, index, data }) => { + // const currentLabel = payload.value; + // const prevLabel = index > 0 ? data[index - 1]?.date : null; - const showLabel = currentLabel !== prevLabel; + // const showLabel = currentLabel !== prevLabel; - return showLabel ? ( - - {currentLabel} - - ) : null; - }; + // return showLabel ? ( + // + // {currentLabel} + // + // ) : null; + // }; return (
@@ -647,7 +674,7 @@ const PortfolioSubpage: React.FC = () => {
{/* Graph */} -
+ {/*
{ -
+
*/}
diff --git a/src/pages/portfolio/[contract].tsx b/src/pages/portfolio/[contract].tsx new file mode 100644 index 0000000..dae2f5d --- /dev/null +++ b/src/pages/portfolio/[contract].tsx @@ -0,0 +1,1116 @@ +import React, { useState, useEffect } from "react"; +import { useRouter } from "next/router"; +import { YieldDetailsView } from "@/components/yield-details-view"; +import Image from "next/image"; +import { ArrowLeft } from "lucide-react"; +import { + type Address, + createPublicClient, + http, + formatUnits, + parseUnits, + getAddress, +} from "viem"; +import { + useAccount, + useTransaction, + useWriteContract, + useChainId, +} from "wagmi"; +import { USD_STRATEGIES, BTC_STRATEGIES, ETH_STRATEGIES } from "../../config/env"; +import { ERC20_ABI } from "../../config/abi/erc20"; +import { SOLVER_ABI } from "../../config/abi/solver"; + +interface StrategyConfig { + network: string; + contract: string; + deposit_token: string; + deposit_token_contract: string; + description: string; + apy: string; + incentives: string; + tvl: string; + rpc: string; +} + +type DurationType = "30_DAYS" | "60_DAYS" | "180_DAYS" | "PERPETUAL_DURATION"; +type StrategyType = "STABLE" | "INCENTIVE"; + +type StrategyDuration = { + [K in StrategyType]: StrategyConfig; +} + +type StrategyAsset = { + [K in DurationType]?: StrategyDuration; +} + +const assetOptions = [ + { + name: "USDC", + contract: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", + image: "/images/icons/usdc.svg", + }, + { + name: "USDS", + contract: "0x820C137fa70C8691f0e44Dc420a5e53c168921Dc", + image: "/images/icons/usds.svg", + }, + { + name: "sUSDS", + contract: "0x5875eEE11Cf8398102FdAd704C9E96607675467a", + image: "/images/icons/sUSDS.svg", + }, +]; + +const requests = [ + { + date: "18th May’25", + fromAmount: "1,000,000", + toAmount: "1,004,000", + canCancel: true, + }, + { + date: "19th May’25", + fromAmount: "100", + toAmount: "104", + canCancel: true, + }, + { + date: "19th May’25", + fromAmount: "900", + toAmount: "909", + canCancel: true, + }, + { + date: "19th May’25", + fromAmount: "1,092", + toAmount: "1,200", + canCancel: true, + }, +]; + +const ExternalLinkIcon = () => ( + + + +); + +const strategy = USD_STRATEGIES.PERPETUAL_DURATION.STABLE; +console.log("Strategy Config:", strategy); +const chainConfigs = { + base: { + rpc: "https://mainnet.base.org", + chainId: 8453, + image: "/images/logo/base.svg", + chainObject: { tokens: strategy.base.tokens } + }, + ethereum: { + rpc: "https://ethereum.llamarpc.com", + chainId: 1, + image: "/images/logo/eth.svg", + chainObject: { tokens: strategy.ethereum.tokens } + }, + arbitrum: { + rpc: "https://arb1.arbitrum.io/rpc", + chainId: 42161, + image: "/images/logo/arb.svg", + chainObject: { tokens: strategy.arbitrum.tokens } + }, +}; +console.log("chain configs:", chainConfigs); +const PortfolioDetailedPage = () => { + const { address, isConnected } = useAccount(); + const router = useRouter(); + const { contract ,asset ,type ,balance ,duration , solverAddress, boringVaultAddress , rpc } = router.query; + + const [depositSuccess, setDepositSuccess] = useState(false); + const [transactionHash, setTransactionHash] = useState<`0x${string}` | null>( + null + ); + const [isDepositing, setIsDepositing] = useState(false); + const [isApproved, setIsApproved] = useState(false); + const [isApproving, setIsApproving] = useState(false); + const [approvalHash, setApprovalHash] = useState<`0x${string}` | null>(null); + const [strategiesWithBalance, setStrategiesWithBalance] = useState([]); + const [withdrawAmount, setWithdrawAmount] = useState(""); + const [slippage, setSlippage] = useState("0.03"); + const [isWithdrawing, setIsWithdrawing] = useState(false); + const [withdrawTxHash, setWithdrawTxHash] = useState<`0x${string}` | null>( + null + ); + const [isRefreshingBalance, setIsRefreshingBalance] = useState(false); + const [errorMessage, setErrorMessage] = useState(null); + const [activeTab, setActiveTab] = useState<"withdraw" | "request">("withdraw"); + const [requestTab, setRequestTab] = useState<"pending" | "completed">("pending"); + const [amountOut, setAmountOut] = useState(null); + const [selectedAssetIdx, setSelectedAssetIdx] = useState(0); + // Add state for custom dropdown + const [isAssetDropdownOpen, setIsAssetDropdownOpen] = useState(false); + const [depositedChains, setDepositedChains] = useState([]); + + const chainId = useChainId(); + const isBase = chainId === 8453; + + // Watch deposit transaction + const { isLoading: isWaitingForDeposit, isSuccess: isDepositSuccess } = + useTransaction({ + hash: transactionHash || undefined, + }); + + // Watch for deposit completion + useEffect(() => { + if (!isWaitingForDeposit && isDepositing) { + setIsDepositing(false); + setIsApproved(false); + if (isDepositSuccess && transactionHash) { + setDepositSuccess(true); + } + } + }, [isWaitingForDeposit, isDepositing, isDepositSuccess, transactionHash]); + + // Watch approval transaction + const { isLoading: isWaitingForApproval, isSuccess: isApprovalSuccess } = + useTransaction({ + hash: approvalHash || undefined, + }); + + // Watch withdraw transaction + const { isLoading: isWaitingForWithdraw, isSuccess: isWithdrawSuccess } = + useTransaction({ + hash: withdrawTxHash || undefined, + }); + + const chainIconMap: Record = { + ethereum: { + src: "/images/logo/eth.svg", + label: "Ethereum", + }, + base: { + src: "/images/logo/base.svg", + label: "Base", + }, + arbitrum: { + src: "/images/logo/arb.svg", + label: "Arbitrum", + }, + }; + + useEffect(() => { + if (!isWaitingForWithdraw && isWithdrawing) { + setIsWithdrawing(false); + if (isWithdrawSuccess && withdrawTxHash) { + // Handle successful withdrawal + // Refresh balances with loading state + setIsRefreshingBalance(true); + checkAllBalances() + .then(() => { + setIsRefreshingBalance(false); + }) + .catch((error) => { + // console.error("Error refreshing balances:", error); + setErrorMessage("Failed to refresh balances."); + setIsRefreshingBalance(false); + }); + setWithdrawAmount(""); + } + } + }, [isWaitingForWithdraw, isWithdrawing, isWithdrawSuccess, withdrawTxHash]); + + useEffect(() => { + if (approvalHash && isApprovalSuccess) { + setIsApproved(true); + setIsApproving(false); + console.log("Approval successful:"); + } else if(approvalHash && !isWaitingForApproval && !isApprovalSuccess) { + setErrorMessage("Approval transaction failed"); + setIsApproving(false); + } + }, [isWaitingForApproval, isApproving, isApprovalSuccess]); + + // Function to check balance for a strategy + const checkStrategyBalance = async (strategy: any) => { + if (!address) return 0; + + try { + // Validate boring vault address + if ( + !strategy.boringVaultAddress || + strategy.boringVaultAddress === "0x0000000000000000000000000000000000000000" + ) { + console.warn("Invalid boring vault address for strategy:", strategy); + return 0; + } + + const client = createPublicClient({ + transport: http(strategy.rpc), + chain: { + id: 8453, + name: "Base", + network: "base", + nativeCurrency: { + decimals: 18, + name: "Ether", + symbol: "ETH", + }, + rpcUrls: { + default: { http: [strategy.rpc] }, + public: { http: [strategy.rpc] }, + }, + }, + }); + + try { + const [balance, decimals] = await Promise.all([ + client.readContract({ + address: strategy.boringVaultAddress as Address, + abi: ERC20_ABI, + functionName: "balanceOf", + args: [address as Address], + }), + client.readContract({ + address: strategy.boringVaultAddress as Address, + abi: ERC20_ABI, + functionName: "decimals", + }), + ]); + console.log(`Checking chain balance: ${balance}`); + + const formattedBalance = parseFloat(formatUnits(balance as bigint, decimals as number)); + + return formattedBalance; + } catch (error) { + // console.error("Error reading boring vault:", error); + setErrorMessage("Failed to read vault balance."); + return 0; + } + } catch (error) { + // console.error("Error checking balance for strategy:", strategy, error); + setErrorMessage("Failed to check strategy balance."); + return 0; + } + }; + + const checkAllBalances = async () => { + if (!address) return; + + try { + setIsRefreshingBalance(true); + const allStrategies = [ + ...Object.entries(USD_STRATEGIES as unknown as StrategyAsset).flatMap(([duration, strategies]) => + Object.entries(strategies as StrategyDuration).map(([type, strategy]) => ({ + ...strategy, + duration, + type: type.toLowerCase(), + asset: "USD" + })) + ), + ...Object.entries(BTC_STRATEGIES as unknown as StrategyAsset).flatMap(([duration, strategies]) => + Object.entries(strategies as StrategyDuration).map(([type, strategy]) => ({ + ...strategy, + duration, + type: type.toLowerCase(), + asset: "BTC" + })) + ), + ...Object.entries(ETH_STRATEGIES as unknown as StrategyAsset).flatMap(([duration, strategies]) => + Object.entries(strategies as StrategyDuration).map(([type, strategy]) => ({ + ...strategy, + duration, + type: type.toLowerCase(), + asset: "ETH" + })) + ), + ]; + + const balances = await Promise.all( + allStrategies.map(async (strategy) => { + const balance = await checkStrategyBalance(strategy); + return { ...strategy, balance }; + }) + ); + setStrategiesWithBalance(balances.filter((s) => s.balance > 0)); + } catch (error) { + // console.error("Error checking all balances:", error); + setErrorMessage("Failed to fetch all balances."); + } finally { + setIsRefreshingBalance(false); + } + }; + + // Check balances for all strategies + useEffect(() => { + checkAllBalances(); + }, [address]); + + // Use wagmi's useWriteContract hook + const { writeContractAsync: writeContract } = useWriteContract(); + + const handleApprove = async () => { + if (!contract || !withdrawAmount || !address) return; + + try { + setIsApproving(true); + setErrorMessage(null); + setApprovalHash(null); + + console.log("Approval details:", { + solverAddress, + boringVaultAddress, + address + }); + + const client = createPublicClient({ + transport: http(Array.isArray(rpc) ? rpc[0] : rpc || "https://base.llamarpc.com"), + chain: { + id: 8453, + name: "Base", + network: "base", + nativeCurrency: { + decimals: 18, + name: "Ether", + symbol: "ETH", + }, + rpcUrls: { + default: { http: ["https://mainnet.base.org"] }, + public: { http: ["https://mainnet.base.org"] }, + }, + }, + }); + + // Get decimals from vault + const decimals = (await client.readContract({ + address: boringVaultAddress as Address, + abi: ERC20_ABI, + functionName: "decimals", + })) as number; + + const sharesAmount = parseUnits(withdrawAmount, decimals); + + console.log("Requesting approval for amount:", sharesAmount.toString()); + + // Approve the solver to spend the vault tokens + const approveTx = await writeContract({ + address: boringVaultAddress as Address, + abi: ERC20_ABI, + functionName: "approve", + args: [solverAddress as `0x${string}`, sharesAmount], + chainId: 8453, + account: address, + }); + + if (approveTx && typeof approveTx === "string" && approveTx.startsWith("0x")) { + setApprovalHash(approveTx as `0x${string}`); + console.log("Approval transaction submitted:", approveTx); + } else { + throw new Error("Failed to get approval transaction hash"); + } + } catch (error: any) { + console.error("Approval failed:", error); + if (error.code === 4001) { + setErrorMessage("Approval cancelled by user."); + } else { + setErrorMessage(error.message || "Approval transaction failed"); + } + setIsApproving(false); + } + }; + + const handleWithdraw = async () => { + if (!contract || !withdrawAmount || !address || !isApproved) return; + + try { + setIsWithdrawing(true); + setErrorMessage(null); + const assetOutAddress = "0x820C137fa70C8691f0e44Dc420a5e53c168921Dc" as Address; + + const client = createPublicClient({ + transport: http(Array.isArray(rpc) ? rpc[0] : rpc || "https://base.llamarpc.com"), + chain: { + id: 8453, + name: "Base", + network: "base", + nativeCurrency: { + decimals: 18, + name: "Ether", + symbol: "ETH", + }, + rpcUrls: { + default: { http: ["https://mainnet.base.org"] }, + public: { http: ["https://mainnet.base.org"] }, + }, + }, + }); + + // Get decimals from vault contract + const decimals = (await client.readContract({ + address: boringVaultAddress as Address, + abi: ERC20_ABI, + functionName: "decimals", + })) as number; + + const sharesAmount = parseUnits(withdrawAmount, decimals); + // Convert to uint128 + const amountOfShares = BigInt(sharesAmount.toString()); + const discount = 100; // uint16 - hardcoded + const secondsToDeadline = 3600; // uint24 - hardcoded (1 hour) + + console.log("Debug - Contract call parameters:", { + functionName: "requestOnChainWithdraw", + contractAddress: solverAddress, + args: { + assetOut: assetOutAddress, + amountOfShares: amountOfShares.toString(), + discount: discount.toString(), + secondsToDeadline: secondsToDeadline.toString() + }, + types: { + assetOut: typeof assetOutAddress, + amountOfShares: typeof amountOfShares, + discount: typeof discount, + secondsToDeadline: typeof secondsToDeadline + } + }); + + const tx = await writeContract({ + address: solverAddress as Address, + abi: SOLVER_ABI, + functionName: "requestOnChainWithdraw", + args: [ + assetOutAddress, + amountOfShares, + discount, + secondsToDeadline + ], + chainId: 8453, + account: address, + }); + + if (tx && typeof tx === "string" && tx.startsWith("0x")) { + console.log("Withdrawal transaction submitted:", tx); + setWithdrawTxHash(tx as `0x${string}`); + } else { + throw new Error("Failed to get transaction hash"); + } + } catch (error: any) { + if (error.code === 4001) { + setErrorMessage("Withdrawal cancelled by user."); + } else { + setErrorMessage("Withdrawal failed. Please try again."); + } + } finally { + setIsWithdrawing(false); + } + }; + + const handleAmountChange = (e: React.ChangeEvent) => { + const value = e.target.value; + if (/^\d*\.?\d*$/.test(value)) { + setWithdrawAmount(value); + } + }; + + const handlePercentageClick = (percentage: number) => { + if (contract) { + const amount = balance !== undefined ? (Number(balance) * percentage).toFixed(6) : "0.000000"; + setWithdrawAmount(amount); + } + }; + + const handleMaxClick = () => { + if (contract) { + if (balance !== undefined) { + setWithdrawAmount(balance.toString()); + } + } + }; + + const getDepositedChainsViem = async ({ + userAddress, + strategy, + chainConfigs, + }: { + userAddress: Address; + strategy: any; + chainConfigs: Record< + string, + { + rpc: string; + chainId: number; + image: string; + chainObject: any; + } + >; + }): Promise => { + const depositedChains: string[] = []; + + for (const [chainKey, chain] of Object.entries(chainConfigs)) { + try { + const client = createPublicClient({ + transport: http(chain.rpc), + chain: chain.chainObject, + }); + + const decimals = strategy.shareAddress_token_decimal ?? 18; + + const balance = await client.readContract({ + address: strategy.shareAddress as Address, + abi: ERC20_ABI, + functionName: "balanceOf", + args: [userAddress], + }); + + const formatted = Number(formatUnits(balance as bigint, decimals)); + + console.log(`[${chainKey}] Balance: ${formatted}`); + + if (formatted > 0) { + depositedChains.push(chainKey); + } + } catch (err) { + console.error(`Error on ${chainKey}:`, err); + } + } + + return depositedChains; + }; + + useEffect(() => { + const fetchDeposits = async () => { + if (!address || !strategy || !chainConfigs) { + console.log("Missing required data:", { address, strategy, chainConfigs }); + return; + } + console.log("Fetching deposits with:", { address, strategy, chainConfigs }); + + const depositedOn = await getDepositedChainsViem({ + userAddress: address as Address, + strategy, + chainConfigs, + }); + + setDepositedChains(depositedOn); + console.log("Deposited on:", depositedOn); + }; + fetchDeposits(); + }, [address,strategy,chainConfigs]); + + useEffect(() => { + const fetchAmountOut = async () => { + if (!contract || !withdrawAmount) return; + + try { + const selectedAssetAddress = getAddress(assetOptions[selectedAssetIdx].contract); + + const client = createPublicClient({ + transport: http(Array.isArray(rpc) ? rpc[0] : rpc || "https://base.llamarpc.com"), + chain: { + id: 8453, + name: "Base", + network: "base", + nativeCurrency: { + decimals: 18, + name: "Ether", + symbol: "ETH", + }, + rpcUrls: { + default: { http: ["https://mainnet.base.org"] }, + public: { http: ["https://mainnet.base.org"] }, + }, + }, + }); + + //Get decimals of the vault + const decimals = (await client.readContract({ + address: boringVaultAddress as Address, + abi: ERC20_ABI, + functionName: "decimals", + })) as number; + + //Convert withdrawAmount to uint128 (BigInt) + const shares = parseUnits(withdrawAmount, decimals); + const discount = 0; + + //Call the previewAssetsOut + const result = (await client.readContract({ + address: solverAddress as Address, + abi: SOLVER_ABI, + functionName: "previewAssetsOut", + args: [ + selectedAssetAddress, + shares, + discount, + ], + })); + + setAmountOut(result.toString()); + } catch (err) { + console.error("Error reading previewAssetsOut:", err); + setAmountOut(null); + } + }; + + fetchAmountOut(); + }, [contract, withdrawAmount]); + + + return ( + <> + +
+
+
+ + +
+ {activeTab === "withdraw" && ( + <> +
+ {/* Header with strategy info and balance */} +
+
+ {typeof +
+
+ {type === "stable" + ? "Base Yield" + : "Incentive Maxi"}{" "} + {asset} +
+
+ +0.00 in 1 year +
+
+
+
+ Balance:{" "} + + {typeof balance === "string" ? parseFloat(balance).toFixed(4) : ""} + +
+
+ + {/* Input field and percentage buttons in same row */} +
+ {/* Input field on the left with no borders */} +
+
+ +
+
+ + {/* Percentage buttons on the right */} +
+ + + + +
+
+ +
+
+ You Will Receive +
+
+
+ {formatUnits(amountOut ? BigInt(amountOut) : BigInt(0), 6)}{" "} +
+ {assetOptions.length > 1 && ( +
+
+ + + {isAssetDropdownOpen && ( +
+ {assetOptions.map((opt, idx) => ( + + ))} +
+ )} +
+
+ )} +
+
+ + + {errorMessage && ( +
+
+ {errorMessage} +
+
+ # + {withdrawTxHash + ? withdrawTxHash.substring(0, 8) + "..." + : ""} +
+
+ )} + {!errorMessage && withdrawTxHash && isWithdrawSuccess && ( +
+
+ Transaction Successful +
+ + #{withdrawTxHash.substring(0, 8)}... + +
+ )} +
+
+
+ Note: By withdrawing, your vault shares will + be converted into the underlying asset, subject to the current + market rates. Withdrawal amounts are calculated based on the + latest market rates and may vary slightly due to price + fluctuations. +
+
+ + )} + + {activeTab === "request" && ( +
+ {/* Tabs */} +
+ + +
+ + {/* Requests List */} + {requestTab === "pending" && ( +
+ {requests.map((req, idx) => ( +
+
+
+ {/* Calendar Icon + Date */} +
+ + {req.date} +
+ {/* Cancel Button */} + {req.canCancel && ( + + )} +
+ + {/* Amounts */} +
+ {/* From Amount */} +
+ {typeof + {req.fromAmount} +
+ + {/* Arrow */} + + + {/* To Amount */} +
+ {req.toAmount} + {typeof +
+
+
+
+ ))} +
+ )} + + {requestTab === "completed" && ( +
+ {requests.map((req, idx) => ( +
+
+
+ {/* Calendar Icon + Date */} +
+ + {req.date} +
+
+ + {/* Amounts */} +
+ {/* From Amount */} +
+ {typeof + {req.fromAmount} +
+ + {/* Arrow */} + + + {/* To Amount */} +
+ {req.toAmount} + {typeof +
+
+
+
+ ))} +
+ )} + +
+ )} + +
+
+ + ); +}; + +export default PortfolioDetailedPage; \ No newline at end of file From 8c5973799fcc2a7d6cba25e05ac2750b8278977f Mon Sep 17 00:00:00 2001 From: Gyanshu Pathak Date: Mon, 30 Jun 2025 10:19:39 +0530 Subject: [PATCH 22/22] fix: build type errors --- next.config.js | 4 + package-lock.json | 143 +++++++++++++++++--------------- package.json | 5 +- src/pages/earn-subpage.tsx | 8 +- src/pages/portfolio-subpage.tsx | 6 +- src/types/tailwind-merge.d.ts | 3 + src/utils/balanceCalculator.ts | 2 +- 7 files changed, 96 insertions(+), 75 deletions(-) create mode 100644 src/types/tailwind-merge.d.ts diff --git a/next.config.js b/next.config.js index bea0abf..36c2313 100644 --- a/next.config.js +++ b/next.config.js @@ -1,6 +1,10 @@ /** @type {import('next').NextConfig} */ const nextConfig = { + transpilePackages: ['@vanilla-extract', '@rainbow-me'], reactStrictMode: true, + experimental: { + esmExternals: 'loose', + }, webpack: (config) => { config.externals.push('pino-pretty', 'lokijs', 'encoding'); return config; diff --git a/package-lock.json b/package-lock.json index 31d7dbb..02fdb68 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "dependencies": { "@radix-ui/react-slot": "^1.1.2", "@radix-ui/react-tooltip": "^1.1.8", - "@rainbow-me/rainbowkit": "^2.2.4", + "@rainbow-me/rainbowkit": "^2.2.8", "@tanstack/react-query": "^5.55.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -24,7 +24,7 @@ "react-error-boundary": "^5.0.0", "react-icons": "^5.5.0", "recharts": "^2.15.3", - "tailwind-merge": "^3.0.2", + "tailwind-merge": "^3.3.1", "tailwindcss-animate": "^1.0.7", "viem": "2.23.3", "wagmi": "^2.14.11" @@ -37,6 +37,7 @@ "autoprefixer": "^10.4.21", "eslint-config-next": "^15.1.7", "postcss": "^8.4.35", + "postinstall-postinstall": "^2.1.0", "tailwindcss": "^3.4.1", "typescript": "5.5.4" } @@ -142,8 +143,7 @@ "node_modules/@emotion/hash": { "version": "0.9.2", "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", - "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", - "license": "MIT" + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==" }, "node_modules/@eslint-community/eslint-utils": { "version": "4.5.1", @@ -2000,16 +2000,15 @@ "license": "MIT" }, "node_modules/@rainbow-me/rainbowkit": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/@rainbow-me/rainbowkit/-/rainbowkit-2.2.4.tgz", - "integrity": "sha512-LUYBcB5bzLf6/BMdnW3dEFHVqoPkTGcFN3u6WamaIHXuqD9HT+HVAeNlcYvKENBXldN2zNBs1Bt3k8Oy7y5bTw==", - "license": "MIT", + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/@rainbow-me/rainbowkit/-/rainbowkit-2.2.8.tgz", + "integrity": "sha512-EdNIK2cdAT6GJ9G11wx7nCVfjqBfxh7dx/1DhPYrB+yg+VFrII6cM1PiMFVC9evD4mqVHe9mmLAt3nvlwDdiPQ==", "dependencies": { - "@vanilla-extract/css": "1.15.5", - "@vanilla-extract/dynamic": "2.1.2", - "@vanilla-extract/sprinkles": "1.6.3", + "@vanilla-extract/css": "1.17.3", + "@vanilla-extract/dynamic": "2.1.4", + "@vanilla-extract/sprinkles": "1.6.4", "clsx": "2.1.1", - "qrcode": "1.5.4", + "cuer": "0.0.2", "react-remove-scroll": "2.6.2", "ua-parser-js": "^1.0.37" }, @@ -2024,23 +2023,6 @@ "wagmi": "^2.9.0" } }, - "node_modules/@rainbow-me/rainbowkit/node_modules/qrcode": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz", - "integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==", - "license": "MIT", - "dependencies": { - "dijkstrajs": "^1.0.1", - "pngjs": "^5.0.0", - "yargs": "^15.3.1" - }, - "bin": { - "qrcode": "bin/qrcode" - }, - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -2763,13 +2745,12 @@ ] }, "node_modules/@vanilla-extract/css": { - "version": "1.15.5", - "resolved": "https://registry.npmjs.org/@vanilla-extract/css/-/css-1.15.5.tgz", - "integrity": "sha512-N1nQebRWnXvlcmu9fXKVUs145EVwmWtMD95bpiEKtvehHDpUhmO1l2bauS7FGYKbi3dU1IurJbGpQhBclTr1ng==", - "license": "MIT", + "version": "1.17.3", + "resolved": "https://registry.npmjs.org/@vanilla-extract/css/-/css-1.17.3.tgz", + "integrity": "sha512-jHivr1UPoJTX5Uel4AZSOwrCf4mO42LcdmnhJtUxZaRWhW4FviFbIfs0moAWWld7GOT+2XnuVZjjA/K32uUnMQ==", "dependencies": { "@emotion/hash": "^0.9.0", - "@vanilla-extract/private": "^1.0.6", + "@vanilla-extract/private": "^1.0.8", "css-what": "^6.1.0", "cssesc": "^3.0.0", "csstype": "^3.0.7", @@ -2783,25 +2764,22 @@ } }, "node_modules/@vanilla-extract/dynamic": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@vanilla-extract/dynamic/-/dynamic-2.1.2.tgz", - "integrity": "sha512-9BGMciD8rO1hdSPIAh1ntsG4LPD3IYKhywR7VOmmz9OO4Lx1hlwkSg3E6X07ujFx7YuBfx0GDQnApG9ESHvB2A==", - "license": "MIT", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vanilla-extract/dynamic/-/dynamic-2.1.4.tgz", + "integrity": "sha512-7+Ot7VlP3cIzhJnTsY/kBtNs21s0YD7WI1rKJJKYP56BkbDxi/wrQUWMGEczKPUDkJuFcvbye+E2ub1u/mHH9w==", "dependencies": { - "@vanilla-extract/private": "^1.0.6" + "@vanilla-extract/private": "^1.0.8" } }, "node_modules/@vanilla-extract/private": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@vanilla-extract/private/-/private-1.0.6.tgz", - "integrity": "sha512-ytsG/JLweEjw7DBuZ/0JCN4WAQgM9erfSTdS1NQY778hFQSZ6cfCDEZZ0sgVm4k54uNz6ImKB33AYvSR//fjxw==", - "license": "MIT" + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@vanilla-extract/private/-/private-1.0.9.tgz", + "integrity": "sha512-gT2jbfZuaaCLrAxwXbRgIhGhcXbRZCG3v4TTUnjw0EJ7ArdBRxkq4msNJkbuRkCgfIK5ATmprB5t9ljvLeFDEA==" }, "node_modules/@vanilla-extract/sprinkles": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/@vanilla-extract/sprinkles/-/sprinkles-1.6.3.tgz", - "integrity": "sha512-oCHlQeYOBIJIA2yWy2GnY5wE2A7hGHDyJplJo4lb+KEIBcJWRnDJDg8ywDwQS5VfWJrBBO3drzYZPFpWQjAMiQ==", - "license": "MIT", + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/@vanilla-extract/sprinkles/-/sprinkles-1.6.4.tgz", + "integrity": "sha512-lW3MuIcdIeHKX81DzhTnw68YJdL1ial05exiuvTLJMdHXQLKcVB93AncLPajMM6mUhaVVx5ALZzNHMTrq/U9Hg==", "peerDependencies": { "@vanilla-extract/css": "^1.0.0" } @@ -4272,10 +4250,9 @@ } }, "node_modules/css-what": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", - "license": "BSD-2-Clause", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", "engines": { "node": ">= 6" }, @@ -4301,6 +4278,30 @@ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "license": "MIT" }, + "node_modules/cuer": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/cuer/-/cuer-0.0.2.tgz", + "integrity": "sha512-MG1BYnnSLqBnO0dOBS1Qm/TEc9DnFa9Sz2jMA24OF4hGzs8UuPjpKBMkRPF3lrpC+7b3EzULwooX9djcvsM8IA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "dependencies": { + "qr": "~0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18", + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/d3-array": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", @@ -4539,10 +4540,9 @@ } }, "node_modules/dedent": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", - "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", - "license": "MIT", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz", + "integrity": "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==", "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, @@ -4563,14 +4563,12 @@ "node_modules/deep-object-diff": { "version": "1.1.9", "resolved": "https://registry.npmjs.org/deep-object-diff/-/deep-object-diff-1.1.9.tgz", - "integrity": "sha512-Rn+RuwkmkDwCi2/oXOFS9Gsr5lJZu/yTGpK7wAaAIE75CC+LCGEZHpY6VQJa/RoJcrmaA/docWJZvYohlNkWPA==", - "license": "MIT" + "integrity": "sha512-Rn+RuwkmkDwCi2/oXOFS9Gsr5lJZu/yTGpK7wAaAIE75CC+LCGEZHpY6VQJa/RoJcrmaA/docWJZvYohlNkWPA==" }, "node_modules/deepmerge": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -7548,7 +7546,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/media-query-parser/-/media-query-parser-2.0.2.tgz", "integrity": "sha512-1N4qp+jE0pL5Xv4uEcwVUhIkwdUO3S/9gML90nqKA7v7FcOS5vUtatfzok9S9U1EJU8dHWlcv95WLnKmmxZI9w==", - "license": "MIT", "dependencies": { "@babel/runtime": "^7.12.5" } @@ -7668,8 +7665,7 @@ "node_modules/modern-ahocorasick": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/modern-ahocorasick/-/modern-ahocorasick-1.1.0.tgz", - "integrity": "sha512-sEKPVl2rM+MNVkGQt3ChdmD8YsigmXdn5NifZn6jiwn9LRJpWm8F3guhaqrJT/JOat6pwpbXEk6kv+b9DMIjsQ==", - "license": "MIT" + "integrity": "sha512-sEKPVl2rM+MNVkGQt3ChdmD8YsigmXdn5NifZn6jiwn9LRJpWm8F3guhaqrJT/JOat6pwpbXEk6kv+b9DMIjsQ==" }, "node_modules/motion": { "version": "10.16.2", @@ -8638,6 +8634,13 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "license": "MIT" }, + "node_modules/postinstall-postinstall": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/postinstall-postinstall/-/postinstall-postinstall-2.1.0.tgz", + "integrity": "sha512-7hQX6ZlZXIoRiWNrbMQaLzUUfH+sSx39u8EJ9HYuDc1kLo9IXKWjM5RSquZN1ad5GnH8CGFM78fsAAQi3OKEEQ==", + "dev": true, + "hasInstallScript": true + }, "node_modules/preact": { "version": "10.26.4", "resolved": "https://registry.npmjs.org/preact/-/preact-10.26.4.tgz", @@ -8723,6 +8726,17 @@ "node": ">=6" } }, + "node_modules/qr": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/qr/-/qr-0.5.0.tgz", + "integrity": "sha512-LtnyJsepKCMzfmHBZKVNo1g29kS+8ZbuxE9294EsRhHgVVpy4x8eFw9o4J9SIolDHoDYuaEIY+z8UjiCv/eudA==", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/qrcode": { "version": "1.5.3", "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.3.tgz", @@ -10027,10 +10041,9 @@ } }, "node_modules/tailwind-merge": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.0.2.tgz", - "integrity": "sha512-l7z+OYZ7mu3DTqrL88RiKrKIqO3NcpEO8V/Od04bNpvk0kiIFndGEoqfuzvj4yuhRkHKjRkII2z+KS2HfPcSxw==", - "license": "MIT", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.3.1.tgz", + "integrity": "sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==", "funding": { "type": "github", "url": "https://github.com/sponsors/dcastil" diff --git a/package.json b/package.json index 438be8a..a8b2b1f 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "dependencies": { "@radix-ui/react-slot": "^1.1.2", "@radix-ui/react-tooltip": "^1.1.8", - "@rainbow-me/rainbowkit": "^2.2.4", + "@rainbow-me/rainbowkit": "^2.2.8", "@tanstack/react-query": "^5.55.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -25,7 +25,7 @@ "react-error-boundary": "^5.0.0", "react-icons": "^5.5.0", "recharts": "^2.15.3", - "tailwind-merge": "^3.0.2", + "tailwind-merge": "^3.3.1", "tailwindcss-animate": "^1.0.7", "viem": "2.23.3", "wagmi": "^2.14.11" @@ -38,6 +38,7 @@ "autoprefixer": "^10.4.21", "eslint-config-next": "^15.1.7", "postcss": "^8.4.35", + "postinstall-postinstall": "^2.1.0", "tailwindcss": "^3.4.1", "typescript": "5.5.4" } diff --git a/src/pages/earn-subpage.tsx b/src/pages/earn-subpage.tsx index 0913a14..9b2343f 100644 --- a/src/pages/earn-subpage.tsx +++ b/src/pages/earn-subpage.tsx @@ -97,10 +97,10 @@ interface YieldSubpageProps { const getStrategyInfo = (duration: DurationType): StrategyData => { const getAssetStrategies = (asset: AssetType) => { - const strategies: Record> = { - USD: USD_STRATEGIES as Record, - BTC: BTC_STRATEGIES as Record, - ETH: ETH_STRATEGIES as Record, + const strategies: Record>> = { + USD: USD_STRATEGIES as unknown as Partial>, + BTC: BTC_STRATEGIES as unknown as Partial>, + ETH: ETH_STRATEGIES as unknown as Partial>, }; const strategy = strategies[asset][duration]; diff --git a/src/pages/portfolio-subpage.tsx b/src/pages/portfolio-subpage.tsx index 0bdf21d..123f66e 100644 --- a/src/pages/portfolio-subpage.tsx +++ b/src/pages/portfolio-subpage.tsx @@ -280,7 +280,7 @@ const PortfolioSubpage: React.FC = () => { try { setIsRefreshingBalance(true); const allStrategies = [ - ...Object.entries(USD_STRATEGIES as StrategyAsset).flatMap(([duration, strategies]) => + ...Object.entries(USD_STRATEGIES as unknown as StrategyAsset).flatMap(([duration, strategies]) => Object.entries(strategies as StrategyDuration).map(([type, strategy]) => ({ ...strategy, duration, @@ -288,7 +288,7 @@ const PortfolioSubpage: React.FC = () => { asset: "USD" })) ), - ...Object.entries(BTC_STRATEGIES as StrategyAsset).flatMap(([duration, strategies]) => + ...Object.entries(BTC_STRATEGIES as unknown as StrategyAsset).flatMap(([duration, strategies]) => Object.entries(strategies as StrategyDuration).map(([type, strategy]) => ({ ...strategy, duration, @@ -296,7 +296,7 @@ const PortfolioSubpage: React.FC = () => { asset: "BTC" })) ), - ...Object.entries(ETH_STRATEGIES as StrategyAsset).flatMap(([duration, strategies]) => + ...Object.entries(ETH_STRATEGIES as unknown as StrategyAsset).flatMap(([duration, strategies]) => Object.entries(strategies as StrategyDuration).map(([type, strategy]) => ({ ...strategy, duration, diff --git a/src/types/tailwind-merge.d.ts b/src/types/tailwind-merge.d.ts new file mode 100644 index 0000000..4ff47d6 --- /dev/null +++ b/src/types/tailwind-merge.d.ts @@ -0,0 +1,3 @@ +declare module 'tailwind-merge' { + export function twMerge(...classLists: string[]): string; + } \ No newline at end of file diff --git a/src/utils/balanceCalculator.ts b/src/utils/balanceCalculator.ts index 13e7ef9..65eefa7 100644 --- a/src/utils/balanceCalculator.ts +++ b/src/utils/balanceCalculator.ts @@ -53,7 +53,7 @@ export async function calculateTotalBalanceInUSD( provider: ethers.Provider ): Promise { const balances: TokenBalance[] = []; - const strategy = USD_STRATEGIES.PERPETUAL_DURATION.STABLE as StrategyConfig; + const strategy = USD_STRATEGIES.PERPETUAL_DURATION.STABLE as unknown as StrategyConfig; // Check each chain configuration const chains = ['base', 'ethereum', 'arbitrum'] as const;