diff --git a/packages/nextjs/app/cow/_components/PoolConfiguration.tsx b/packages/nextjs/app/cow/_components/PoolConfiguration.tsx index 0a558c0..fd0ce0e 100644 --- a/packages/nextjs/app/cow/_components/PoolConfiguration.tsx +++ b/packages/nextjs/app/cow/_components/PoolConfiguration.tsx @@ -157,12 +157,14 @@ export const PoolConfiguration = () => { isPending={false} isDisabled={!canProceedToCreate} onClick={() => { + if (!chain?.id) throw new Error("Missing chain id!"); + if (!token1) throw new Error("Missing token 1 selection!"); + if (!token2) throw new Error("Missing token 2 selection!"); + setPoolCreation({ - chainId: chain?.id || 0, - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - token1: token1!, - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - token2: token2!, + chainId: chain.id, + token1: token1, + token2: token2, token1Amount, token2Amount, name: poolName.trim(), @@ -170,14 +172,13 @@ export const PoolConfiguration = () => { poolAddress: undefined, step: 1, tokenWeights, - createPoolTxHash: undefined, - pendingSafeTxHash: undefined, - approveToken1TxHash: undefined, - approveToken2TxHash: undefined, - bindToken1TxHash: undefined, - bindToken2TxHash: undefined, - setSwapFeeTxHash: undefined, - finalizePoolTxHash: undefined, + createPoolTx: { safeHash: undefined, wagmiHash: undefined, isSuccess: false }, + approveToken1Tx: { safeHash: undefined, wagmiHash: undefined, isSuccess: false }, + approveToken2Tx: { safeHash: undefined, wagmiHash: undefined, isSuccess: false }, + bindToken1Tx: { safeHash: undefined, wagmiHash: undefined, isSuccess: false }, + bindToken2Tx: { safeHash: undefined, wagmiHash: undefined, isSuccess: false }, + setSwapFeeTx: { safeHash: undefined, wagmiHash: undefined, isSuccess: false }, + finalizePoolTx: { safeHash: undefined, wagmiHash: undefined, isSuccess: false }, }); }} /> diff --git a/packages/nextjs/app/cow/_components/PoolCreation.tsx b/packages/nextjs/app/cow/_components/PoolCreation.tsx index 9eda69d..b9d3f9c 100644 --- a/packages/nextjs/app/cow/_components/PoolCreation.tsx +++ b/packages/nextjs/app/cow/_components/PoolCreation.tsx @@ -1,4 +1,3 @@ -import { useEffect } from "react"; import { PoolCreated } from "./"; import { parseUnits } from "viem"; import { useSwitchChain } from "wagmi"; @@ -14,15 +13,18 @@ import { import { CHAIN_NAMES } from "~~/hooks/balancer/"; import { type PoolCreationState, + useApproveTokenTxHash, useBindToken, + useBindTokenTxHash, useCreatePool, - useFetchPoolAddress, + useCreatePoolTxHash, useFinalizePool, - useReadPool, + useFinalizePoolTxHash, useSetSwapFee, + useSetSwapFeeTxHash, } from "~~/hooks/cow/"; import { useTargetNetwork } from "~~/hooks/scaffold-eth/useTargetNetwork"; -import { useApproveToken, useReadToken } from "~~/hooks/token"; +import { useApproveToken } from "~~/hooks/token"; import { getBlockExplorerAddressLink } from "~~/utils/scaffold-eth"; import { getBlockExplorerTxLink } from "~~/utils/scaffold-eth"; import { getPerTokenWeights } from "~~/utils/token-weights"; @@ -36,17 +38,16 @@ interface ManagePoolCreationProps { export const PoolCreation = ({ poolCreation, updatePoolCreation, clearPoolCreation }: ManagePoolCreationProps) => { const { poolAddress, - createPoolTxHash, - approveToken1TxHash, - approveToken2TxHash, - bindToken1TxHash, - bindToken2TxHash, - setSwapFeeTxHash, - finalizePoolTxHash, + tokenWeights, + createPoolTx, + approveToken1Tx, + approveToken2Tx, + bindToken1Tx, + bindToken2Tx, + setSwapFeeTx, + finalizePoolTx, } = poolCreation; - const { isFetching: isFetchPoolAddressPending, error: fetchPoolAddressError } = useFetchPoolAddress(); - const token1RawAmount = parseUnits(poolCreation.token1Amount, poolCreation.token1.decimals); const token2RawAmount = parseUnits(poolCreation.token2Amount, poolCreation.token2.decimals); const { token1Weight, token2Weight } = getPerTokenWeights(poolCreation.tokenWeights); @@ -55,72 +56,69 @@ export const PoolCreation = ({ poolCreation, updatePoolCreation, clearPoolCreati const { switchChain } = useSwitchChain(); const isWrongNetwork = targetNetwork.id !== poolCreation.chainId; - const { data: poolData, refetch: refetchPool } = useReadPool(poolAddress); - const { allowance: allowance1, refetchAllowance: refetchAllowance1 } = useReadToken( - poolCreation.token1.address, - poolAddress, - ); - const { allowance: allowance2, refetchAllowance: refetchAllowance2 } = useReadToken( - poolCreation.token2.address, - poolAddress, - ); const { mutate: createPool, isPending: isCreatePending, error: createPoolError } = useCreatePool(); - const { mutate: approve1, isPending: isApprove1Pending, error: approve1Error } = useApproveToken(); - const { mutate: approve2, isPending: isApprove2Pending, error: approve2Error } = useApproveToken(); - const { mutate: bind1, isPending: isBind1Pending, error: bind1Error } = useBindToken(poolCreation.tokenWeights, true); + const { isFetching: isCreatePoolTxPending, error: createPoolTxError } = useCreatePoolTxHash(); + const { - mutate: bind2, - isPending: isBind2Pending, - error: bind2Error, - } = useBindToken(poolCreation.tokenWeights, false); + mutate: approve1, + isPending: isApprove1Pending, + error: approve1Error, + } = useApproveToken({ + onSafeTxHash: safeHash => + updatePoolCreation({ approveToken1Tx: { safeHash, wagmiHash: undefined, isSuccess: false } }), + onWagmiTxHash: wagmiHash => + updatePoolCreation({ approveToken1Tx: { wagmiHash, safeHash: undefined, isSuccess: false } }), + }); + const { isFetching: isApproveToken1TxHashFetching, error: approveToken1TxHashError } = useApproveTokenTxHash({ + tokenNumber: 1, + }); + + const { + mutate: approve2, + isPending: isApprove2Pending, + error: approve2Error, + } = useApproveToken({ + onSafeTxHash: safeHash => + updatePoolCreation({ approveToken2Tx: { safeHash, wagmiHash: undefined, isSuccess: false } }), + onWagmiTxHash: wagmiHash => + updatePoolCreation({ approveToken2Tx: { wagmiHash, safeHash: undefined, isSuccess: false } }), + }); + const { isFetching: isApproveToken2TxHashFetching, error: approveToken2TxHashError } = useApproveTokenTxHash({ + tokenNumber: 2, + }); + + const { mutate: bind1, isPending: isBind1Pending, error: bind1Error } = useBindToken(tokenWeights, true); + const { isFetching: isBindToken1TxHashFetching, error: bindToken1TxHashError } = useBindTokenTxHash({ + tokenNumber: 1, + }); + + const { mutate: bind2, isPending: isBind2Pending, error: bind2Error } = useBindToken(tokenWeights, false); + const { isFetching: isBindToken2TxHashFetching, error: bindToken2TxHashError } = useBindTokenTxHash({ + tokenNumber: 2, + }); const { mutate: setSwapFee, isPending: isSetSwapFeePending, error: setSwapFeeError } = useSetSwapFee(); + const { isFetching: isSetSwapFeeTxHashFetching, error: setSwapFeeTxHashError } = useSetSwapFeeTxHash(); + const { mutate: finalizePool, isPending: isFinalizePending, error: finalizeError } = useFinalizePool(); + const { isFetching: isFinalizePoolTxHashFetching, error: finalizePoolTxHashError } = useFinalizePoolTxHash(); const txError = createPoolError || - fetchPoolAddressError || + createPoolTxError || approve1Error || + approveToken1TxHashError || approve2Error || + approveToken2TxHashError || bind1Error || + bindToken1TxHashError || bind2Error || + bindToken2TxHashError || setSwapFeeError || - finalizeError; + finalizeError || + setSwapFeeTxHashError || + finalizePoolTxHashError; - useEffect(() => { - if (!poolData || !poolAddress) return; - - if (poolData.isFinalized) { - updatePoolCreation({ step: 8 }); - return; - } - - switch (poolData.numTokens) { - case 0n: - if (allowance1 < token1RawAmount) { - updatePoolCreation({ step: 2 }); - } else if (allowance2 < token2RawAmount) { - updatePoolCreation({ step: 3 }); - } else if (allowance1 >= token1RawAmount && allowance2 >= token2RawAmount) { - updatePoolCreation({ step: 4 }); - } - break; - - case 1n: - updatePoolCreation({ step: 5 }); - break; - - case 2n: - if (poolData.swapFee !== poolData.MAX_FEE) { - updatePoolCreation({ step: 6 }); - } else { - updatePoolCreation({ step: 7 }); - } - break; - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [poolData, allowance1, allowance2, token1RawAmount, token2RawAmount]); - - const etherscanURL = poolData && getBlockExplorerAddressLink(targetNetwork, poolData.address); + const etherscanURL = poolAddress && getBlockExplorerAddressLink(targetNetwork, poolAddress); return ( <> @@ -154,8 +152,8 @@ export const PoolCreation = ({ poolCreation, updatePoolCreation, clearPoolCreati return ( { createPool({ name: poolCreation.name, symbol: poolCreation.symbol }); }} @@ -165,24 +163,14 @@ export const PoolCreation = ({ poolCreation, updatePoolCreation, clearPoolCreati return ( { - approve1( - { - token: poolCreation.token1.address, - spender: poolData?.address, - rawAmount: token1RawAmount, - }, - { - onSuccess: hash => { - refetchAllowance1(); - - if (allowance1 >= token1RawAmount) - updatePoolCreation({ approveToken1TxHash: hash, step: 3 }); - }, - }, - ); + approve1({ + token: poolCreation.token1.address, + spender: poolAddress, + rawAmount: token1RawAmount, + }); }} /> ); @@ -190,23 +178,14 @@ export const PoolCreation = ({ poolCreation, updatePoolCreation, clearPoolCreati return ( { - approve2( - { - token: poolCreation.token2.address, - spender: poolData?.address, - rawAmount: token2RawAmount, - }, - { - onSuccess: hash => { - refetchAllowance2(); - if (allowance2 >= token2RawAmount) - updatePoolCreation({ approveToken2TxHash: hash, step: 4 }); - }, - }, - ); + approve2({ + token: poolCreation.token2.address, + spender: poolAddress, + rawAmount: token2RawAmount, + }); }} /> ); @@ -215,22 +194,14 @@ export const PoolCreation = ({ poolCreation, updatePoolCreation, clearPoolCreati return ( { - bind1( - { - pool: poolData?.address, - token: poolCreation.token1.address, - rawAmount: token1RawAmount, - }, - { - onSuccess: hash => { - refetchPool(); - updatePoolCreation({ bindToken1TxHash: hash, step: 5 }); - }, - }, - ); + bind1({ + pool: poolAddress, + token: poolCreation.token1.address, + rawAmount: token1RawAmount, + }); }} /> ); @@ -238,22 +209,14 @@ export const PoolCreation = ({ poolCreation, updatePoolCreation, clearPoolCreati return ( { - bind2( - { - pool: poolData?.address, - token: poolCreation.token2.address, - rawAmount: token2RawAmount, - }, - { - onSuccess: hash => { - refetchPool(); - updatePoolCreation({ bindToken2TxHash: hash, step: 6 }); - }, - }, - ); + bind2({ + pool: poolAddress, + token: poolCreation.token2.address, + rawAmount: token2RawAmount, + }); }} /> ); @@ -262,18 +225,10 @@ export const PoolCreation = ({ poolCreation, updatePoolCreation, clearPoolCreati <> { - setSwapFee( - { pool: poolData?.address, rawAmount: poolData?.MAX_FEE }, - { - onSuccess: hash => { - refetchPool(); - updatePoolCreation({ setSwapFeeTxHash: hash, step: 7 }); - }, - }, - ); + setSwapFee({ pool: poolAddress }); }} /> @@ -282,15 +237,10 @@ export const PoolCreation = ({ poolCreation, updatePoolCreation, clearPoolCreati return ( { - finalizePool(poolData?.address, { - onSuccess: hash => { - refetchPool(); - updatePoolCreation({ finalizePoolTxHash: hash, step: 8 }); - }, - }); + finalizePool(poolAddress); }} /> ); @@ -342,7 +292,7 @@ export const PoolCreation = ({ poolCreation, updatePoolCreation, clearPoolCreati )} @@ -353,31 +303,31 @@ export const PoolCreation = ({ poolCreation, updatePoolCreation, clearPoolCreati steps={[ { label: "Create Pool", - blockExplorerUrl: getBlockExplorerTxLink(poolCreation.chainId, createPoolTxHash), + blockExplorerUrl: getBlockExplorerTxLink(poolCreation.chainId, createPoolTx?.wagmiHash), }, { label: `Approve ${poolCreation.token1.symbol}`, - blockExplorerUrl: getBlockExplorerTxLink(poolCreation.chainId, approveToken1TxHash), + blockExplorerUrl: getBlockExplorerTxLink(poolCreation.chainId, approveToken1Tx?.wagmiHash), }, { label: `Approve ${poolCreation.token2.symbol}`, - blockExplorerUrl: getBlockExplorerTxLink(poolCreation.chainId, approveToken2TxHash), + blockExplorerUrl: getBlockExplorerTxLink(poolCreation.chainId, approveToken2Tx?.wagmiHash), }, { label: `Add ${poolCreation.token1.symbol}`, - blockExplorerUrl: getBlockExplorerTxLink(poolCreation.chainId, bindToken1TxHash), + blockExplorerUrl: getBlockExplorerTxLink(poolCreation.chainId, bindToken1Tx?.wagmiHash), }, { label: `Add ${poolCreation.token2.symbol}`, - blockExplorerUrl: getBlockExplorerTxLink(poolCreation.chainId, bindToken2TxHash), + blockExplorerUrl: getBlockExplorerTxLink(poolCreation.chainId, bindToken2Tx?.wagmiHash), }, { label: "Set Swap Fee", - blockExplorerUrl: getBlockExplorerTxLink(poolCreation.chainId, setSwapFeeTxHash), + blockExplorerUrl: getBlockExplorerTxLink(poolCreation.chainId, setSwapFeeTx?.wagmiHash), }, { label: "Finalize Pool", - blockExplorerUrl: getBlockExplorerTxLink(poolCreation.chainId, finalizePoolTxHash), + blockExplorerUrl: getBlockExplorerTxLink(poolCreation.chainId, finalizePoolTx?.wagmiHash), }, ]} /> diff --git a/packages/nextjs/app/v3/_components/ApproveOnTokenManager.tsx b/packages/nextjs/app/v3/_components/ApproveOnTokenManager.tsx index 106d079..b082867 100644 --- a/packages/nextjs/app/v3/_components/ApproveOnTokenManager.tsx +++ b/packages/nextjs/app/v3/_components/ApproveOnTokenManager.tsx @@ -12,7 +12,7 @@ type MinimalToken = { address: Address; amount: string; decimals: number; symbol export const ApproveOnTokenManager = ({ token }: { token: MinimalToken }) => { const { targetNetwork } = useTargetNetwork(); const { step, updatePool } = usePoolCreationStore(); - const { mutateAsync: approveOnToken, isPending: isApprovePending, error: approveError } = useApproveToken(); + const { mutateAsync: approveOnToken, isPending: isApprovePending, error: approveError } = useApproveToken({}); const rawAmount = parseUnits(token.amount, token.decimals); const spender = PERMIT2[targetNetwork.id]; diff --git a/packages/nextjs/hooks/cow/index.ts b/packages/nextjs/hooks/cow/index.ts index 9a894fc..b93660f 100644 --- a/packages/nextjs/hooks/cow/index.ts +++ b/packages/nextjs/hooks/cow/index.ts @@ -1,10 +1,14 @@ export * from "./types"; export * from "./useReadPool"; export * from "./useCheckIfPoolExists"; -export * from "./useCreatePool"; export * from "./useFinalizePool"; export * from "./useBindToken"; export * from "./useSetSwapFee"; export * from "./getPoolUrl"; export * from "./usePoolCreationStore"; -export * from "./useFetchPoolAddress"; +export * from "./useCreatePool"; +export * from "./useCreatePoolTxHash"; +export * from "./useApproveTokenTxHash"; +export * from "./useBindTokenTxHash"; +export * from "./useSetSwapFeeTxHash"; +export * from "./useFinalizePoolTxHash"; diff --git a/packages/nextjs/hooks/cow/useApproveTokenTxHash.ts b/packages/nextjs/hooks/cow/useApproveTokenTxHash.ts new file mode 100644 index 0000000..6109bf9 --- /dev/null +++ b/packages/nextjs/hooks/cow/useApproveTokenTxHash.ts @@ -0,0 +1,66 @@ +import { erc20Abi } from "@balancer/sdk"; +import { useSafeAppsSDK } from "@safe-global/safe-apps-react-sdk"; +import { useQuery } from "@tanstack/react-query"; +import { parseEventLogs, parseUnits } from "viem"; +import { usePublicClient } from "wagmi"; +import { usePoolCreationStore } from "~~/hooks/cow/usePoolCreationStore"; +import { useIsSafeWallet } from "~~/hooks/safe/useIsSafeWallet"; +import { pollSafeTxStatus } from "~~/utils/safe"; + +interface UseApproveTokenTxHashProps { + tokenNumber: 1 | 2; +} + +export function useApproveTokenTxHash({ tokenNumber }: UseApproveTokenTxHashProps) { + const publicClient = usePublicClient(); + const { sdk } = useSafeAppsSDK(); + const isSafeWallet = useIsSafeWallet(); + + const { poolCreation, updatePoolCreation } = usePoolCreationStore(); + const txKey = `approveToken${tokenNumber}Tx` as const; + const { safeHash, wagmiHash, isSuccess } = poolCreation?.[txKey] || {}; + + return useQuery({ + queryKey: [`approveToken${tokenNumber}TxHash`, safeHash, wagmiHash, isSuccess], + queryFn: async () => { + if (!publicClient) throw new Error("No public client for fetching pool address"); + + if (isSafeWallet && safeHash && !wagmiHash) { + const hash = await pollSafeTxStatus(sdk, safeHash); + updatePoolCreation({ [txKey]: { safeHash, wagmiHash: hash, isSuccess: false } }); + return null; + } + + if (!wagmiHash) return null; + + const txReceipt = await publicClient.waitForTransactionReceipt({ hash: wagmiHash }); + + if (txReceipt.status === "success") { + const amount = poolCreation?.[`token${tokenNumber}Amount`]; + const decimals = poolCreation?.[`token${tokenNumber}`].decimals; + if (!amount || !decimals) throw new Error(`Missing info for token ${tokenNumber}`); + + const rawAmount = parseUnits(amount, decimals); + const logs = parseEventLogs({ + abi: erc20Abi, + logs: txReceipt.logs, + }); + + const approvalEvent = logs.find(log => log.eventName === "Approval"); + if (!approvalEvent) throw new Error("No Approval event found in logs"); + + const newAllowance = approvalEvent.args.value; + if (newAllowance < rawAmount) throw new Error(`Approval amount for token ${tokenNumber} is less than required`); + + updatePoolCreation({ + [txKey]: { safeHash, wagmiHash, isSuccess: true }, + step: poolCreation.step + 1, + }); + return { isSuccess: true }; + } else { + throw new Error("Approve token transaction reverted"); + } + }, + enabled: Boolean(!isSuccess && (safeHash || wagmiHash)), + }); +} diff --git a/packages/nextjs/hooks/cow/useBindToken.ts b/packages/nextjs/hooks/cow/useBindToken.ts index 57d2a86..361cdd6 100644 --- a/packages/nextjs/hooks/cow/useBindToken.ts +++ b/packages/nextjs/hooks/cow/useBindToken.ts @@ -1,3 +1,4 @@ +import { usePoolCreationStore } from "./usePoolCreationStore"; import { useMutation } from "@tanstack/react-query"; import { Address } from "viem"; import { usePublicClient, useWalletClient } from "wagmi"; @@ -16,6 +17,7 @@ export const useBindToken = (tokenWeights: SupportedTokenWeight, isToken1: boole const publicClient = usePublicClient(); const writeTx = useTransactor(); // scaffold hook for tx status toast notifications const denormalizedTokenWeight = getDenormalizedTokenWeight(tokenWeights, isToken1); + const { updatePoolCreation } = usePoolCreationStore(); const bind = async ({ pool, token, rawAmount }: BindPayload) => { if (!pool) throw new Error("Cannot bind token without pool address"); @@ -31,9 +33,22 @@ export const useBindToken = (tokenWeights: SupportedTokenWeight, isToken1: boole }); const txHash = await writeTx(() => walletClient.writeContract(bind), { - blockConfirmations: 1, - onBlockConfirmation: () => { - console.log("Bound token:", token, "to pool:", pool); + onSafeTxHash: safeHash => { + const safeUpdate = { safeHash, wagmiHash: undefined, isSuccess: false }; + + if (isToken1) { + updatePoolCreation({ bindToken1Tx: safeUpdate }); + } else { + updatePoolCreation({ bindToken2Tx: safeUpdate }); + } + }, + onWagmiTxHash: wagmiHash => { + const wagmiUpdate = { wagmiHash, safeHash: undefined, isSuccess: false }; + if (isToken1) { + updatePoolCreation({ bindToken1Tx: wagmiUpdate }); + } else { + updatePoolCreation({ bindToken2Tx: wagmiUpdate }); + } }, }); diff --git a/packages/nextjs/hooks/cow/useBindTokenTxHash.ts b/packages/nextjs/hooks/cow/useBindTokenTxHash.ts new file mode 100644 index 0000000..74cfd8e --- /dev/null +++ b/packages/nextjs/hooks/cow/useBindTokenTxHash.ts @@ -0,0 +1,50 @@ +import { useSafeAppsSDK } from "@safe-global/safe-apps-react-sdk"; +import { useQuery } from "@tanstack/react-query"; +import { usePublicClient } from "wagmi"; +import { usePoolCreationStore } from "~~/hooks/cow/usePoolCreationStore"; +import { useIsSafeWallet } from "~~/hooks/safe/useIsSafeWallet"; +import { pollSafeTxStatus } from "~~/utils/safe"; + +interface UseBindTokenTxHashProps { + tokenNumber: 1 | 2; +} + +export function useBindTokenTxHash({ tokenNumber }: UseBindTokenTxHashProps) { + const publicClient = usePublicClient(); + const { sdk } = useSafeAppsSDK(); + const isSafeWallet = useIsSafeWallet(); + + const { poolCreation, updatePoolCreation } = usePoolCreationStore(); + const txKey = `bindToken${tokenNumber}Tx` as const; + const { safeHash, wagmiHash, isSuccess } = poolCreation?.[txKey] || {}; + + return useQuery({ + queryKey: [`bindToken${tokenNumber}TxHash`, safeHash, wagmiHash, isSuccess], + queryFn: async () => { + if (!publicClient) throw new Error("No public client for fetching pool address"); + + if (isSafeWallet && safeHash && !wagmiHash) { + const hash = await pollSafeTxStatus(sdk, safeHash); + updatePoolCreation({ [txKey]: { safeHash, wagmiHash: hash, isSuccess: false } }); + return null; + } + + if (!wagmiHash) return null; + + const txReceipt = await publicClient.waitForTransactionReceipt({ hash: wagmiHash }); + + if (txReceipt.status === "success") { + if (!poolCreation?.step) throw new Error("Missing pool creation step"); + + updatePoolCreation({ + [txKey]: { safeHash, wagmiHash, isSuccess: true }, + step: poolCreation.step + 1, + }); + return { isSuccess: true }; + } else { + throw new Error("Bind token transaction reverted"); + } + }, + enabled: Boolean(!isSuccess && (safeHash || wagmiHash)), + }); +} diff --git a/packages/nextjs/hooks/cow/useCreatePool.ts b/packages/nextjs/hooks/cow/useCreatePool.ts index 107e33b..e2ca583 100644 --- a/packages/nextjs/hooks/cow/useCreatePool.ts +++ b/packages/nextjs/hooks/cow/useCreatePool.ts @@ -27,7 +27,10 @@ export const useCreatePool = () => { }); const txHash = await writeTx(() => walletClient.writeContract(request), { - onTransactionHash: txHash => updatePoolCreation({ createPoolTxHash: txHash }), + onSafeTxHash: safeHash => + updatePoolCreation({ createPoolTx: { safeHash, wagmiHash: undefined, isSuccess: false } }), + onWagmiTxHash: wagmiHash => + updatePoolCreation({ createPoolTx: { wagmiHash, safeHash: undefined, isSuccess: false } }), }); return txHash; diff --git a/packages/nextjs/hooks/cow/useCreatePoolTxHash.ts b/packages/nextjs/hooks/cow/useCreatePoolTxHash.ts new file mode 100644 index 0000000..7067f48 --- /dev/null +++ b/packages/nextjs/hooks/cow/useCreatePoolTxHash.ts @@ -0,0 +1,55 @@ +import { useSafeAppsSDK } from "@safe-global/safe-apps-react-sdk"; +import { useQuery } from "@tanstack/react-query"; +import { parseEventLogs } from "viem"; +import { usePublicClient } from "wagmi"; +import { abis } from "~~/contracts/abis"; +import { usePoolCreationStore } from "~~/hooks/cow/usePoolCreationStore"; +import { useIsSafeWallet } from "~~/hooks/safe/useIsSafeWallet"; +import { pollSafeTxStatus } from "~~/utils/safe"; + +export function useCreatePoolTxHash() { + const isSafeWallet = useIsSafeWallet(); + const publicClient = usePublicClient(); + const { sdk } = useSafeAppsSDK(); + const { poolCreation, updatePoolCreation } = usePoolCreationStore(); + const { createPoolTx, poolAddress } = poolCreation || {}; + const { safeHash, wagmiHash, isSuccess } = createPoolTx || {}; + + return useQuery({ + queryKey: ["cowPoolAddress", safeHash, wagmiHash, isSuccess], + queryFn: async () => { + if (!publicClient) throw new Error("No public client for fetching pool address"); + + // If safe wallet, poll for safe tx status to update createPoolTxHash + if (isSafeWallet && safeHash && !wagmiHash) { + const hash = await pollSafeTxStatus(sdk, safeHash); + updatePoolCreation({ createPoolTx: { safeHash, wagmiHash: hash, isSuccess: false } }); + return null; // Trigger a re-query with the new createPoolTxHash + } + + if (!wagmiHash) return null; + + const txReceipt = await publicClient.waitForTransactionReceipt({ hash: wagmiHash }); + + if (txReceipt.status === "success") { + const logs = parseEventLogs({ + abi: abis.CoW.BCoWFactory, + logs: txReceipt.logs, + }); + + const newPoolAddress = (logs[0].args as { caller: string; bPool: string }).bPool; + if (!newPoolAddress) throw new Error("No new pool address from pool creation tx receipt"); + + updatePoolCreation({ + createPoolTx: { safeHash, wagmiHash, isSuccess: true }, + poolAddress: newPoolAddress, + step: 2, + }); + return newPoolAddress; + } else { + throw new Error("Create pool transaction reverted"); + } + }, + enabled: Boolean(!poolAddress && (safeHash || wagmiHash)), + }); +} diff --git a/packages/nextjs/hooks/cow/useFetchPoolAddress.ts b/packages/nextjs/hooks/cow/useFetchPoolAddress.ts deleted file mode 100644 index b3a4659..0000000 --- a/packages/nextjs/hooks/cow/useFetchPoolAddress.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { useSafeAppsSDK } from "@safe-global/safe-apps-react-sdk"; -import { useQuery } from "@tanstack/react-query"; -import { parseEventLogs } from "viem"; -import { usePublicClient } from "wagmi"; -import { abis } from "~~/contracts/abis"; -import { usePoolCreationStore } from "~~/hooks/cow/usePoolCreationStore"; -import { useIsSafeWallet } from "~~/hooks/safe/useIsSafeWallet"; -import { pollSafeTxStatus } from "~~/utils/safe"; - -export function useFetchPoolAddress() { - const isSafeWallet = useIsSafeWallet(); - const publicClient = usePublicClient(); - const { sdk } = useSafeAppsSDK(); - const { poolCreation, updatePoolCreation } = usePoolCreationStore(); - const { createPoolTxHash, pendingSafeTxHash, poolAddress } = poolCreation ?? {}; - - return useQuery({ - queryKey: ["cowPoolAddress", createPoolTxHash, pendingSafeTxHash], - queryFn: async () => { - if (!publicClient) throw new Error("No public client for fetching pool address"); - - // If safe wallet, poll for safe tx status to update createPoolTxHash - if (isSafeWallet && pendingSafeTxHash && !createPoolTxHash) { - const hash = await pollSafeTxStatus(sdk, pendingSafeTxHash); - updatePoolCreation({ createPoolTxHash: hash }); - return null; // Trigger a re-query with the new createPoolTxHash - } - - if (!createPoolTxHash) return null; - - const txReceipt = await publicClient.waitForTransactionReceipt({ hash: createPoolTxHash }); - - const logs = parseEventLogs({ - abi: abis.CoW.BCoWFactory, - logs: txReceipt.logs, - }); - - const newPoolAddress = (logs[0].args as { caller: string; bPool: string }).bPool; - if (!newPoolAddress) throw new Error("No new pool address from pool creation tx receipt"); - - updatePoolCreation({ poolAddress: newPoolAddress, step: 2 }); - return newPoolAddress; - }, - enabled: Boolean(!poolAddress && (createPoolTxHash || pendingSafeTxHash)), - }); -} diff --git a/packages/nextjs/hooks/cow/useFinalizePool.ts b/packages/nextjs/hooks/cow/useFinalizePool.ts index 27bc29b..37e7d4c 100644 --- a/packages/nextjs/hooks/cow/useFinalizePool.ts +++ b/packages/nextjs/hooks/cow/useFinalizePool.ts @@ -2,13 +2,14 @@ import { useMutation } from "@tanstack/react-query"; import { Address } from "viem"; import { usePublicClient, useWalletClient } from "wagmi"; import { abis } from "~~/contracts/abis"; +import { usePoolCreationStore } from "~~/hooks/cow/usePoolCreationStore"; import { useTransactor } from "~~/hooks/scaffold-eth"; export const useFinalizePool = () => { const { data: walletClient } = useWalletClient(); const publicClient = usePublicClient(); const writeTx = useTransactor(); // scaffold hook for tx status toast notifications - + const { updatePoolCreation } = usePoolCreationStore(); const finalize = async (pool: Address | undefined) => { if (!publicClient) throw new Error("No public client found!"); if (!walletClient) throw new Error("No wallet client found!"); @@ -22,9 +23,11 @@ export const useFinalizePool = () => { }); const txHash = await writeTx(() => walletClient.writeContract(finalizePool), { - blockConfirmations: 1, - onBlockConfirmation: () => { - console.log("Successfully finalized pool:", pool); + onSafeTxHash: safeHash => { + updatePoolCreation({ finalizePoolTx: { safeHash, wagmiHash: undefined, isSuccess: false } }); + }, + onWagmiTxHash: wagmiHash => { + updatePoolCreation({ finalizePoolTx: { wagmiHash, safeHash: undefined, isSuccess: false } }); }, }); return txHash; diff --git a/packages/nextjs/hooks/cow/useFinalizePoolTxHash.ts b/packages/nextjs/hooks/cow/useFinalizePoolTxHash.ts new file mode 100644 index 0000000..2d976e3 --- /dev/null +++ b/packages/nextjs/hooks/cow/useFinalizePoolTxHash.ts @@ -0,0 +1,42 @@ +import { useSafeAppsSDK } from "@safe-global/safe-apps-react-sdk"; +import { useQuery } from "@tanstack/react-query"; +import { usePublicClient } from "wagmi"; +import { usePoolCreationStore } from "~~/hooks/cow/usePoolCreationStore"; +import { useIsSafeWallet } from "~~/hooks/safe/useIsSafeWallet"; +import { pollSafeTxStatus } from "~~/utils/safe"; + +export function useFinalizePoolTxHash() { + const isSafeWallet = useIsSafeWallet(); + const publicClient = usePublicClient(); + const { sdk } = useSafeAppsSDK(); + const { poolCreation, updatePoolCreation } = usePoolCreationStore(); + const { finalizePoolTx } = poolCreation || {}; + const { safeHash, wagmiHash, isSuccess } = finalizePoolTx || {}; + + return useQuery({ + queryKey: ["finalizePoolTxHash", safeHash, wagmiHash, isSuccess], + queryFn: async () => { + if (!publicClient) throw new Error("No public client for fetching pool address"); + + // If safe wallet, poll for safe tx status to update createPoolTxHash + if (isSafeWallet && safeHash && !wagmiHash) { + const hash = await pollSafeTxStatus(sdk, safeHash); + updatePoolCreation({ finalizePoolTx: { safeHash, wagmiHash: hash, isSuccess: false } }); + return null; // Trigger a re-query with the new createPoolTxHash + } + + if (!wagmiHash) return null; + + const txReceipt = await publicClient.waitForTransactionReceipt({ hash: wagmiHash }); + + if (txReceipt.status === "success") { + if (!poolCreation?.step) throw new Error("Missing pool creation step"); + updatePoolCreation({ finalizePoolTx: { safeHash, wagmiHash, isSuccess: true }, step: poolCreation.step + 1 }); + return { isSuccess: true }; + } else { + throw new Error("Create pool transaction reverted"); + } + }, + enabled: Boolean(!isSuccess && (safeHash || wagmiHash)), + }); +} diff --git a/packages/nextjs/hooks/cow/usePoolCreationStore.ts b/packages/nextjs/hooks/cow/usePoolCreationStore.ts index acff693..91ea6ba 100644 --- a/packages/nextjs/hooks/cow/usePoolCreationStore.ts +++ b/packages/nextjs/hooks/cow/usePoolCreationStore.ts @@ -3,6 +3,12 @@ import { create } from "zustand"; import { persist } from "zustand/middleware"; import { Token } from "~~/hooks/token"; +export interface TransactionDetails { + safeHash: `0x${string}` | undefined; + wagmiHash: `0x${string}` | undefined; + isSuccess: boolean; +} + export interface PoolCreationState { chainId: number; token1: Token; @@ -14,23 +20,24 @@ export interface PoolCreationState { poolAddress: Address | undefined; // updated by tx receipt from completion of step 1 step: number; tokenWeights: "5050" | "8020"; - createPoolTxHash: `0x${string}` | undefined; - pendingSafeTxHash: `0x${string}` | undefined; - approveToken1TxHash: `0x${string}` | undefined; - approveToken2TxHash: `0x${string}` | undefined; - bindToken1TxHash: `0x${string}` | undefined; - bindToken2TxHash: `0x${string}` | undefined; - setSwapFeeTxHash: `0x${string}` | undefined; - finalizePoolTxHash: `0x${string}` | undefined; + createPoolTx: TransactionDetails; + approveToken1Tx: TransactionDetails; + approveToken2Tx: TransactionDetails; + bindToken1Tx: TransactionDetails; + bindToken2Tx: TransactionDetails; + setSwapFeeTx: TransactionDetails; + finalizePoolTx: TransactionDetails; +} + +export interface PoolCreationStore { + poolCreation: PoolCreationState | null; + setPoolCreation: (pool: PoolCreationState) => void; + updatePoolCreation: (updates: Partial) => void; + clearPoolCreation: () => void; } export const usePoolCreationStore = create( - persist<{ - poolCreation: PoolCreationState | null; - setPoolCreation: (pool: PoolCreationState) => void; - updatePoolCreation: (updates: Partial) => void; - clearPoolCreation: () => void; - }>( + persist( set => ({ poolCreation: null, setPoolCreation: (pool: PoolCreationState) => set({ poolCreation: pool }), diff --git a/packages/nextjs/hooks/cow/useSetSwapFee.ts b/packages/nextjs/hooks/cow/useSetSwapFee.ts index 3bd821a..fc6e5aa 100644 --- a/packages/nextjs/hooks/cow/useSetSwapFee.ts +++ b/packages/nextjs/hooks/cow/useSetSwapFee.ts @@ -2,36 +2,44 @@ import { useMutation } from "@tanstack/react-query"; import { Address } from "viem"; import { usePublicClient, useWalletClient } from "wagmi"; import { abis } from "~~/contracts/abis"; +import { usePoolCreationStore } from "~~/hooks/cow/usePoolCreationStore"; import { useTransactor } from "~~/hooks/scaffold-eth"; type SetSwapFeePayload = { pool: Address | undefined; - rawAmount: bigint | undefined; }; export const useSetSwapFee = () => { const { data: walletClient } = useWalletClient(); const publicClient = usePublicClient(); const writeTx = useTransactor(); // scaffold hook for tx status toast notifications - - const setSwapFee = async ({ pool, rawAmount }: SetSwapFeePayload) => { + const { updatePoolCreation } = usePoolCreationStore(); + const setSwapFee = async ({ pool }: SetSwapFeePayload) => { if (!publicClient) throw new Error("Cannot set swap fee public client"); if (!walletClient) throw new Error("Cannot set swap fee wallet client"); if (!pool) throw new Error("Cannot set swap fee without pool address"); - if (!rawAmount) throw new Error("Cannot set swap fee without swap fee amount"); + + const MAX_FEE = await publicClient.readContract({ + abi: abis.CoW.BCoWPool, + address: pool, + functionName: "MAX_FEE", + }); + if (!MAX_FEE) throw new Error("Failed to fetch value for max swap fee"); const { request: setSwapFee } = await publicClient.simulateContract({ abi: abis.CoW.BCoWPool, address: pool, functionName: "setSwapFee", account: walletClient.account, - args: [rawAmount], + args: [MAX_FEE], }); const txHash = await writeTx(() => walletClient.writeContract(setSwapFee), { - blockConfirmations: 1, - onBlockConfirmation: () => { - console.log("Set swap fee to", rawAmount, "for pool:", pool); + onSafeTxHash: safeHash => { + updatePoolCreation({ setSwapFeeTx: { safeHash, wagmiHash: undefined, isSuccess: false } }); + }, + onWagmiTxHash: wagmiHash => { + updatePoolCreation({ setSwapFeeTx: { wagmiHash, safeHash: undefined, isSuccess: false } }); }, }); return txHash; diff --git a/packages/nextjs/hooks/cow/useSetSwapFeeTxHash.ts b/packages/nextjs/hooks/cow/useSetSwapFeeTxHash.ts new file mode 100644 index 0000000..ce4f951 --- /dev/null +++ b/packages/nextjs/hooks/cow/useSetSwapFeeTxHash.ts @@ -0,0 +1,59 @@ +import { useSafeAppsSDK } from "@safe-global/safe-apps-react-sdk"; +import { useQuery } from "@tanstack/react-query"; +import { usePublicClient } from "wagmi"; +import { abis } from "~~/contracts/abis"; +import { usePoolCreationStore } from "~~/hooks/cow/usePoolCreationStore"; +import { useIsSafeWallet } from "~~/hooks/safe/useIsSafeWallet"; +import { pollSafeTxStatus } from "~~/utils/safe"; + +export function useSetSwapFeeTxHash() { + const isSafeWallet = useIsSafeWallet(); + const publicClient = usePublicClient(); + const { sdk } = useSafeAppsSDK(); + const { poolCreation, updatePoolCreation } = usePoolCreationStore(); + const { setSwapFeeTx } = poolCreation || {}; + const { safeHash, wagmiHash, isSuccess } = setSwapFeeTx || {}; + + return useQuery({ + queryKey: ["setSwapFeeTxHash", safeHash, wagmiHash, isSuccess], + queryFn: async () => { + if (!publicClient) throw new Error("No public client for fetching pool address"); + + // If safe wallet, poll for safe tx status to update createPoolTxHash + if (isSafeWallet && safeHash && !wagmiHash) { + const hash = await pollSafeTxStatus(sdk, safeHash); + updatePoolCreation({ setSwapFeeTx: { safeHash, wagmiHash: hash, isSuccess: false } }); + return null; // Trigger a re-query with the new createPoolTxHash + } + + if (!wagmiHash) return null; + + const txReceipt = await publicClient.waitForTransactionReceipt({ hash: wagmiHash }); + + if (txReceipt.status === "success") { + if (!poolCreation?.step) throw new Error("Missing pool creation step"); + if (!poolCreation?.poolAddress) throw new Error("Missing pool address"); + + const MAX_FEE = await publicClient.readContract({ + abi: abis.CoW.BCoWPool, + address: poolCreation?.poolAddress, + functionName: "MAX_FEE", + }); + + const swapFee = await publicClient.readContract({ + abi: abis.CoW.BCoWPool, + address: poolCreation?.poolAddress, + functionName: "getSwapFee", + }); + + if (swapFee !== MAX_FEE) throw new Error("Swap fee is not set to max fee"); + + updatePoolCreation({ setSwapFeeTx: { safeHash, wagmiHash, isSuccess: true }, step: poolCreation?.step + 1 }); + return { isSuccess: true }; + } else { + throw new Error("Set swap fee transaction reverted"); + } + }, + enabled: Boolean(!isSuccess && (safeHash || wagmiHash)), + }); +} diff --git a/packages/nextjs/hooks/scaffold-eth/useTransactor.tsx b/packages/nextjs/hooks/scaffold-eth/useTransactor.tsx index 899ca19..e9d3ad5 100644 --- a/packages/nextjs/hooks/scaffold-eth/useTransactor.tsx +++ b/packages/nextjs/hooks/scaffold-eth/useTransactor.tsx @@ -3,7 +3,7 @@ import { getPublicClient } from "@wagmi/core"; import { Hash, SendTransactionParameters, WalletClient } from "viem"; import { Config, useWalletClient } from "wagmi"; import { SendTransactionMutate } from "wagmi/query"; -import { usePoolCreationStore } from "~~/hooks/cow/usePoolCreationStore"; +// import { usePoolCreationStore } from "~~/hooks/cow/usePoolCreationStore"; import { useIsSafeWallet } from "~~/hooks/safe/useIsSafeWallet"; import { wagmiConfig } from "~~/services/web3/wagmiConfig"; import { pollSafeTxStatus } from "~~/utils/safe"; @@ -44,7 +44,7 @@ export const useTransactor = (_walletClient?: WalletClient): TransactionFunc => } const isSafeWallet = useIsSafeWallet(); const { sdk } = useSafeAppsSDK(); - const { updatePoolCreation } = usePoolCreationStore(); + // const { updatePoolCreation } = usePoolCreationStore(); const result: TransactionFunc = async (tx, options) => { if (!walletClient) { @@ -78,8 +78,8 @@ export const useTransactor = (_walletClient?: WalletClient): TransactionFunc => if (isSafeWallet) { const pendingSafeTxHash = transactionHash; // save to state in case of user disconnection - updatePoolCreation({ pendingSafeTxHash }); - // update transactionHash to the on chain tx hash + if (options?.onSafeTxHash) options.onSafeTxHash(pendingSafeTxHash); + // set transactionHash to on chain tx hash returned by transactionHash = await pollSafeTxStatus(sdk, pendingSafeTxHash); } @@ -91,8 +91,8 @@ export const useTransactor = (_walletClient?: WalletClient): TransactionFunc => , ); // Matt added this callback to save tx hash to local storage incase user disconnects while tx pending - if (options?.onTransactionHash && transactionHash) { - options.onTransactionHash(transactionHash); + if (options?.onWagmiTxHash && transactionHash) { + options.onWagmiTxHash(transactionHash); } const transactionReceipt = await publicClient.waitForTransactionReceipt({ diff --git a/packages/nextjs/hooks/token/useApproveToken.ts b/packages/nextjs/hooks/token/useApproveToken.ts index 5a7808d..ddd2e90 100644 --- a/packages/nextjs/hooks/token/useApproveToken.ts +++ b/packages/nextjs/hooks/token/useApproveToken.ts @@ -10,7 +10,12 @@ type ApproveOnTokenPayload = { rawAmount: bigint; }; -export const useApproveToken = () => { +type UseApproveTokenOptions = { + onSafeTxHash?: (safeHash: `0x${string}`) => void; + onWagmiTxHash?: (wagmiHash: `0x${string}`) => void; +}; + +export const useApproveToken = ({ onSafeTxHash, onWagmiTxHash }: UseApproveTokenOptions) => { const { data: walletClient } = useWalletClient(); const publicClient = usePublicClient(); const writeTx = useTransactor(); // scaffold hook for tx status toast notifications @@ -29,7 +34,10 @@ export const useApproveToken = () => { args: [spender, rawAmount], }); - const txHash = await writeTx(() => walletClient.writeContract(approveSpenderOnToken)); + const txHash = await writeTx(() => walletClient.writeContract(approveSpenderOnToken), { + onSafeTxHash: onSafeTxHash, + onWagmiTxHash: onWagmiTxHash, + }); console.log("Approved pool contract to spend token, txHash:", txHash); return txHash; }; diff --git a/packages/nextjs/hooks/v3/useCreatePool.ts b/packages/nextjs/hooks/v3/useCreatePool.ts index 458755a..e6d79c7 100644 --- a/packages/nextjs/hooks/v3/useCreatePool.ts +++ b/packages/nextjs/hooks/v3/useCreatePool.ts @@ -42,6 +42,7 @@ export const useCreatePool = () => { disableUnbalancedLiquidity, amplificationParameter, updatePool, + createPoolTx, } = usePoolCreationStore(); const { data: boostableWhitelist } = useBoostableWhitelist(); @@ -102,9 +103,9 @@ export const useCreatePool = () => { to: call.to, }), { - // tx not considered successful until tx receipt is parsed - onTransactionHash: txHash => - updatePool({ createPoolTx: { wagmiHash: txHash, safeHash: undefined, isSuccess: false } }), + // callbacks to save tx hash's to store + onSafeTxHash: safeHash => updatePool({ createPoolTx: { ...createPoolTx, safeHash } }), + onWagmiTxHash: wagmiHash => updatePool({ createPoolTx: { ...createPoolTx, wagmiHash } }), }, ); diff --git a/packages/nextjs/hooks/v3/useInitializePool.ts b/packages/nextjs/hooks/v3/useInitializePool.ts index 3b32021..1629085 100644 --- a/packages/nextjs/hooks/v3/useInitializePool.ts +++ b/packages/nextjs/hooks/v3/useInitializePool.ts @@ -16,7 +16,7 @@ export const useInitializePool = () => { const chainId = publicClient?.chain.id; const rpcUrl = publicClient?.transport.transports[0].value.url; const protocolVersion = 3; - const { poolAddress, poolType, tokenConfigs, updatePool } = usePoolCreationStore(); + const { poolAddress, poolType, tokenConfigs, updatePool, initPoolTx } = usePoolCreationStore(); const { data: boostableWhitelist } = useBoostableWhitelist(); async function initializePool() { @@ -81,9 +81,9 @@ export const useInitializePool = () => { console.log("router.permitBatchAndCall args for initialize pool", args); const hash = await writeTx(() => router.write.permitBatchAndCall(args), { - // Should be okay to set off-chain safeHash undefined once we have on chain wagmiHash - onTransactionHash: txHash => - updatePool({ initPoolTx: { wagmiHash: txHash, safeHash: undefined, isSuccess: false } }), + // callbacks to save tx hash's to store + onSafeTxHash: safeHash => updatePool({ initPoolTx: { ...initPoolTx, safeHash } }), + onWagmiTxHash: wagmiHash => updatePool({ initPoolTx: { ...initPoolTx, wagmiHash } }), }); if (!hash) throw new Error("Missing init pool transaction hash"); diff --git a/packages/nextjs/hooks/v3/useInitializePoolTxHash.ts b/packages/nextjs/hooks/v3/useInitializePoolTxHash.ts index 0c1c9c5..e92603b 100644 --- a/packages/nextjs/hooks/v3/useInitializePoolTxHash.ts +++ b/packages/nextjs/hooks/v3/useInitializePoolTxHash.ts @@ -34,6 +34,7 @@ export function useInitializePoolTxHash() { if (txReceipt.status === "success") { updatePool({ step: step + 1, initPoolTx: { safeHash, wagmiHash, isSuccess: true } }); + return { isSuccess: true }; } else { throw new Error("Init pool transaction reverted"); } diff --git a/packages/nextjs/hooks/v3/useMultiSwap.ts b/packages/nextjs/hooks/v3/useMultiSwap.ts index a3e205b..0c9ec9f 100644 --- a/packages/nextjs/hooks/v3/useMultiSwap.ts +++ b/packages/nextjs/hooks/v3/useMultiSwap.ts @@ -22,7 +22,7 @@ export const useMultiSwap = () => { const publicClient = usePublicClient(); const writeTx = useTransactor(); const chainId = publicClient?.chain.id; - const { tokenConfigs, updatePool } = usePoolCreationStore(); + const { tokenConfigs, updatePool, swapToBoostedTx } = usePoolCreationStore(); const { data: boostableWhitelist } = useBoostableWhitelist(); const userData = "0x"; @@ -103,9 +103,8 @@ export const useMultiSwap = () => { console.log("batchRouter.permitBatchAndCall args", args); const hash = await writeTx(() => batchRouterContract.write.permitBatchAndCall(args), { - // tx not considered successful until tx receipt is parsed - onTransactionHash: txHash => - updatePool({ swapToBoostedTx: { wagmiHash: txHash, safeHash: undefined, isSuccess: false } }), + onSafeTxHash: safeHash => updatePool({ swapToBoostedTx: { ...swapToBoostedTx, safeHash } }), + onWagmiTxHash: wagmiHash => updatePool({ swapToBoostedTx: { ...swapToBoostedTx, wagmiHash } }), }); if (!hash) throw new Error("No multi swap transaction hash"); diff --git a/packages/nextjs/hooks/v3/useMultiSwapTxHash.ts b/packages/nextjs/hooks/v3/useMultiSwapTxHash.ts index 97c401f..806bf66 100644 --- a/packages/nextjs/hooks/v3/useMultiSwapTxHash.ts +++ b/packages/nextjs/hooks/v3/useMultiSwapTxHash.ts @@ -66,6 +66,7 @@ export function useMultiSwapTxHash() { }); updatePool({ step: step + 1, swapToBoostedTx: { wagmiHash, safeHash, isSuccess: true } }); + return { isSuccess: true }; } else { throw new Error("Swap to boosted variant transaction reverted"); } diff --git a/packages/nextjs/utils/scaffold-eth/contract.ts b/packages/nextjs/utils/scaffold-eth/contract.ts index 258c818..2792bc7 100644 --- a/packages/nextjs/utils/scaffold-eth/contract.ts +++ b/packages/nextjs/utils/scaffold-eth/contract.ts @@ -196,7 +196,9 @@ type WriteVariables = WriteContractVariables export type TransactorFuncOptions = { onBlockConfirmation?: (txnReceipt: TransactionReceipt) => void; blockConfirmations?: number; - onTransactionHash?: (txnHash: `0x${string}`) => void; // Matt added this to save tx hash to local storage incase user disconnects while tx pending + // Save tx hash to local storage incase user disconnects while tx pending + onSafeTxHash?: (txnHash: `0x${string}`) => void; + onWagmiTxHash?: (txnHash: `0x${string}`) => void; }; export type ScaffoldWriteContractOptions = MutateOptions<