Skip to content

Commit

Permalink
Merge pull request #31 from balancer/improve-safe-support
Browse files Browse the repository at this point in the history
Improve safe support
  • Loading branch information
MattPereira authored Jan 16, 2025
2 parents 9b79041 + b4c85ae commit d84be27
Show file tree
Hide file tree
Showing 23 changed files with 499 additions and 270 deletions.
27 changes: 14 additions & 13 deletions packages/nextjs/app/cow/_components/PoolConfiguration.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -157,27 +157,28 @@ 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(),
symbol: poolSymbol.trim(),
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 },
});
}}
/>
Expand Down
266 changes: 108 additions & 158 deletions packages/nextjs/app/cow/_components/PoolCreation.tsx

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down
8 changes: 6 additions & 2 deletions packages/nextjs/hooks/cow/index.ts
Original file line number Diff line number Diff line change
@@ -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";
66 changes: 66 additions & 0 deletions packages/nextjs/hooks/cow/useApproveTokenTxHash.ts
Original file line number Diff line number Diff line change
@@ -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)),
});
}
21 changes: 18 additions & 3 deletions packages/nextjs/hooks/cow/useBindToken.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { usePoolCreationStore } from "./usePoolCreationStore";
import { useMutation } from "@tanstack/react-query";
import { Address } from "viem";
import { usePublicClient, useWalletClient } from "wagmi";
Expand All @@ -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");
Expand All @@ -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 });
}
},
});

Expand Down
50 changes: 50 additions & 0 deletions packages/nextjs/hooks/cow/useBindTokenTxHash.ts
Original file line number Diff line number Diff line change
@@ -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)),
});
}
5 changes: 4 additions & 1 deletion packages/nextjs/hooks/cow/useCreatePool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
55 changes: 55 additions & 0 deletions packages/nextjs/hooks/cow/useCreatePoolTxHash.ts
Original file line number Diff line number Diff line change
@@ -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)),
});
}
46 changes: 0 additions & 46 deletions packages/nextjs/hooks/cow/useFetchPoolAddress.ts

This file was deleted.

11 changes: 7 additions & 4 deletions packages/nextjs/hooks/cow/useFinalizePool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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!");
Expand All @@ -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;
Expand Down
Loading

0 comments on commit d84be27

Please sign in to comment.