diff --git a/.github/workflows/ci.protocol.yaml b/.github/workflows/ci.protocol.yaml index 75000ea74f..58cb6fba87 100644 --- a/.github/workflows/ci.protocol.yaml +++ b/.github/workflows/ci.protocol.yaml @@ -6,7 +6,27 @@ on: - "protocol/**" jobs: + format: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + ref: ${{ github.head_ref }} + - uses: actions/setup-node@v3 + with: + node-version: "18" + - run: yarn add prettier + - run: yarn add prettier-plugin-solidity + - run: shopt -s globstar; yarn prettier --write --config .prettierrc --plugin=prettier-plugin-solidity protocol/**/*.sol || true + - name: Commit changes + uses: stefanzweifel/git-auto-commit-action@v4 + with: + commit_message: prettier auto formatting changes + branch: ${{ github.head_ref }} + - name: check format + run: shopt -s globstar; yarn prettier --check --config .prettierrc --plugin=prettier-plugin-solidity protocol/**/*.sol test: + needs: format runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -25,12 +45,8 @@ jobs: - name: Install Dependencies if: steps.node-modules-cache.outputs.cache-hit != 'true' - run: yarn install --no-immutable - - - name: Generate (with cache) - id: generate-with-cache - continue-on-error: true - run: yarn generate + run: yarn install --immutable + - run: yarn generate working-directory: protocol - name: Clear cache and reinstall on generate failure diff --git a/.gitignore b/.gitignore index 0ff7f1713b..bcce178b1d 100644 --- a/.gitignore +++ b/.gitignore @@ -40,4 +40,6 @@ node_modules # Local Netlify folder .netlify +# forge libraries. +protocol/lib/ diff --git a/.gitmodules b/.gitmodules index fd650d3ed0..842b0f598d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ [submodule "protocol/lib/forge-std"] path = protocol/lib/forge-std url = https://github.com/foundry-rs/forge-std -[submodule "protocol/lib/solmate"] - path = protocol/lib/solmate - url = https://github.com/transmissions11/solmate diff --git a/.prettierrc b/.prettierrc index 43834780bb..d80ec5f738 100644 --- a/.prettierrc +++ b/.prettierrc @@ -3,7 +3,19 @@ "singleQuote": false, "semi": true, "trailingComma": "none", + "plugins": ["prettier-plugin-solidity"], "overrides": [ + { + "files": "*.sol", + "options": { + "parser": "solidity-parse", + "printWidth": 100, + "tabWidth": 4, + "useTabs": false, + "singleQuote": false, + "bracketSpacing": false + } + }, { "files": "projects/subgraph-*/**", "options": { diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000000..6ffa8c983b --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + "recommendations": [ + "streetsidesoftware.code-spell-checker", + "esbenp.prettier-vscode", + "juanblanco.solidity" + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000000..ac7aa8d29e --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "editor.formatOnSave": true, + "solidity.formatter": "prettier" +} \ No newline at end of file diff --git a/package.json b/package.json index b20e9d7273..83b3378217 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "jest": "29.2.2", "jest-serial-runner": "1.2.1", "lint-staged": "13.3.0", - "prettier": "3.2.5", + "prettier": "3.3.3", "ts-jest": "29.1.2", "ts-node": "10.9.2", "typescript": "5.3.3" @@ -42,13 +42,22 @@ "sdk:prettier": "yarn prettier projects/sdk -w", "sdk:publish": "yarn workspace @beanstalk/sdk publish", "sdk:version": "yarn workspace @beanstalk/sdk version", + "dex-ui:dev": "yarn workspace dex-ui dev", + "dex-ui:build": "yarn workspace dex-ui build", + "dex-ui:generate": "yarn workspace dex-ui generate", "ui:generate": "yarn workspace ui generate", + "ui:dev": "yarn workspace ui dev", "ui:start": "yarn workspace ui start", "ui:build": "yarn workspace ui build", "ui:test": "yarn workspace ui test", "test:browser": "yarn workspace tests test:browser", "ex": "yarn workspace @beanstalk/examples x", + "anvil-arbitrum": "yarn cli:anvil-arbitrum", + "anvil-eth-mainnet": "yarn cli:anvil-eth-mainnet", "anvil": "anvil --fork-url https://eth-mainnet.g.alchemy.com/v2/5ubn94zT7v7DnB5bNW1VOnoIbX5-AG2N --chain-id 1337", "anvil4tests": "anvil --fork-url https://eth-mainnet.g.alchemy.com/v2/Kk7ktCQL5wz4v4AG8bR2Gun8TAASQ-qi --chain-id 1337 --fork-block-number 18629000" + }, + "dependencies": { + "prettier-plugin-solidity": "1.4.1" } } diff --git a/projects/cli/.env.example b/projects/cli/.env.example new file mode 100644 index 0000000000..a900f24edb --- /dev/null +++ b/projects/cli/.env.example @@ -0,0 +1,5 @@ +# DEV API key +DEV_ALCHEMY_API_KEY="" + +# Test API key +DEV_TEST_ALCHEMY_API_KEY="" diff --git a/projects/cli/.gitignore b/projects/cli/.gitignore index dd87e2d73f..7f8efdeace 100644 --- a/projects/cli/.gitignore +++ b/projects/cli/.gitignore @@ -1,2 +1,4 @@ node_modules build + +.env \ No newline at end of file diff --git a/projects/cli/anvil.sh b/projects/cli/anvil.sh new file mode 100644 index 0000000000..da688875af --- /dev/null +++ b/projects/cli/anvil.sh @@ -0,0 +1,48 @@ +#!/bin/bash + +source .env + +# Set variables based on arguments +keyType="$1" +chainIdType="$2" + +# Set chain IDs +mainnet_local_chain_id=1338 + +arbitrum_local_chain_id=1337 + +# Determine which API key to use +if [ "$keyType" = "test" ]; then + apiKey="$DEV_TEST_ALCHEMY_API_KEY" +else + apiKey="$DEV_ALCHEMY_API_KEY" +fi + +# Determine which chain ID to use. Defaults to arbitrum local host +if [ "$chainIdType" = "eth-mainnet" ]; then + chainId=$mainnet_local_chain_id + prefix="eth" + port=9545 +else + chainId=$arbitrum_local_chain_id + prefix="arb" + port=8545 +fi + +# Check if required variables are set +if [ -z "$prefix" ] || [ -z "$apiKey" ] || [ -z "$chainId" ]; then + echo "Error: Missing required variables. Please set keyType and chainIdType." + exit 1 +fi + +anvil \ + --fork-url "https://$prefix-mainnet.g.alchemy.com/v2/$apiKey" \ + --chain-id "$chainId" \ + --port "$port" \ + "${@:3}" + +# Check if Anvil exited with an error +if [ $? -ne 0 ]; then + echo "Error: Anvil exited with a non-zero status." + exit 1 +fi \ No newline at end of file diff --git a/projects/cli/package.json b/projects/cli/package.json index aeeac121f1..f06eaa79e5 100644 --- a/projects/cli/package.json +++ b/projects/cli/package.json @@ -1,6 +1,6 @@ { "name": "@beanstalk/cli", - "version": "0.0.10", + "version": "0.0.20", "description": "Beanstalk protocol development cli tool", "license": "MIT", "repository": { @@ -16,7 +16,11 @@ "scripts": { "cli:publish": "yarn cli:build && yarn npm publish --access public", "cli:build": "rimraf build && tsc && chmod u+x build/cli.js", - "g:bean": "yarn ts-node-esm src/cli.ts" + "g:bean": "yarn ts-node-esm src/cli.ts", + "cli:anvil-eth-mainnet": "bash anvil.sh dev eth-mainnet", + "cli:anvil-arbitrum": "bash anvil.sh dev arbitrum-mainnet", + "cli:anvil4tests-mainnet": "bash anvil.sh test eth-mainnet --fork-block-number 18629000", + "cli:anvil4tests-arbitrum": "bash anvil.sh test arbitrum-mainnet --fork-block-number 18629000" }, "devDependencies": { "@types/command-line-args": "^5.2.3", diff --git a/projects/cli/src/commands/balance.ts b/projects/cli/src/commands/balance.ts index 5f1b665f54..5f258255db 100644 --- a/projects/cli/src/commands/balance.ts +++ b/projects/cli/src/commands/balance.ts @@ -14,16 +14,21 @@ export const balance = async (sdk, { account, symbol }) => { [ "ETH", "WETH", + "WSTETH", + "WEETH", + "WBTC", "BEAN", - "USDT", - "USDC", "DAI", - "CRV3", - "UNRIPE_BEAN", - "UNRIPE_BEAN_wstETH", - "BEAN_CRV3_LP", - "BEAN_ETH_WELL_LP", - "ROOT" + "USDC", + "USDT", + "urBEAN", + "urBEANWSTETH", + "BEANWETH", + "BEANWEETH", + "BEANWEETH", + "BEANWBTC", + "BEANUSDC", + "BEANUSDT" ].map((s) => getBal(sdk, s, account)) ); res.push(...bals); @@ -32,7 +37,17 @@ export const balance = async (sdk, { account, symbol }) => { }; async function getBal(sdk, symbol: string, account: string) { - const token = sdk.tokens[symbol]; + let token = sdk.tokens[symbol]; + if (!token) { + if (symbol === "urBEAN") token = sdk.tokens.UNRIPE_BEAN; + if (symbol === "urBEANWSTETH") token = sdk.tokens.UNRIPE_BEAN_WSTETH; + if (symbol === "BEANWETH") token = sdk.tokens.BEAN_ETH_WELL_LP; + if (symbol === "BEANWEETH") token = sdk.tokens.BEAN_WEETH_WELL_LP; + if (symbol === "BEANWSTETH") token = sdk.tokens.BEAN_WSTETH_WELL_LP; + if (symbol === "BEANWBTC") token = sdk.tokens.BEAN_WBTC_WELL_LP; + if (symbol === "BEANUSDC") token = sdk.tokens.BEAN_USDC_WELL_LP; + if (symbol === "BEANUSDT") token = sdk.tokens.BEAN_USDT_WELL_LP; + } if (!token) throw new Error(`No token found: ${symbol}`); try { diff --git a/projects/cli/src/commands/setbalance.ts b/projects/cli/src/commands/setbalance.ts index f75d0346e4..a4a38a5554 100644 --- a/projects/cli/src/commands/setbalance.ts +++ b/projects/cli/src/commands/setbalance.ts @@ -14,16 +14,21 @@ export const setbalance = async (sdk, chain, { account, symbol, amount }) => { const symbols = [ "ETH", "WETH", + "WSTETH", + "WEETH", + "WBTC", "BEAN", - "USDT", - "USDC", "DAI", - "CRV3", - "BEAN3CRV", - "BEANWETH", + "USDC", + "USDT", "urBEAN", - "urBEANwstETH", - "ROOT" + "urBEANWSTETH", + "BEANWETH", + "BEANWSTETH", + "BEANWEETH", + "BEANWBTC", + "BEANUSDC", + "BEANUSDT" ]; if (!symbols.includes(symbol)) { console.log( @@ -33,10 +38,16 @@ export const setbalance = async (sdk, chain, { account, symbol, amount }) => { process.exit(-1); } let t = sdk.tokens[symbol] as Token; - if (symbol === "urBEAN") t = sdk.tokens.UNRIPE_BEAN; - if (symbol === "urBEANwstETH") t = sdk.tokens.UNRIPE_BEAN_WSTETH; - if (symbol === "BEAN3CRV") t = sdk.tokens.BEAN_CRV3_LP; - if (symbol === "BEANWETH") t = sdk.tokens.BEAN_ETH_WELL_LP; + if (!t) { + if (symbol === "urBEAN") t = sdk.tokens.UNRIPE_BEAN; + if (symbol === "urBEANWSTETH") t = sdk.tokens.UNRIPE_BEAN_WSTETH; + if (symbol === "BEANWETH") t = sdk.tokens.BEAN_ETH_WELL_LP; + if (symbol === "BEANWEETH") t = sdk.tokens.BEAN_WEETH_WELL_LP; + if (symbol === "BEANWSTETH") t = sdk.tokens.BEAN_WSTETH_WELL_LP; + if (symbol === "BEANWBTC") t = sdk.tokens.BEAN_WBTC_WELL_LP; + if (symbol === "BEANUSDC") t = sdk.tokens.BEAN_USDC_WELL_LP; + if (symbol === "BEANUSDT") t = sdk.tokens.BEAN_USDT_WELL_LP; + } if (typeof chain[`set${symbol}Balance`] !== "function") throw new Error(`${symbol} is not a valid token or the method ${chalk.bold.whiteBright("")}`); diff --git a/projects/cli/src/commands/setprice.ts b/projects/cli/src/commands/setprice.ts index 6b9caacd52..66c9254d47 100644 --- a/projects/cli/src/commands/setprice.ts +++ b/projects/cli/src/commands/setprice.ts @@ -14,18 +14,18 @@ export const setPrice = async (sdk: BeanstalkSDK, chain: TestUtils.BlockchainUti console.log(beanInput, crv3Input); const newBeanAmount = (beanInput ? beanInput : 20) * 1_000_000; - const newCrv3Amount = (crv3Input ? crv3Input : beanInput ? beanInput : 20) * 1_000_000; + // const newCrv3Amount = (crv3Input ? crv3Input : beanInput ? beanInput : 20) * 1_000_000; - const newBean = sdk.tokens.BEAN.amount(newBeanAmount); - const newCrv3 = sdk.tokens.CRV3.amount(newCrv3Amount); + // const newBean = sdk.tokens.BEAN.amount(newBeanAmount); + // const newCrv3 = sdk.tokens.CRV3.amount(newCrv3Amount); ////// Set the new balance - console.log(`New Balances: ${newBean.toHuman()} ${newCrv3.toHuman()}`); + // console.log(`New Balances: ${newBean.toHuman()} ${newCrv3.toHuman()}`); // update the array tracking balances - await setBalance(sdk, POOL_ADDRESS, BALANCE_SLOT, newBean, newCrv3); + // await setBalance(sdk, POOL_ADDRESS, BALANCE_SLOT, newBean, newCrv3); // actually give the pool the ERC20's - await chain.setBEANBalance(POOL_ADDRESS, newBean); - await chain.setCRV3Balance(POOL_ADDRESS, newCrv3); + // await chain.setBEANBalance(POOL_ADDRESS, newBean); + // await chain.setCRV3Balance(POOL_ADDRESS, newCrv3); // Curve also keeps track of the previous balance, so we just copy the existing current to old. await setBalance(sdk, POOL_ADDRESS, PREV_BALANCE_SLOT, currentBean, currentCrv3); diff --git a/projects/dex-ui/.env.local.example b/projects/dex-ui/.env.local.example index c15b585eca..ad84550116 100644 --- a/projects/dex-ui/.env.local.example +++ b/projects/dex-ui/.env.local.example @@ -1,5 +1,7 @@ -VITE_AQUIFER_ADDRESS=local/fork deployed address +VITE_AQUIFER_ADDRESS_ETH="deployed address here" +VITE_AQUIFER_ADDRESS_ARBITRUM="deployed address here" VITE_ALCHEMY_API_KEY="your key here" +VITE_THEGRAPH_API_KEY="your key here" +VITE_WALLETCONNECT_PROJECT_ID="project key here" VITE_WELLS_ORIGIN_BLOCK=17138465 -VITE_LOAD_HISTORY_FROM_GRAPH=0 -VITE_WALLET_CONNECT_PROJECT_ID="project key here" \ No newline at end of file +VITE_LOAD_HISTORY_FROM_GRAPH=0 \ No newline at end of file diff --git a/projects/dex-ui/.env.production b/projects/dex-ui/.env.production index 0df3966e5a..87359512ec 100644 --- a/projects/dex-ui/.env.production +++ b/projects/dex-ui/.env.production @@ -1,5 +1,10 @@ // DO NOT ACTUALLY SAVE THINGS HERE // ONLY USE THIS FILE TO TRACK WHAT ENV VARS NEED TO // BE ADDED TO NETLIFY CONFIG +VITE_AQUIFER_ADDRESS_ETH="" +VITE_AQUIFER_ADDRESS_ARBITRUM="" VITE_ALCHEMY_API_KEY="" -VITE_WALLET_CONNECT_PROJECT_ID="" \ No newline at end of file +VITE_THEGRAPH_API_KEY="" +VITE_WALLETCONNECT_PROJECT_ID="" +VITE_WELLS_ORIGIN_BLOCK="" +VITE_LOAD_HISTORY_FROM_GRAPH="" \ No newline at end of file diff --git a/projects/dex-ui/.eslintrc b/projects/dex-ui/.eslintrc index 5e526b6a46..f2fa3d80af 100644 --- a/projects/dex-ui/.eslintrc +++ b/projects/dex-ui/.eslintrc @@ -2,12 +2,21 @@ "settings": { "react": { "version": "detect" + }, + "import/resolver": { + "typescript": {}, + "node": { + "paths": ["src"], + "extensions": [".js", ".jsx", ".ts", ".tsx"] + } } }, "extends": [ "plugin:react/recommended", "plugin:jsx-a11y/recommended", // "plugin:@typescript-eslint/recommended", + // "plugin:import/errors", + // "plugin:import/warnings", "plugin:import/typescript" ], "parser": "@typescript-eslint/parser", @@ -18,13 +27,7 @@ "ecmaVersion": "latest", "sourceType": "module" }, - "plugins": [ - "react", - "react-hooks", - "@typescript-eslint", - "import", - "jsx-a11y" - ], + "plugins": ["react", "react-hooks", "@typescript-eslint", "import", "jsx-a11y"], "rules": { "@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-non-null-assertion": "off", @@ -39,6 +42,41 @@ "react-hooks/rules-of-hooks": "error", "react-hooks/exhaustive-deps": "warn", "react/prop-types": 0, - "react/display-name": 0 + "react/display-name": 0, + "import/order": [ + "warn", + { + "groups": [ + "builtin", + "external", + "internal", + ["parent", "sibling", "index"], + "object", + "type" + ], + "pathGroups": [ + { + "pattern": "react", + "group": "external", + "position": "before" + }, + { + "pattern": "@beanstalk/sdk*", + "group": "external", + "position": "after" + }, + { + "pattern": "src/**", + "group": "internal" + } + ], + "pathGroupsExcludedImportTypes": ["react", "@beanstalk/sdk*"], + "alphabetize": { + "order": "asc", + "caseInsensitive": true + }, + "newlines-between": "always" + } + ] } -} \ No newline at end of file +} diff --git a/projects/dex-ui/codegen.ts b/projects/dex-ui/codegen.ts index 07d4686e04..526d09bdca 100644 --- a/projects/dex-ui/codegen.ts +++ b/projects/dex-ui/codegen.ts @@ -5,7 +5,8 @@ const config: CodegenConfig = { schema: [ "graphql.schema.json", // beanstalk subgraph - "https://graph.node.bean.money/subgraphs/name/beanstalk" + "https://graph.bean.money/beanstalk", + "https://graph.bean.money/beanstalk_eth" ], documents: "src/**/*.graphql", ignoreNoDocuments: true, diff --git a/projects/dex-ui/package.json b/projects/dex-ui/package.json index a01c41b755..f63f669919 100644 --- a/projects/dex-ui/package.json +++ b/projects/dex-ui/package.json @@ -30,6 +30,7 @@ "connectkit": "1.7.2", "ethers": "^5.7.2", "graphql-request": "5.2.0", + "jotai": "2.9.3", "lightweight-charts": "4.1.3", "prettier": "3.2.5", "react": "^18.2.0", diff --git a/projects/dex-ui/src/assets/images/tokens/ARB.svg b/projects/dex-ui/src/assets/images/tokens/ARB.svg new file mode 100644 index 0000000000..d6b9a7527d --- /dev/null +++ b/projects/dex-ui/src/assets/images/tokens/ARB.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/projects/dex-ui/src/assets/images/tokens/WBTC.svg b/projects/dex-ui/src/assets/images/tokens/WBTC.svg new file mode 100644 index 0000000000..2940e9c232 --- /dev/null +++ b/projects/dex-ui/src/assets/images/tokens/WBTC.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/projects/dex-ui/src/assets/images/tokens/index.tsx b/projects/dex-ui/src/assets/images/tokens/index.tsx index 59963997b8..840c4c8c34 100644 --- a/projects/dex-ui/src/assets/images/tokens/index.tsx +++ b/projects/dex-ui/src/assets/images/tokens/index.tsx @@ -1,12 +1,16 @@ -const glob = import.meta.glob("src/assets/images/tokens/*.svg", { - eager: true, - as: "url" - // import: "default" -}); +const glob = import.meta.glob( + ["src/assets/images/tokens/*.svg", "src/assets/images/tokens/*.png"], + { + eager: true, + as: "url" + } +); export const images: Record = {}; for (const key of Object.keys(glob)) { - let symbol = key.replace("/src/assets/images/tokens/", "").replace(".svg", ""); + const parts = key.split("/"); + const filename = parts[parts.length - 1]; + const symbol = filename.replace(/\.(svg|png)$/, ""); images[symbol] = glob[key]; } diff --git a/projects/dex-ui/src/assets/images/tokens/weETH.png b/projects/dex-ui/src/assets/images/tokens/weETH.png new file mode 100644 index 0000000000..e0ba7d239a Binary files /dev/null and b/projects/dex-ui/src/assets/images/tokens/weETH.png differ diff --git a/projects/dex-ui/src/components/App/App.tsx b/projects/dex-ui/src/components/App/App.tsx index cacac60c7c..c87014302f 100644 --- a/projects/dex-ui/src/components/App/App.tsx +++ b/projects/dex-ui/src/components/App/App.tsx @@ -1,33 +1,40 @@ import React from "react"; + import { Route, Routes } from "react-router-dom"; + +import { Frame } from "src/components/Frame/Frame"; import { NotFound } from "src/pages/404"; -import { Home } from "src/pages/Home"; +import { Build } from "src/pages/Build"; +import { Create } from "src/pages/Create"; import { Dev } from "src/pages/Dev"; +import { Home } from "src/pages/Home"; +import { Liquidity } from "src/pages/Liquidity"; +import { Swap } from "src/pages/Swap"; import { Well } from "src/pages/Well"; import { Wells } from "src/pages/Wells"; -import { Frame } from "src/components/Frame/Frame"; -import { Build } from "src/pages/Build"; -import { Swap } from "src/pages/Swap"; import { Settings } from "src/settings"; -import { Liquidity } from "src/pages/Liquidity"; -import { Create } from "src/pages/Create"; + +import { ForceSupportedChainId } from "./ForceSupportedChainId"; export const App = ({}) => { const isNotProd = !Settings.PRODUCTION; return ( - - - } /> - } /> - } /> - } /> - } /> - } /> - } /> - {isNotProd && } />} - } /> - - + <> + + + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + {isNotProd && } />} + } /> + + + ); }; diff --git a/projects/dex-ui/src/components/App/ForceSupportedChainId.tsx b/projects/dex-ui/src/components/App/ForceSupportedChainId.tsx new file mode 100644 index 0000000000..d85ea732bd --- /dev/null +++ b/projects/dex-ui/src/components/App/ForceSupportedChainId.tsx @@ -0,0 +1,197 @@ +import React, { useEffect, useState } from "react"; + +import { ConnectKitButton, useModal } from "connectkit"; +import { useLocation, useNavigate } from "react-router-dom"; +import styled from "styled-components"; + +import { ChainId, ChainResolver } from "@beanstalk/sdk-core"; + +import { ChainIdError, useChainErr, useSetChainErr } from "src/state/atoms/chain.atoms"; +import { useSdkChainId } from "src/utils/chain"; +import { theme } from "src/utils/ui/theme"; + +import { ButtonPrimary } from "../Button"; +import { Logo } from "../Icons"; +import { Flex } from "../Layout"; +import { Modal } from "../Modal"; +import { LinksNav, Text } from "../Typography"; + +const UNSUPPORTED_CHAIN_ID = -1; + +/** + * + * @returns + * - Returns -1 if unsupported chainId (e.g., Not ETH or Arbitrum) + * - Returns chainId if supported + */ +function parseChainIdFromPath(location: ReturnType) { + const segments = location.pathname.split("/"); + const first = segments?.[1]; + + if (first?.toLowerCase() !== "wells") return; + + const chainId = segments?.[2]; + + try { + const cidNum = parseInt(chainId); + if (Object.values(ChainId).includes(cidNum)) { + return cidNum; + } + } catch (_e: any) { + // do nothing; + } + + return UNSUPPORTED_CHAIN_ID; +} + +function getNavigateUrl( + toChainId: ChainId, + location: ReturnType, + stay: boolean +) { + const segments = location.pathname.split("/"); + const first = segments?.[1]; + + if (first?.toLowerCase() === "wells") { + const address = segments?.[3]; + const addressPortion = address && stay ? `/${address}` : ""; + return `/wells/${toChainId}${addressPortion}`; + } + return `/`; +} + +const CHAIN_ID_TO_NAME = { + [ChainId.ETH_MAINNET]: "Ethereum Mainnet", + [ChainId.ARBITRUM_MAINNET]: "Arbitrum Mainnet" +}; + +export const ForceSupportedChainId = () => { + const location = useLocation(); + const navigate = useNavigate(); + const setChainErr = useSetChainErr(); + const [navLink, setNavLink] = useState(null); + + const urlChainId = parseChainIdFromPath(location); + const chainId = useSdkChainId(); + const chainIdErr = useChainErr(); + + const { openSwitchNetworks, setOpen } = useModal(); + + const handleStayOnCurrentNetwork = () => { + navigate(getNavigateUrl(chainId, location, false)); + }; + + const handleSetSwitchNetworkUrl = () => { + if (urlChainId) { + setNavLink(getNavigateUrl(urlChainId, location, true)); + } + }; + + useEffect(() => { + if (chainId !== urlChainId) return; + if (navLink) { + const url = navLink; + setOpen(false); + setNavLink(null); + navigate(url); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [chainId, urlChainId, navLink]); + + useEffect(() => { + if (urlChainId) { + if (urlChainId === UNSUPPORTED_CHAIN_ID) { + setChainErr(ChainIdError.INVALID); + return; + } else if ( + ChainId[urlChainId] && + ChainResolver.resolveToMainnetChainId(urlChainId) !== + ChainResolver.resolveToMainnetChainId(chainId) + ) { + setChainErr(ChainIdError.INCORRECT); + return; + } + } + + if (!!chainIdErr) { + setChainErr(null); + } + }, [chainIdErr, location, urlChainId, chainId, setChainErr]); + + return ( + <> + {}}> + + + + + + BASIN + + + {chainIdErr === ChainIdError.INVALID ? ( + <> + This network is not supported + + Basin is currently only available on Ethereum and Arbitrum Mainnet. + + + ) : null} + {chainIdErr === ChainIdError.INCORRECT && urlChainId ? ( + <> + Looks like you're connected to the wrong network + + Switch to{" "} + {CHAIN_ID_TO_NAME[ChainResolver.resolveToMainnetChainId(urlChainId)]} or stay + on this network to continue. + + + ) : null} + + + + {() => { + return ( + { + e.preventDefault(); + openSwitchNetworks(); + handleSetSwitchNetworkUrl(); + }} + > + Switch Network + + ); + }} + + {chainIdErr === ChainIdError.INCORRECT ? ( + + Stay on {CHAIN_ID_TO_NAME[ChainResolver.resolveToMainnetChainId(chainId)]} + + ) : null} + + + + + + + ); +}; + +const ModalContentWrapper = styled(Flex)` + max-width: 290px; + min-width: 290px; + width: 100%; + + ${theme.media.query.sm.only} { + min-width: min(calc(100vw - 96px), 400px); + max-width: min(calc(100vw - 96px), 400px); + width: 100%; + } +`; + +const Brand = styled.div` + text-transform: uppercase; + ${LinksNav}; + margin-bottom: -6px; +`; diff --git a/projects/dex-ui/src/components/App/OnLoad.tsx b/projects/dex-ui/src/components/App/OnLoad.tsx index 963a730e45..a15f182327 100644 --- a/projects/dex-ui/src/components/App/OnLoad.tsx +++ b/projects/dex-ui/src/components/App/OnLoad.tsx @@ -1,7 +1,9 @@ import React, { useEffect } from "react"; + +import { useAccount } from "wagmi"; + import { useAllTokensBalance } from "src/tokens/useAllTokenBalance"; import { FC } from "src/types"; -import { useAccount } from "wagmi"; export const OnLoad: FC<{}> = ({ children }) => { const { address, chain } = useAccount(); @@ -13,20 +15,20 @@ export const OnLoad: FC<{}> = ({ children }) => { refetch(); }, [address, chain?.id, refetch]); - // useEffect(() => { - // const unwatch = watchAccount(config, { - // onChange(account, prevAccount) { - // // if (account.chain?.id !== chain?.id) { - // // console.log("CHECK ME"); - // // } - // // if (prevAccount.address !== account.address) { - // // console.log(`CHANGED! - from(${prevAccount.address}) to => ${account.address}`); - // // } - // } - // }); - - // return () => unwatch(); - // }); - return <>{children}; }; + +// useEffect(() => { +// const unwatch = watchAccount(config, { +// onChange(account, prevAccount) { +// // if (account.chain?.id !== chain?.id) { +// // console.log("CHECK ME"); +// // } +// // if (prevAccount.address !== account.address) { +// // console.log(`CHANGED! - from(${prevAccount.address}) to => ${account.address}`); +// // } +// } +// }); + +// return () => unwatch(); +// }); diff --git a/projects/dex-ui/src/components/App/Wrapper.tsx b/projects/dex-ui/src/components/App/Wrapper.tsx index ef4c809edf..0d5148f122 100644 --- a/projects/dex-ui/src/components/App/Wrapper.tsx +++ b/projects/dex-ui/src/components/App/Wrapper.tsx @@ -1,16 +1,18 @@ import React from "react"; -import { HashRouter } from "react-router-dom"; -import { FC } from "src/types"; -import { ConnectKitProvider } from "connectkit"; -import { WagmiProvider } from "wagmi"; + import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; +import { ConnectKitProvider } from "connectkit"; +import { HashRouter } from "react-router-dom"; +import { WagmiProvider } from "wagmi"; + +import JotaiProvider from "src/state"; +import { FC } from "src/types"; import { Avatar } from "src/utils/wagmi/Avatar"; -import { TokenProvider } from "src/tokens/TokenProvider"; -import { OnLoad } from "./OnLoad"; -import { SdkProvider } from "src/utils/sdk/SdkProvider"; import { config } from "src/utils/wagmi/config"; +import { OnLoad } from "./OnLoad"; + export const Wrapper: FC<{}> = ({ children }) => { const queryClient = new QueryClient(); return ( @@ -31,11 +33,9 @@ export const Wrapper: FC<{}> = ({ children }) => { }} > - - - {children} - - + + {children} + diff --git a/projects/dex-ui/src/components/BottomDrawer.tsx b/projects/dex-ui/src/components/BottomDrawer.tsx index 09d4629fcf..9257a81661 100644 --- a/projects/dex-ui/src/components/BottomDrawer.tsx +++ b/projects/dex-ui/src/components/BottomDrawer.tsx @@ -1,10 +1,13 @@ import React from "react"; -import { FC } from "src/types"; + import styled from "styled-components"; -import { BodyXS } from "./Typography"; + import x from "src/assets/images/x.svg"; -import { ImageButton } from "./ImageButton"; import { size } from "src/breakpoints"; +import { FC } from "src/types"; + +import { ImageButton } from "./ImageButton"; +import { BodyXS } from "./Typography"; interface Composition { Header: typeof Header; @@ -26,7 +29,12 @@ type Props = { toggleDrawer?: (isDrawerOpen: boolean) => void; }; -export const BottomDrawer: FC & Composition = ({ children, showDrawer, headerText, toggleDrawer }) => { +export const BottomDrawer: FC & Composition = ({ + children, + showDrawer, + headerText, + toggleDrawer +}) => { return ( <> diff --git a/projects/dex-ui/src/components/Button.tsx b/projects/dex-ui/src/components/Button.tsx index 2a1df8b064..f2b6737050 100644 --- a/projects/dex-ui/src/components/Button.tsx +++ b/projects/dex-ui/src/components/Button.tsx @@ -1,4 +1,7 @@ import React, { ButtonHTMLAttributes, CSSProperties, forwardRef } from "react"; + +import styled from "styled-components"; + import { CommonCssProps, CommonCssStyles, @@ -7,7 +10,7 @@ import { makeCssStyle } from "src/utils/ui/styled"; import { theme } from "src/utils/ui/theme"; -import styled from "styled-components"; + import { Spinner } from "./Spinner"; export type ButtonVariant = "outlined" | "contained"; // | "text" (Add Text Variant later) diff --git a/projects/dex-ui/src/components/Checkbox.tsx b/projects/dex-ui/src/components/Checkbox.tsx index 22b82c7cdf..9447c2309b 100644 --- a/projects/dex-ui/src/components/Checkbox.tsx +++ b/projects/dex-ui/src/components/Checkbox.tsx @@ -1,8 +1,11 @@ -import { FC } from "src/types"; import React from "react"; + import styled from "styled-components"; -import { BodyXS } from "./Typography"; + import { size } from "src/breakpoints"; +import { FC } from "src/types"; + +import { BodyXS } from "./Typography"; type Props = { label?: string; @@ -12,12 +15,23 @@ type Props = { onClick?: () => void; }; -export const Checkbox: FC = ({ label, checked = false, mode, checkboxColor = "black", onClick = () => {} }) => { +export const Checkbox: FC = ({ + label, + checked = false, + mode, + checkboxColor = "black", + onClick = () => {} +}) => { return ( - + {checked && ( ` - border: 1px solid ${(props) => (props.checkboxColor && props.checked ? props.checkboxColor : "#000")}; + border: 1px solid + ${(props) => (props.checkboxColor && props.checked ? props.checkboxColor : "#000")}; width: 16px; height: 16px; position: ${(props) => (props.mode === "checkOnly" ? "relative" : "absolute")}; diff --git a/projects/dex-ui/src/components/Create/ComponentLibraryTable.tsx b/projects/dex-ui/src/components/Create/ComponentLibraryTable.tsx index a442972848..87123e69f2 100644 --- a/projects/dex-ui/src/components/Create/ComponentLibraryTable.tsx +++ b/projects/dex-ui/src/components/Create/ComponentLibraryTable.tsx @@ -1,10 +1,12 @@ import React from "react"; + +import { Link } from "react-router-dom"; import styled from "styled-components"; import { Table, Td, THead, ResponsiveTr, Th, TBody, Row } from "src/components//Table"; -import { Link } from "react-router-dom"; -import { theme } from "src/utils/ui/theme"; import { Text } from "src/components/Typography"; +import { theme } from "src/utils/ui/theme"; + import { useWhitelistedWellComponents } from "./useWhitelistedWellComponents"; export const ComponentLibraryTable = () => { diff --git a/projects/dex-ui/src/components/Create/CreateWellProvider.tsx b/projects/dex-ui/src/components/Create/CreateWellProvider.tsx index bc9b2a8a44..f41c68e746 100644 --- a/projects/dex-ui/src/components/Create/CreateWellProvider.tsx +++ b/projects/dex-ui/src/components/Create/CreateWellProvider.tsx @@ -1,14 +1,19 @@ import React, { createContext, useCallback, useMemo, useState } from "react"; -import { ERC20Token, TokenValue } from "@beanstalk/sdk-core"; + import { DeepRequired } from "react-hook-form"; -import useSdk from "src/utils/sdk/useSdk"; -import { Log } from "src/utils/logger"; -import { Pump, WellFunction } from "@beanstalk/sdk-wells"; import { useAccount } from "wagmi"; -import { usePumps } from "src/wells/pump/usePumps"; + +import { ERC20Token, TokenValue } from "@beanstalk/sdk-core"; +import { Pump, WellFunction } from "@beanstalk/sdk-wells"; + +import { clearWellsCache } from "src/state/providers/WellsProvider"; +import { Log } from "src/utils/logger"; +import { queryKeys } from "src/utils/query/queryKeys"; +import { useFetchChainScopedQueryData } from "src/utils/query/useChainScopedQuery"; +import useSdk from "src/utils/sdk/useSdk"; +import { useAquifer } from "src/wells/aquifer/aquifer"; import BoreWellUtils from "src/wells/boreWell"; -import { clearWellsCache } from "src/wells/useWells"; -import { useQueryClient } from "@tanstack/react-query"; +import { usePumps } from "src/wells/pump/usePumps"; /** * Architecture notes: @Space-Bean @@ -123,7 +128,8 @@ export const CreateWellProvider = ({ children }: { children: React.ReactNode }) const { address: walletAddress } = useAccount(); const sdk = useSdk(); const pumps = usePumps(); - const queryClient = useQueryClient(); + const aquifer = useAquifer(); + const fetchScopedQueryData = useFetchChainScopedQueryData(); /// ----- Local State ----- const [deploying, setDeploying] = useState(false); @@ -242,6 +248,7 @@ export const CreateWellProvider = ({ children }: { children: React.ReactNode }) const { wellAddress } = await BoreWellUtils.boreWell( sdk, + aquifer, walletAddress, wellImplementation, wellFunction, @@ -255,7 +262,7 @@ export const CreateWellProvider = ({ children }: { children: React.ReactNode }) ); clearWellsCache(); - queryClient.fetchQuery({ queryKey: ["wells", sdk] }); + fetchScopedQueryData(queryKeys.wells(sdk)); Log.module("wellDeployer").debug("Well deployed at address: ", wellAddress || ""); setDeploying(false); @@ -268,7 +275,6 @@ export const CreateWellProvider = ({ children }: { children: React.ReactNode }) }, [ pumpAddress, - queryClient, walletAddress, wellImplementation, wellFunction, @@ -277,7 +283,9 @@ export const CreateWellProvider = ({ children }: { children: React.ReactNode }) wellTokens.token2, wellDetails.name, wellDetails.symbol, - sdk + sdk, + aquifer, + fetchScopedQueryData ] ); diff --git a/projects/dex-ui/src/components/Create/CreateWellStep1.tsx b/projects/dex-ui/src/components/Create/CreateWellStep1.tsx index a7b83dea74..9a600a5692 100644 --- a/projects/dex-ui/src/components/Create/CreateWellStep1.tsx +++ b/projects/dex-ui/src/components/Create/CreateWellStep1.tsx @@ -1,13 +1,15 @@ import React from "react"; + +import { FormProvider, useForm } from "react-hook-form"; +import styled from "styled-components"; + import { Flex } from "src/components/Layout"; import { Text } from "src/components/Typography"; +import { theme } from "src/utils/ui/theme"; -import { FormProvider, useForm } from "react-hook-form"; import { CreateWellStepProps, useCreateWell } from "./CreateWellProvider"; import { ComponentInputWithCustom } from "./shared/ComponentInputWithCustom"; import { CreateWellButtonRow } from "./shared/CreateWellButtonRow"; -import styled from "styled-components"; -import { theme } from "src/utils/ui/theme"; import { StyledForm } from "../Form"; type FormValues = CreateWellStepProps["step1"]; diff --git a/projects/dex-ui/src/components/Create/CreateWellStep2.tsx b/projects/dex-ui/src/components/Create/CreateWellStep2.tsx index 8a1aa9ca53..998f0da273 100644 --- a/projects/dex-ui/src/components/Create/CreateWellStep2.tsx +++ b/projects/dex-ui/src/components/Create/CreateWellStep2.tsx @@ -1,24 +1,28 @@ import React, { useEffect, useMemo, useState } from "react"; -import styled from "styled-components"; + import { FormProvider, useForm, useFormContext, useWatch } from "react-hook-form"; -import { getIsValidEthereumAddress } from "src/utils/addresses"; -import { theme } from "src/utils/ui/theme"; -import { Divider, Flex, FlexCard } from "src/components/Layout"; -import { Text } from "src/components/Typography"; -import { CreateWellButtonRow } from "./shared/CreateWellButtonRow"; +import styled from "styled-components"; + +import { ERC20Token } from "@beanstalk/sdk"; + +import { images } from "src/assets/images/tokens"; +import { Dropdown } from "src/components/Dropdown"; import { StyledForm, TextInputField } from "src/components/Form"; import { XIcon } from "src/components/Icons"; -import { CreateWellStepProps, useCreateWell } from "./CreateWellProvider"; -import { CreateWellFormProgress } from "./shared/CreateWellFormProgress"; -import { ComponentInputWithCustom } from "./shared/ComponentInputWithCustom"; +import { Divider, Flex, FlexCard } from "src/components/Layout"; +import { Text } from "src/components/Typography"; import { useERC20TokenWithAddress } from "src/tokens/useERC20Token"; -import { ERC20Token } from "@beanstalk/sdk"; +import { getIsValidEthereumAddress } from "src/utils/addresses"; import useSdk from "src/utils/sdk/useSdk"; +import { theme } from "src/utils/ui/theme"; +import { useBoolean } from "src/utils/ui/useBoolean"; import BoreWellUtils from "src/wells/boreWell"; import { useValidateWellFunction } from "src/wells/wellFunction/useValidateWellFunction"; -import { useBoolean } from "src/utils/ui/useBoolean"; -import { Dropdown } from "src/components/Dropdown"; -import { images } from "src/assets/images/tokens"; + +import { CreateWellStepProps, useCreateWell } from "./CreateWellProvider"; +import { ComponentInputWithCustom } from "./shared/ComponentInputWithCustom"; +import { CreateWellButtonRow } from "./shared/CreateWellButtonRow"; +import { CreateWellFormProgress } from "./shared/CreateWellFormProgress"; const additionalOptions = [ { diff --git a/projects/dex-ui/src/components/Create/CreateWellStep3.tsx b/projects/dex-ui/src/components/Create/CreateWellStep3.tsx index c39b09969b..dc3cb06eda 100644 --- a/projects/dex-ui/src/components/Create/CreateWellStep3.tsx +++ b/projects/dex-ui/src/components/Create/CreateWellStep3.tsx @@ -1,32 +1,48 @@ import React from "react"; + +import { FormProvider, useForm } from "react-hook-form"; +import styled from "styled-components"; + import { Divider, Flex } from "src/components/Layout"; import { Text } from "src/components/Typography"; +import useSdk from "src/utils/sdk/useSdk"; import { theme } from "src/utils/ui/theme"; -import styled from "styled-components"; +import { useWells } from "src/wells/useWells"; + import { CreateWellStepProps, useCreateWell } from "./CreateWellProvider"; -import { FormProvider, useForm } from "react-hook-form"; +import { CreateWellButtonRow } from "./shared/CreateWellButtonRow"; import { CreateWellFormProgress } from "./shared/CreateWellFormProgress"; import { StyledForm, TextInputField } from "../Form"; -import { useWells } from "src/wells/useWells"; -import { CreateWellButtonRow } from "./shared/CreateWellButtonRow"; export type WellDetailsFormValues = CreateWellStepProps["step3"]; // If the user goes back to step 2 changes the well function & returns to this step, // the default values will not be updated. -// TODO: priofity sm. const useWellDetailsDefaultValues = () => { - const { wellTokens, wellFunction } = useCreateWell(); + const { wellImplementation, wellTokens, wellFunction } = useCreateWell(); + const sdk = useSdk(); + + const wellDotSolL2 = sdk.wells.addresses.WELL_DOT_SOL.ARBITRUM_MAINNET; const wellName = wellFunction?.name; const wellSymbol = wellFunction?.symbol; const token1 = wellTokens?.token1?.symbol; const token2 = wellTokens?.token2?.symbol; - const defaultName = - wellName && token1 && token2 ? `${token1}:${token2} ${wellName} Well` : undefined; + const upgradeable = wellImplementation?.toLowerCase() === wellDotSolL2.toLowerCase(); + const upgradeableNameFragment = upgradeable ? " Upgradeable " : ""; + const upgradeableSymbolFragment = upgradeable ? "U-" : ""; - const defaultSymbol = wellSymbol && token1 && token2 && `${token1}${token2}${wellSymbol}w`; + const defaultName = + wellName && token1 && token2 + ? `${token1}:${token2} ${wellName}${upgradeableNameFragment}Well` + : undefined; + + const defaultSymbol = + wellSymbol && + token1 && + token2 && + `${upgradeableSymbolFragment}${token1}${token2}${wellSymbol}w`; return { name: defaultName, diff --git a/projects/dex-ui/src/components/Create/CreateWellStep4.tsx b/projects/dex-ui/src/components/Create/CreateWellStep4.tsx index 90bc2568f7..ce5731108e 100644 --- a/projects/dex-ui/src/components/Create/CreateWellStep4.tsx +++ b/projects/dex-ui/src/components/Create/CreateWellStep4.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useState } from "react"; -import styled from "styled-components"; + import { Control, Controller, @@ -8,30 +8,31 @@ import { useFormContext, useWatch } from "react-hook-form"; -import { theme } from "src/utils/ui/theme"; +import { useNavigate } from "react-router-dom"; +import styled from "styled-components"; +import { useAccount } from "wagmi"; + +import { ERC20Token, TokenValue } from "@beanstalk/sdk"; import { StyledForm, SwitchField, TextInputField } from "src/components/Form"; import { Box, Divider, Flex, FlexCard } from "src/components/Layout"; import { SelectCard } from "src/components/Selectable"; +import { TokenInput } from "src/components/Swap/TokenInput"; import { Text } from "src/components/Typography"; +import { useTokenAllowance } from "src/tokens/useTokenAllowance"; +import { queryKeys } from "src/utils/query/queryKeys"; +import { useInvalidateQueries } from "src/utils/query/useInvalidateQueries"; +import useSdk from "src/utils/sdk/useSdk"; +import { theme } from "src/utils/ui/theme"; +import { useBoolean } from "src/utils/ui/useBoolean"; import { CreateWellContext, CreateWellStepProps, useCreateWell } from "./CreateWellProvider"; -import { WellComponentInfo, useWhitelistedWellComponents } from "./useWhitelistedWellComponents"; - -import { ERC20Token, TokenValue } from "@beanstalk/sdk"; -import { TokenInput } from "src/components/Swap/TokenInput"; import { CreateWellButtonRow } from "./shared/CreateWellButtonRow"; -import { useTokenAllowance } from "src/tokens/useTokenAllowance"; -import useSdk from "src/utils/sdk/useSdk"; +import { WellComponentInfo, useWhitelistedWellComponents } from "./useWhitelistedWellComponents"; import { ButtonPrimary } from "../Button"; import { ensureAllowance } from "../Liquidity/allowance"; -import { useAccount } from "wagmi"; -import { useQueryClient } from "@tanstack/react-query"; -import { queryKeys } from "src/utils/query/queryKeys"; -import { useBoolean } from "src/utils/ui/useBoolean"; -import { ProgressCircle } from "../ProgressCircle"; -import { useNavigate } from "react-router-dom"; import { Modal } from "../Modal"; +import { ProgressCircle } from "../ProgressCircle"; type FormValues = CreateWellStepProps["step4"] & { usingSalt: boolean; @@ -73,11 +74,15 @@ const FormContent = ({ } }); - const [seeding, _amt1, _amt2] = methods.watch(['seedingLiquidity', 'token1Amount', 'token2Amount']); + const [seeding, _amt1, _amt2] = methods.watch([ + "seedingLiquidity", + "token1Amount", + "token2Amount" + ]); const amt1 = Number(_amt1 || 0); const amt2 = Number(_amt2 || 0); - const bothAmountsNeeded = seeding ? (amt1 > 0 && amt2 <= 0) || (amt1 <= 0 && amt2 > 0) : false; + const bothAmountsNeeded = seeding ? (amt1 > 0 && amt2 <= 0) || (amt1 <= 0 && amt2 > 0) : false; const handleSave = (formValues?: FormValues) => { const values = formValues || methods.getValues(); @@ -280,7 +285,8 @@ const AllowanceButtons = ({ }) => { const { address } = useAccount(); const sdk = useSdk(); - const queryClient = useQueryClient(); + + const invalidateQueries = useInvalidateQueries(); const { data: token1Allowance } = useTokenAllowance(token1, sdk.contracts.beanstalk.address); const { data: token2Allowance } = useTokenAllowance(token2, sdk.contracts.beanstalk.address); @@ -294,9 +300,7 @@ const AllowanceButtons = ({ const approveToken = async (token: ERC20Token, amount: TokenValue) => { if (!address) return; await ensureAllowance(address, sdk.contracts.beanstalk.address, token, amount); - queryClient.invalidateQueries({ - queryKey: queryKeys.tokenAllowance(token.address, sdk.contracts.beanstalk.address) - }); + invalidateQueries(queryKeys.tokenAllowance(token.address, sdk.contracts.beanstalk.address)); }; useEffect(() => { diff --git a/projects/dex-ui/src/components/Create/shared/ComponentAccordionCard.tsx b/projects/dex-ui/src/components/Create/shared/ComponentAccordionCard.tsx index d8f4432b52..9f948bad6d 100644 --- a/projects/dex-ui/src/components/Create/shared/ComponentAccordionCard.tsx +++ b/projects/dex-ui/src/components/Create/shared/ComponentAccordionCard.tsx @@ -1,12 +1,15 @@ import React from "react"; -import styled from "styled-components"; + import { Link } from "react-router-dom"; -import { theme } from "src/utils/ui/theme"; +import styled from "styled-components"; + +import { ChainExplorerIcon, Github } from "src/components/Icons"; import { Box, Flex } from "src/components/Layout"; +import { AccordionSelectCard } from "src/components/Selectable"; import { Text } from "src/components/Typography"; +import { theme } from "src/utils/ui/theme"; + import { WellComponentInfo } from "../useWhitelistedWellComponents"; -import { AccordionSelectCard } from "../../Selectable"; -import { Etherscan, Github } from "../../Icons"; export type WellComponentAccordionCardProps = { selected: boolean; @@ -94,9 +97,9 @@ export const WellComponentAccordionCard = ({ - {links.etherscan && ( - - + {links.explorer && ( + + )} {links.github && ( diff --git a/projects/dex-ui/src/components/Create/shared/ComponentInputWithCustom.tsx b/projects/dex-ui/src/components/Create/shared/ComponentInputWithCustom.tsx index 2f132349e4..9cb90a9498 100644 --- a/projects/dex-ui/src/components/Create/shared/ComponentInputWithCustom.tsx +++ b/projects/dex-ui/src/components/Create/shared/ComponentInputWithCustom.tsx @@ -1,16 +1,19 @@ import React, { useCallback } from "react"; + import { FieldValues, Path, PathValue, useFormContext, useWatch } from "react-hook-form"; -import { useWhitelistedWellComponents } from "../useWhitelistedWellComponents"; -import { useBoolean } from "src/utils/ui/useBoolean"; -import { TextInputField } from "../../Form"; +import styled from "styled-components"; + import { Flex } from "src/components/Layout"; import { ToggleSwitch } from "src/components/ToggleSwitch"; -import { WellComponentAccordionCard } from "./ComponentAccordionCard"; import { Text } from "src/components/Typography"; +import { getIsValidEthereumAddress } from "src/utils/addresses"; import { theme } from "src/utils/ui/theme"; -import styled from "styled-components"; +import { useBoolean } from "src/utils/ui/useBoolean"; + +import { WellComponentAccordionCard } from "./ComponentAccordionCard"; +import { TextInputField } from "../../Form"; import { CircleFilledCheckIcon, CircleEmptyIcon } from "../../Icons"; -import { getIsValidEthereumAddress } from "src/utils/addresses"; +import { useWhitelistedWellComponents } from "../useWhitelistedWellComponents"; type AdditionalOptionProps = { value: string; diff --git a/projects/dex-ui/src/components/Create/shared/CreateWellButtonRow.tsx b/projects/dex-ui/src/components/Create/shared/CreateWellButtonRow.tsx index e6c769bfcd..f3d56468cd 100644 --- a/projects/dex-ui/src/components/Create/shared/CreateWellButtonRow.tsx +++ b/projects/dex-ui/src/components/Create/shared/CreateWellButtonRow.tsx @@ -1,13 +1,16 @@ import React, { useMemo } from "react"; + import { useFormContext, useWatch } from "react-hook-form"; import { useNavigate } from "react-router-dom"; -import { theme } from "src/utils/ui/theme"; import styled from "styled-components"; + +import { ActionWalletButtonWrapper } from "src/components/Wallet"; +import { theme } from "src/utils/ui/theme"; + import { ButtonPrimary } from "../../Button"; import { LeftArrow, RightArrow } from "../../Icons"; import { Flex } from "../../Layout"; import { useCreateWell } from "../CreateWellProvider"; -import { ActionWalletButtonWrapper } from "src/components/Wallet"; const ButtonLabels = [ { @@ -72,7 +75,7 @@ export const CreateWellButtonRow = ({ const goNextEnabled = noErrors && hasRequiredValues; const goBackLabel = ButtonLabels[step].back || "Back"; - const nextLabel = disabled && disabledMessage || ButtonLabels[step].next || "Next"; + const nextLabel = (disabled && disabledMessage) || ButtonLabels[step].next || "Next"; return ( diff --git a/projects/dex-ui/src/components/Create/shared/CreateWellFormProgress.tsx b/projects/dex-ui/src/components/Create/shared/CreateWellFormProgress.tsx index 27ceddd0b2..42b22c42f7 100644 --- a/projects/dex-ui/src/components/Create/shared/CreateWellFormProgress.tsx +++ b/projects/dex-ui/src/components/Create/shared/CreateWellFormProgress.tsx @@ -1,11 +1,14 @@ import React, { useMemo } from "react"; + import { useFormContext, useWatch } from "react-hook-form"; -import { theme } from "src/utils/ui/theme"; -import { CheckIcon, CircleEmptyIcon } from "src/components/Icons"; -import { Flex } from "src/components/Layout"; import { Link } from "react-router-dom"; import styled from "styled-components"; + +import { CheckIcon, CircleEmptyIcon } from "src/components/Icons"; +import { Flex } from "src/components/Layout"; import { Text } from "src/components/Typography"; +import { theme } from "src/utils/ui/theme"; + import { FunctionTokenPumpFormValues } from "../CreateWellStep2"; import { WellDetailsFormValues } from "../CreateWellStep3"; diff --git a/projects/dex-ui/src/components/Create/useWhitelistedWellComponents.ts b/projects/dex-ui/src/components/Create/useWhitelistedWellComponents.ts index b2c1eb6df8..89b832f3b0 100644 --- a/projects/dex-ui/src/components/Create/useWhitelistedWellComponents.ts +++ b/projects/dex-ui/src/components/Create/useWhitelistedWellComponents.ts @@ -1,21 +1,20 @@ import { useMemo } from "react"; + +import { ChainId, ChainResolver } from "@beanstalk/sdk-core"; + import BeanstalkFarmsLogo from "src/assets/images/beanstalk-farms.png"; -import HalbornLogo from "src/assets/images/halborn-logo.png"; -import { - WELL_DOT_SOL_ADDRESS, - toAddressMap, - MULTI_FLOW_PUMP_V_1PT1_ADDRESS, - CONSTANT_PRODUCT_2_V2_ADDRESS -} from "src/utils/addresses"; import BrendanTwitterPFP from "src/assets/images/brendan-twitter-pfp.png"; -import CyrfinLogo from "src/assets/images/cyrfin-logo.svg"; -import Code4renaLogo from "src/assets/images/code4rena-logo.png"; import ClockIcon from "src/assets/images/clock-icon.svg"; -import { useWells } from "src/wells/useWells"; +import Code4renaLogo from "src/assets/images/code4rena-logo.png"; +import CyrfinLogo from "src/assets/images/cyrfin-logo.svg"; +import HalbornLogo from "src/assets/images/halborn-logo.png"; +import { AddressMap } from "src/types"; +import { toAddressMap } from "src/utils/addresses"; +import useSdk from "src/utils/sdk/useSdk"; +import { usePumps } from "src/wells/pump/usePumps"; import { useWellImplementations } from "src/wells/useWellImplementations"; +import { useWells } from "src/wells/useWells"; import { useWellFunctions } from "src/wells/wellFunction/useWellFunctions"; -import { usePumps } from "src/wells/pump/usePumps"; -import { AddressMap } from "src/types"; export enum WellComponentType { WellImplementation = "WellImplementation", @@ -51,7 +50,9 @@ export type WellComponentInfo = { }; info: ComponentInfo[]; links: { - etherscan?: string; + explorer?: string; + // arbiscan?: string; + // etherscan?: string; github?: string; learnMore?: string; }; @@ -82,7 +83,7 @@ const basinAuditInfo = [ ]; const WellDotSol: WellComponentInfo = { - address: WELL_DOT_SOL_ADDRESS, + address: "", component: { name: "Well.sol", summary: "A standard Well implementation that prioritizes flexibility and composability.", @@ -103,14 +104,13 @@ const WellDotSol: WellComponentInfo = { { label: "Audited by", value: basinAuditInfo } ], links: { - etherscan: `https://etherscan.io/address/${WELL_DOT_SOL_ADDRESS}`, github: "https://github.com/BeanstalkFarms/Basin/blob/master/src/Well.sol", learnMore: "https://github.com/BeanstalkFarms/Basin/blob/master/src/Well.sol" } }; const MultiFlowPump: WellComponentInfo = { - address: MULTI_FLOW_PUMP_V_1PT1_ADDRESS, + address: "", component: { name: "Multi Flow", fullName: "Multi Flow Pump V1.1", @@ -136,14 +136,13 @@ const MultiFlowPump: WellComponentInfo = { { label: "Audited by", value: basinAuditInfo } ], links: { - etherscan: `https://etherscan.io/address/${MULTI_FLOW_PUMP_V_1PT1_ADDRESS}`, github: "https://github.com/BeanstalkFarms/Basin/blob/master/src/pumps/MultiFlowPump.sol", learnMore: "https://github.com/BeanstalkFarms/Basin/blob/master/src/pumps/MultiFlowPump.sol" } }; const ConstantProduct2: WellComponentInfo = { - address: CONSTANT_PRODUCT_2_V2_ADDRESS, + address: "", component: { name: "Constant Product 2", summary: "A standard x*y = k token pricing function for two tokens.", @@ -162,7 +161,6 @@ const ConstantProduct2: WellComponentInfo = { { label: "Audited by", value: basinAuditInfo } ], links: { - etherscan: `https://etherscan.io/address/${CONSTANT_PRODUCT_2_V2_ADDRESS}`, github: "https://github.com/BeanstalkFarms/Basin/blob/master/src/functions/ConstantProduct2.sol", learnMore: @@ -176,16 +174,21 @@ type WellComponentMap = { wellFunctions: T; }; -const ComponentWhiteList: WellComponentMap> = { - wellImplementations: { - [WellDotSol.address]: WellDotSol - }, - pumps: { - [MultiFlowPump.address]: MultiFlowPump - }, - wellFunctions: { - [ConstantProduct2.address]: ConstantProduct2 - } +const getComponentWithUpdateLinks = ( + wellComponent: WellComponentInfo, + chainId: ChainId, + address: string +) => { + const explorer = `https://${ChainResolver.isL2Chain(chainId) ? "arbiscan" : "etherscan"}.io/address/${address}`; + + return { + ...wellComponent, + address, + links: { + ...wellComponent.links, + explorer + } + }; }; export const useWhitelistedWellComponents = () => { @@ -193,10 +196,42 @@ export const useWhitelistedWellComponents = () => { const { data: implementations } = useWellImplementations(); const wellFunctions = useWellFunctions(); const pumps = usePumps(); + const sdk = useSdk(); + + const whitelist = useMemo(() => { + // set Addresses + const wellDotSol = getComponentWithUpdateLinks( + WellDotSol, + sdk.chainId, + sdk.wells.addresses.WELL_DOT_SOL.get(sdk.chainId) + ); + const multiFlow = getComponentWithUpdateLinks( + MultiFlowPump, + sdk.chainId, + sdk.wells.addresses.MULTI_FLOW_PUMP_V1_1.get(sdk.chainId) + ); + const cp2 = getComponentWithUpdateLinks( + ConstantProduct2, + sdk.chainId, + sdk.wells.addresses.CONSTANT_PRODUCT_2_V2.get(sdk.chainId) + ); + + return { + wellImplementations: { + [wellDotSol.address]: wellDotSol + }, + pumps: { + [multiFlow.address]: multiFlow + }, + wellFunctions: { + [cp2.address]: cp2 + } + }; + }, [sdk]); return useMemo(() => { // make deep copy of ComponentWhiteList - const map = JSON.parse(JSON.stringify(ComponentWhiteList)) as WellComponentMap< + const map = JSON.parse(JSON.stringify(whitelist)) as WellComponentMap< AddressMap >; @@ -237,5 +272,5 @@ export const useWhitelistedWellComponents = () => { components, lookup: map }; - }, [implementations, pumps, wellFunctions, wells]); + }, [whitelist, implementations, pumps, wellFunctions, wells]); }; diff --git a/projects/dex-ui/src/components/Dropdown.tsx b/projects/dex-ui/src/components/Dropdown.tsx index c2208c3409..0ac1dadb08 100644 --- a/projects/dex-ui/src/components/Dropdown.tsx +++ b/projects/dex-ui/src/components/Dropdown.tsx @@ -1,10 +1,13 @@ import React from "react"; + import * as DropdownMenu from "@radix-ui/react-dropdown-menu"; import styled from "styled-components"; + import { theme } from "src/utils/ui/theme"; -import { Flex } from "./Layout"; import useElementDimensions from "src/utils/ui/useDimensions"; +import { Flex } from "./Layout"; + export type DropdownProps = { open: boolean; trigger: React.ReactNode; @@ -21,8 +24,8 @@ const Dropdown = ({ open, children, trigger, offset, setOpen }: DropdownProps) = e.preventDefault()} - onClick={(e) => e.preventDefault()} + onMouseDown={(e: any) => e.preventDefault()} + onClick={(e: any) => e.preventDefault()} > {trigger} @@ -31,7 +34,7 @@ const Dropdown = ({ open, children, trigger, offset, setOpen }: DropdownProps) = e.preventDefault()} + onFocus={(e: any) => e.preventDefault()} > <>{children} diff --git a/projects/dex-ui/src/components/Error.tsx b/projects/dex-ui/src/components/Error.tsx index cc1305d2a7..9be1483db3 100644 --- a/projects/dex-ui/src/components/Error.tsx +++ b/projects/dex-ui/src/components/Error.tsx @@ -1,28 +1,24 @@ import React from "react"; + +import styled from "styled-components"; + import { Footer } from "src/components/Frame/Footer"; import { Frame } from "src/components/Frame/Frame"; -import styled from "styled-components"; type ErrorProps = { message: string; errorOnly?: boolean; -} +}; export const Error = ({ message, errorOnly }: ErrorProps) => { return ( <> {!errorOnly && } - - - Oops! - - - {"Something went wrong :("} - - - {message} - - + + Oops! + {"Something went wrong :("} + {message} + {!errorOnly &&