diff --git a/packages/dev-frontend/.eslintrc.cjs b/packages/dev-frontend/.eslintrc.cjs index 4020bcbf4..47fcb4803 100644 --- a/packages/dev-frontend/.eslintrc.cjs +++ b/packages/dev-frontend/.eslintrc.cjs @@ -1,14 +1,20 @@ module.exports = { env: { browser: true, es2020: true }, extends: [ - 'eslint:recommended', - 'plugin:@typescript-eslint/recommended', - 'plugin:react-hooks/recommended', + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + "plugin:react-hooks/recommended" ], - parser: '@typescript-eslint/parser', - parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, - plugins: ['react-refresh'], + parser: "@typescript-eslint/parser", + parserOptions: { ecmaVersion: "latest", sourceType: "module" }, + plugins: ["react-refresh"], rules: { - 'react-refresh/only-export-components': 'warn', - }, -} + // "react-refresh/only-export-components": "warn", + "react-hooks/exhaustive-deps": [ + "warn", + { + additionalHooks: "useTransaction" + } + ] + } +}; diff --git a/packages/dev-frontend/package.json b/packages/dev-frontend/package.json index fe4c00a7b..b91af4de5 100644 --- a/packages/dev-frontend/package.json +++ b/packages/dev-frontend/package.json @@ -6,6 +6,7 @@ "type": "module", "dependencies": { "@ethersproject/abi": "5.7.0", + "@ethersproject/experimental": "5.7.0", "@fortawesome/fontawesome-svg-core": "1.2.34", "@fortawesome/free-regular-svg-icons": "5.15.2", "@fortawesome/free-solid-svg-icons": "5.15.2", @@ -13,7 +14,7 @@ "@liquity/chicken-bonds": "file:.yalc/@liquity/chicken-bonds", "@metamask/eth-sig-util": "5.0.1", "@tippyjs/react": "4.2.5", - "connectkit": "1.2.4", + "connectkit": "^1.3.0", "ethers": "5.7.2", "lambert-w-function": "3.0.0", "react": "17.0.1", @@ -24,7 +25,7 @@ "react-router-dom": "5.2.0", "recharts": "2.1.10", "theme-ui": "0.6.0-canary.1544.5359f8a1e408a4dfeb74a9ae39688270286e534a.0", - "wagmi": "0.12.13" + "wagmi": "^0.12.0" }, "devDependencies": { "@esbuild-plugins/node-modules-polyfill": "^0.2.2", diff --git a/packages/dev-frontend/src/App.tsx b/packages/dev-frontend/src/App.tsx index e9db13fa3..fd6a2cbdb 100644 --- a/packages/dev-frontend/src/App.tsx +++ b/packages/dev-frontend/src/App.tsx @@ -15,11 +15,7 @@ import theme from "./theme"; import { DisposableWalletProvider } from "./testUtils/DisposableWalletProvider"; import { LiquityFrontend } from "./LiquityFrontend"; import { AppLoader } from "./components/AppLoader"; - -if (window.ethereum) { - // Silence MetaMask warning in console - Object.assign(window.ethereum, { autoRefreshOnNetworkChange: false }); -} +import { useAsyncValue } from "./hooks/AsyncValue"; if (import.meta.env.REACT_APP_DEMO_MODE === "true") { const ethereum = new DisposableWalletProvider( @@ -37,21 +33,6 @@ getConfig().then(config => { Object.assign(window, { config }); }); -const wagmiClient = createClient( - getDefaultClient({ - appName: "Liquity", - chains: [mainnet, goerli] - }) -); - -// const EthersWeb3ReactProvider: React.FC = ({ children }) => { -// return ( -// new BatchedWebSocketAugmentedWeb3Provider(provider)}> -// {children} -// -// ); -// }; - const UnsupportedMainnetFallback: React.FC = () => ( ( This app is for testing purposes only. - - Please change your network to Ropsten, Rinkeby, Kovan, Görli or Kiln. - + Please change your network to Görli. If you'd like to use the Liquity Protocol on mainnet, please pick a frontend{" "} @@ -80,43 +59,57 @@ const UnsupportedMainnetFallback: React.FC = () => ( ); +const UnsupportedNetworkFallback: React.FC = () => ( + + + Liquity is not deployed to this network. + + Please switch to Ropsten, Rinkeby, Kovan, Görli or Kiln. + +); + const App = () => { - const unsupportedNetworkFallback = (chainId: number) => ( - - - Liquity is not yet deployed to{" "} - {chainId === 1 ? "mainnet" : "this network"}. - - Please switch to Ropsten, Rinkeby, Kovan, Görli or Kiln. - - ); + const config = useAsyncValue(getConfig); + const loader = ; return ( - - - - }> - } - unsupportedNetworkFallback={unsupportedNetworkFallback} - unsupportedMainnetFallback={} - > - - } /> - - - - - - + + {config.loaded && ( + + + + } + unsupportedMainnetFallback={} + > + + + + + + + + )} + ); }; diff --git a/packages/dev-frontend/src/components/Bonds/context/BondViewContext.tsx b/packages/dev-frontend/src/components/Bonds/context/BondViewContext.tsx index e6734fbf6..fb06f226a 100644 --- a/packages/dev-frontend/src/components/Bonds/context/BondViewContext.tsx +++ b/packages/dev-frontend/src/components/Bonds/context/BondViewContext.tsx @@ -32,6 +32,7 @@ export type BondViewContextType = { bLusdAmmBLusdBalance?: Decimal; bLusdAmmLusdBalance?: Decimal; statuses: BondTransactionStatuses; + isInfiniteBondApproved: boolean; isSynchronizing: boolean; getLusdFromFaucet: () => Promise; simulatedProtocolInfo?: ProtocolInfo; diff --git a/packages/dev-frontend/src/components/Bonds/context/BondViewProvider.tsx b/packages/dev-frontend/src/components/Bonds/context/BondViewProvider.tsx index 2515e741e..7a64ad9e2 100644 --- a/packages/dev-frontend/src/components/Bonds/context/BondViewProvider.tsx +++ b/packages/dev-frontend/src/components/Bonds/context/BondViewProvider.tsx @@ -51,6 +51,7 @@ export const BondViewProvider: React.FC = props => { const [stats, setStats] = useState(); const [protocolInfo, setProtocolInfo] = useState(); const [simulatedProtocolInfo, setSimulatedProtocolInfo] = useState(); + const [isInfiniteBondApproved, setIsInfiniteBondApproved] = useState(false); const [lpRewards, setLpRewards] = useState(); const [isLusdApprovedWithBlusdAmm, setIsLusdApprovedWithBlusdAmm] = useState(false); const [isBLusdApprovedWithBlusdAmm, setIsBLusdApprovedWithBlusdAmm] = useState(false); @@ -63,6 +64,7 @@ export const BondViewProvider: React.FC = props => { BLusdAmmTokenIndex.BLUSD ); const [statuses, setStatuses] = useState({ + APPROVE: "IDLE", CREATE: "IDLE", CANCEL: "IDLE", CLAIM: "IDLE", @@ -141,16 +143,39 @@ export const BondViewProvider: React.FC = props => { ); const getLusdFromFaucet = useCallback(async () => { - if (contracts.lusdToken === undefined) return; + if (contracts.lusdToken === undefined || liquity.connection.signer === undefined) return; + if ( LUSD_OVERRIDE_ADDRESS !== null && (await contracts.lusdToken.balanceOf(account)).eq(0) && "tap" in contracts.lusdToken ) { - await (await ((contracts.lusdToken as unknown) as ERC20Faucet).tap()).wait(); + await ( + await ((contracts.lusdToken as unknown) as ERC20Faucet) + .connect(liquity.connection.signer) + .tap() + ).wait(); setShouldSynchronize(true); } - }, [contracts.lusdToken, account, LUSD_OVERRIDE_ADDRESS]); + }, [contracts.lusdToken, account, LUSD_OVERRIDE_ADDRESS, liquity.connection.signer]); + + useEffect(() => { + (async () => { + if ( + contracts.lusdToken === undefined || + contracts.chickenBondManager === undefined || + account === undefined || + isInfiniteBondApproved + ) + return; + const isApproved = await api.isInfiniteBondApproved( + account, + contracts.lusdToken, + contracts.chickenBondManager + ); + setIsInfiniteBondApproved(isApproved); + })(); + }, [contracts.lusdToken, contracts.chickenBondManager, account, isInfiniteBondApproved]); useEffect(() => { (async () => { @@ -324,25 +349,48 @@ export const BondViewProvider: React.FC = props => { })(); }, [isSynchronizing, shouldSynchronize, account, contracts, simulatedProtocolInfo]); + const [approveInfiniteBond, approveStatus] = useTransaction(async () => { + await api.approveInfiniteBond( + contracts.lusdToken, + contracts.chickenBondManager, + liquity.connection.signer + ); + setIsInfiniteBondApproved(true); + }, [contracts.lusdToken, contracts.chickenBondManager, liquity.connection.signer]); + const [approveAmm, approveAmmStatus] = useTransaction( async (tokensNeedingApproval: BLusdAmmTokenIndex[]) => { for (const token of tokensNeedingApproval) { if (token === BLusdAmmTokenIndex.BLUSD) { await (isMainnet - ? api.approveTokenWithBLusdAmmMainnet(contracts.bLusdToken) - : api.approveTokenWithBLusdAmm(contracts.bLusdToken, BLUSD_AMM_ADDRESS)); + ? api.approveTokenWithBLusdAmmMainnet(contracts.bLusdToken, liquity.connection.signer) + : api.approveTokenWithBLusdAmm( + contracts.bLusdToken, + BLUSD_AMM_ADDRESS, + liquity.connection.signer + )); setIsBLusdApprovedWithBlusdAmm(true); } else { await (isMainnet - ? api.approveTokenWithBLusdAmmMainnet(contracts.lusdToken) - : api.approveTokenWithBLusdAmm(contracts.lusdToken, BLUSD_AMM_ADDRESS)); + ? api.approveTokenWithBLusdAmmMainnet(contracts.lusdToken, liquity.connection.signer) + : api.approveTokenWithBLusdAmm( + contracts.lusdToken, + BLUSD_AMM_ADDRESS, + liquity.connection.signer + )); setIsLusdApprovedWithBlusdAmm(true); } } }, - [contracts.bLusdToken, contracts.lusdToken, isMainnet, BLUSD_AMM_ADDRESS] + [ + contracts.bLusdToken, + contracts.lusdToken, + isMainnet, + BLUSD_AMM_ADDRESS, + liquity.connection.signer + ] ); const [approveTokens, approveTokensStatus] = useTransaction( @@ -350,22 +398,26 @@ export const BondViewProvider: React.FC = props => { if (contracts.bLusdAmm === undefined) return; for (const [token, spender] of Array.from(tokensNeedingApproval)) { if (token === BLusdAmmTokenIndex.BLUSD) { - await api.approveToken(contracts.bLusdToken, spender); + await api.approveToken(contracts.bLusdToken, spender, liquity.connection.signer); if (spender === BLUSD_AMM_ADDRESS) { setIsBLusdApprovedWithBlusdAmm(true); } else if (spender === BLUSD_LP_ZAP_ADDRESS) { setIsBLusdApprovedWithAmmZapper(true); } } else if (token === BLusdAmmTokenIndex.LUSD) { - await api.approveToken(contracts.lusdToken, BLUSD_LP_ZAP_ADDRESS); + await api.approveToken( + contracts.lusdToken, + BLUSD_LP_ZAP_ADDRESS, + liquity.connection.signer + ); setIsLusdApprovedWithAmmZapper(true); } else if (token === BLusdAmmTokenIndex.BLUSD_LUSD_LP && spender === undefined) { const lpToken = await api.getLpToken(contracts.bLusdAmm); - await api.approveToken(lpToken, BLUSD_LP_ZAP_ADDRESS); + await api.approveToken(lpToken, BLUSD_LP_ZAP_ADDRESS, liquity.connection.signer); setIsBLusdLpApprovedWithAmmZapper(true); } else if (token === BLusdAmmTokenIndex.BLUSD_LUSD_LP) { const lpToken = await api.getLpToken(contracts.bLusdAmm); - await api.approveToken(lpToken, spender); + await api.approveToken(lpToken, spender, liquity.connection.signer); if (spender === BLUSD_LP_ZAP_ADDRESS) { setIsBLusdLpApprovedWithAmmZapper(true); } else if (spender === BLUSD_AMM_STAKING_ADDRESS) { @@ -381,20 +433,15 @@ export const BondViewProvider: React.FC = props => { BLUSD_LP_ZAP_ADDRESS, BLUSD_AMM_STAKING_ADDRESS, BLUSD_AMM_ADDRESS, - isBLusdApprovedWithAmmZapper, - isLusdApprovedWithAmmZapper + liquity.connection.signer ] ); const [createBond, createStatus] = useTransaction( async (lusdAmount: Decimal) => { - if (liquity.connection.signer === undefined) return; - await api.createBond( lusdAmount, account, - LUSD_OVERRIDE_ADDRESS ?? liquity.connection.addresses.lusdToken, - contracts.lusdToken, contracts.chickenBondManager, liquity.connection.signer ); @@ -407,25 +454,31 @@ export const BondViewProvider: React.FC = props => { setOptimisticBond(optimisticBond); setShouldSynchronize(true); }, - [contracts.chickenBondManager, contracts.lusdToken, liquity.connection.signer, account] + [contracts.chickenBondManager, liquity.connection.signer, account] ); const [cancelBond, cancelStatus] = useTransaction( async (bondId: string, minimumLusd: Decimal) => { - await api.cancelBond(bondId, minimumLusd, contracts.chickenBondManager); + await api.cancelBond( + bondId, + minimumLusd, + account, + contracts.chickenBondManager, + liquity.connection.signer + ); removeBondFromList(bondId); setShouldSynchronize(true); }, - [contracts.chickenBondManager, removeBondFromList] + [contracts.chickenBondManager, removeBondFromList, liquity.connection.signer, account] ); const [claimBond, claimStatus] = useTransaction( async (bondId: string) => { - await api.claimBond(bondId, contracts.chickenBondManager); + await api.claimBond(bondId, account, contracts.chickenBondManager, liquity.connection.signer); changeBondStatusToClaimed(bondId); setShouldSynchronize(true); }, - [contracts.chickenBondManager, changeBondStatusToClaimed] + [contracts.chickenBondManager, changeBondStatusToClaimed, liquity.connection.signer, account] ); const getExpectedSwapOutput = useCallback( @@ -446,22 +499,22 @@ export const BondViewProvider: React.FC = props => { inputToken, inputAmount, minOutputAmount, - contracts.bLusdAmm + contracts.bLusdAmm, + liquity.connection.signer, + account ); setShouldSynchronize(true); }, - [contracts.bLusdAmm] + [contracts.bLusdAmm, isMainnet, liquity.connection.signer, account] ); const getExpectedLpTokens = useCallback( async (bLusdAmount: Decimal, lusdAmount: Decimal) => { - if (liquity.connection.signer === undefined) return Decimal.ZERO; - return contracts.bLusdAmmZapper ? api.getExpectedLpTokens(bLusdAmount, lusdAmount, contracts.bLusdAmmZapper) : Decimal.ZERO; }, - [contracts.bLusdAmmZapper, liquity.connection.signer] + [contracts.bLusdAmmZapper] ); const [manageLiquidity, manageLiquidityStatus] = useTransaction( @@ -472,14 +525,17 @@ export const BondViewProvider: React.FC = props => { params.lusdAmount, params.minLpTokens, params.shouldStakeInGauge, - contracts.bLusdAmmZapper + contracts.bLusdAmmZapper, + liquity.connection.signer, + account ); } else if (params.action === "removeLiquidity") { await api.removeLiquidity( params.burnLpTokens, params.minBLusdAmount, params.minLusdAmount, - contracts.bLusdAmmZapper + contracts.bLusdAmmZapper, + liquity.connection.signer ); } else if (params.action === "removeLiquidityOneCoin") { await api.removeLiquidityOneCoin( @@ -487,18 +543,34 @@ export const BondViewProvider: React.FC = props => { params.output, params.minAmount, contracts.bLusdAmmZapper, - contracts.bLusdAmm + contracts.bLusdAmm, + liquity.connection.signer, + account ); } else if (params.action === "stakeLiquidity") { - await api.stakeLiquidity(params.stakeAmount, contracts.bLusdGauge); + await api.stakeLiquidity( + params.stakeAmount, + contracts.bLusdGauge, + liquity.connection.signer + ); } else if (params.action === "unstakeLiquidity") { - await api.unstakeLiquidity(params.unstakeAmount, contracts.bLusdGauge); + await api.unstakeLiquidity( + params.unstakeAmount, + contracts.bLusdGauge, + liquity.connection.signer + ); } else if (params.action === "claimLpRewards") { - await api.claimLpRewards(contracts.bLusdGauge); + await api.claimLpRewards(contracts.bLusdGauge, liquity.connection.signer); } setShouldSynchronize(true); }, - [contracts.bLusdAmmZapper, contracts.bLusdGauge] + [ + contracts.bLusdAmmZapper, + contracts.bLusdGauge, + contracts.bLusdAmm, + liquity.connection.signer, + account + ] ); const getExpectedWithdrawal = useCallback( @@ -546,7 +618,9 @@ export const BondViewProvider: React.FC = props => { viewRef.current === _view && event === _event; try { - if (isCurrentViewEvent("CREATING", "CONFIRM_PRESSED")) { + if (isCurrentViewEvent("CREATING", "APPROVE_PRESSED")) { + await approveInfiniteBond(); + } else if (isCurrentViewEvent("CREATING", "CONFIRM_PRESSED")) { await createBond((payload as CreateBondPayload).deposit); await dispatchEvent("CREATE_BOND_CONFIRMED"); } else if (isCurrentViewEvent("CANCELLING", "CONFIRM_PRESSED")) { @@ -585,6 +659,7 @@ export const BondViewProvider: React.FC = props => { }, [ selectedBondId, + approveInfiniteBond, cancelBond, createBond, claimBond, @@ -600,6 +675,7 @@ export const BondViewProvider: React.FC = props => { useEffect(() => { setStatuses(statuses => ({ ...statuses, + APPROVE: approveStatus, CREATE: createStatus, CANCEL: cancelStatus, CLAIM: claimStatus, @@ -609,6 +685,7 @@ export const BondViewProvider: React.FC = props => { MANAGE_LIQUIDITY: manageLiquidityStatus })); }, [ + approveStatus, createStatus, cancelStatus, claimStatus, @@ -663,6 +740,7 @@ export const BondViewProvider: React.FC = props => { lpTokenSupply, bLusdAmmBLusdBalance, bLusdAmmLusdBalance, + isInfiniteBondApproved, isSynchronizing, getLusdFromFaucet, setSimulatedMarketPrice, @@ -689,8 +767,7 @@ export const BondViewProvider: React.FC = props => { lpRewards }; - // @ts-ignore - window.__LIQUITY_BONDS__ = provider.current; + // window.__LIQUITY_BONDS__ = provider.current; return {children}; }; diff --git a/packages/dev-frontend/src/components/Bonds/context/api.ts b/packages/dev-frontend/src/components/Bonds/context/api.ts index dc6c0f9a3..8c2d3e486 100644 --- a/packages/dev-frontend/src/components/Bonds/context/api.ts +++ b/packages/dev-frontend/src/components/Bonds/context/api.ts @@ -8,7 +8,7 @@ import { providers, Signer } from "ethers"; -import { splitSignature } from "ethers/lib/utils"; +// import { splitSignature } from "ethers/lib/utils"; import type { BLUSDToken, BondNFT, @@ -52,7 +52,6 @@ import { TokenExchangeEvent, TokenExchangeEventObject } from "@liquity/chicken-bonds/lusd/types/external/CurveCryptoSwap2ETH"; -import type { EthersSigner } from "@liquity/lib-ethers"; import mainnet from "@liquity/chicken-bonds/lusd/addresses/mainnet.json"; import type { CurveLiquidityGaugeV5, @@ -137,7 +136,7 @@ type CachedYearnApys = { bLusdLusd3Crv: Decimal | undefined; }; -let cachedApys: CachedYearnApys = { +const cachedApys: CachedYearnApys = { lusd3Crv: undefined, stabilityPool: undefined, bLusdLusd3Crv: undefined @@ -535,7 +534,7 @@ const getProtocolInfo = async ( let yieldAmplification: Maybe = undefined; let bLusdApr: Maybe = undefined; - let bLusdLpApr: Maybe = cachedApys.bLusdLusd3Crv; + const bLusdLpApr: Maybe = cachedApys.bLusdLusd3Crv; const fairPrice = { lower: treasury.total.sub(treasury.pending).div(bLusdSupply), @@ -655,7 +654,84 @@ const getTokenTotalSupply = async (token: ERC20): Promise => { return decimalify(await token.totalSupply()); }; +const isInfiniteBondApproved = async ( + account: string, + lusdToken: LUSDToken, + chickenBondManager: ChickenBondManager +): Promise => { + const allowance = await lusdToken.allowance(account, chickenBondManager.address); + + // Unlike bLUSD, LUSD doesn't explicitly handle infinite approvals, therefore the allowance will + // start to decrease from 2**64. + // However, it is practically impossible that it would decrease below 2**63. + return allowance.gt(constants.MaxInt256); +}; + +const approveInfiniteBond = async ( + lusdToken: LUSDToken | undefined, + chickenBondManager: ChickenBondManager | undefined, + signer: Signer | undefined +): Promise => { + if (lusdToken === undefined || chickenBondManager === undefined || signer === undefined) { + throw new Error("approveInfiniteBond() failed: a dependency is null"); + } + + console.log("approveInfiniteBond() started"); + + try { + await ( + await ((lusdToken as unknown) as Contract) + .connect(signer) + .approve(chickenBondManager.address, constants.MaxUint256._hex) + ).wait(); + + console.log("approveInfiniteBond() succceeded"); + } catch (error: unknown) { + throw new Error(`approveInfiniteBond() failed: ${error}`); + } +}; + const createBond = async ( + lusdAmount: Decimal, + owner: string, + chickenBondManager: ChickenBondManager | undefined, + signer: Signer | undefined +): Promise => { + if (chickenBondManager === undefined || signer === undefined) { + throw new Error("createBond() failed: a dependency is null"); + } + + const gasEstimate = await chickenBondManager.estimateGas.createBond(lusdAmount.hex, { + from: owner + }); + + const receipt = await ( + await chickenBondManager.connect(signer).createBond(lusdAmount.hex, { + gasLimit: gasEstimate.add(LQTY_ISSUANCE_GAS_HEADROOM) + }) + ).wait(); + + console.log( + "CREATE BOND", + receipt?.events, + receipt?.events?.map(c => c.event), + receipt?.events?.find(e => e.event === "BondCreated") + ); + + const createdEvent = receipt?.events?.find( + e => e.event === "BondCreated" + ) as Maybe; + + if (createdEvent === undefined) { + throw new Error("createBond() failed: couldn't find BondCreated event"); + } + + console.log("createBond() finished:", createdEvent.args); + return createdEvent.args; +}; + +/* +const createBondWithPermit = async ( lusdAmount: Decimal, owner: string, lusdAddress: string, @@ -664,7 +740,7 @@ const createBond = async ( signer: EthersSigner ): Promise => { if (chickenBondManager === undefined || lusdToken === undefined) { - throw new Error("createBond() failed: a dependency is null"); + throw new Error("createBondWithPermit() failed: a dependency is null"); } const TEN_MINUTES_IN_SECONDS = 60 * 10; @@ -731,19 +807,27 @@ const createBond = async ( console.log("createBond() finished:", createdEvent.args); return createdEvent.args; }; +*/ const cancelBond = async ( bondId: string, minimumLusd: Decimal, - chickenBondManager: ChickenBondManager | undefined + owner: string, + chickenBondManager: ChickenBondManager | undefined, + signer: Signer | undefined ): Promise => { - if (chickenBondManager === undefined) throw new Error("cancelBond() failed: a dependency is null"); + if (chickenBondManager === undefined || signer === undefined) { + throw new Error("cancelBond() failed: a dependency is null"); + } + console.log("cancelBond() started:", bondId, minimumLusd.toString()); - const gasEstimate = await chickenBondManager.estimateGas.chickenOut(bondId, minimumLusd.hex); + const gasEstimate = await chickenBondManager.estimateGas.chickenOut(bondId, minimumLusd.hex, { + from: owner + }); const receipt = await ( - await chickenBondManager.chickenOut(bondId, minimumLusd.hex, { + await chickenBondManager.connect(signer).chickenOut(bondId, minimumLusd.hex, { gasLimit: gasEstimate.add(LQTY_ISSUANCE_GAS_HEADROOM) }) ).wait(); @@ -755,23 +839,28 @@ const cancelBond = async ( if (cancelledEvent === undefined) { throw new Error("cancelBond() failed: couldn't find BondCancelled event"); } + console.log("cancelBond() finished:", cancelledEvent.args); return cancelledEvent.args; }; const claimBond = async ( bondId: string, - chickenBondManager: ChickenBondManager | undefined + owner: string, + chickenBondManager: ChickenBondManager | undefined, + signer: Signer | undefined ): Promise => { try { - if (chickenBondManager === undefined) + if (chickenBondManager === undefined || signer === undefined) { throw new Error("claimBond() failed: a dependency is null"); + } + console.log("claimBond() started", bondId); - const gasEstimate = await chickenBondManager.estimateGas.chickenIn(bondId); + const gasEstimate = await chickenBondManager.estimateGas.chickenIn(bondId, { from: owner }); const receipt = await ( - await chickenBondManager.chickenIn(bondId, { + await chickenBondManager.connect(signer).chickenIn(bondId, { gasLimit: gasEstimate.add(LQTY_ISSUANCE_GAS_HEADROOM) }) ).wait(); @@ -835,34 +924,47 @@ const isTokenApprovedWithAmmZapper = async ( const approveTokenWithBLusdAmm = async ( token: LUSDToken | BLUSDToken | undefined, - bLusdAmmAddress: string | null + bLusdAmmAddress: string | null, + signer: Signer | undefined ) => { - if (token === undefined || bLusdAmmAddress === null) { + if (token === undefined || bLusdAmmAddress === null || signer === undefined) { throw new Error("approveTokenWithBLusdAmm() failed: a dependency is null"); } - await (await token.approve(bLusdAmmAddress, constants.MaxUint256)).wait(); + await ( + await (token as Contract).connect(signer).approve(bLusdAmmAddress, constants.MaxUint256) + ).wait(); return; }; const approveToken = async ( token: LUSDToken | BLUSDToken | ERC20 | undefined, - spenderAddress: string | null + spenderAddress: string | null, + signer: Signer | undefined ) => { - if (token === undefined || spenderAddress === null) { + if (token === undefined || spenderAddress === null || signer === undefined) { throw new Error("approveToken() failed: a dependency is null"); } - await (await token.approve(spenderAddress, constants.MaxUint256)).wait(); + await ( + await (token as Contract).connect(signer).approve(spenderAddress, constants.MaxUint256) + ).wait(); return; }; -const approveTokenWithBLusdAmmMainnet = async (token: LUSDToken | BLUSDToken | undefined) => { - if (token === undefined) { +const approveTokenWithBLusdAmmMainnet = async ( + token: LUSDToken | BLUSDToken | undefined, + signer: Signer | undefined +) => { + if (token === undefined || signer === undefined) { throw new Error("approveTokenWithBLusdAmmMainnet() failed: a dependency is null"); } - await (await token.approve(CURVE_REGISTRY_SWAPS_ADDRESS, constants.MaxUint256)).wait(); + await ( + await (token as Contract) + .connect(signer) + .approve(CURVE_REGISTRY_SWAPS_ADDRESS, constants.MaxUint256) + ).wait(); return; }; @@ -886,7 +988,7 @@ const getExpectedSwapOutputMainnet = async ( const reciprocal = Decimal.from(1).div(1.29); if (bLusdAmmBalance.eq(0)) return inputAmount.div(reciprocal); - const swaps = CurveRegistrySwaps__factory.connect(CURVE_REGISTRY_SWAPS_ADDRESS, bLusdAmm.signer); + const swaps = CurveRegistrySwaps__factory.connect(CURVE_REGISTRY_SWAPS_ADDRESS, bLusdAmm.provider); return decimalify( await swaps["get_exchange_multiple_amount(address[9],uint256[3][4],uint256)"]( @@ -900,19 +1002,20 @@ const swapTokens = async ( inputToken: BLusdAmmTokenIndex, inputAmount: Decimal, minOutputAmount: Decimal, - bLusdAmm: CurveCryptoSwap2ETH | undefined + bLusdAmm: CurveCryptoSwap2ETH | undefined, + signer: Signer | undefined, + account: string ): Promise => { - if (bLusdAmm === undefined) throw new Error("swapTokens() failed: a dependency is null"); + if (bLusdAmm === undefined || signer === undefined) { + throw new Error("swapTokens() failed: a dependency is null"); + } - const gasEstimate = await bLusdAmm.estimateGas["exchange(uint256,uint256,uint256,uint256)"]( - inputToken, - getOtherToken(inputToken), - inputAmount.hex, - minOutputAmount.hex - ); + const gasEstimate = await bLusdAmm.estimateGas[ + "exchange(uint256,uint256,uint256,uint256)" + ](inputToken, getOtherToken(inputToken), inputAmount.hex, minOutputAmount.hex, { from: account }); const receipt = await ( - await bLusdAmm["exchange(uint256,uint256,uint256,uint256)"]( + await bLusdAmm.connect(signer)["exchange(uint256,uint256,uint256,uint256)"]( inputToken, getOtherToken(inputToken), inputAmount.hex, @@ -937,19 +1040,23 @@ const swapTokensMainnet = async ( inputToken: BLusdAmmTokenIndex, inputAmount: Decimal, minOutputAmount: Decimal, - bLusdAmm: CurveCryptoSwap2ETH | undefined + bLusdAmm: CurveCryptoSwap2ETH | undefined, + signer: Signer | undefined, + account: string ): Promise => { - if (bLusdAmm === undefined) throw new Error("swapTokensMainnet() failed: a dependency is null"); + if (bLusdAmm === undefined || signer === undefined) { + throw new Error("swapTokensMainnet() failed: a dependency is null"); + } const swaps = CurveRegistrySwaps__factory.connect(CURVE_REGISTRY_SWAPS_ADDRESS, bLusdAmm.signer); const route = getRoute(inputToken); const gasEstimate = await swaps.estimateGas[ "exchange_multiple(address[9],uint256[3][4],uint256,uint256)" - ](...route, inputAmount.hex, minOutputAmount.hex); + ](...route, inputAmount.hex, minOutputAmount.hex, { from: account }); const receipt = await ( - await swaps["exchange_multiple(address[9],uint256[3][4],uint256,uint256)"]( + await swaps.connect(signer)["exchange_multiple(address[9],uint256[3][4],uint256,uint256)"]( ...route, inputAmount.hex, minOutputAmount.hex, @@ -999,20 +1106,25 @@ const addLiquidity = async ( lusdAmount: Decimal, minLpTokens: Decimal, shouldStakeInGauge: boolean, - bLusdZapper: BLUSDLPZap | undefined + bLusdZapper: BLUSDLPZap | undefined, + signer: Signer | undefined, + account: string ): Promise => { - if (bLusdZapper === undefined) throw new Error("addLiquidity() failed: a dependency is null"); + if (bLusdZapper === undefined || signer === undefined) { + throw new Error("addLiquidity() failed: a dependency is null"); + } const zapperFunction = shouldStakeInGauge ? "addLiquidityAndStake" : "addLiquidity"; const gasEstimate = await bLusdZapper.estimateGas[zapperFunction]( bLusdAmount.hex, lusdAmount.hex, - minLpTokens.hex + minLpTokens.hex, + { from: account } ); const receipt = await ( - await bLusdZapper[zapperFunction]( + await bLusdZapper.connect(signer)[zapperFunction]( bLusdAmount.hex, lusdAmount.hex, minLpTokens.hex, @@ -1056,16 +1168,17 @@ const removeLiquidity = async ( burnLpTokens: Decimal, minBLusdAmount: Decimal, minLusdAmount: Decimal, - bLusdZapper: BLUSDLPZap | undefined + bLusdZapper: BLUSDLPZap | undefined, + signer: Signer | undefined ): Promise => { - if (bLusdZapper === undefined) throw new Error("removeLiquidity() failed: a dependency is null"); + if (bLusdZapper === undefined || signer === undefined) { + throw new Error("removeLiquidity() failed: a dependency is null"); + } const receipt = await ( - await bLusdZapper.removeLiquidityBalanced( - burnLpTokens.hex, - minBLusdAmount.hex, - minLusdAmount.hex - ) + await bLusdZapper + .connect(signer) + .removeLiquidityBalanced(burnLpTokens.hex, minBLusdAmount.hex, minLusdAmount.hex) ).wait(); if (!receipt.status) { @@ -1078,20 +1191,24 @@ const removeLiquidity = async ( const removeLiquidityLUSD = async ( burnLpTokens: Decimal, minAmount: Decimal, - bLusdZapper: BLUSDLPZap | undefined + bLusdZapper: BLUSDLPZap | undefined, + signer: Signer | undefined, + account: string ): Promise => { - if (bLusdZapper === undefined) + if (bLusdZapper === undefined || signer === undefined) { throw new Error("removeLiquidityLUSD() failed: a dependency is null"); + } const removeLiquidityFunction = "removeLiquidityLUSD"; const gasEstimate = await bLusdZapper.estimateGas[removeLiquidityFunction]( burnLpTokens.hex, - minAmount.hex + minAmount.hex, + { from: account } ); const receipt = await ( - await bLusdZapper[removeLiquidityFunction]( + await bLusdZapper.connect(signer)[removeLiquidityFunction]( burnLpTokens.hex, minAmount.hex, { gasLimit: gasEstimate.mul(6).div(5) } // Add 20% overhead (we've seen it fail otherwise) @@ -1108,9 +1225,13 @@ const removeLiquidityLUSD = async ( const removeLiquidityBLUSD = async ( burnLpTokens: Decimal, minAmount: Decimal, - bLusdAmm: CurveCryptoSwap2ETH | undefined + bLusdAmm: CurveCryptoSwap2ETH | undefined, + signer: Signer | undefined, + account: string ): Promise => { - if (bLusdAmm === undefined) throw new Error("removeLiquidityBLUSD() failed: a dependency is null"); + if (bLusdAmm === undefined || signer === undefined) { + throw new Error("removeLiquidityBLUSD() failed: a dependency is null"); + } const removeLiquidityFunction = "remove_liquidity_one_coin(uint256,uint256,uint256,bool)"; @@ -1118,11 +1239,12 @@ const removeLiquidityBLUSD = async ( burnLpTokens.hex, 0, minAmount.hex, - false + false, + { from: account } ); const receipt = await ( - await bLusdAmm[removeLiquidityFunction]( + await bLusdAmm.connect(signer)[removeLiquidityFunction]( burnLpTokens.hex, 0, minAmount.hex, @@ -1143,23 +1265,29 @@ const removeLiquidityOneCoin = async ( output: BLusdAmmTokenIndex, minAmount: Decimal, bLusdZapper: BLUSDLPZap | undefined, - bLusdAmm: CurveCryptoSwap2ETH | undefined + bLusdAmm: CurveCryptoSwap2ETH | undefined, + signer: Signer | undefined, + account: string ): Promise => { if (output === BLusdAmmTokenIndex.LUSD) { - return removeLiquidityLUSD(burnLpTokens, minAmount, bLusdZapper); + return removeLiquidityLUSD(burnLpTokens, minAmount, bLusdZapper, signer, account); } else { - return removeLiquidityBLUSD(burnLpTokens, minAmount, bLusdAmm); + return removeLiquidityBLUSD(burnLpTokens, minAmount, bLusdAmm, signer, account); } }; const stakeLiquidity = async ( stakeAmount: Decimal, - bLusdGauge: CurveLiquidityGaugeV5 | undefined + bLusdGauge: CurveLiquidityGaugeV5 | undefined, + signer: Signer | undefined ): Promise => { - if (bLusdGauge === undefined) { + if (bLusdGauge === undefined || signer === undefined) { throw new Error("stakeLiquidity() failed: a dependency is null"); } - const receipt = await (await bLusdGauge["deposit(uint256)"](stakeAmount.hex)).wait(); + + const receipt = await ( + await bLusdGauge.connect(signer)["deposit(uint256)"](stakeAmount.hex) + ).wait(); const depositEvent = receipt?.events?.find(e => e?.event === "Deposit") as Maybe; @@ -1173,14 +1301,15 @@ const stakeLiquidity = async ( const unstakeLiquidity = async ( unstakeAmount: Decimal, - bLusdGauge: CurveLiquidityGaugeV5 | undefined + bLusdGauge: CurveLiquidityGaugeV5 | undefined, + signer: Signer | undefined ): Promise => { - if (bLusdGauge === undefined) { + if (bLusdGauge === undefined || signer === undefined) { throw new Error("unstakeLiquidity() failed: a dependency is null"); } - const claimRewards = true; + const receipt = await ( - await bLusdGauge["withdraw(uint256,bool)"](unstakeAmount.hex, claimRewards) + await bLusdGauge.connect(signer)["withdraw(uint256,bool)"](unstakeAmount.hex, true) ).wait(); const withdrawEvent = receipt?.events?.find(e => e?.event === "Withdraw") as Maybe; @@ -1193,12 +1322,15 @@ const unstakeLiquidity = async ( return withdrawEvent.args; }; -const claimLpRewards = async (bLusdGauge: CurveLiquidityGaugeV5 | undefined): Promise => { - if (bLusdGauge === undefined) { +const claimLpRewards = async ( + bLusdGauge: CurveLiquidityGaugeV5 | undefined, + signer: Signer | undefined +): Promise => { + if (bLusdGauge === undefined || signer === undefined) { throw new Error("claimLpRewards() failed: a dependency is null"); } - const receipt = await (await bLusdGauge["claim_rewards()"]()).wait(); + const receipt = await (await bLusdGauge.connect(signer)["claim_rewards()"]()).wait(); if (!receipt.status) { throw new Error("claimLpRewards() failed: no transaction receipt status received."); @@ -1209,7 +1341,7 @@ const getLpRewards = async ( account: string, bLusdGauge: CurveLiquidityGaugeV5 ): Promise => { - let rewards: BLusdLpRewards = []; + const rewards: BLusdLpRewards = []; const totalRewardTokens = (await bLusdGauge.reward_count()).toNumber(); @@ -1233,6 +1365,8 @@ export const api = { getTokenBalance, getTokenTotalSupply, getProtocolInfo, + approveInfiniteBond, + isInfiniteBondApproved, createBond, cancelBond, claimBond, diff --git a/packages/dev-frontend/src/components/Bonds/context/transitions.ts b/packages/dev-frontend/src/components/Bonds/context/transitions.ts index cfdbe2448..ebc332f25 100644 --- a/packages/dev-frontend/src/components/Bonds/context/transitions.ts +++ b/packages/dev-frontend/src/components/Bonds/context/transitions.ts @@ -59,6 +59,7 @@ export const transitions: BondEventTransitions = { ABORT_PRESSED: "IDLE", CREATE_BOND_CONFIRMED: "IDLE", BACK_PRESSED: "IDLE", + APPROVE_PRESSED: "CREATING", CONFIRM_PRESSED: "CREATING" }, CLAIMING: { @@ -245,6 +246,7 @@ export type ProtocolInfo = { export type TransactionStatus = "IDLE" | "PENDING" | "CONFIRMED" | "FAILED"; export type BondTransaction = + | "APPROVE" | "CREATE" | "CANCEL" | "CLAIM" diff --git a/packages/dev-frontend/src/components/Bonds/context/useBondContracts.tsx b/packages/dev-frontend/src/components/Bonds/context/useBondContracts.tsx index 905247c30..1e6a41a2a 100644 --- a/packages/dev-frontend/src/components/Bonds/context/useBondContracts.tsx +++ b/packages/dev-frontend/src/components/Bonds/context/useBondContracts.tsx @@ -106,7 +106,7 @@ export const useBondContracts = (): BondContracts => { CurveCryptoSwap2ETH__factory.abi ); - const [bLusdAmmZapper, bLusAmmZapperStatus] = useContract( + const [bLusdAmmZapper, bLusdAmmZapperStatus] = useContract( BLUSD_LP_ZAP_ADDRESS, BLUSDLPZap__factory.abi ); @@ -123,7 +123,7 @@ export const useBondContracts = (): BondContracts => { chickenBondManagerStatus, bLusdTokenStatus, bLusdAmmStatus, - bLusAmmZapperStatus, + ...(isMainnet ? [bLusdAmmZapperStatus] : []), bLusdGaugeStatus ].find(status => status === "FAILED") === undefined; @@ -135,8 +135,7 @@ export const useBondContracts = (): BondContracts => { chickenBondManager === undefined || bLusdToken === undefined || bLusdAmm === undefined || - bLusdGauge === undefined || - BLUSD_AMM_STAKING_ADDRESS === null + bLusdGauge === undefined ) { return undefined; } @@ -155,11 +154,10 @@ export const useBondContracts = (): BondContracts => { await protocolInfoPromise ); - const [protocolInfo, stats, lpToken, lpStakingContract] = await Promise.all([ + const [protocolInfo, stats, lpToken] = await Promise.all([ protocolInfoPromise, api.getStats(chickenBondManager), - api.getLpToken(bLusdAmm), - api.erc20From(BLUSD_AMM_STAKING_ADDRESS, bLusdAmm.provider) + api.getLpToken(bLusdAmm) ]); const [ @@ -174,10 +172,10 @@ export const useBondContracts = (): BondContracts => { api.getTokenBalance(account, bLusdToken), api.getTokenBalance(account, lusdToken), api.getTokenBalance(account, lpToken), - api.getTokenBalance(account, lpStakingContract), + isMainnet ? api.getTokenBalance(account, bLusdGauge) : Decimal.ZERO, api.getTokenTotalSupply(lpToken), api.getCoinBalances(bLusdAmm), - api.getLpRewards(account, bLusdGauge) + isMainnet ? api.getLpRewards(account, bLusdGauge) : [] ]); const bonds = await bondsPromise; @@ -196,16 +194,7 @@ export const useBondContracts = (): BondContracts => { lpRewards }; }, - [ - chickenBondManager, - bondNft, - bLusdToken, - lusdToken, - bLusdAmm, - isMainnet, - BLUSD_AMM_STAKING_ADDRESS, - bLusdGauge - ] + [chickenBondManager, bondNft, bLusdToken, lusdToken, bLusdAmm, isMainnet, bLusdGauge] ); return { diff --git a/packages/dev-frontend/src/components/Bonds/views/creating/Details.tsx b/packages/dev-frontend/src/components/Bonds/views/creating/Details.tsx index d34430403..52d39ebe4 100644 --- a/packages/dev-frontend/src/components/Bonds/views/creating/Details.tsx +++ b/packages/dev-frontend/src/components/Bonds/views/creating/Details.tsx @@ -1,12 +1,13 @@ /** @jsxImportSource theme-ui */ import React, { useEffect, useMemo, useState } from "react"; -import { Flex, Heading, Button, Card, Grid, Close, Image, Spinner } from "theme-ui"; +import { Flex, Heading, Button, Card, Grid, Close, Text, Image, Spinner } from "theme-ui"; import { Decimal } from "@liquity/lib-base"; import { EditableRow } from "../../../Trove/Editor"; import { Record } from "../../Record"; import { InfoIcon } from "../../../InfoIcon"; import { useBondView } from "../../context/BondViewContext"; import { HorizontalTimeline, Label, SubLabel, UNKNOWN_DATE } from "../../../HorizontalTimeline"; +import { ActionDescription } from "../../../ActionDescription"; import { EXAMPLE_NFT } from "../../context/BondViewProvider"; import * as l from "../../lexicon"; import { useWizard } from "../../../Wizard/Context"; @@ -34,6 +35,7 @@ export const Details: React.FC = ({ onBack }) => { const { dispatchEvent, statuses, + isInfiniteBondApproved, lusdBalance, simulatedProtocolInfo, setSimulatedMarketPrice, @@ -43,7 +45,10 @@ export const Details: React.FC = ({ onBack }) => { const { back } = useWizard(); const [deposit, setDeposit] = useState(lusdBalance ?? Decimal.ZERO); const depositEditingState = useState(); - const isConfirming = useMemo(() => statuses.CREATE === "PENDING", [statuses.CREATE]); + const isApprovingOrConfirming = useMemo( + () => statuses.APPROVE === "PENDING" || statuses.CREATE === "PENDING", + [statuses.APPROVE, statuses.CREATE] + ); const handleBack = back ?? onBack ?? (() => dispatchEvent("BACK_PRESSED")); const [isDepositEnough, setIsDepositEnough] = useState(lusdBalance?.gte(100) ?? true); const [doesDepositExceedBalance, setDoesDepositExceedBalance] = useState(false); @@ -52,6 +57,10 @@ export const Details: React.FC = ({ onBack }) => { dispatchEvent("ABORT_PRESSED"); }; + const handleApprovePressed = () => { + dispatchEvent("APPROVE_PRESSED"); + }; + const handleConfirmPressed = () => { dispatchEvent("CONFIRM_PRESSED", { deposit } as CreateBondPayload); }; @@ -259,8 +268,6 @@ export const Details: React.FC = ({ onBack }) => { onReset={() => resetSimulatedMarketPrice()} /> - {statuses.CREATE === "FAILED" && Failed to create bond. Please try again.} - {!protocolInfo.hasMarketPremium && ( When the bLUSD market price is less than 3% above the floor price, it's not profitable to @@ -269,6 +276,24 @@ export const Details: React.FC = ({ onBack }) => { )} + {!isInfiniteBondApproved && ( + + You are approving LUSD for bonding + + )} + + {statuses.APPROVE === "FAILED" && ( + Failed to approve spend of LUSD. Please try again. + )} + + {statuses.CREATE === "FAILED" && Failed to create bond. Please try again.} + + {isInfiniteBondApproved && ( + + You are bonding {deposit.prettify(2)} LUSD + + )} + {!isDepositEnough && The minimum bond amount is 100 LUSD.} {doesDepositExceedBalance && ( @@ -281,13 +306,23 @@ export const Details: React.FC = ({ onBack }) => { - - + {!isInfiniteBondApproved && ( + + )} + {isInfiniteBondApproved && ( + + )} ); diff --git a/packages/dev-frontend/src/components/UserAccount.tsx b/packages/dev-frontend/src/components/UserAccount.tsx index 55ada904f..7c2f6b648 100644 --- a/packages/dev-frontend/src/components/UserAccount.tsx +++ b/packages/dev-frontend/src/components/UserAccount.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { Text, Flex, Box, Heading } from "theme-ui"; +import { Text, Flex, Box, Heading, Button } from "theme-ui"; import { Decimal, LiquityStoreState } from "@liquity/lib-base"; import { useLiquitySelector } from "@liquity/lib-react"; @@ -11,6 +11,7 @@ import { shortenAddress } from "../utils/shortenAddress"; import { Icon } from "./Icon"; import { useBondView } from "./Bonds/context/BondViewContext"; import { useBondAddresses } from "./Bonds/context/BondAddressesContext"; +import { ConnectKitButton } from "connectkit"; const select = ({ accountBalance, lusdBalance, lqtyBalance }: LiquityStoreState) => ({ accountBalance, @@ -27,18 +28,28 @@ export const UserAccount: React.FC = () => { const lusdBalance = LUSD_OVERRIDE_ADDRESS === null ? realLusdBalance : customLusdBalance; return ( - - - - - Connected as - - {shortenAddress(account)} - - - - - + + + {connectKit => ( + + )} + + + {([ @@ -52,7 +63,7 @@ export const UserAccount: React.FC = () => { {balance.prettify()} ))} - - + + ); }; diff --git a/packages/dev-frontend/src/components/WalletConnector.tsx b/packages/dev-frontend/src/components/WalletConnector.tsx index 89c1a07f4..183149107 100644 --- a/packages/dev-frontend/src/components/WalletConnector.tsx +++ b/packages/dev-frontend/src/components/WalletConnector.tsx @@ -1,9 +1,26 @@ import { ConnectKitButton } from "connectkit"; +import { Box, Button, Flex } from "theme-ui"; +import { Icon } from "./Icon"; type WalletConnectorProps = { loader?: React.ReactNode; }; -export const WalletConnector: React.FC = () => { - return ; +export const WalletConnector: React.FC = ({ children }) => { + return ( + + {connectKit => + connectKit.isConnected ? ( + children + ) : ( + + + + ) + } + + ); }; diff --git a/packages/dev-frontend/src/config/index.ts b/packages/dev-frontend/src/config/index.ts index e8e4e6893..419f98069 100644 --- a/packages/dev-frontend/src/config/index.ts +++ b/packages/dev-frontend/src/config/index.ts @@ -4,11 +4,14 @@ import { isAddress, getAddress } from "@ethersproject/address"; export type LiquityFrontendConfig = { frontendTag: string; infuraApiKey?: string; + alchemyApiKey?: string; testnetOnly?: boolean; + walletConnectProjectId: string; }; const defaultConfig: LiquityFrontendConfig = { - frontendTag: AddressZero + frontendTag: AddressZero, + walletConnectProjectId: "b16efb4fd41473c0f45dbad8efa15a00" }; function hasKey(o: object, k: K): o is Record { diff --git a/packages/dev-frontend/src/hooks/LiquityContext.tsx b/packages/dev-frontend/src/hooks/LiquityContext.tsx index 5de9b3423..a16e58a1c 100644 --- a/packages/dev-frontend/src/hooks/LiquityContext.tsx +++ b/packages/dev-frontend/src/hooks/LiquityContext.tsx @@ -24,7 +24,7 @@ const LiquityContext = createContext(undefined) type LiquityProviderProps = { loader?: React.ReactNode; - unsupportedNetworkFallback?: (chainId: number) => React.ReactNode; + unsupportedNetworkFallback?: React.ReactNode; unsupportedMainnetFallback?: React.ReactNode; }; @@ -55,7 +55,9 @@ export const LiquityProvider: React.FC = ({ frontendTag: config.frontendTag, useStore: "blockPolled" }); - } catch {} + } catch (err) { + console.error(err); + } } }, [config, provider, signer.data, account.address, chainId]); @@ -91,7 +93,7 @@ export const LiquityProvider: React.FC = ({ } }, [config, connection]); - if (!config || !provider || !account.address || !chainId) { + if (!config || !account.address || !connection) { return <>{loader}; } @@ -100,14 +102,16 @@ export const LiquityProvider: React.FC = ({ } if (!connection) { - return unsupportedNetworkFallback ? <>{unsupportedNetworkFallback(chainId)} : null; + return <>{unsupportedNetworkFallback}; } const liquity = EthersLiquity._from(connection); liquity.store.logging = true; return ( - + {children} ); diff --git a/packages/dev-frontend/src/hooks/useContract.ts b/packages/dev-frontend/src/hooks/useContract.ts index d55174858..035499be3 100644 --- a/packages/dev-frontend/src/hooks/useContract.ts +++ b/packages/dev-frontend/src/hooks/useContract.ts @@ -9,7 +9,7 @@ export function useContract( address: string | null, abi: ContractInterface ): [TContractType | undefined, ContractStatus] { - const { provider, liquity } = useLiquity(); + const { provider } = useLiquity(); const [contract, setContract] = useState>(); useEffect(() => { @@ -27,7 +27,7 @@ export function useContract( const connectedContract = (new ethers.Contract( address, abi, - liquity.connection.signer + provider ) as unknown) as TContractType; setContract({ instance: connectedContract, status: "LOADED" }); @@ -39,7 +39,7 @@ export function useContract( ); } })(); - }, [provider, liquity.connection.signer, address, abi, contract]); + }, [provider, address, abi, contract]); return [contract?.instance, contract?.status || "UNKNOWN"]; } diff --git a/packages/dev-frontend/vite.config.ts b/packages/dev-frontend/vite.config.ts index a249c9209..5c9d6efae 100644 --- a/packages/dev-frontend/vite.config.ts +++ b/packages/dev-frontend/vite.config.ts @@ -37,5 +37,8 @@ export default defineConfig({ globals: true, environment: "jsdom", setupFiles: "./src/setupTests.ts" + }, + server: { + cors: false } }); diff --git a/yarn.lock b/yarn.lock index f9af233c9..9f793a16a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1179,6 +1179,17 @@ __metadata: languageName: node linkType: hard +"@ethersproject/experimental@npm:5.7.0": + version: 5.7.0 + resolution: "@ethersproject/experimental@npm:5.7.0" + dependencies: + "@ethersproject/web": ^5.7.0 + ethers: ^5.7.0 + scrypt-js: 3.0.1 + checksum: a4973371be9b984d5834df5d6fe3d4d1641204ec859a6d4e11e4a972a3a4a2dd7c0eae6a9a86b90f30faa3633a86ab153197bff6e853fb3ff8f847e0340a3c24 + languageName: node + linkType: hard + "@ethersproject/hash@npm:5.7.0, @ethersproject/hash@npm:>=5.0.0-beta.128, @ethersproject/hash@npm:^5.0.4, @ethersproject/hash@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/hash@npm:5.7.0" @@ -1736,6 +1747,7 @@ __metadata: dependencies: "@esbuild-plugins/node-modules-polyfill": ^0.2.2 "@ethersproject/abi": 5.7.0 + "@ethersproject/experimental": 5.7.0 "@fortawesome/fontawesome-svg-core": 1.2.34 "@fortawesome/free-regular-svg-icons": 5.15.2 "@fortawesome/free-solid-svg-icons": 5.15.2 @@ -1758,7 +1770,7 @@ __metadata: "@typescript-eslint/eslint-plugin": ^5.57.1 "@typescript-eslint/parser": ^5.57.1 "@vitejs/plugin-react-swc": ^3.0.0 - connectkit: 1.2.4 + connectkit: ^1.3.0 cross-env: 7.0.3 eslint: ^8.38.0 eslint-plugin-react-hooks: ^4.6.0 @@ -1781,7 +1793,7 @@ __metadata: typescript: ^5.0.4 vite: ^4.3.2 vitest: ^0.31.0 - wagmi: 0.12.13 + wagmi: ^0.12.0 yalc: 1.0.0-pre.53 languageName: unknown linkType: soft @@ -8015,9 +8027,9 @@ __metadata: languageName: node linkType: hard -"connectkit@npm:1.2.4": - version: 1.2.4 - resolution: "connectkit@npm:1.2.4" +"connectkit@npm:^1.3.0": + version: 1.3.0 + resolution: "connectkit@npm:1.3.0" dependencies: buffer: ^6.0.3 detect-browser: ^5.3.0 @@ -8032,7 +8044,7 @@ __metadata: react: 17.x || 18.x react-dom: 17.x || 18.x wagmi: 0.12.x - checksum: f2ac12b7229542bd54d51e650d7fd9f3f64e969b1fb868056d19ec31e5caa532106b5553c694c4a681e22993c6db7d8d3e7978907d0f81b4ea7f8905e21049b7 + checksum: b54fc6b7267be17d3fff69cb51f38d8537ca40d4611fb2685bb41501cac05b77e91cc35c1b5542958aae2d6d0bc1f987210ca3951b7e55e88da7b417f90195a0 languageName: node linkType: hard @@ -10412,7 +10424,7 @@ __metadata: languageName: node linkType: hard -"ethers@npm:5.7.2, ethers@npm:^5.4.3, ethers@npm:^5.7.2": +"ethers@npm:5.7.2, ethers@npm:^5.4.3, ethers@npm:^5.7.0, ethers@npm:^5.7.2": version: 5.7.2 resolution: "ethers@npm:5.7.2" dependencies: @@ -22795,7 +22807,7 @@ __metadata: languageName: node linkType: hard -"wagmi@npm:0.12.13": +"wagmi@npm:^0.12.0": version: 0.12.13 resolution: "wagmi@npm:0.12.13" dependencies: