From a6de8aced8a1beee74f55a2b1eef933c0b23a690 Mon Sep 17 00:00:00 2001 From: Pedro Yves Fracari Date: Fri, 25 Oct 2024 11:18:45 -0300 Subject: [PATCH 1/3] add retry on gas estimation --- apps/create-vesting/src/app/signing/page.tsx | 18 ++----- apps/deposit-pool/src/app/signing/page.tsx | 17 ++----- apps/withdraw-pool/src/app/signing/page.tsx | 23 +++------ .../cow-hooks-ui/src/hooks/useEstimateGas.ts | 28 +++++++++++ .../cow-hooks-ui/src/hooks/useSubmitHook.ts | 49 ++++++------------- packages/cow-hooks-ui/src/utils/retry.ts | 40 +++++++++++++++ 6 files changed, 96 insertions(+), 79 deletions(-) create mode 100644 packages/cow-hooks-ui/src/hooks/useEstimateGas.ts create mode 100644 packages/cow-hooks-ui/src/utils/retry.ts diff --git a/apps/create-vesting/src/app/signing/page.tsx b/apps/create-vesting/src/app/signing/page.tsx index 511c73f..e7a0bda 100644 --- a/apps/create-vesting/src/app/signing/page.tsx +++ b/apps/create-vesting/src/app/signing/page.tsx @@ -16,22 +16,10 @@ import type { Address } from "viem"; export default function Page() { const [currentStepIndex, setCurrentStepIndex] = useState(0); const [permitTxs, setPermitTxs] = useState([]); - const { - actions, - hookInfo, - cowShed, - signer, - context, - publicClient, - cowShedProxy, - } = useIFrameContext(); + const { hookInfo, cowShed, signer, context, cowShedProxy } = + useIFrameContext(); - const submitHook = useSubmitHook({ - actions, - context, - publicClient, - recipientOverride: hookInfo?.recipientOverride, - }); + const submitHook = useSubmitHook(hookInfo?.recipientOverride); const cowShedSignature = useCowShedSignature({ cowShed, signer, diff --git a/apps/deposit-pool/src/app/signing/page.tsx b/apps/deposit-pool/src/app/signing/page.tsx index a37b619..42d8008 100644 --- a/apps/deposit-pool/src/app/signing/page.tsx +++ b/apps/deposit-pool/src/app/signing/page.tsx @@ -19,22 +19,11 @@ import type { Address } from "viem"; export default function Page() { const [currentStepIndex, setCurrentStepIndex] = useState(0); const [permitTxs, setPermitTxs] = useState([]); - const { - actions, - hookInfo, - cowShed, - signer, - context, - publicClient, - cowShedProxy, - } = useIFrameContext(); + const { hookInfo, cowShed, signer, context, cowShedProxy } = + useIFrameContext(); const [account, setAccount] = useState(); const router = useRouter(); - const submitHook = useSubmitHook({ - actions, - context, - publicClient, - }); + const submitHook = useSubmitHook(); const cowShedSignature = useCowShedSignature({ cowShed, signer, diff --git a/apps/withdraw-pool/src/app/signing/page.tsx b/apps/withdraw-pool/src/app/signing/page.tsx index 6a08b21..5d97475 100644 --- a/apps/withdraw-pool/src/app/signing/page.tsx +++ b/apps/withdraw-pool/src/app/signing/page.tsx @@ -21,23 +21,12 @@ import type { WithdrawSchemaType } from "#/utils/schema"; export default function Page() { const [currentStepIndex, setCurrentStepIndex] = useState(0); const [permitTxs, setPermitTxs] = useState([]); - const { - actions, - hookInfo, - cowShed, - signer, - context, - publicClient, - cowShedProxy, - } = useIFrameContext(); + const { hookInfo, cowShed, signer, context, cowShedProxy } = + useIFrameContext(); const { getValues } = useFormContext(); const [account, setAccount] = useState(); const router = useRouter(); - const submitHook = useSubmitHook({ - actions, - context, - publicClient, - }); + const submitHook = useSubmitHook(); const cowShedSignature = useCowShedSignature({ cowShed, signer, @@ -68,8 +57,10 @@ export default function Page() { const cowShedCall = await cowShedSignature(txs); if (!cowShedCall) throw new Error("Error signing hooks"); const withdrawPct = getValues("withdrawPct"); - const cowShedCallDataWithWithdrawPct = - cowShedCall + Number(Number(withdrawPct).toFixed()).toString(16); // adding to facilitate editing the hook + const withdrawPctHex = Number(Number(withdrawPct).toFixed()) + .toString(16) + .padStart(4, "0"); + const cowShedCallDataWithWithdrawPct = cowShedCall + withdrawPctHex; // adding to facilitate editing the hook await submitHook({ target: cowShed.getFactoryAddress(), callData: cowShedCallDataWithWithdrawPct, diff --git a/packages/cow-hooks-ui/src/hooks/useEstimateGas.ts b/packages/cow-hooks-ui/src/hooks/useEstimateGas.ts new file mode 100644 index 0000000..d65604f --- /dev/null +++ b/packages/cow-hooks-ui/src/hooks/useEstimateGas.ts @@ -0,0 +1,28 @@ +import { COW_PROTOCOL_SETTLEMENT_CONTRACT_ADDRESS } from "@cowprotocol/cow-sdk"; +import type { CowHook } from "@cowprotocol/hook-dapp-lib"; +import { useCallback } from "react"; +import type { Address } from "viem"; +import { useIFrameContext } from "../context/iframe"; +import { retryAsync } from "../utils/retry"; + +export function useEstimateGas() { + const { context, actions, publicClient } = useIFrameContext(); + return useCallback( + async (hook: Omit) => { + if (!context || !actions || !publicClient) + throw new Error("Missing context"); + + return await retryAsync(() => { + return publicClient.estimateGas({ + account: COW_PROTOCOL_SETTLEMENT_CONTRACT_ADDRESS[ + context.chainId + ] as `0x${string}`, + to: hook.target as Address, + value: BigInt("0"), + data: hook.callData as `0x${string}`, + }); + }); + }, + [context, actions, publicClient], + ); +} diff --git a/packages/cow-hooks-ui/src/hooks/useSubmitHook.ts b/packages/cow-hooks-ui/src/hooks/useSubmitHook.ts index 61886f8..8e7a212 100644 --- a/packages/cow-hooks-ui/src/hooks/useSubmitHook.ts +++ b/packages/cow-hooks-ui/src/hooks/useSubmitHook.ts @@ -1,43 +1,24 @@ -import { COW_PROTOCOL_SETTLEMENT_CONTRACT_ADDRESS } from "@cowprotocol/cow-sdk"; -import type { CoWHookDappActions, CowHook } from "@cowprotocol/hook-dapp-lib"; +import type { CowHook } from "@cowprotocol/hook-dapp-lib"; import { BigNumber } from "ethers"; import { useCallback } from "react"; -import type { Address, PublicClient } from "viem"; -import type { HookDappContextAdjusted } from "../types"; +import { useIFrameContext } from "../context/iframe"; +import { useEstimateGas } from "./useEstimateGas"; -export function useSubmitHook({ - actions, - context, - publicClient, - recipientOverride, -}: { - actions: CoWHookDappActions | undefined; - context: HookDappContextAdjusted | undefined; - publicClient: PublicClient | undefined; - recipientOverride?: string; -}) { +export function useSubmitHook(recipientOverride?: string) { + const { context, actions } = useIFrameContext(); + const estimateGas = useEstimateGas(); return useCallback( async (hook: Omit) => { - if (!context || !actions || !publicClient) - throw new Error("Missing context"); + if (!context || !actions) throw new Error("Missing context"); - const estimatedGas = await publicClient - .estimateGas({ - account: COW_PROTOCOL_SETTLEMENT_CONTRACT_ADDRESS[ - context.chainId - ] as `0x${string}`, - to: hook.target as Address, - value: BigInt(0), - data: hook.callData as `0x${string}`, - }) - .catch(() => { - console.error("Failed to estimated hook gas", { - chainId: context.chainId, - calldata: hook.callData, - target: hook.target, - }); - throw new Error("Failed to estimate hook gas"); + const estimatedGas = await estimateGas(hook).catch(() => { + console.error("Failed to estimated hook gas", { + chainId: context.chainId, + calldata: hook.callData, + target: hook.target, }); + throw new Error("Failed to estimated hook gas"); + }); const gasLimit = BigNumber.from(estimatedGas) .mul(120) @@ -60,6 +41,6 @@ export function useSubmitHook({ actions.addHook({ hook: hookWithGasLimit, recipientOverride }); }, - [actions, context, recipientOverride, publicClient], + [actions, context, recipientOverride, estimateGas], ); } diff --git a/packages/cow-hooks-ui/src/utils/retry.ts b/packages/cow-hooks-ui/src/utils/retry.ts new file mode 100644 index 0000000..2e0de62 --- /dev/null +++ b/packages/cow-hooks-ui/src/utils/retry.ts @@ -0,0 +1,40 @@ +type RetryOptions = { + maxAttempts?: number; + delayMs?: number; + onRetry?: (error: Error, attempt: number) => void; +}; + +export async function retryAsync( + fn: () => Promise, + options: RetryOptions = {}, +): Promise { + const { + maxAttempts = 5, + delayMs = 1000, + onRetry = (error, attempt) => + console.warn( + `Attempt ${attempt} failed with error: ${error.message}. Retrying...`, + ), + } = options; + + let lastError: Error | null = null; + + for (let attempt = 1; attempt <= maxAttempts; attempt++) { + try { + return await fn(); + } catch (error) { + lastError = error instanceof Error ? error : new Error(String(error)); + + if (attempt === maxAttempts) { + break; + } + + onRetry(lastError, attempt); + await new Promise((resolve) => setTimeout(resolve, delayMs)); + } + } + + throw new Error( + `Failed after ${maxAttempts} attempts. Last error: ${lastError?.message}`, + ); +} From b6c549d1edf01de67e1d2149507f742594062b10 Mon Sep 17 00:00:00 2001 From: Pedro Yves Fracari Date: Fri, 25 Oct 2024 11:34:04 -0300 Subject: [PATCH 2/3] fix: filter low liquditity pools --- apps/withdraw-pool/src/app/page.tsx | 3 ++- .../src/components/PoolBalancePreview.tsx | 6 ++--- apps/withdraw-pool/src/hooks/useUserPools.ts | 7 ++++- .../cow-hooks-ui/src/PoolsDropdownMenu.tsx | 26 ++++++++++++------- 4 files changed, 27 insertions(+), 15 deletions(-) diff --git a/apps/withdraw-pool/src/app/page.tsx b/apps/withdraw-pool/src/app/page.tsx index 815ed4a..326f2a7 100644 --- a/apps/withdraw-pool/src/app/page.tsx +++ b/apps/withdraw-pool/src/app/page.tsx @@ -75,7 +75,7 @@ export default function Page() { if (!pools || pools.length === 0) { return ( - You don't have liquidity in a CoW AMM pool + You don't have unstaked liquidity in a CoW AMM pool ); } @@ -87,6 +87,7 @@ export default function Page() { pools={pools || []} PoolItemInfo={PoolItemInfo} selectedPool={selectedPool} + tooltipText="Withdraw of staked or too low liquidity are not supported" /> diff --git a/apps/withdraw-pool/src/components/PoolBalancePreview.tsx b/apps/withdraw-pool/src/components/PoolBalancePreview.tsx index 8ec8a8c..f76c9fc 100644 --- a/apps/withdraw-pool/src/components/PoolBalancePreview.tsx +++ b/apps/withdraw-pool/src/components/PoolBalancePreview.tsx @@ -26,7 +26,7 @@ export function PoolBalancesPreview({ const { withdrawPct } = useWatch({ control }); - const _withdrawBalance = useMemo(() => { + const withdrawBalance = useMemo(() => { if (!poolBalances || !withdrawPct) return []; return poolBalances.map((poolBalance) => ({ ...poolBalance, @@ -55,7 +55,7 @@ export function PoolBalancesPreview({ - {poolBalances.map((balance, _index) => { + {poolBalances.map((balance, index) => { return ( + BigNumber.from(pool.userBalance.walletBalance).gt(BigNumber.from("10")), + ); + return { ...useSwrData, data }; } diff --git a/packages/cow-hooks-ui/src/PoolsDropdownMenu.tsx b/packages/cow-hooks-ui/src/PoolsDropdownMenu.tsx index d08db1f..1f6921e 100644 --- a/packages/cow-hooks-ui/src/PoolsDropdownMenu.tsx +++ b/packages/cow-hooks-ui/src/PoolsDropdownMenu.tsx @@ -18,17 +18,20 @@ import { CommandItem, CommandList, } from "./ui/Command"; +import { InfoTooltip } from "./ui/TooltipBase"; export function PoolsDropdownMenu({ onSelect, pools, PoolItemInfo, selectedPool, + tooltipText, }: { onSelect: (pool: IPool) => void; pools: IPool[]; PoolItemInfo: React.ComponentType<{ pool: IPool }>; selectedPool?: IPool; + tooltipText?: string; }) { const [open, setOpen] = useState(false); const [search, setSearch] = useState(""); @@ -49,22 +52,25 @@ export function PoolsDropdownMenu({ return (
- setOpen(true)} - > - {selectedPool ? : "Select a pool"} - - +
+ setOpen(true)} + > + {selectedPool ? : "Select a pool"} + + +
- Select a pool + Select a pool + {tooltipText && }
{ From df4d27bcdc11fb6e2cb9160db45d3d3c32cec90d Mon Sep 17 00:00:00 2001 From: Pedro Yves Fracari Date: Fri, 25 Oct 2024 11:37:59 -0300 Subject: [PATCH 3/3] refactor pool dropdown tooltip --- apps/withdraw-pool/src/app/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/withdraw-pool/src/app/page.tsx b/apps/withdraw-pool/src/app/page.tsx index 326f2a7..a6ea6f8 100644 --- a/apps/withdraw-pool/src/app/page.tsx +++ b/apps/withdraw-pool/src/app/page.tsx @@ -87,7 +87,7 @@ export default function Page() { pools={pools || []} PoolItemInfo={PoolItemInfo} selectedPool={selectedPool} - tooltipText="Withdraw of staked or too low liquidity are not supported" + tooltipText="Withdraw of staked liquidity or pool with low user balance are not supported" />