-
-
Notifications
You must be signed in to change notification settings - Fork 14
Feature/smart contract integration 4 #31
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,6 +19,9 @@ import { | |
| isDeployedAddress, | ||
| } from "@/utils/addresses"; | ||
|
|
||
| import { motion, AnimatePresence } from "framer-motion"; | ||
| import { Loader2, ArrowDownUp, CheckCircle2, AlertCircle, Info, Wallet } from "lucide-react"; | ||
|
|
||
| import DJED_ABI from "@/utils/abi/Djed.json"; | ||
| import COIN_ABI from "@/utils/abi/Coin.json"; | ||
| import UnsupportedNetwork from "@/components/UnsupportedNetwork"; | ||
|
|
@@ -118,6 +121,24 @@ function TradePage() { | |
| enabled: isDeployedAddress(contractAddress), | ||
| }); | ||
|
|
||
| const { data: baseCoinBalance } = useReadContract({ | ||
| address: baseCoinAddress as `0x${string}`, | ||
| abi: COIN_ABI, | ||
| functionName: "balanceOf", | ||
| args: address ? [address] : undefined, | ||
| chainId, | ||
| enabled: isDeployedAddress(baseCoinAddress) && Boolean(address), | ||
| }); | ||
|
|
||
| const { data: stableCoinBalance } = useReadContract({ | ||
| address: stableCoin, | ||
| abi: COIN_ABI, | ||
| functionName: "balanceOf", | ||
| args: address ? [address] : undefined, | ||
| chainId, | ||
| enabled: isDeployedAddress(stableCoin) && Boolean(address), | ||
| }); | ||
|
|
||
| const { data: baseCoinAllowance } = useReadContract({ | ||
| address: baseCoinAddress as `0x${string}`, | ||
| abi: COIN_ABI, | ||
|
|
@@ -280,16 +301,180 @@ function TradePage() { | |
| return <div>Connect Wallet</div>; | ||
| } | ||
|
|
||
| /* ================= UI ================= */ | ||
| /* ================= UI / FEE / STATE CALCS ================= */ | ||
|
|
||
| return ( | ||
| <div> | ||
| <button onClick={handleTrade} disabled={!parsedAmount || isPending}> | ||
| Execute Trade | ||
| </button> | ||
| const isBuy = tradeType === "buy-stable"; | ||
|
|
||
| // Balances | ||
| const dBaseBalance = baseCoinBalance ? Number(formatUnits(baseCoinBalance as bigint, 18)).toFixed(2) : "0.00"; | ||
| const dStableBalance = stableCoinBalance ? Number(formatUnits(stableCoinBalance as bigint, 18)).toFixed(2) : "0.00"; | ||
|
|
||
| // Complex fee calculation (Mocked 2% platform fee for demonstration) | ||
| const numericAmount = parseFloat(amount || "0"); | ||
| const feePercentage = 0.02; | ||
| const feeAmount = numericAmount * feePercentage; | ||
| const finalAmount = numericAmount > 0 ? numericAmount - feeAmount : 0; | ||
|
Comment on lines
+312
to
+316
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mocked fee calculation displayed as actual transaction cost. The 2% fee is hardcoded and labeled "Platform Fee" in the UI, but the actual on-chain fee logic may differ. This could mislead users about the real cost of their transaction. Either fetch the actual fee from the contract or clearly label this as an estimate/placeholder. 🤖 Prompt for AI Agents |
||
|
|
||
| // Deriving transaction stages | ||
| const needsApproval = isBuy | ||
| ? (baseCoinAllowance ?? 0n) < parsedAmount | ||
| : (stableCoinAllowance ?? 0n) < parsedAmount; | ||
|
|
||
| const isInsufficientBalance = isBuy | ||
| ? (baseCoinBalance !== undefined && parsedAmount > (baseCoinBalance as bigint)) | ||
| : (stableCoinBalance !== undefined && parsedAmount > (stableCoinBalance as bigint)); | ||
|
|
||
| {error && <div>{error.message}</div>} | ||
| {isConfirmed && <div>Trade Confirmed</div>} | ||
| const isApproving = isPending && isApprovalConfirmed === false && approvalHash !== undefined; | ||
| const isExecuting = isPending && !isApproving; | ||
|
|
||
| const getButtonState = () => { | ||
| if (isPending) { | ||
| if (needsApproval) return { text: "Processing Approval...", icon: <Loader2 className="w-5 h-5 animate-spin" />, disabled: true }; | ||
| return { text: "Processing Trade...", icon: <Loader2 className="w-5 h-5 animate-spin" />, disabled: true }; | ||
| } | ||
| if (isConfirmed && !error && numericAmount > 0) return { text: "Confirmed! ✅", icon: null, disabled: false }; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Button shows "Confirmed!" even when a previous transaction error exists. The condition Consider resetting transaction state when the user changes the amount or trade type. 🤖 Prompt for AI Agents |
||
|
|
||
| if (numericAmount === 0) return { text: "Enter an amount", icon: null, disabled: true }; | ||
| if (isInsufficientBalance) return { text: "Insufficient Balance", icon: null, disabled: true }; | ||
| if (needsApproval) return { text: `Step 1: Approve ${isBuy ? 'BaseCoin' : 'StableCoin'}`, icon: null, disabled: false }; | ||
|
|
||
| return { text: `Step 2: ${isBuy ? 'Buy StableCoin' : 'Sell StableCoin'}`, icon: null, disabled: false }; | ||
| }; | ||
|
|
||
| const btnState = getButtonState(); | ||
|
|
||
| return ( | ||
| <div className="w-full max-w-lg mx-auto mt-12 p-1"> | ||
| <motion.div | ||
| initial={{ opacity: 0, y: 20 }} | ||
| animate={{ opacity: 1, y: 0 }} | ||
| className="bg-white/50 dark:bg-slate-900/50 backdrop-blur-xl rounded-[2rem] p-6 shadow-2xl border border-white/20 dark:border-slate-800/50" | ||
| > | ||
| <div className="flex items-center justify-between mb-8"> | ||
| <h2 className="text-2xl font-bold bg-gradient-to-r from-orange-600 to-orange-400 bg-clip-text text-transparent"> | ||
| Swap StableCoins | ||
| </h2> | ||
| <div className="flex bg-slate-200/50 dark:bg-slate-800/50 rounded-full p-1 border border-slate-300/30 dark:border-slate-700/50"> | ||
| <button | ||
| onClick={() => { setTradeType("buy-stable"); setAmount(""); }} | ||
| className={`px-4 py-1.5 rounded-full text-sm font-semibold transition-all ${isBuy ? 'bg-white dark:bg-slate-700 shadow-md text-orange-600 dark:text-orange-400' : 'text-slate-600 dark:text-slate-400'}`} | ||
| > | ||
| Buy | ||
| </button> | ||
| <button | ||
| onClick={() => { setTradeType("sell-stable"); setAmount(""); }} | ||
| className={`px-4 py-1.5 rounded-full text-sm font-semibold transition-all ${!isBuy ? 'bg-white dark:bg-slate-700 shadow-md text-orange-600 dark:text-orange-400' : 'text-slate-600 dark:text-slate-400'}`} | ||
| > | ||
| Sell | ||
| </button> | ||
| </div> | ||
| </div> | ||
|
|
||
| {/* Input Box */} | ||
| <div className="bg-slate-100/50 dark:bg-slate-800/50 rounded-2xl p-4 mb-2 border border-slate-200/50 dark:border-slate-700/50 transition-all focus-within:ring-2 focus-within:ring-orange-500/50"> | ||
| <div className="flex justify-between items-center mb-2"> | ||
| <label className="text-sm font-medium text-slate-500 dark:text-slate-400"> | ||
| You {isBuy ? 'Pay' : 'Convert'} | ||
| </label> | ||
| <div className="flex items-center gap-1.5 text-xs font-semibold text-slate-500 bg-white/50 dark:bg-slate-900/50 px-2.5 py-1 rounded-lg"> | ||
| <Wallet className="w-3.5 h-3.5 text-orange-500" /> | ||
| {isBuy ? `${dBaseBalance} BaseCoin` : `${dStableBalance} DJED`} | ||
| </div> | ||
| </div> | ||
|
|
||
| <div className="flex items-center gap-3"> | ||
| <input | ||
| type="number" | ||
| value={amount} | ||
| onChange={(e) => setAmount(e.target.value)} | ||
| placeholder="0.00" | ||
| className="w-full bg-transparent text-4xl font-black text-slate-800 dark:text-white outline-none placeholder:text-slate-300 dark:placeholder:text-slate-700" | ||
| /> | ||
| <div className="shrink-0 bg-white dark:bg-slate-800 shadow-sm border border-slate-200 dark:border-slate-700 rounded-xl px-4 py-2 flex items-center gap-2 font-bold text-base md:text-lg"> | ||
| {isBuy ? '🪙 Base' : '🌟 DJED'} | ||
| </div> | ||
| </div> | ||
| </div> | ||
|
|
||
| {/* Swap Arrow */} | ||
| <div className="flex justify-center -my-3 relative z-10 pointer-events-none"> | ||
| <div className="bg-white dark:bg-slate-800 border-4 border-slate-50 dark:border-slate-900 p-2 rounded-full shadow-lg"> | ||
| <ArrowDownUp className="w-5 h-5 text-orange-500" /> | ||
| </div> | ||
| </div> | ||
|
|
||
| {/* Output Box */} | ||
| <div className="bg-white/60 dark:bg-slate-800/30 rounded-2xl p-4 mt-2 border border-slate-200/50 dark:border-slate-700/50 relative overflow-hidden"> | ||
| <div className="flex justify-between items-center mb-2"> | ||
| <label className="text-sm font-medium text-slate-500 dark:text-slate-400"> | ||
| You Receive (Estimated) | ||
| </label> | ||
| <div className="flex items-center gap-1.5 text-xs font-semibold text-slate-500 bg-white/50 dark:bg-slate-900/50 px-2.5 py-1 rounded-lg"> | ||
| <Wallet className="w-3.5 h-3.5 text-orange-500" /> | ||
| {!isBuy ? `${dBaseBalance} BaseCoin` : `${dStableBalance} DJED`} | ||
| </div> | ||
| </div> | ||
|
|
||
| <div className="flex items-center gap-3"> | ||
| <input | ||
| type="text" | ||
| readOnly | ||
| value={finalAmount > 0 ? finalAmount.toFixed(4) : ""} | ||
| placeholder="0.00" | ||
| className="w-full bg-transparent text-3xl font-bold text-slate-600 dark:text-slate-200 outline-none placeholder:text-slate-300 dark:placeholder:text-slate-700" | ||
| /> | ||
| <div className="shrink-0 bg-slate-100 dark:bg-slate-900 shadow-inner rounded-xl px-4 py-2 flex items-center gap-2 font-bold text-base md:text-lg text-slate-500"> | ||
| {!isBuy ? '🪙 Base' : '🌟 DJED'} | ||
| </div> | ||
| </div> | ||
| </div> | ||
|
|
||
| {/* Complex Fee Details */} | ||
| <AnimatePresence> | ||
| {numericAmount > 0 && ( | ||
| <motion.div | ||
| initial={{ height: 0, opacity: 0 }} | ||
| animate={{ height: "auto", opacity: 1 }} | ||
| exit={{ height: 0, opacity: 0 }} | ||
| className="overflow-hidden mt-4" | ||
| > | ||
| <div className="bg-slate-50 dark:bg-slate-900/50 rounded-xl p-4 space-y-2 border border-slate-200/50 dark:border-slate-800"> | ||
| <div className="flex justify-between items-center text-sm"> | ||
| <span className="text-slate-500 flex items-center gap-1"><Info className="w-4 h-4"/> Input Amount</span> | ||
| <span className="font-semibold">{numericAmount.toFixed(2)} {isBuy ? 'BaseCoin' : 'DJED'}</span> | ||
| </div> | ||
| <div className="flex justify-between items-center text-sm"> | ||
| <span className="text-slate-500 flex items-center gap-1">Platform Fee (2%)</span> | ||
| <span className="font-semibold text-red-500">- {feeAmount.toFixed(4)} {isBuy ? 'BaseCoin' : 'DJED'}</span> | ||
| </div> | ||
| <div className="h-px w-full bg-slate-200 dark:bg-slate-700 my-2" /> | ||
| <div className="flex justify-between items-center font-bold text-orange-600 dark:text-orange-400"> | ||
| <span>Final Output</span> | ||
| <span>{finalAmount.toFixed(4)} {!isBuy ? 'BaseCoin' : 'DJED'}</span> | ||
| </div> | ||
| </div> | ||
| </motion.div> | ||
| )} | ||
| </AnimatePresence> | ||
|
|
||
| {/* Transaction Status Area */} | ||
| {error && ( | ||
| <div className="mt-4 bg-red-50 dark:bg-red-900/20 text-red-600 dark:text-red-400 p-4 rounded-xl flex items-start gap-3 border border-red-200 dark:border-red-800/30"> | ||
| <AlertCircle className="w-5 h-5 shrink-0 mt-0.5" /> | ||
| <div className="text-sm font-medium pr-2 break-all overflow-hidden overflow-ellipsis line-clamp-2"> | ||
| Transaction Failed: {error?.message?.split('\n')[0] || "Unknown error"} | ||
| </div> | ||
| </div> | ||
| )} | ||
|
|
||
| <button | ||
| onClick={handleTrade} | ||
| disabled={!parsedAmount || btnState.disabled} | ||
| className="w-full mt-6 bg-gradient-to-r from-orange-600 to-orange-500 hover:from-orange-500 hover:to-orange-400 text-white font-bold py-4 px-6 rounded-xl shadow-xl shadow-orange-500/30 transition-all active:scale-[0.98] disabled:opacity-50 disabled:active:scale-100 disabled:shadow-none flex items-center justify-center gap-2" | ||
| > | ||
| {btnState.text} {btnState.icon} | ||
| </button> | ||
| </motion.div> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.