diff --git a/plugins/lockToVote/hooks/useCreateProposal.ts b/plugins/lockToVote/hooks/useCreateProposal.ts index d26edae..899bb7f 100644 --- a/plugins/lockToVote/hooks/useCreateProposal.ts +++ b/plugins/lockToVote/hooks/useCreateProposal.ts @@ -1,13 +1,13 @@ import { useRouter } from "next/router"; -import { useEffect, useState } from "react"; +import { useState } from "react"; import { ProposalMetadata, RawAction } from "@/utils/types"; -import { useWaitForTransactionReceipt, useWriteContract } from "wagmi"; import { useAlerts } from "@/context/Alerts"; import { PUB_APP_NAME, PUB_CHAIN, PUB_LOCK_TO_VOTE_PLUGIN_ADDRESS, PUB_PROJECT_URL } from "@/constants"; import { uploadToPinata } from "@/utils/ipfs"; import { LockToVetoPluginAbi } from "../artifacts/LockToVetoPlugin.sol"; import { URL_PATTERN } from "@/utils/input-values"; import { toHex } from "viem"; +import { useTransactionManager } from "@/hooks/useTransactionManager"; const UrlRegex = new RegExp(URL_PATTERN); @@ -22,45 +22,18 @@ export function useCreateProposal() { const [resources, setResources] = useState<{ name: string; url: string }[]>([ { name: PUB_APP_NAME, url: PUB_PROJECT_URL }, ]); - const { writeContract: createProposalWrite, data: createTxHash, error, status } = useWriteContract(); - const { isLoading: isConfirming, isSuccess: isConfirmed } = useWaitForTransactionReceipt({ hash: createTxHash }); - useEffect(() => { - if (status === "idle" || status === "pending") return; - else if (status === "error") { - if (error?.message?.startsWith("User rejected the request")) { - addAlert("The transaction signature was declined", { - description: "Nothing will be sent to the network", - timeout: 4 * 1000, - }); - } else { - console.error(error); - addAlert("Could not create the proposal", { type: "error" }); - } - setIsCreating(false); - return; - } - - // success - if (!createTxHash) return; - else if (isConfirming) { - addAlert("Proposal submitted", { - description: "Waiting for the transaction to be validated", - txHash: createTxHash, - }); - return; - } else if (!isConfirmed) return; - - addAlert("Proposal created", { - description: "The transaction has been validated", - type: "success", - txHash: createTxHash, - }); - setTimeout(() => { - push("#/"); - window.scroll(0, 0); - }, 1000 * 2); - }, [status, createTxHash, isConfirming, isConfirmed]); + const { writeContract: createProposalWrite, isConfirming } = useTransactionManager({ + onSuccessMessage: "Proposal created", + onSuccess() { + setTimeout(() => { + push("#/"); + window.scroll(0, 0); + }, 1000 * 2); + }, + onErrorMessage: "Could not create the proposal", + onError: () => setIsCreating(false), + }); const submitProposal = async () => { // Check metadata diff --git a/plugins/lockToVote/hooks/useProposalClaimLock.ts b/plugins/lockToVote/hooks/useProposalClaimLock.ts index a557202..02958e7 100644 --- a/plugins/lockToVote/hooks/useProposalClaimLock.ts +++ b/plugins/lockToVote/hooks/useProposalClaimLock.ts @@ -1,20 +1,16 @@ -import { useEffect } from "react"; import { useAccount, useReadContract, useWaitForTransactionReceipt, useWriteContract } from "wagmi"; -import { AlertContextProps, useAlerts } from "@/context/Alerts"; import { useRouter } from "next/router"; import { PUB_CHAIN, PUB_LOCK_TO_VOTE_PLUGIN_ADDRESS } from "@/constants"; import { LockToVetoPluginAbi } from "../artifacts/LockToVetoPlugin.sol"; +import { useTransactionManager } from "@/hooks/useTransactionManager"; +import { useState } from "react"; export function useProposalClaimLock(proposalIdx: number) { const { reload } = useRouter(); const account = useAccount(); - const { addAlert } = useAlerts() as AlertContextProps; + const [isClaiming, setIsClaiming] = useState(false); - const { - data: hasClaimed, - isError: isCanVoteError, - isLoading: isCanVoteLoading, - } = useReadContract({ + const { data: hasClaimed } = useReadContract({ address: PUB_LOCK_TO_VOTE_PLUGIN_ADDRESS, abi: LockToVetoPluginAbi, chainId: PUB_CHAIN.id, @@ -24,19 +20,26 @@ export function useProposalClaimLock(proposalIdx: number) { enabled: !!account.address, }, }); - const { - writeContract: claimLockWrite, - data: executeTxHash, - error: executingError, - status: claimingStatus, - } = useWriteContract(); - const { isLoading: isConfirming, isSuccess: isConfirmed } = useWaitForTransactionReceipt({ hash: executeTxHash }); + + const { writeContract, isConfirming, isConfirmed } = useTransactionManager({ + onSuccessMessage: "Claim executed", + onSuccess() { + reload(); + setIsClaiming(false); + }, + onErrorMessage: "Could not claim the locked tokens", + onErrorDescription: "Please get in touch with us", + onError() { + setIsClaiming(false); + }, + }); const claimLockProposal = () => { if (hasClaimed) return; - console.log(proposalIdx, account.address); - claimLockWrite({ + setIsClaiming(true); + + writeContract({ chainId: PUB_CHAIN.id, abi: LockToVetoPluginAbi, address: PUB_LOCK_TO_VOTE_PLUGIN_ADDRESS, @@ -45,47 +48,10 @@ export function useProposalClaimLock(proposalIdx: number) { }); }; - useEffect(() => { - if (claimingStatus === "idle" || claimingStatus === "pending") return; - else if (claimingStatus === "error") { - if (executingError?.message?.startsWith("User rejected the request")) { - addAlert("Transaction rejected by the user", { - timeout: 4 * 1000, - }); - } else { - console.error(executingError); - addAlert("Could not claim locked tokens", { - type: "error", - description: "The proposal may contain actions with invalid operations. Please get in contact with us.", - }); - } - return; - } - - // success - if (!executeTxHash) return; - else if (isConfirming) { - addAlert("Claim submitted", { - description: "Waiting for the transaction to be validated", - type: "info", - txHash: executeTxHash, - }); - return; - } else if (!isConfirmed) return; - - addAlert("Claim executed", { - description: "The transaction has been validated", - type: "success", - txHash: executeTxHash, - }); - - setTimeout(() => reload(), 1000 * 2); - }, [claimingStatus, executeTxHash, isConfirming, isConfirmed]); - return { claimLockProposal, hasClaimed: !!hasClaimed, - isConfirming, + isConfirming: isConfirming || isClaiming, isConfirmed, }; } diff --git a/plugins/lockToVote/hooks/useProposalExecute.ts b/plugins/lockToVote/hooks/useProposalExecute.ts index e19d9ae..a191a2b 100644 --- a/plugins/lockToVote/hooks/useProposalExecute.ts +++ b/plugins/lockToVote/hooks/useProposalExecute.ts @@ -1,13 +1,12 @@ -import { useEffect, useState } from "react"; -import { useReadContract, useWaitForTransactionReceipt, useWriteContract } from "wagmi"; -import { AlertContextProps, useAlerts } from "@/context/Alerts"; +import { useState } from "react"; +import { useReadContract } from "wagmi"; import { useRouter } from "next/router"; import { PUB_CHAIN, PUB_LOCK_TO_VOTE_PLUGIN_ADDRESS } from "@/constants"; import { LockToVetoPluginAbi } from "../artifacts/LockToVetoPlugin.sol"; +import { useTransactionManager } from "@/hooks/useTransactionManager"; export function useProposalExecute(proposalIdx: number) { const { reload } = useRouter(); - const { addAlert } = useAlerts() as AlertContextProps; const [isExecuting, setIsExecuting] = useState(false); const { @@ -21,13 +20,18 @@ export function useProposalExecute(proposalIdx: number) { functionName: "canExecute", args: [BigInt(proposalIdx)], }); - const { - writeContract: executeWrite, - data: executeTxHash, - error: executingError, - status: executingStatus, - } = useWriteContract(); - const { isLoading: isConfirming, isSuccess: isConfirmed } = useWaitForTransactionReceipt({ hash: executeTxHash }); + + const { writeContract, isConfirming, isConfirmed } = useTransactionManager({ + onSuccessMessage: "Proposal executed", + onSuccess() { + setTimeout(() => reload(), 1000 * 2); + }, + onErrorMessage: "Could not execute the proposal", + onErrorDescription: "The proposal may contain actions with invalid operations", + onError() { + setIsExecuting(false); + }, + }); const executeProposal = () => { if (!canExecute) return; @@ -35,7 +39,7 @@ export function useProposalExecute(proposalIdx: number) { setIsExecuting(true); - executeWrite({ + writeContract({ chainId: PUB_CHAIN.id, abi: LockToVetoPluginAbi, address: PUB_LOCK_TO_VOTE_PLUGIN_ADDRESS, @@ -44,45 +48,6 @@ export function useProposalExecute(proposalIdx: number) { }); }; - useEffect(() => { - if (executingStatus === "idle" || executingStatus === "pending") return; - else if (executingStatus === "error") { - if (executingError?.message?.startsWith("User rejected the request")) { - addAlert("The transaction signature was declined", { - description: "Nothing will be sent to the network", - timeout: 4 * 1000, - }); - } else { - console.error(executingError); - addAlert("Could not execute the proposal", { - type: "error", - description: "The proposal may contain actions with invalid operations", - }); - } - setIsExecuting(false); - return; - } - - // success - if (!executeTxHash) return; - else if (isConfirming) { - addAlert("Transaction submitted", { - description: "Waiting for the transaction to be validated", - type: "info", - txHash: executeTxHash, - }); - return; - } else if (!isConfirmed) return; - - addAlert("Proposal executed", { - description: "The transaction has been validated", - type: "success", - txHash: executeTxHash, - }); - - setTimeout(() => reload(), 1000 * 2); - }, [executingStatus, executeTxHash, isConfirming, isConfirmed]); - return { executeProposal, canExecute: !isCanVoteError && !isCanVoteLoading && !isConfirmed && !!canExecute, diff --git a/plugins/lockToVote/hooks/useProposalVeto.ts b/plugins/lockToVote/hooks/useProposalVeto.ts index 6860c8e..8124ce9 100644 --- a/plugins/lockToVote/hooks/useProposalVeto.ts +++ b/plugins/lockToVote/hooks/useProposalVeto.ts @@ -1,5 +1,4 @@ -import { useEffect } from "react"; -import { usePublicClient, useWaitForTransactionReceipt, useWriteContract, useReadContract, useAccount } from "wagmi"; +import { useReadContract, useAccount } from "wagmi"; import { Address } from "viem"; import { ERC20PermitAbi } from "@/artifacts/ERC20Permit.sol"; import { useProposal } from "./useProposal"; @@ -7,63 +6,38 @@ import { useProposalVetoes } from "./useProposalVetoes"; import { useUserCanVeto } from "./useUserCanVeto"; import { LockToVetoPluginAbi } from "../artifacts/LockToVetoPlugin.sol"; import { usePermit } from "@/hooks/usePermit"; -import { useAlerts, AlertContextProps } from "@/context/Alerts"; -import { PUB_CHAIN, PUB_TOKEN_ADDRESS, PUB_LOCK_TO_VOTE_PLUGIN_ADDRESS } from "@/constants"; +import { PUB_TOKEN_ADDRESS, PUB_LOCK_TO_VOTE_PLUGIN_ADDRESS } from "@/constants"; +import { useTransactionManager } from "@/hooks/useTransactionManager"; +import { ADDRESS_ZERO } from "@/utils/evm"; export function useProposalVeto(proposalId: number) { - const publicClient = usePublicClient({ chainId: PUB_CHAIN.id }); - const { proposal, status: proposalFetchStatus, refetch: refetchProposal } = useProposal(proposalId, true); - const vetoes = useProposalVetoes(publicClient!, PUB_LOCK_TO_VOTE_PLUGIN_ADDRESS, proposalId, proposal); + const vetoes = useProposalVetoes(PUB_LOCK_TO_VOTE_PLUGIN_ADDRESS, proposalId, proposal); const { signPermit, refetchPermitData } = usePermit(); - - const { addAlert } = useAlerts() as AlertContextProps; - const account_address = useAccount().address!; + const { address } = useAccount(); const { data: balanceData } = useReadContract({ address: PUB_TOKEN_ADDRESS, abi: ERC20PermitAbi, functionName: "balanceOf", - args: [account_address], + args: [address || ADDRESS_ZERO], }); - - const { writeContract: vetoWrite, data: vetoTxHash, error: vetoingError, status: vetoingStatus } = useWriteContract(); - const { isLoading: isConfirming, isSuccess: isConfirmed } = useWaitForTransactionReceipt({ hash: vetoTxHash }); const { canVeto, refetch: refetchCanVeto } = useUserCanVeto(proposalId); - useEffect(() => { - if (vetoingStatus === "idle" || vetoingStatus === "pending") return; - else if (vetoingStatus === "error") { - if (vetoingError?.message?.startsWith("User rejected the request")) { - addAlert("Transaction rejected by the user", { - timeout: 4 * 1000, - }); - } else { - console.error(vetoingError); - addAlert("Could not create the veto", { type: "error" }); - } - return; - } - - // success - if (!vetoTxHash) return; - else if (isConfirming) { - addAlert("Veto submitted", { - description: "Waiting for the transaction to be validated", - txHash: vetoTxHash, - }); - return; - } else if (!isConfirmed) return; - - addAlert("Veto registered", { - description: "The transaction has been validated", - type: "success", - txHash: vetoTxHash, - }); - refetchCanVeto(); - refetchProposal(); - refetchPermitData(); - }, [vetoingStatus, vetoTxHash, isConfirming, isConfirmed]); + const { + writeContract, + status: vetoingStatus, + isConfirming, + isConfirmed, + } = useTransactionManager({ + onSuccessMessage: "Veto registered", + onSuccess() { + refetchCanVeto(); + refetchProposal(); + refetchPermitData(); + }, + onErrorMessage: "Could not submit the veto", + }); const vetoProposal = () => { const dest: Address = PUB_LOCK_TO_VOTE_PLUGIN_ADDRESS; @@ -73,7 +47,7 @@ export function useProposalVeto(proposalId: number) { signPermit(dest, value, deadline).then((sig) => { if (!sig?.yParity) throw new Error("Invalid signature"); - vetoWrite({ + writeContract({ abi: LockToVetoPluginAbi, address: dest, functionName: "vetoPermit", diff --git a/plugins/lockToVote/hooks/useProposalVetoes.ts b/plugins/lockToVote/hooks/useProposalVetoes.ts index e057567..2d4981a 100644 --- a/plugins/lockToVote/hooks/useProposalVetoes.ts +++ b/plugins/lockToVote/hooks/useProposalVetoes.ts @@ -1,27 +1,24 @@ import { useState, useEffect } from "react"; import { Address, getAbiItem } from "viem"; -import { PublicClient } from "viem"; import { LockToVetoPluginAbi } from "../artifacts/LockToVetoPlugin.sol"; import { Proposal, VetoCastEvent, VoteCastResponse } from "../utils/types"; +import { usePublicClient } from "wagmi"; +import { PUB_CHAIN } from "@/constants"; const event = getAbiItem({ abi: LockToVetoPluginAbi, name: "VetoCast", }); -export function useProposalVetoes( - publicClient: PublicClient, - address: Address, - proposalId: number, - proposal: Proposal | null -) { +export function useProposalVetoes(pluginAddress: Address, proposalId: number, proposal: Proposal | null) { + const publicClient = usePublicClient({ chainId: PUB_CHAIN.id }); const [proposalLogs, setLogs] = useState([]); async function getLogs() { - if (!proposal?.parameters?.snapshotBlock) return; + if (!publicClient || !proposal?.parameters?.snapshotBlock) return; const logs: VoteCastResponse[] = (await publicClient.getLogs({ - address, + address: pluginAddress, event, args: { proposalId: BigInt(proposalId), @@ -36,7 +33,7 @@ export function useProposalVetoes( useEffect(() => { getLogs(); - }, [proposal?.parameters?.snapshotBlock]); + }, [!!publicClient, proposal?.parameters?.snapshotBlock]); return proposalLogs; } diff --git a/plugins/members/hooks/useDelegateVotingPower.ts b/plugins/members/hooks/useDelegateVotingPower.ts index 8472e56..38a8aca 100644 --- a/plugins/members/hooks/useDelegateVotingPower.ts +++ b/plugins/members/hooks/useDelegateVotingPower.ts @@ -1,54 +1,25 @@ import { iVotesAbi } from "../artifacts/iVotes.sol"; import { PUB_TOKEN_ADDRESS } from "@/constants"; -import { useAlerts } from "@/context/Alerts"; -import { useEffect, useState } from "react"; +import { useTransactionManager } from "@/hooks/useTransactionManager"; +import { useState } from "react"; import { type Address } from "viem"; -import { useWaitForTransactionReceipt, useWriteContract } from "wagmi"; export const useDelegateVotingPower = (targetAddress: Address, onSuccess?: () => void) => { - const { addAlert } = useAlerts(); - const [isConfirming, setIsConfirming] = useState(false); - const { writeContract, data: hash, error, status } = useWriteContract(); - const { isLoading, isSuccess: isConfirmed } = useWaitForTransactionReceipt({ hash }); - - useEffect(() => { - if (status === "idle" || status === "pending") return; - else if (status === "error") { - if (error?.message?.startsWith("User rejected the request")) { - addAlert("The transaction signature was declined", { - description: "Nothing will be sent to the network", - timeout: 4 * 1000, - }); - } else { - console.error(`Could not delegate`, error); - addAlert(`Could not delegate`, { type: "error" }); - } - setIsConfirming(false); - return; - } - - // success - if (!hash) return; - else if (isLoading) { - addAlert("Delegation submitted", { - description: "Waiting for the transaction to be validated", - txHash: hash, - }); - setIsConfirming(false); - return; - } else if (!isConfirmed) return; - - addAlert("Delegation registered", { - description: "The transaction has been validated", - type: "success", - txHash: hash, - }); - - if (typeof onSuccess === "function") onSuccess(); - }, [status, hash, isLoading, isConfirmed]); + const [isDelegating, setIsDelegating] = useState(false); + + const { writeContract, status, isConfirming, isConfirmed } = useTransactionManager({ + onSuccessMessage: "Delegation registered", + onSuccess() { + if (typeof onSuccess === "function") onSuccess(); + }, + onErrorMessage: "Could not delegate", + onError() { + setIsDelegating(false); + }, + }); const delegateVotingPower = () => { - setIsConfirming(true); + setIsDelegating(true); writeContract({ abi: iVotesAbi, @@ -61,7 +32,7 @@ export const useDelegateVotingPower = (targetAddress: Address, onSuccess?: () => return { delegateVotingPower, isConfirmed, - isLoading: isConfirming || isLoading, + isLoading: isDelegating || isConfirming, status, }; }; diff --git a/plugins/multisig/hooks/useCreateProposal.ts b/plugins/multisig/hooks/useCreateProposal.ts index 48a1f8f..e4ee264 100644 --- a/plugins/multisig/hooks/useCreateProposal.ts +++ b/plugins/multisig/hooks/useCreateProposal.ts @@ -14,6 +14,7 @@ import { uploadToPinata } from "@/utils/ipfs"; import { MultisigPluginAbi } from "../artifacts/MultisigPlugin"; import { URL_PATTERN } from "@/utils/input-values"; import { toHex } from "viem"; +import { useTransactionManager } from "@/hooks/useTransactionManager"; const UrlRegex = new RegExp(URL_PATTERN); @@ -28,45 +29,18 @@ export function useCreateProposal() { const [resources, setResources] = useState<{ name: string; url: string }[]>([ { name: PUB_APP_NAME, url: PUB_PROJECT_URL }, ]); - const { writeContract: createProposalWrite, data: createTxHash, error, status } = useWriteContract(); - const { isLoading: isConfirming, isSuccess: isConfirmed } = useWaitForTransactionReceipt({ hash: createTxHash }); - useEffect(() => { - if (status === "idle" || status === "pending") return; - else if (status === "error") { - if (error?.message?.startsWith("User rejected the request")) { - addAlert("The transaction signature was declined", { - description: "Nothing will be sent to the network", - timeout: 4 * 1000, - }); - } else { - console.error(error); - addAlert("Could not create the proposal", { type: "error" }); - } - setIsCreating(false); - return; - } - - // success - if (!createTxHash) return; - else if (isConfirming) { - addAlert("Proposal submitted", { - description: "Waiting for the transaction to be validated", - txHash: createTxHash, - }); - return; - } else if (!isConfirmed) return; - - addAlert("Proposal created", { - description: "The transaction has been validated", - type: "success", - txHash: createTxHash, - }); - setTimeout(() => { - push("#/"); - window.scroll(0, 0); - }, 1000 * 2); - }, [status, createTxHash, isConfirming, isConfirmed]); + const { writeContract: createProposalWrite, isConfirming } = useTransactionManager({ + onSuccessMessage: "Proposal created", + onSuccess() { + setTimeout(() => { + push("#/"); + window.scroll(0, 0); + }, 1000 * 2); + }, + onErrorMessage: "Could not create the proposal", + onError: () => setIsCreating(false), + }); const submitProposal = async () => { // Check metadata diff --git a/plugins/multisig/hooks/useProposalExecute.ts b/plugins/multisig/hooks/useProposalExecute.ts index 9fef98d..0c9da3c 100644 --- a/plugins/multisig/hooks/useProposalExecute.ts +++ b/plugins/multisig/hooks/useProposalExecute.ts @@ -1,14 +1,13 @@ -import { useEffect, useState } from "react"; -import { useReadContract, useWaitForTransactionReceipt, useWriteContract } from "wagmi"; -import { AlertContextProps, useAlerts } from "@/context/Alerts"; +import { useState } from "react"; +import { useReadContract } from "wagmi"; import { useRouter } from "next/router"; import { PUB_CHAIN, PUB_MULTISIG_PLUGIN_ADDRESS } from "@/constants"; import { MultisigPluginAbi } from "../artifacts/MultisigPlugin"; +import { useTransactionManager } from "@/hooks/useTransactionManager"; export function useProposalExecute(proposalId: string) { const { push } = useRouter(); const [isExecuting, setIsExecuting] = useState(false); - const { addAlert } = useAlerts() as AlertContextProps; const { data: canExecute, @@ -21,20 +20,28 @@ export function useProposalExecute(proposalId: string) { functionName: "canExecute", args: [BigInt(proposalId)], }); - const { - writeContract: executeWrite, - data: executeTxHash, - error: executingError, - status: executingStatus, - } = useWriteContract(); - const { isLoading: isConfirming, isSuccess: isConfirmed } = useWaitForTransactionReceipt({ hash: executeTxHash }); + + const { writeContract, isConfirming, isConfirmed } = useTransactionManager({ + onSuccessMessage: "Proposal executed", + onSuccess() { + setTimeout(() => { + push("#/"); + window.scroll(0, 0); + }, 1000 * 2); + }, + onErrorMessage: "Could not execute the proposal", + onErrorDescription: "The proposal may contain actions with invalid operations", + onError() { + setIsExecuting(false); + }, + }); const executeProposal = () => { if (!canExecute) return; setIsExecuting(true); - executeWrite({ + writeContract({ chainId: PUB_CHAIN.id, abi: MultisigPluginAbi, address: PUB_MULTISIG_PLUGIN_ADDRESS, @@ -43,48 +50,6 @@ export function useProposalExecute(proposalId: string) { }); }; - useEffect(() => { - if (executingStatus === "idle" || executingStatus === "pending") return; - else if (executingStatus === "error") { - if (executingError?.message?.startsWith("User rejected the request")) { - addAlert("The transaction signature was declined", { - description: "Nothing will be sent to the network", - timeout: 4 * 1000, - }); - } else { - console.error(executingError); - addAlert("Could not execute the proposal", { - type: "error", - description: "The proposal may contain actions with invalid operations", - }); - } - setIsExecuting(false); - return; - } - - // success - if (!executeTxHash) return; - else if (isConfirming) { - addAlert("Transaction submitted", { - description: "Waiting for the transaction to be validated", - type: "info", - txHash: executeTxHash, - }); - return; - } else if (!isConfirmed) return; - - addAlert("Proposal executed", { - description: "The transaction has been validated", - type: "success", - txHash: executeTxHash, - }); - - setTimeout(() => { - push("#/"); - window.scroll(0, 0); - }, 1000 * 2); - }, [executingStatus, executeTxHash, isConfirming, isConfirmed]); - return { executeProposal, canExecute: !isCanVoteError && !isCanVoteLoading && !isConfirmed && !!canExecute, diff --git a/plugins/optimistic-proposals/hooks/useProposalExecute.ts b/plugins/optimistic-proposals/hooks/useProposalExecute.ts index 782fe45..739982a 100644 --- a/plugins/optimistic-proposals/hooks/useProposalExecute.ts +++ b/plugins/optimistic-proposals/hooks/useProposalExecute.ts @@ -1,10 +1,11 @@ -import { useEffect, useState } from "react"; -import { useReadContract, useWaitForTransactionReceipt, useWriteContract } from "wagmi"; +import { useState } from "react"; +import { useReadContract } from "wagmi"; import { OptimisticTokenVotingPluginAbi } from "../artifacts/OptimisticTokenVotingPlugin.sol"; import { AlertContextProps, useAlerts } from "@/context/Alerts"; import { useRouter } from "next/router"; import { PUB_CHAIN, PUB_DUAL_GOVERNANCE_PLUGIN_ADDRESS } from "@/constants"; import { useProposalId } from "./useProposalId"; +import { useTransactionManager } from "@/hooks/useTransactionManager"; export function useProposalExecute(index: number) { const { reload } = useRouter(); @@ -23,13 +24,18 @@ export function useProposalExecute(index: number) { functionName: "canExecute", args: [proposalId ?? BigInt("0")], }); - const { - writeContract: executeWrite, - data: executeTxHash, - error: executingError, - status: executingStatus, - } = useWriteContract(); - const { isLoading: isConfirming, isSuccess: isConfirmed } = useWaitForTransactionReceipt({ hash: executeTxHash }); + + const { writeContract, isConfirming, isConfirmed } = useTransactionManager({ + onSuccessMessage: "Proposal executed", + onSuccess() { + setTimeout(() => reload(), 1000 * 2); + }, + onErrorMessage: "Could not execute the proposal", + onErrorDescription: "The proposal may contain actions with invalid operations", + onError() { + setIsExecuting(false); + }, + }); const executeProposal = () => { if (!canExecute) return; @@ -37,7 +43,7 @@ export function useProposalExecute(index: number) { setIsExecuting(true); - executeWrite({ + writeContract({ chainId: PUB_CHAIN.id, abi: OptimisticTokenVotingPluginAbi, address: PUB_DUAL_GOVERNANCE_PLUGIN_ADDRESS, @@ -46,45 +52,6 @@ export function useProposalExecute(index: number) { }); }; - useEffect(() => { - if (executingStatus === "idle" || executingStatus === "pending") return; - else if (executingStatus === "error") { - if (executingError?.message?.startsWith("User rejected the request")) { - addAlert("The transaction signature was declined", { - description: "Nothing will be sent to the network", - timeout: 4 * 1000, - }); - } else { - console.error(executingError); - addAlert("Could not execute the proposal", { - type: "error", - description: "The proposal may contain actions with invalid operations", - }); - } - setIsExecuting(false); - return; - } - - // success - if (!executeTxHash) return; - else if (isConfirming) { - addAlert("Transaction submitted", { - description: "Waiting for the transaction to be validated", - type: "info", - txHash: executeTxHash, - }); - return; - } else if (!isConfirmed) return; - - addAlert("Proposal executed", { - description: "The transaction has been validated", - type: "success", - txHash: executeTxHash, - }); - - setTimeout(() => reload(), 1000 * 2); - }, [executingStatus, executeTxHash, isConfirming, isConfirmed]); - return { executeProposal, canExecute: !isCanVoteError && !isCanVoteLoading && !isConfirmed && !!canExecute, diff --git a/plugins/optimistic-proposals/hooks/useProposalVeto.ts b/plugins/optimistic-proposals/hooks/useProposalVeto.ts index e34fa2f..59b7044 100644 --- a/plugins/optimistic-proposals/hooks/useProposalVeto.ts +++ b/plugins/optimistic-proposals/hooks/useProposalVeto.ts @@ -1,59 +1,33 @@ -import { useEffect } from "react"; -import { useWaitForTransactionReceipt, useWriteContract } from "wagmi"; import { useProposal } from "./useProposal"; import { useProposalVetoes } from "@/plugins/optimistic-proposals/hooks/useProposalVetoes"; import { useUserCanVeto } from "@/plugins/optimistic-proposals/hooks/useUserCanVeto"; import { OptimisticTokenVotingPluginAbi } from "@/plugins/optimistic-proposals/artifacts/OptimisticTokenVotingPlugin.sol"; -import { useAlerts, type AlertContextProps } from "@/context/Alerts"; import { PUB_DUAL_GOVERNANCE_PLUGIN_ADDRESS } from "@/constants"; import { useProposalId } from "./useProposalId"; +import { useTransactionManager } from "@/hooks/useTransactionManager"; export function useProposalVeto(index: number) { const { proposalId } = useProposalId(index); - const { proposal, status: proposalFetchStatus, refetch: refetchProposal } = useProposal(proposalId, true); const vetoes = useProposalVetoes(proposalId); - - const { addAlert } = useAlerts() as AlertContextProps; - const { writeContract: vetoWrite, data: vetoTxHash, error: vetoingError, status: vetoingStatus } = useWriteContract(); - const { isLoading: isConfirming, isSuccess: isConfirmed } = useWaitForTransactionReceipt({ hash: vetoTxHash }); const { canVeto, refetch: refetchCanVeto } = useUserCanVeto(proposalId); - useEffect(() => { - if (vetoingStatus === "idle" || vetoingStatus === "pending") return; - else if (vetoingStatus === "error") { - if (vetoingError?.message?.startsWith("User rejected the request")) { - addAlert("The transaction signature was declined", { - description: "Nothing will be sent to the network", - timeout: 4 * 1000, - }); - } else { - addAlert("Could not create the proposal", { type: "error" }); - } - return; - } - - // success - if (!vetoTxHash) return; - else if (isConfirming) { - addAlert("Veto submitted", { - description: "Waiting for the transaction to be validated", - txHash: vetoTxHash, - }); - return; - } else if (!isConfirmed) return; - - addAlert("Veto registered", { - description: "The transaction has been validated", - type: "success", - txHash: vetoTxHash, - }); - refetchCanVeto(); - refetchProposal(); - }, [vetoingStatus, vetoTxHash, isConfirming, isConfirmed]); + const { + writeContract, + status: vetoingStatus, + isConfirming, + isConfirmed, + } = useTransactionManager({ + onSuccessMessage: "Veto registered", + onSuccess() { + refetchCanVeto(); + refetchProposal(); + }, + onErrorMessage: "Could not submit the veto", + }); const vetoProposal = () => { - vetoWrite({ + writeContract({ abi: OptimisticTokenVotingPluginAbi, address: PUB_DUAL_GOVERNANCE_PLUGIN_ADDRESS, functionName: "veto", diff --git a/plugins/tokenVoting/hooks/useCreateProposal.ts b/plugins/tokenVoting/hooks/useCreateProposal.ts index 3576033..5b68a1d 100644 --- a/plugins/tokenVoting/hooks/useCreateProposal.ts +++ b/plugins/tokenVoting/hooks/useCreateProposal.ts @@ -1,7 +1,6 @@ import { useRouter } from "next/router"; -import { useEffect, useState } from "react"; +import { useState } from "react"; import { ProposalMetadata, RawAction } from "@/utils/types"; -import { useWaitForTransactionReceipt, useWriteContract } from "wagmi"; import { useAlerts } from "@/context/Alerts"; import { PUB_APP_NAME, PUB_CHAIN, PUB_TOKEN_VOTING_PLUGIN_ADDRESS, PUB_PROJECT_URL } from "@/constants"; import { uploadToPinata } from "@/utils/ipfs"; @@ -9,6 +8,7 @@ import { TokenVotingAbi } from "../artifacts/TokenVoting.sol"; import { URL_PATTERN } from "@/utils/input-values"; import { toHex } from "viem"; import { VotingMode } from "../utils/types"; +import { useTransactionManager } from "@/hooks/useTransactionManager"; const UrlRegex = new RegExp(URL_PATTERN); @@ -23,45 +23,18 @@ export function useCreateProposal() { const [resources, setResources] = useState<{ name: string; url: string }[]>([ { name: PUB_APP_NAME, url: PUB_PROJECT_URL }, ]); - const { writeContract: createProposalWrite, data: createTxHash, error, status } = useWriteContract(); - const { isLoading: isConfirming, isSuccess: isConfirmed } = useWaitForTransactionReceipt({ hash: createTxHash }); - useEffect(() => { - if (status === "idle" || status === "pending") return; - else if (status === "error") { - if (error?.message?.startsWith("User rejected the request")) { - addAlert("The transaction signature was declined", { - description: "Nothing will be sent to the network", - timeout: 4 * 1000, - }); - } else { - console.error(error); - addAlert("Could not create the proposal", { type: "error" }); - } - setIsCreating(false); - return; - } - - // success - if (!createTxHash) return; - else if (isConfirming) { - addAlert("Proposal submitted", { - description: "Waiting for the transaction to be validated", - txHash: createTxHash, - }); - return; - } else if (!isConfirmed) return; - - addAlert("Proposal created", { - description: "The transaction has been validated", - type: "success", - txHash: createTxHash, - }); - setTimeout(() => { - push("#/"); - window.scroll(0, 0); - }, 1000 * 2); - }, [status, createTxHash, isConfirming, isConfirmed]); + const { writeContract: createProposalWrite, isConfirming } = useTransactionManager({ + onSuccessMessage: "Proposal created", + onSuccess() { + setTimeout(() => { + push("#/"); + window.scroll(0, 0); + }, 1000 * 2); + }, + onErrorMessage: "Could not create the proposal", + onError: () => setIsCreating(false), + }); const submitProposal = async () => { // Check metadata diff --git a/plugins/tokenVoting/hooks/useProposalExecute.ts b/plugins/tokenVoting/hooks/useProposalExecute.ts index 29b387a..6cc0113 100644 --- a/plugins/tokenVoting/hooks/useProposalExecute.ts +++ b/plugins/tokenVoting/hooks/useProposalExecute.ts @@ -1,13 +1,13 @@ -import { useEffect } from "react"; -import { useWaitForTransactionReceipt, useWriteContract, useReadContract } from "wagmi"; +import { useState } from "react"; +import { useReadContract } from "wagmi"; import { TokenVotingAbi } from "../artifacts/TokenVoting.sol"; -import { AlertContextProps, useAlerts } from "@/context/Alerts"; import { useRouter } from "next/router"; import { PUB_CHAIN, PUB_TOKEN_VOTING_PLUGIN_ADDRESS } from "@/constants"; +import { useTransactionManager } from "@/hooks/useTransactionManager"; export function useProposalExecute(proposalId: number) { const { reload } = useRouter(); - const { addAlert } = useAlerts() as AlertContextProps; + const [isExecuting, setIsExecuting] = useState(false); const { data: canExecute, @@ -20,18 +20,26 @@ export function useProposalExecute(proposalId: number) { functionName: "canExecute", args: [BigInt(proposalId)], }); - const { - writeContract: executeWrite, - data: executeTxHash, - error: executingError, - status: executingStatus, - } = useWriteContract(); - const { isLoading: isConfirming, isSuccess: isConfirmed } = useWaitForTransactionReceipt({ hash: executeTxHash }); + + const { writeContract, isConfirming, isConfirmed } = useTransactionManager({ + onSuccessMessage: "Proposal executed", + onSuccess() { + setTimeout(() => reload(), 1000 * 2); + }, + onErrorMessage: "Could not execute the proposal", + onErrorDescription: "The proposal may contain actions with invalid operations", + onError() { + setIsExecuting(false); + }, + }); const executeProposal = () => { if (!canExecute) return; + else if (typeof proposalId === "undefined") return; - executeWrite({ + setIsExecuting(true); + + writeContract({ chainId: PUB_CHAIN.id, abi: TokenVotingAbi, address: PUB_TOKEN_VOTING_PLUGIN_ADDRESS, @@ -40,47 +48,10 @@ export function useProposalExecute(proposalId: number) { }); }; - useEffect(() => { - if (executingStatus === "idle" || executingStatus === "pending") return; - else if (executingStatus === "error") { - if (executingError?.message?.startsWith("User rejected the request")) { - addAlert("Transaction rejected by the user", { - timeout: 4 * 1000, - }); - } else { - console.error(executingError); - addAlert("Could not execute the proposal", { - type: "error", - description: "The proposal may contain actions with invalid operations", - }); - } - return; - } - - // success - if (!executeTxHash) return; - else if (isConfirming) { - addAlert("Proposal submitted", { - description: "Waiting for the transaction to be validated", - type: "info", - txHash: executeTxHash, - }); - return; - } else if (!isConfirmed) return; - - addAlert("Proposal executed", { - description: "The transaction has been validated", - type: "success", - txHash: executeTxHash, - }); - - setTimeout(() => reload(), 1000 * 2); - }, [executingStatus, executeTxHash, isConfirming, isConfirmed]); - return { executeProposal, canExecute: !isCanVoteError && !isCanVoteLoading && !isConfirmed && !!canExecute, - isConfirming, + isConfirming: isExecuting || isConfirming, isConfirmed, }; } diff --git a/plugins/tokenVoting/hooks/useProposalVoting.ts b/plugins/tokenVoting/hooks/useProposalVoting.ts index dd9e621..f0934a5 100644 --- a/plugins/tokenVoting/hooks/useProposalVoting.ts +++ b/plugins/tokenVoting/hooks/useProposalVoting.ts @@ -1,52 +1,24 @@ -import { useEffect } from "react"; -import { useWriteContract, useWaitForTransactionReceipt } from "wagmi"; import { TokenVotingAbi } from "@/plugins/tokenVoting/artifacts/TokenVoting.sol"; -import { AlertContextProps, useAlerts } from "@/context/Alerts"; import { useRouter } from "next/router"; import { PUB_TOKEN_VOTING_PLUGIN_ADDRESS } from "@/constants"; +import { useTransactionManager } from "@/hooks/useTransactionManager"; export function useProposalVoting(proposalIdx: number) { const { reload } = useRouter(); - const { addAlert } = useAlerts() as AlertContextProps; - const { writeContract: voteWrite, data: votingTxHash, error: votingError, status: votingStatus } = useWriteContract(); - const { isLoading: isConfirming, isSuccess: isConfirmed } = useWaitForTransactionReceipt({ hash: votingTxHash }); - // Loading status and errors - useEffect(() => { - if (votingStatus === "idle" || votingStatus === "pending") return; - else if (votingStatus === "error") { - if (votingError?.message?.startsWith("User rejected the request")) { - addAlert("Transaction rejected by the user", { - timeout: 4 * 1000, - }); - } else { - console.error(votingError); - addAlert("Could not create the proposal", { type: "error" }); - } - return; - } - - // success - if (!votingTxHash) return; - else if (isConfirming) { - addAlert("Vote submitted", { - description: "Waiting for the transaction to be validated", - txHash: votingTxHash, - }); - return; - } else if (!isConfirmed) return; - - addAlert("Vote registered", { - description: "The transaction has been validated", - type: "success", - txHash: votingTxHash, - }); - - reload(); - }, [votingStatus, votingTxHash, isConfirming, isConfirmed]); + const { + writeContract, + status: votingStatus, + isConfirming, + isConfirmed, + } = useTransactionManager({ + onSuccessMessage: "Vote registered", + onSuccess: reload, + onErrorMessage: "Could not submit the vote", + }); const voteProposal = (votingOption: number, autoExecute: boolean = false) => { - voteWrite({ + writeContract({ abi: TokenVotingAbi, address: PUB_TOKEN_VOTING_PLUGIN_ADDRESS, functionName: "vote",