Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

General Housekeeping #44

Merged
merged 4 commits into from
Jun 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ ETHERSCAN_API_KEY=...
SEPOLIA_RPC_URL=...
```

- The `DEPLOYER_PRIVATE_KEY` must start with `0x` and will be referred to as 'DEPLOYER' throughout this README.
- The `DEPLOYER_PRIVATE_KEY` must start with `0x` and will be referred to as 'DEPLOYER' throughout this README.
- The `ETHERSCAN_API_KEY` is used to conveniently verify contracts from the command line with `yarn verify`
- The `SEPOLIA_RPC_URL` facilitates running a local fork and sending transactions to sepolia testnet

Expand Down Expand Up @@ -171,12 +171,14 @@ const scaffoldConfig = {

<details><summary><strong>0.3.5 Changing The Forked Network</strong></summary>

You can modify the `"fork"` alias in the `packages/foundry/package.json` file, but do not change the chain id. By default, the `yarn fork` command uses sepolia, but any of the network aliases from the `[rpc_endpoints]` of `foundry.toml` can be used
By default, the `yarn fork` command points at sepolia, but any of the network aliases from the `[rpc_endpoints]` of `foundry.toml` can be used to modify the `"fork"` alias in the `packages/foundry/package.json` file

```json
"fork": "anvil --fork-url ${0:-sepolia} --chain-id 31337 --config-out localhost.json",
```

To point the frontend at a different forked network, simply change the `targetFork` in `scaffold.config.ts`

</details>

---
Expand All @@ -201,23 +203,23 @@ https://github.com/Dev-Rel-as-a-Service/scaffold-balancer-v3/assets/73561520/cc3

### 🚰 1.2 Use Your Pool

Connect the account you specified in the `.env` file using your favorite wallet extension and start splashing around in your pool with swaps, joins, and exits!
Connect the account you specified in the `.env` file using your favorite wallet extension and start splashing around in your pool by swapping, adding liquidity, and removing liquidity!

<details><summary><strong>👀 Swap Preview</strong></summary>

![Swap](https://github.com/Dev-Rel-as-a-Service/scaffold-balancer-v3/assets/73561520/64629016-5bb5-40ce-a3bd-2e421000b33d)

</details>

<details><summary><strong>👀 Join Preview</strong></summary>
<details><summary><strong>👀 Add Liquidity Preview</strong></summary>

![Join](https://github.com/Dev-Rel-as-a-Service/scaffold-balancer-v3/assets/73561520/cf8dc531-d98b-49fa-9195-ec86d7018e09)
![Add Liquidity](https://github.com/Dev-Rel-as-a-Service/scaffold-balancer-v3/assets/73561520/cf8dc531-d98b-49fa-9195-ec86d7018e09)

</details>

<details><summary><strong>👀 Exit Preview</strong></summary>
<details><summary><strong>👀 Remove Liquidity Preview</strong></summary>

![Exit](https://github.com/Dev-Rel-as-a-Service/scaffold-balancer-v3/assets/73561520/3604dbfb-fea2-414f-8e62-c01dc12cc691)
![Remove Liquidity](https://github.com/Dev-Rel-as-a-Service/scaffold-balancer-v3/assets/73561520/3604dbfb-fea2-414f-8e62-c01dc12cc691)

</details>

Expand Down
10 changes: 5 additions & 5 deletions packages/nextjs/app/pools/_components/PoolActions.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
import { useState } from "react";
import { ExitForm, JoinForm, SwapForm } from "./actions";
import { AddLiquidityForm, RemoveLiquidityForm, SwapForm } from "./actions";
import { type Pool } from "~~/hooks/balancer/types";
import { type RefetchPool } from "~~/hooks/balancer/usePoolContract";

type Action = "Swap" | "Join" | "Exit";
type Action = "Swap" | "AddLiquidity" | "RemoveLiquidity";

export interface PoolActionsProps {
pool: Pool;
refetchPool: RefetchPool;
}

/**
* Allow user to perform swap, join, and exit transactions with a pool
* Allow user to swap, add liquidity, and remove liquidity from a pool
*/
export const PoolActions: React.FC<PoolActionsProps> = ({ pool, refetchPool }) => {
const [activeTab, setActiveTab] = useState<Action>("Swap");

const tabs = {
Swap: <SwapForm pool={pool} refetchPool={refetchPool} />,
Join: <JoinForm pool={pool} refetchPool={refetchPool} />,
Exit: <ExitForm pool={pool} refetchPool={refetchPool} />,
AddLiquidity: <AddLiquidityForm pool={pool} refetchPool={refetchPool} />,
RemoveLiquidity: <RemoveLiquidityForm pool={pool} refetchPool={refetchPool} />,
};

return (
Expand Down
2 changes: 1 addition & 1 deletion packages/nextjs/app/pools/_components/PoolComposition.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const PoolComposition = ({ pool }: { pool: Pool }) => {

<div className="border border-base-100 rounded-lg">
<div className="p-3 flex flex-col gap-3">
{pool.poolTokens.map((token: any) => (
{pool.poolTokens.map(token => (
<div key={token.address} className="flex justify-between items-center">
<div>
<div className="font-bold">{token.symbol}</div>
Expand Down
18 changes: 10 additions & 8 deletions packages/nextjs/app/pools/_components/PoolSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { useScaffoldEventHistory, useScaffoldEventSubscriber } from "~~/hooks/sc
export const PoolSelector = ({ setSelectedPoolAddress }: { setSelectedPoolAddress: (_: Address) => void }) => {
const [inputValue, setInputValue] = useState<string>("");
const [isOpen, setIsOpen] = useState<boolean>(false);
const [createdPools, setCreatedPools] = useState<any[]>([]);
const [createdPools, setCreatedPools] = useState<Address[]>([]);

const router = useRouter();
const pathname = usePathname();
Expand All @@ -30,20 +30,22 @@ export const PoolSelector = ({ setSelectedPoolAddress }: { setSelectedPoolAddres
eventName: "PoolCreated",
listener: logs => {
logs.forEach(log => {
console.log("PoolCreated Event!", log);
const { pool } = log.args;
setCreatedPools(pools => [...pools, pool]);
if (pool) {
setCreatedPools(pools => [...pools, pool]);
}
});
},
});

useEffect(() => {
if (!createdPools?.length && !!eventsHistory?.length && !isLoadingEventsHistory) {
setCreatedPools(
eventsHistory.map(({ args }) => {
return args.pool;
}) || [],
);
const pools = eventsHistory
.map(({ args }) => {
if (args.pool && isAddress(args.pool)) return args.pool;
})
.filter((pool): pool is string => typeof pool === "string");
setCreatedPools(pools);
}
}, [createdPools.length, eventsHistory, isLoadingEventsHistory]);

Expand Down
12 changes: 6 additions & 6 deletions packages/nextjs/app/pools/_components/UserLiquidity.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useEffect, useState } from "react";
import { type TokenAmount } from "@balancer/sdk";
import { useAccount } from "wagmi";
import { useExit } from "~~/hooks/balancer/";
import { useRemoveLiquidity } from "~~/hooks/balancer/";
import { type Pool } from "~~/hooks/balancer/types";
import { formatToHuman } from "~~/utils/formatToHuman";

Expand All @@ -12,20 +12,20 @@ export const UserLiquidity = ({ pool }: { pool: Pool }) => {
const [expectedAmountsOut, setExpectedAmountsOut] = useState<TokenAmount[] | undefined>();

const { isConnected } = useAccount();
const { queryExit } = useExit(pool);
const { queryRemoveLiquidity } = useRemoveLiquidity(pool);

useEffect(() => {
async function fetchExitQuery() {
async function sendQuery() {
if (pool.userBalance > 0n) {
const { expectedAmountsOut } = await queryExit(pool.userBalance);
const { expectedAmountsOut } = await queryRemoveLiquidity(pool.userBalance);
setExpectedAmountsOut(expectedAmountsOut);
} else {
setExpectedAmountsOut(undefined);
}
}
fetchExitQuery();
sendQuery();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [pool.userBalance]); // excluded queryExit from deps array because it causes infinite re-renders
}, [pool.userBalance]); // excluded queryRemoveLiquidity from deps array because it causes infinite re-renders

// only render the component if the pool is initialized and the user is connected
if (!isConnected || !pool?.poolConfig?.isPoolInitialized) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,37 @@ import { InputAmount } from "@balancer/sdk";
import { formatUnits, parseAbi, parseUnits } from "viem";
import { useContractEvent, usePublicClient, useWalletClient } from "wagmi";
import abis from "~~/contracts/abis";
import { useJoin } from "~~/hooks/balancer/";
import { QueryJoinResponse, QueryPoolActionError } from "~~/hooks/balancer/types";
import { useAddLiquidity } from "~~/hooks/balancer/";
import { PoolActionReceipt, QueryAddLiquidityResponse, QueryPoolActionError, TokenInfo } from "~~/hooks/balancer/types";
import { useTransactor } from "~~/hooks/scaffold-eth";
import { formatToHuman } from "~~/utils/formatToHuman";

/**
* 1. Query the results of join transaction
* 2. User approves the vault for the tokens used in the join transaction (if necessary)
* 3. User sends transaction to join the pool
* 1. Query adding some amount of liquidity to the pool
* 2. Approve the Balancer vault to spend the tokens to be used in the transaction (if necessary)
* 3. Send transaction to add liquidity to the pool
* 4. Display the transaction results to the user
*/
export const JoinForm: React.FC<PoolActionsProps> = ({ pool, refetchPool }) => {
export const AddLiquidityForm: React.FC<PoolActionsProps> = ({ pool, refetchPool }) => {
const initialTokenInputs = pool.poolTokens.map(token => ({
address: token.address as `0x${string}`,
decimals: token.decimals,
rawAmount: 0n,
}));
const [tokensToApprove, setTokensToApprove] = useState<InputAmount[]>([]);
const [tokenInputs, setTokenInputs] = useState<InputAmount[]>(initialTokenInputs);
const [queryResponse, setQueryResponse] = useState<QueryJoinResponse | null>(null);
const [queryResponse, setQueryResponse] = useState<QueryAddLiquidityResponse | null>(null);
const [sufficientAllowances, setSufficientAllowances] = useState(false);
const [queryError, setQueryError] = useState<QueryPoolActionError>();
const [isApproving, setIsApproving] = useState(false);
const [isQuerying, setIsQuerying] = useState(false);
const [isJoining, setIsJoining] = useState(false);
const [joinReceipt, setJoinReceipt] = useState<any>(null);
const [isAddingLiquidity, setIsAddingLiquidity] = useState(false);
const [addLiquidityReceipt, setAddLiquidityReceipt] = useState<PoolActionReceipt>(null);

const { queryJoin, joinPool, allowances, refetchAllowances, tokenBalances } = useJoin(pool, tokenInputs);
const { queryAddLiquidity, addLiquidity, allowances, refetchAllowances, balances } = useAddLiquidity(
pool,
tokenInputs,
);
const writeTx = useTransactor(); // scaffold hook for tx status toast notifications
const publicClient = usePublicClient();
const { data: walletClient } = useWalletClient();
Expand All @@ -41,7 +45,7 @@ export const JoinForm: React.FC<PoolActionsProps> = ({ pool, refetchPool }) => {
async function determineTokensToApprove() {
if (allowances) {
const tokensNeedingApproval = tokenInputs.filter((token, index) => {
const allowance = BigInt((allowances[index]?.result as string) || "0");
const allowance = allowances[index] || 0n;
return allowance < token.rawAmount;
});
setTokensToApprove(tokensNeedingApproval);
Expand All @@ -59,7 +63,7 @@ export const JoinForm: React.FC<PoolActionsProps> = ({ pool, refetchPool }) => {
const handleInputChange = (index: number, value: string) => {
setQueryError(null);
setQueryResponse(null);
setJoinReceipt(null);
setAddLiquidityReceipt(null);
const updatedTokens = tokenInputs.map((token, idx) => {
if (idx === index) {
return { ...token, rawAmount: parseUnits(value, token.decimals) };
Expand All @@ -69,11 +73,11 @@ export const JoinForm: React.FC<PoolActionsProps> = ({ pool, refetchPool }) => {
setTokenInputs(updatedTokens);
};

const handleQueryJoin = async () => {
const handlequeryAddLiquidity = async () => {
setQueryResponse(null);
setJoinReceipt(null);
setAddLiquidityReceipt(null);
setIsQuerying(true);
const response = await queryJoin();
const response = await queryAddLiquidity();
if (response.error) {
setQueryError(response.error);
} else {
Expand Down Expand Up @@ -110,16 +114,16 @@ export const JoinForm: React.FC<PoolActionsProps> = ({ pool, refetchPool }) => {
});
};

const handleJoinPool = async () => {
const handleAddLiquidity = async () => {
try {
setIsJoining(true);
await joinPool();
setIsAddingLiquidity(true);
await addLiquidity();
refetchAllowances();
refetchPool();
} catch (e) {
console.error("error", e);
} finally {
setIsJoining(false);
setIsAddingLiquidity(false);
}
};

Expand All @@ -128,14 +132,13 @@ export const JoinForm: React.FC<PoolActionsProps> = ({ pool, refetchPool }) => {
abi: abis.balancer.Pool,
eventName: "Transfer",
listener(log: any[]) {
const bptOut = {
const data: TokenInfo = {
symbol: pool.symbol,
name: pool.name,
decimals: pool.decimals,
rawAmount: log[0].args.value,
};

setJoinReceipt({ bptOut, transactionHash: log[0].transactionHash });
setAddLiquidityReceipt({ data: [data], transactionHash: log[0].transactionHash });
},
});

Expand All @@ -153,15 +156,15 @@ export const JoinForm: React.FC<PoolActionsProps> = ({ pool, refetchPool }) => {
formatUnits(token.rawAmount, token.decimals) === "0" ? "" : formatUnits(token.rawAmount, token.decimals)
}
onAmountChange={value => handleInputChange(index, value)}
allowance={allowances && formatToHuman((allowances[index].result as bigint) || 0n, token.decimals)}
balance={tokenBalances && formatToHuman((tokenBalances[index].result as bigint) || 0n, token.decimals)}
allowance={allowances && formatToHuman(allowances[index] || 0n, token.decimals)}
balance={balances && formatToHuman(balances[index] || 0n, token.decimals)}
/>
))}
</div>

{!expectedBptOut || (expectedBptOut && joinReceipt) ? (
{!expectedBptOut || (expectedBptOut && addLiquidityReceipt) ? (
<PoolActionButton
onClick={handleQueryJoin}
onClick={handlequeryAddLiquidity}
isDisabled={isQuerying}
isFormEmpty={tokenInputs.every(token => token.rawAmount === 0n)}
>
Expand All @@ -172,16 +175,16 @@ export const JoinForm: React.FC<PoolActionsProps> = ({ pool, refetchPool }) => {
Approve
</PoolActionButton>
) : (
<PoolActionButton isDisabled={isJoining} onClick={handleJoinPool}>
<PoolActionButton isDisabled={isAddingLiquidity} onClick={handleAddLiquidity}>
Add Liquidity
</PoolActionButton>
)}

{joinReceipt && (
{addLiquidityReceipt && (
<TransactionReceiptAlert
title="Actual BPT Out"
transactionHash={joinReceipt.transactionHash}
data={[joinReceipt.bptOut]}
transactionHash={addLiquidityReceipt.transactionHash}
data={addLiquidityReceipt.data}
/>
)}

Expand Down
9 changes: 2 additions & 7 deletions packages/nextjs/app/pools/_components/actions/Alerts.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { usePublicClient } from "wagmi";
import { ArrowTopRightOnSquareIcon } from "@heroicons/react/24/outline";
import { type TokenInfo } from "~~/hooks/balancer/types";
import { formatToHuman } from "~~/utils/formatToHuman";
import { getBlockExplorerTxLink } from "~~/utils/scaffold-eth";

Expand Down Expand Up @@ -58,14 +59,8 @@ export const QueryErrorAlert: React.FC<{ message: string }> = ({ message }) => {
interface TransactionReceiptAlertProps {
transactionHash: string;
title: string;
data: tokenData[];
data: TokenInfo[];
}
type tokenData = {
symbol: string;
name: string;
rawAmount: bigint;
decimals: number;
};
/**
* Displays after successful pool operation transaction
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,8 @@ interface PoolActionButtonProps {
}

/**
* Styled button for approve and join/swap/exit transactions
*
* Approve button is outlined styles
* Join/Swap/Exit buttons are solid styles
* Approve button is outlined style
* Swap, AddLiquidity, and RemoveLiquidity buttons are solid gradient style
*/
export const PoolActionButton: React.FC<PoolActionButtonProps> = ({ onClick, children, isDisabled, isFormEmpty }) => {
const outlined = `border border-neutral hover:bg-neutral hover:text-neutral-content`;
Expand Down
Loading
Loading