Skip to content

Commit

Permalink
fix min swap fee % and pending tx notifications
Browse files Browse the repository at this point in the history
  • Loading branch information
MattPereira committed Dec 13, 2024
1 parent bf29e2b commit 7222401
Show file tree
Hide file tree
Showing 10 changed files with 126 additions and 99 deletions.
30 changes: 18 additions & 12 deletions packages/nextjs/app/v3/_components/ChooseParameters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from "react";
import { PoolType } from "@balancer/sdk";
import { useQueryClient } from "@tanstack/react-query";
import { useAccount } from "wagmi";
import { InformationCircleIcon } from "@heroicons/react/24/outline";
import { ArrowTopRightOnSquareIcon } from "@heroicons/react/24/outline";
import { Checkbox, NumberInput, RadioInput, TextField } from "~~/components/common";
import { type HookFlags, usePoolCreationStore } from "~~/hooks/v3";

Expand Down Expand Up @@ -58,19 +58,25 @@ export const ChooseParameters = () => {
}
};

const MIN_SWAP_FEE_PERCENTAGE = poolType === PoolType.Stable ? 0.0001 : 0.001;
const SWAP_FEE_LINK =
poolType === PoolType.Stable
? "https://docs-v3.balancer.fi/developer-reference/contracts/vault-config.html#minimum-swap-fee-percentage-1"
: "https://docs-v3.balancer.fi/developer-reference/contracts/vault-config.html#minimum-swap-fee-percentage";

return (
<div className="flex flex-col gap-4">
<div className="text-xl">Choose pool parameters:</div>
<div className="bg-base-100 p-5 rounded-xl">
<div className="text-lg font-bold mb-3 inline-flex">
<a
className="flex items-center gap-2 link no-underline hover:underline"
href="https://docs-v3.balancer.fi/concepts/vault/swap-fee.html"
href={SWAP_FEE_LINK}
target="_blank"
rel="noreferrer"
>
Swap fee percentage
<InformationCircleIcon className="w-5 h-5 mt-0.5" />
<ArrowTopRightOnSquareIcon className="w-5 h-5 mt-0.5" />
</a>
</div>
<div className="flex gap-2">
Expand All @@ -85,12 +91,12 @@ export const ChooseParameters = () => {
))}
<div>
<NumberInput
placeholder=".001 - 10"
placeholder={`${MIN_SWAP_FEE_PERCENTAGE} - 10`}
value={swapFeePercentage}
onChange={e => handleNumberInputChange(e, "swapFeePercentage", 0.001, 10)}
min={0.001}
onChange={e => handleNumberInputChange(e, "swapFeePercentage", MIN_SWAP_FEE_PERCENTAGE, 10)}
min={MIN_SWAP_FEE_PERCENTAGE}
max={10}
step={0.001}
step={MIN_SWAP_FEE_PERCENTAGE}
isPercentage={true}
/>
</div>
Expand All @@ -102,12 +108,12 @@ export const ChooseParameters = () => {
<div className="text-lg font-bold mb-3 inline-flex">
<a
className="flex items-center gap-2 link no-underline hover:underline"
href="https://docs-v3.balancer.fi/concepts/explore-available-balancer-pools/stable-pool/stable-math.html"
href="https://docs-v3.balancer.fi/developer-reference/contracts/vault-config.html#minimum-maximum-amplification-parameter"
target="_blank"
rel="noreferrer"
>
Amplification Parameter
<InformationCircleIcon className="w-5 h-5 mt-0.5" />
<ArrowTopRightOnSquareIcon className="w-5 h-5 mt-0.5" />
</a>
</div>

Expand Down Expand Up @@ -144,7 +150,7 @@ export const ChooseParameters = () => {
rel="noreferrer"
>
Swap fee manager
<InformationCircleIcon className="w-5 h-5 mt-0.5" />
<ArrowTopRightOnSquareIcon className="w-5 h-5 mt-0.5" />
</a>
</label>
<RadioInput
Expand Down Expand Up @@ -191,7 +197,7 @@ export const ChooseParameters = () => {
rel="noreferrer"
>
Pause manager
<InformationCircleIcon className="w-5 h-5 mt-0.5" />
<ArrowTopRightOnSquareIcon className="w-5 h-5 mt-0.5" />
</a>
</label>
<RadioInput
Expand Down Expand Up @@ -240,7 +246,7 @@ export const ChooseParameters = () => {
rel="noreferrer"
>
Pool hooks
<InformationCircleIcon className="w-5 h-5 mt-0.5" />
<ArrowTopRightOnSquareIcon className="w-5 h-5 mt-0.5" />
</a>
</label>
<RadioInput
Expand Down
12 changes: 7 additions & 5 deletions packages/nextjs/app/v3/_components/ChooseToken.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -380,15 +380,15 @@ const RateProviderModal = ({

return (
<div className="fixed inset-0 bg-black bg-opacity-75 flex justify-center items-center z-50">
<div className="w-[650px] min-h-[333px] bg-base-200 rounded-lg p-7 flex flex-col gap-5 justify-around">
<h3 className="font-bold text-3xl text-center">Rate Provider</h3>
<div className="w-[650px] min-h-[333px] bg-base-200 rounded-lg py-7 px-10 flex flex-col gap-5 justify-around">
<h3 className="font-bold text-3xl text-center">Confirm Rate Provider</h3>

<div className="flex flex-col gap-5">
<div className="text-xl">
Consider using the following rate provider for <b>{tokenInfo.symbol}</b>
</div>
{rateProviderData ? (
<div className="overflow-x-auto">
<div className="overflow-x-auto px-5">
<table className="w-full text-xl table border border-neutral-500">
<tbody>
<tr className="border-b border-neutral-500">
Expand Down Expand Up @@ -431,8 +431,10 @@ const RateProviderModal = ({
) : (
<div>No rate provider data</div>
)}
<div className="text-xl">If you wish to use this rate provider, please click confirm</div>
<div className="text-xl"> Otherwise, choose deny and paste in a rate provider address</div>
<div className="text-xl">
If you wish to use this rate provider, click confirm. Otherwise, choose deny and paste in a rate provider
address
</div>
</div>
<div className="w-full flex gap-4 justify-end mt-3">
<button
Expand Down
72 changes: 7 additions & 65 deletions packages/nextjs/app/v3/_components/PoolCreationManager.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
import { useEffect, useState } from "react";
import { ApproveOnTokenManager } from ".";
import { parseEventLogs } from "viem";
import { usePublicClient } from "wagmi";
import { ArrowTopRightOnSquareIcon } from "@heroicons/react/24/outline";
import { PoolDetails } from "~~/app/v3/_components";
import {
Expand All @@ -12,46 +9,34 @@ import {
TransactionButton,
} from "~~/components/common";
import {
poolFactoryAbi,
useBoostableWhitelist,
useCreatePool,
useInitializePool,
useMultiSwap,
usePoolCreationStore,
useUserDataStore,
useWaitForTransactionReceipt,
} from "~~/hooks/v3/";
import { bgBeigeGradient, bgBeigeGradientHover, bgPrimaryGradient } from "~~/utils";
import { getBlockExplorerTxLink } from "~~/utils/scaffold-eth/";
import { getBlockExplorerTxLink } from "~~/utils/scaffold-eth";

/**
* Manages the pool creation process using a modal that cannot be closed after execution of the first step
*/
export function PoolCreationManager({ setIsModalOpen }: { setIsModalOpen: (isOpen: boolean) => void }) {
const [isCreatePoolReceiptLoading, setIsCreatePoolReceiptLoading] = useState(false);
const [isInitPoolReceiptLoading, setIsInitPoolReceiptLoading] = useState(false);

const {
step,
tokenConfigs,
clearPoolStore,
createPoolTxHash,
swapTxHash,
initPoolTxHash,
chain,
updatePool,
poolType,
} = usePoolCreationStore();
const { step, tokenConfigs, clearPoolStore, createPoolTxHash, swapTxHash, initPoolTxHash, chain } =
usePoolCreationStore();
const { clearUserData } = useUserDataStore();
const { data: boostableWhitelist } = useBoostableWhitelist();

const publicClient = usePublicClient();
const { mutate: createPool, isPending: isCreatePoolPending, error: createPoolError } = useCreatePool();
const { mutate: multiSwap, isPending: isMultiSwapPending, error: multiSwapError } = useMultiSwap();
const {
mutate: initializePool,
isPending: isInitializePoolPending,
error: initializePoolError,
} = useInitializePool();
const { isLoadingTxReceipt } = useWaitForTransactionReceipt(); // Fetches tx from tx hash if user disconnects during pending tx state

const poolDeploymentUrl = createPoolTxHash ? getBlockExplorerTxLink(chain?.id, createPoolTxHash) : undefined;
const poolInitializationUrl = initPoolTxHash ? getBlockExplorerTxLink(chain?.id, initPoolTxHash) : undefined;
Expand All @@ -61,7 +46,7 @@ export function PoolCreationManager({ setIsModalOpen }: { setIsModalOpen: (isOpe
label: "Deploy Pool",
blockExplorerUrl: poolDeploymentUrl,
onSubmit: createPool,
isPending: isCreatePoolPending || isCreatePoolReceiptLoading,
isPending: isCreatePoolPending || isLoadingTxReceipt,
error: createPoolError,
});

Expand Down Expand Up @@ -118,7 +103,7 @@ export function PoolCreationManager({ setIsModalOpen }: { setIsModalOpen: (isOpe
const initializeStep = createTransactionStep({
label: "Initialize Pool",
onSubmit: initializePool,
isPending: isInitializePoolPending || isInitPoolReceiptLoading,
isPending: isInitializePoolPending || isLoadingTxReceipt,
error: initializePoolError,
blockExplorerUrl: poolInitializationUrl,
});
Expand All @@ -131,49 +116,6 @@ export function PoolCreationManager({ setIsModalOpen }: { setIsModalOpen: (isOpe
initializeStep,
];

// If user disconnects during pending tx state, this will look up tx receipt based on tx hash saved in local storage immediately after tx is sent (i.e. right after button is clicked)
useEffect(() => {
async function getTxReceipt() {
if (!publicClient || !createPoolTxHash || poolType === undefined || step !== 1) return;
setIsCreatePoolReceiptLoading(true);
try {
const txReceipt = await publicClient.waitForTransactionReceipt({ hash: createPoolTxHash });
const logs = parseEventLogs({
abi: poolFactoryAbi[poolType],
logs: txReceipt.logs,
});
if (logs.length > 0 && "args" in logs[0] && "pool" in logs[0].args) {
const newPool = logs[0].args.pool;
updatePool({ poolAddress: newPool, step: 2 });
} else {
throw new Error("Expected pool address not found in event logs");
}
} catch (error) {
console.error("Error getting create pool transaction receipt:", error);
} finally {
setIsCreatePoolReceiptLoading(false);
}
}
getTxReceipt();
}, [createPoolTxHash, publicClient, poolType, updatePool, step]);

// Handle edge case where user disconnects while pool init tx is pending
useEffect(() => {
async function getTxReceipt() {
if (!publicClient || !initPoolTxHash || step < poolCreationSteps.length) return;
try {
setIsInitPoolReceiptLoading(true);
const txReceipt = await publicClient.waitForTransactionReceipt({ hash: initPoolTxHash });
if (txReceipt.status === "success") updatePool({ step: poolCreationSteps.length + 1 });
} catch (error) {
console.error("Error getting init pool transaction receipt:", error);
} finally {
setIsInitPoolReceiptLoading(false);
}
}
getTxReceipt();
}, [initPoolTxHash, publicClient, updatePool, step]);

return (
<div className="fixed inset-0 bg-black bg-opacity-75 flex gap-7 justify-center items-center z-50">
<div
Expand Down
6 changes: 5 additions & 1 deletion packages/nextjs/hooks/scaffold-eth/useTransactor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ type TransactionFunc = (
/**
* Custom notification content for TXs.
*/
const TxnNotification = ({ message, blockExplorerLink }: { message: string; blockExplorerLink?: string }) => {
export const TxnNotification = ({ message, blockExplorerLink }: { message: string; blockExplorerLink?: string }) => {
return (
<div className={`flex flex-col ml-1 cursor-default`}>
<p className="my-0">{message}</p>
Expand Down Expand Up @@ -70,6 +70,10 @@ export const useTransactor = (_walletClient?: WalletClient): TransactionFunc =>
notificationId = notification.loading(
<TxnNotification message="Waiting for transaction to complete." blockExplorerLink={blockExplorerTxURL} />,
);
// Matt added this callback to save tx hash to local storage incase user disconnects while tx pending
if (options?.onTransactionHash && transactionHash) {
options.onTransactionHash(transactionHash);
}

const transactionReceipt = await publicClient.waitForTransactionReceipt({
hash: transactionHash,
Expand Down
1 change: 1 addition & 0 deletions packages/nextjs/hooks/v3/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ export * from "./useValidateHooksContract";
export * from "./useValidateRateProvider";
export * from "./useUserDataStore";
export * from "./useCheckIfV3PoolExists";
export * from "./useWaitForTransactionReceipt";
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
import { useMutation } from "@tanstack/react-query";
import { parseEventLogs, parseUnits, zeroAddress } from "viem";
import { usePublicClient, useWalletClient } from "wagmi";
// import { useTransactor } from "~~/hooks/scaffold-eth";
import { useTransactor } from "~~/hooks/scaffold-eth";
import { useBoostableWhitelist, usePoolCreationStore } from "~~/hooks/v3";

export const poolFactoryAbi = {
Expand All @@ -24,7 +24,7 @@ const TOKEN_WEIGHT_DECIMALS = 16;
export const useCreatePool = () => {
const { data: walletClient } = useWalletClient();
const publicClient = usePublicClient();
// const writeTx = useTransactor();
const writeTx = useTransactor();

const {
tokenConfigs,
Expand Down Expand Up @@ -91,17 +91,19 @@ export const useCreatePool = () => {
const input = createPoolInput(poolType);
const call = createPool.buildCall(input);

// Not using transactor here because we need to immediately save tx hash to local storage for zen dragon edge case
// He sends transactions with low gas limit which means they stay pending for hours
const hash = await walletClient.sendTransaction({
account: walletClient.account,
data: call.callData,
to: call.to,
});
const hash = await writeTx(
() =>
walletClient.sendTransaction({
account: walletClient.account,
data: call.callData,
to: call.to,
}),
{
onTransactionHash: txHash => updatePool({ createPoolTxHash: txHash }),
},
);
if (!hash) throw new Error("Failed to generate pool creation transaction hash");

updatePool({ createPoolTxHash: hash });

const txReceipt = await publicClient.waitForTransactionReceipt({ hash });
const logs = parseEventLogs({
abi: poolFactoryAbi[poolType],
Expand Down
11 changes: 6 additions & 5 deletions packages/nextjs/hooks/v3/useInitializePool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ import { BALANCER_ROUTER, InitPool, InitPoolDataProvider, InitPoolInput, balance
import { useMutation } from "@tanstack/react-query";
import { getContract, parseUnits } from "viem";
import { usePublicClient, useWalletClient } from "wagmi";
// import { useTransactor } from "~~/hooks/scaffold-eth";
import { useTransactor } from "~~/hooks/scaffold-eth";
import { useBoostableWhitelist, usePoolCreationStore } from "~~/hooks/v3";
import { createPermit2 } from "~~/utils/permit2Helper";

export const useInitializePool = () => {
const { data: walletClient } = useWalletClient();
const publicClient = usePublicClient();
// const writeTx = useTransactor();
const writeTx = useTransactor();
const chainId = publicClient?.chain.id;
const rpcUrl = publicClient?.transport.transports[0].value.url;
const protocolVersion = 3;
Expand Down Expand Up @@ -78,14 +78,15 @@ export const useInitializePool = () => {
console.log("router.permitBatchAndCall args for initialize pool", args);

// Execute the transaction
const hash = await router.write.permitBatchAndCall(args);
const hash = await writeTx(() => router.write.permitBatchAndCall(args), {
onTransactionHash: txHash => updatePool({ initPoolTxHash: txHash }),
});
if (!hash) throw new Error("No pool initialization transaction hash");
updatePool({ initPoolTxHash: hash });

// Move the step forward if the transaction is successful
const txReceipt = await publicClient.waitForTransactionReceipt({ hash });
if (txReceipt.status === "success") {
updatePool({ step: step + 1 });
updatePool({ step: step + 1, hasBeenInitialized: true });
}

return hash;
Expand Down
2 changes: 2 additions & 0 deletions packages/nextjs/hooks/v3/usePoolCreationStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export interface PoolCreationStore {
createPoolTxHash: `0x${string}` | undefined;
swapTxHash: `0x${string}` | undefined;
initPoolTxHash: `0x${string}` | undefined;
hasBeenInitialized: boolean;
updatePool: (updates: Partial<PoolCreationStore>) => void;
updateTokenConfig: (index: number, updates: Partial<TokenConfig>) => void;
clearPoolStore: () => void;
Expand Down Expand Up @@ -88,6 +89,7 @@ export const initialPoolCreationState = {
createPoolTxHash: undefined,
initPoolTxHash: undefined,
swapTxHash: undefined,
hasBeenInitialized: false,
};

// Stores all the data that will be used for pool creation
Expand Down
Loading

0 comments on commit 7222401

Please sign in to comment.