Skip to content

Commit

Permalink
Add support for weighted pool to FE
Browse files Browse the repository at this point in the history
  • Loading branch information
MattPereira committed Aug 24, 2024
1 parent 62d3e13 commit 1f199ec
Show file tree
Hide file tree
Showing 8 changed files with 296 additions and 181 deletions.
4 changes: 2 additions & 2 deletions packages/foundry/script/03_DeployWeighted.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ contract DeployWeighted is PoolHelpers, ScaffoldHelpers {
false, // bool disableUnbalancedLiquidity
keccak256(abi.encode(block.number)) // bytes32 salt
);
console.log("Constant Product Pool deployed at: %s", pool);
console.log("Weighted Pool deployed at: %s", pool);

// Approve Permit2 contract to spend tokens on behalf of deployer
approveSpenderOnToken(address(permit2), initConfig.tokens);
Expand All @@ -74,7 +74,7 @@ contract DeployWeighted is PoolHelpers, ScaffoldHelpers {
initConfig.wethIsEth,
initConfig.userData
);
console.log("Constant Product Pool initialized successfully!");
console.log("Weighted Pool initialized successfully!");
vm.stopBroadcast();
}

Expand Down
2 changes: 1 addition & 1 deletion packages/nextjs/app/pools/_components/PoolActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useAccount } from "wagmi";
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
import { useTokens } from "~~/hooks/balancer";
import { type Pool, type TokenBalances } from "~~/hooks/balancer/types";
import { type RefetchPool } from "~~/hooks/balancer/usePoolContract";
import { type RefetchPool } from "~~/hooks/balancer/useReadPool";
import { useAccountBalance, useScaffoldContractWrite } from "~~/hooks/scaffold-eth";

type Action = "Swap" | "AddLiquidity" | "RemoveLiquidity";
Expand Down
301 changes: 130 additions & 171 deletions packages/nextjs/app/pools/_components/PoolSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,189 +1,148 @@
import { useEffect, useState } from "react";
import { Dispatch, SetStateAction, useState } from "react";
import { usePathname, useRouter } from "next/navigation";
import { blo } from "blo";
import { type Address, isAddress } from "viem";
import { MagnifyingGlassIcon } from "@heroicons/react/24/outline";
import { useScaffoldEventHistory, useScaffoldEventSubscriber } from "~~/hooks/scaffold-eth";
import { useFactoryHistory } from "~~/hooks/balancer";

// TODO: Figure out if this can be fetched from etherscan with contract address?
const FROM_BLOCK_NUMBER = 6278000n;

/**
* The dropdown selector for internal custom pool and the external pool address input
*/
export const PoolSelector = ({
setSelectedPoolAddress,
selectedPoolAddress,
}: {
setSelectedPoolAddress: (_: Address) => void;
type PoolSelectorProps = {
setSelectedPoolAddress: Dispatch<SetStateAction<string | null>>;
selectedPoolAddress: Address | null;
}) => {
};

export const PoolSelector = ({ setSelectedPoolAddress, selectedPoolAddress }: PoolSelectorProps) => {
const [inputValue, setInputValue] = useState<string>("");
const [sumPools, setSumPools] = useState<Address[]>([]);
const [productPools, setProductPools] = useState<Address[]>([]);

const router = useRouter();
const pathname = usePathname();
const isValidAddress = isAddress(inputValue);
const { sumPools, productPools, weightedPools } = useFactoryHistory();

// Fetches the history of pools deployed via factory
const { data: sumPoolHistory, isLoading: isLoadingSumPoolHistory } = useScaffoldEventHistory({
contractName: "ConstantSumFactory",
eventName: "PoolCreated",
fromBlock: FROM_BLOCK_NUMBER,
});
const poolTypes = [
{ label: "Constant Sum", addresses: sumPools },
{ label: "Constant Product", addresses: productPools },
{ label: "Weighted", addresses: weightedPools },
];

const { data: productPoolHistory, isLoading: isLoadingProductPoolHistory } = useScaffoldEventHistory({
contractName: "ConstantProductFactory",
eventName: "PoolCreated",
fromBlock: FROM_BLOCK_NUMBER,
});
return (
<section className="mb-7">
<SearchBar
inputValue={inputValue}
setInputValue={setInputValue}
setSelectedPoolAddress={setSelectedPoolAddress}
/>
<div className="flex flex-wrap justify-center gap-3 mt-4">
{poolTypes.map(
({ label, addresses }) =>
addresses.length > 0 &&
addresses.map(address => (
<PoolSelectButton
key={address}
label={label}
address={address}
setInputValue={setInputValue}
selectedPoolAddress={selectedPoolAddress}
setSelectedPoolAddress={setSelectedPoolAddress}
/>
)),
)}
</div>
</section>
);
};

type SearchBarProps = {
setSelectedPoolAddress: (_: Address) => void;
inputValue: string;
setInputValue: Dispatch<SetStateAction<string>>;
};

// Adds pools deployed using the factories
useScaffoldEventSubscriber({
contractName: "ConstantSumFactory",
eventName: "PoolCreated",
listener: logs => {
logs.forEach(log => {
const { pool } = log.args;
if (pool) {
setSumPools(pools => [...pools, pool]);
}
});
},
});
const SearchBar = ({ setSelectedPoolAddress, inputValue, setInputValue }: SearchBarProps) => {
const router = useRouter();
const pathname = usePathname();
const isValidAddress = isAddress(inputValue);

// Adds pools deployed using the factories
useScaffoldEventSubscriber({
contractName: "ConstantProductFactory",
eventName: "PoolCreated",
listener: logs => {
logs.forEach(log => {
const { pool } = log.args;
if (pool) {
setProductPools(pools => [...pools, pool]);
}
});
},
});
return (
<div className="flex justify-center flex-wrap gap-5 w-full items-center text-xl">
<form
className="flex flex-wrap items-center gap-5"
onSubmit={event => {
event.preventDefault();
setSelectedPoolAddress(inputValue);
router.push(`${pathname}?address=${inputValue}`);
setInputValue("");
}}
>
<div className="relative">
{inputValue && (
// eslint-disable-next-line @next/next/no-img-element
<img
alt=""
className="!rounded-full absolute top-1 left-1"
src={blo(inputValue as `0x${string}`)}
width="37"
height="37"
/>
)}
<input
value={inputValue}
onChange={e => setInputValue(e.target.value)}
className={`input input-bordered bg-base-200 text-center h-[44px] w-[355px] sm:w-[550px] ${
inputValue && "pl-10 pr-14"
}`}
placeholder="Search by pool addresss"
/>
<button
className={`btn btn-sm bg-neutral w-12 absolute top-1.5 right-1.5 border-none ${
isValidAddress ? "bg-violet-400 hover:bg-violet-400" : ""
}`}
type="submit"
disabled={!isValidAddress}
>
<MagnifyingGlassIcon className="h-5 w-5" />
</button>
</div>
</form>
</div>
);
};

useEffect(() => {
if (!isLoadingSumPoolHistory && !isLoadingProductPoolHistory && sumPoolHistory && productPoolHistory) {
const sumPools = sumPoolHistory
.map(({ args }) => {
if (args.pool && isAddress(args.pool)) return args.pool;
})
.filter((pool): pool is Address => typeof pool === "string");
type PoolSelectButtonProps = PoolSelectorProps & {
address: Address;
label: string;
setInputValue: Dispatch<SetStateAction<string>>;
};

const productPools = productPoolHistory
.map(({ args }) => {
if (args.pool && isAddress(args.pool)) return args.pool;
})
.filter((pool): pool is Address => typeof pool === "string");
setProductPools(productPools);
setSumPools(sumPools);
}
}, [sumPoolHistory, productPoolHistory, isLoadingSumPoolHistory, isLoadingProductPoolHistory]);
const PoolSelectButton = ({
selectedPoolAddress,
setSelectedPoolAddress,
address,
label,
setInputValue,
}: PoolSelectButtonProps) => {
const router = useRouter();
const pathname = usePathname();

return (
<section className="mb-7">
<div className="flex flex-wrap justify-center gap-3 h-12">
{sumPools.length > 0 &&
sumPools.map(pool => {
console.log("selectedPoolAddress", selectedPoolAddress);
console.log("pool", pool);
return (
<button
key={pool}
className={`btn btn-sm btn-secondary flex relative pl-[35px] border-none font-normal text-lg ${
selectedPoolAddress === pool
? " text-neutral-700 bg-gradient-to-b from-custom-beige-start to-custom-beige-end to-100%"
: ""
}`}
onClick={() => {
setSelectedPoolAddress(pool);
router.push(`${pathname}?address=${pool}`);
}}
>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
alt=""
className={`!rounded-full absolute top-0.5 left-1 `}
src={blo(pool as `0x${string}`)}
width="25"
height="25"
/>
Constant Sum
</button>
);
})}
{productPools.length > 0 &&
productPools.map(pool => (
<button
key={pool}
className={`btn btn-sm btn-secondary flex relative pl-[35px] border-none font-normal text-lg ${
selectedPoolAddress === pool
? "text-neutral-700 bg-gradient-to-b from-custom-beige-start to-custom-beige-end to-100%"
: ""
}`}
onClick={() => {
setSelectedPoolAddress(pool);
router.push(`${pathname}?address=${pool}`);
}}
>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
alt=""
className={`!rounded-full absolute top-0.5 left-1 `}
src={blo(pool as `0x${string}`)}
width="25"
height="25"
/>
Constant Product
</button>
))}
</div>
<div className="flex justify-center flex-wrap gap-5 w-full items-center text-xl">
<form
className="flex flex-wrap items-center gap-5"
onSubmit={event => {
event.preventDefault();
setSelectedPoolAddress(inputValue);
router.push(`${pathname}?address=${inputValue}`);
setInputValue("");
}}
>
<div className="relative">
{inputValue && (
// eslint-disable-next-line @next/next/no-img-element
<img
alt=""
className="!rounded-full absolute top-1 left-1"
src={blo(inputValue as `0x${string}`)}
width="37"
height="37"
/>
)}
<input
value={inputValue}
onChange={e => setInputValue(e.target.value)}
className={`input input-bordered bg-base-200 text-center h-[44px] w-[355px] sm:w-[550px] ${
inputValue && "pl-10 pr-14"
}`}
placeholder="Search by pool addresss"
/>
<button
className={`btn btn-sm bg-neutral w-12 absolute top-1.5 right-1.5 border-none ${
isValidAddress ? "bg-violet-400 hover:bg-violet-400" : ""
}`}
type="submit"
disabled={!isValidAddress}
>
<MagnifyingGlassIcon className="h-5 w-5" />
</button>
</div>
</form>
</div>
</section>
<button
key={address}
className={`btn btn-sm btn-secondary flex relative pl-[35px] border-none font-normal text-lg ${
selectedPoolAddress === address
? " text-neutral-700 bg-gradient-to-b from-custom-beige-start to-custom-beige-end to-100%"
: ""
}`}
onClick={() => {
setSelectedPoolAddress(address);
setInputValue(address);
router.push(`${pathname}?address=${address}`);
}}
>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
alt=""
className={`!rounded-full absolute top-0.5 left-1 `}
src={blo(address as `0x${string}`)}
width="25"
height="25"
/>
{label}
</button>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ interface PoolActionButtonProps {
*/
export const PoolActionButton: React.FC<PoolActionButtonProps> = ({ onClick, children, isDisabled, isFormEmpty }) => {
const outlined = `border border-base-100 hover:bg-base-100`;
const gradient = `bg-gradient-to-r from-violet-400 via-orange-100 to-orange-300 hover:from-violet-300 hover:via-orange-100 hover:to-orange-400 text-neutral-700 `;
const gradient = `shadow-md bg-gradient-to-r from-violet-400 via-orange-100 to-orange-300 hover:from-violet-300 hover:via-orange-100 hover:to-orange-400 text-neutral-700 `;

const colorStyles =
children === "Approve" ? outlined : isFormEmpty ? `bg-neutral-400 opacity-70 text-white` : gradient;
Expand Down
6 changes: 3 additions & 3 deletions packages/nextjs/app/pools/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ import {
import { type NextPage } from "next";
import { type Address } from "viem";
import { SkeletonLoader } from "~~/components/common";
import { usePoolContract } from "~~/hooks/balancer";
import { useReadPool } from "~~/hooks/balancer";
import { type Pool } from "~~/hooks/balancer/types";
import { type RefetchPool } from "~~/hooks/balancer/usePoolContract";
import { type RefetchPool } from "~~/hooks/balancer/useReadPool";

/**
* 1. Search by pool address or select from dropdown
Expand Down Expand Up @@ -45,7 +45,7 @@ export default Pools;
const PoolPageContent = () => {
const [selectedPoolAddress, setSelectedPoolAddress] = useState<Address | null>(null);

const { data: pool, refetch: refetchPool, isLoading, isError, isSuccess } = usePoolContract(selectedPoolAddress);
const { data: pool, refetch: refetchPool, isLoading, isError, isSuccess } = useReadPool(selectedPoolAddress);

const searchParams = useSearchParams();
const poolAddress = searchParams.get("address");
Expand Down
3 changes: 2 additions & 1 deletion packages/nextjs/hooks/balancer/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export * from "./usePoolContract";
export * from "./useReadPool";
export * from "./types";
export * from "./useSwap";
export * from "./useAddLiquidity";
Expand All @@ -7,3 +7,4 @@ export * from "./useTargetFork";
export * from "./useToken";
export * from "./useTokens";
export * from "./useApprove";
export * from "./useFactoryHistory";
Loading

0 comments on commit 1f199ec

Please sign in to comment.