Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
294 changes: 239 additions & 55 deletions web/src/app/[cat]/InteractionClient.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
"use client";

import React, { useEffect, useState } from "react";
import { Info } from "lucide-react";
import { Info, Coins, Settings, ArrowUpRight, ArrowDownRight, Lock, Unlock } from "lucide-react";
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
import { getPublicClient } from "@wagmi/core";
import { config } from "@/utils/config";
import { useSearchParams } from "next/navigation";
import { CONTRIBUTION_ACCOUNTING_TOKEN_ABI } from "@/contractsABI/ContributionAccountingTokenABI";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
import { useAccount, useWriteContract, useWaitForTransactionReceipt } from "wagmi";
import { parseEther } from "viem";

// Define supported chain IDs
type SupportedChainId = 1 | 137 | 534351 | 5115 | 61 | 2001;
Expand All @@ -22,9 +28,16 @@ interface TokenDetailsState {
}

export default function InteractionClient() {
const { address } = useAccount();
const searchParams = useSearchParams();
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [mintAmount, setMintAmount] = useState("");
const [newMaxSupply, setNewMaxSupply] = useState("");
const [newThresholdSupply, setNewThresholdSupply] = useState("");
const [newMaxExpansionRate, setNewMaxExpansionRate] = useState("");
const [transferAmount, setTransferAmount] = useState("");
const [transferTo, setTransferTo] = useState("");

const [tokenAddress, setTokenAddress] = useState<`0x${string}`>("0x0");
const [chainId, setChainId] = useState<SupportedChainId | null>(null);
Expand Down Expand Up @@ -130,7 +143,38 @@ export default function InteractionClient() {
}
}, [tokenAddress, chainId]);

// Rest of the component remains the same...
// Contract write hooks
const { writeContract: mint, data: mintData } = useWriteContract();

const { writeContract: reduceMaxSupply, data: reduceMaxSupplyData } = useWriteContract();

const { writeContract: reduceThresholdSupply, data: reduceThresholdSupplyData } = useWriteContract();

const { writeContract: reduceMaxExpansionRate, data: reduceMaxExpansionRateData } = useWriteContract();

const { writeContract: disableTransferRestriction, data: disableTransferRestrictionData } = useWriteContract();

// Transaction hooks
const { isLoading: isMinting } = useWaitForTransactionReceipt({
hash: mintData,
});

const { isLoading: isReducingMaxSupply } = useWaitForTransactionReceipt({
hash: reduceMaxSupplyData,
});

const { isLoading: isReducingThresholdSupply } = useWaitForTransactionReceipt({
hash: reduceThresholdSupplyData,
});

const { isLoading: isReducingMaxExpansionRate } = useWaitForTransactionReceipt({
hash: reduceMaxExpansionRateData,
});

const { isLoading: isDisablingTransferRestriction } = useWaitForTransactionReceipt({
hash: disableTransferRestrictionData,
});
Comment on lines +146 to +176
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add transaction success and error handling.

The component tracks loading states but doesn't handle transaction success or errors. Users need feedback when their transactions complete or fail.

Consider using the transaction receipt hooks to provide user feedback:

const { isLoading: isMinting, isSuccess: isMintSuccess, isError: isMintError } = useWaitForTransactionReceipt({
  hash: mintData,
});

// Add useEffect to handle success/error states
useEffect(() => {
  if (isMintSuccess) {
    alert("Tokens minted successfully!");
    setMintAmount("");
    getTokenDetails(); // Refresh token details
  }
  if (isMintError) {
    alert("Minting failed. Please try again.");
  }
}, [isMintSuccess, isMintError]);
🤖 Prompt for AI Agents
In web/src/app/[cat]/InteractionClient.tsx between lines 146 and 176, the
transaction hooks track loading states but lack handling for transaction success
and errors, which is necessary for user feedback. Update each
useWaitForTransactionReceipt hook to also destructure isSuccess and isError
flags. Then, add useEffect hooks for each transaction to listen for these flags
and trigger appropriate user notifications, such as alerts on success or
failure, and perform any needed state updates or data refreshes accordingly.


if (isLoading) {
return (
<div className="w-full h-screen flex items-center justify-center bg-gray-50 dark:bg-black">
Expand All @@ -150,73 +194,213 @@ export default function InteractionClient() {
}

return (
<div className="w-full pt-14">
<Card className="w-full mt-8 mx-8">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-xl font-bold">
<div className="flex items-center space-x-2">
<Info className="h-6 w-6 text-gray-500 dark:text-gray-400" />
<span>Token Information</span>
</div>
</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-4">
<div className="bg-white dark:bg-[#141414] border border-gray-200 dark:border-gray-800 rounded-md p-4">
<div className="text-sm text-gray-600 dark:text-gray-400 font-semibold">
Token Name
</div>
<div className="text-lg text-gray-900 dark:text-white">
{tokenDetails.tokenName}
<div className="min-h-screen bg-gradient-to-b">
<div className="max-w-7xl mx-auto space-y-8">
{/* Header Section */}
<div className="text-center mb-12">
<h1 className="text-3xl text-gray-600 dark:text-gray-400">
{tokenDetails.tokenSymbol} Token Management
</h1>
</div>

{/* Token Overview Card */}
<Card className="bg-white/50 dark:bg-gray-900/50 backdrop-blur-sm border-2 border-gray-200 dark:border-gray-800 shadow-xl">
<CardHeader className="border-b border-gray-200 dark:border-gray-800">
<CardTitle className="flex items-center gap-2 text-2xl">
<Info className="h-6 w-6 text-blue-500" />
Token Overview
</CardTitle>
</CardHeader>
<CardContent className="pt-6">
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div className="bg-white dark:bg-gray-800 rounded-xl p-6 shadow-sm">
<div className="flex items-center gap-2 mb-2">
<Coins className="h-5 w-5 text-green-500" />
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">Max Supply</h3>
</div>
<p className="text-3xl font-bold text-gray-900 dark:text-white">
{tokenDetails.maxSupply}
</p>
</div>
<div className="bg-white dark:bg-[#141414] border border-gray-200 dark:border-gray-800 rounded-md p-4">
<div className="text-sm text-gray-600 dark:text-gray-400 font-semibold">
Token Symbol
</div>
<div className="text-lg text-gray-900 dark:text-white">
{tokenDetails.tokenSymbol}
<div className="bg-white dark:bg-gray-800 rounded-xl p-6 shadow-sm">
<div className="flex items-center gap-2 mb-2">
<ArrowUpRight className="h-5 w-5 text-blue-500" />
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">Threshold Supply</h3>
</div>
<p className="text-3xl font-bold text-gray-900 dark:text-white">
{tokenDetails.thresholdSupply}
</p>
</div>
<div className="bg-white dark:bg-[#141414] border border-gray-200 dark:border-gray-800 rounded-md p-4">
<div className="text-sm text-gray-600 dark:text-gray-400 font-semibold">
Max Supply
</div>
<div className="text-lg text-gray-900 dark:text-white">
{tokenDetails.maxSupply}
<div className="bg-white dark:bg-gray-800 rounded-xl p-6 shadow-sm">
<div className="flex items-center gap-2 mb-2">
<ArrowDownRight className="h-5 w-5 text-purple-500" />
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">Max Expansion Rate</h3>
</div>
<p className="text-3xl font-bold text-gray-900 dark:text-white">
{tokenDetails.maxExpansionRate}%
</p>
</div>
</div>
<div className="mt-6 bg-white dark:bg-gray-800 rounded-xl p-6 shadow-sm">
<div className="flex items-center gap-2 mb-2">
<Info className="h-5 w-5 text-gray-500" />
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">Contract Address</h3>
</div>
<p className="text-sm font-mono text-gray-600 dark:text-gray-400 break-all">
{tokenDetails.transactionHash}
</p>
</div>
</CardContent>
</Card>

{/* Mint Tokens Card */}
<Card className="bg-white/50 dark:bg-gray-900/50 backdrop-blur-sm border-2 border-gray-200 dark:border-gray-800 shadow-xl">
<CardHeader className="border-b border-gray-200 dark:border-gray-800">
<CardTitle className="flex items-center gap-2 text-2xl">
<Coins className="h-6 w-6 text-green-500" />
Mint Tokens
</CardTitle>
</CardHeader>
<CardContent className="pt-6">
<div className="max-w-md mx-auto space-y-6">
<div className="space-y-2">
<Label htmlFor="mintAmount" className="text-lg">Amount to Mint</Label>
<Input
id="mintAmount"
type="number"
placeholder="Enter amount"
value={mintAmount}
onChange={(e) => setMintAmount(e.target.value)}
className="h-12 text-lg"
/>
</div>
<Button
onClick={() => mint({
abi: CONTRIBUTION_ACCOUNTING_TOKEN_ABI,
address: tokenAddress,
functionName: "mint",
args: [address, parseEther(mintAmount)]
})}
disabled={!mintAmount || isMinting}
className="w-full h-12 text-lg"
>
{isMinting ? "Minting..." : "Mint Tokens"}
</Button>
</div>
Comment on lines +278 to 289
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add input validation and error handling for minting.

The mint function should validate inputs before execution:

  1. Check if the user is connected (address is defined)
  2. Validate that mintAmount is a positive number
  3. Handle potential parsing errors
 <Button
   onClick={() => {
+    if (!address) {
+      alert("Please connect your wallet");
+      return;
+    }
+    const amount = parseFloat(mintAmount);
+    if (isNaN(amount) || amount <= 0) {
+      alert("Please enter a valid positive amount");
+      return;
+    }
     mint({
       abi: CONTRIBUTION_ACCOUNTING_TOKEN_ABI,
       address: tokenAddress,
       functionName: "mint",
       args: [address, parseEther(mintAmount)]
     })
   }}
   disabled={!mintAmount || isMinting}
   className="w-full h-12 text-lg"
 >
   {isMinting ? "Minting..." : "Mint Tokens"}
 </Button>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
onClick={() => mint({
abi: CONTRIBUTION_ACCOUNTING_TOKEN_ABI,
address: tokenAddress,
functionName: "mint",
args: [address, parseEther(mintAmount)]
})}
disabled={!mintAmount || isMinting}
className="w-full h-12 text-lg"
>
{isMinting ? "Minting..." : "Mint Tokens"}
</Button>
</div>
<Button
onClick={() => {
if (!address) {
alert("Please connect your wallet");
return;
}
const amount = parseFloat(mintAmount);
if (isNaN(amount) || amount <= 0) {
alert("Please enter a valid positive amount");
return;
}
mint({
abi: CONTRIBUTION_ACCOUNTING_TOKEN_ABI,
address: tokenAddress,
functionName: "mint",
args: [address, parseEther(mintAmount)]
})
}}
disabled={!mintAmount || isMinting}
className="w-full h-12 text-lg"
>
{isMinting ? "Minting..." : "Mint Tokens"}
</Button>
🤖 Prompt for AI Agents
In web/src/app/[cat]/InteractionClient.tsx around lines 278 to 289, the mint
function call lacks input validation and error handling. Before calling mint,
add checks to ensure the user address is defined, verify that mintAmount is a
positive number, and wrap the parseEther call in a try-catch block to handle any
parsing errors gracefully. If validation fails, prevent the mint call and
provide appropriate user feedback.

<div className="space-y-4">
<div className="bg-white dark:bg-[#141414] border border-gray-200 dark:border-gray-800 rounded-md p-4">
<div className="text-sm text-gray-600 dark:text-gray-400 font-semibold">
Threshold Supply
</CardContent>
</Card>

{/* Admin Functions Card */}
<Card className="bg-white/50 dark:bg-gray-900/50 backdrop-blur-sm border-2 border-gray-200 dark:border-gray-800 shadow-xl">
<CardHeader className="border-b border-gray-200 dark:border-gray-800">
<CardTitle className="flex items-center gap-2 text-2xl">
<Settings className="h-6 w-6 text-blue-500" />
Admin Functions
</CardTitle>
</CardHeader>
<CardContent className="pt-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-4">
<div className="space-y-2">
<Label htmlFor="newMaxSupply" className="text-lg">New Max Supply</Label>
<Input
id="newMaxSupply"
type="number"
placeholder="Enter new max supply"
value={newMaxSupply}
onChange={(e) => setNewMaxSupply(e.target.value)}
className="h-12 text-lg"
/>
<Button
onClick={() => reduceMaxSupply({
abi: CONTRIBUTION_ACCOUNTING_TOKEN_ABI,
address: tokenAddress,
functionName: "reduceMaxSupply",
args: [parseEther(newMaxSupply)]
})}
disabled={!newMaxSupply || isReducingMaxSupply}
className="w-full h-12 text-lg"
>
{isReducingMaxSupply ? "Updating..." : "Update Max Supply"}
</Button>
</div>
Comment on lines +314 to 326
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

⚠️ Potential issue

Clarify UI labels and add validation for reduction operations.

The contract function reduceMaxSupply implies a one-way reduction, but the UI label "Update Max Supply" could mislead users into thinking they can increase it. Additionally, there's no validation to ensure the new value is actually lower than the current value.

+                 <Label htmlFor="newMaxSupply" className="text-lg">Reduce Max Supply (Current: {tokenDetails.maxSupply})</Label>
                  <Input
                    id="newMaxSupply"
                    type="number"
-                   placeholder="Enter new max supply"
+                   placeholder="Enter new max supply (must be lower)"
                    value={newMaxSupply}
                    onChange={(e) => setNewMaxSupply(e.target.value)}
                    className="h-12 text-lg"
                  />
                  <Button
-                   onClick={() => reduceMaxSupply({
+                   onClick={() => {
+                     const newValue = parseFloat(newMaxSupply);
+                     if (isNaN(newValue) || newValue <= 0) {
+                       alert("Please enter a valid positive amount");
+                       return;
+                     }
+                     if (newValue >= tokenDetails.maxSupply) {
+                       alert("New max supply must be lower than current value");
+                       return;
+                     }
+                     reduceMaxSupply({
                       abi: CONTRIBUTION_ACCOUNTING_TOKEN_ABI,
                       address: tokenAddress,
                       functionName: "reduceMaxSupply",
                       args: [parseEther(newMaxSupply)]
-                   })}
+                     })
+                   }}
                    disabled={!newMaxSupply || isReducingMaxSupply}
                    className="w-full h-12 text-lg"
                  >
-                   {isReducingMaxSupply ? "Updating..." : "Update Max Supply"}
+                   {isReducingMaxSupply ? "Reducing..." : "Reduce Max Supply"}
                  </Button>

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In web/src/app/[cat]/InteractionClient.tsx around lines 314 to 326, the button
label "Update Max Supply" is misleading because the function only allows
reducing the max supply. Change the button label to something like "Reduce Max
Supply" to clarify this. Also, add validation before enabling the button to
ensure the newMaxSupply value is strictly less than the current max supply,
disabling the button otherwise to prevent invalid operations.

<div className="text-lg text-gray-900 dark:text-white">
{tokenDetails.thresholdSupply}

<div className="space-y-2">
<Label htmlFor="newThresholdSupply" className="text-lg">New Threshold Supply</Label>
<Input
id="newThresholdSupply"
type="number"
placeholder="Enter new threshold supply"
value={newThresholdSupply}
onChange={(e) => setNewThresholdSupply(e.target.value)}
className="h-12 text-lg"
/>
<Button
onClick={() => reduceThresholdSupply({
abi: CONTRIBUTION_ACCOUNTING_TOKEN_ABI,
address: tokenAddress,
functionName: "reduceThresholdSupply",
args: [parseEther(newThresholdSupply)]
})}
disabled={!newThresholdSupply || isReducingThresholdSupply}
className="w-full h-12 text-lg"
>
{isReducingThresholdSupply ? "Updating..." : "Update Threshold Supply"}
</Button>
</div>
</div>
<div className="bg-white dark:bg-[#141414] border border-gray-200 dark:border-gray-800 rounded-md p-4">
<div className="text-sm text-gray-600 dark:text-gray-400 font-semibold">
Max Expansion Rate

<div className="space-y-4">
<div className="space-y-2">
<Label htmlFor="newMaxExpansionRate" className="text-lg">New Max Expansion Rate (%)</Label>
<Input
id="newMaxExpansionRate"
type="number"
placeholder="Enter new max expansion rate"
value={newMaxExpansionRate}
onChange={(e) => setNewMaxExpansionRate(e.target.value)}
className="h-12 text-lg"
/>
<Button
onClick={() => reduceMaxExpansionRate({
abi: CONTRIBUTION_ACCOUNTING_TOKEN_ABI,
address: tokenAddress,
functionName: "reduceMaxExpansionRate",
args: [Number(newMaxExpansionRate) * 100]
})}
disabled={!newMaxExpansionRate || isReducingMaxExpansionRate}
className="w-full h-12 text-lg"
>
{isReducingMaxExpansionRate ? "Updating..." : "Update Max Expansion Rate"}
</Button>
</div>
<div className="text-lg text-gray-900 dark:text-white">
{tokenDetails.maxExpansionRate}%

<div className="space-y-2">
<Label className="text-lg">Transfer Restriction</Label>
<Button
onClick={() => disableTransferRestriction({
abi: CONTRIBUTION_ACCOUNTING_TOKEN_ABI,
address: tokenAddress,
functionName: "disableTransferRestriction"
})}
disabled={isDisablingTransferRestriction}
className="w-full h-12 text-lg"
>
{isDisablingTransferRestriction ? (
"Disabling..."
) : (
<div className="flex items-center gap-2">
<Unlock className="h-5 w-5" />
Disable Transfer Restriction
</div>
)}
</Button>
</div>
</div>
</div>
</div>
<div className="mt-4 bg-white dark:bg-[#141414] border border-gray-200 dark:border-gray-800 rounded-md p-4">
<div className="text-sm text-gray-600 dark:text-gray-400 font-semibold">
Transaction Hash
</div>
<div className="text-sm text-gray-900 dark:text-white font-mono break-all">
{tokenDetails.transactionHash}
</div>
</div>
</CardContent>
</Card>
</CardContent>
</Card>
</div>
</div>
);
}