From 0b037598db3740521532c37ab116b3b4ea258dc6 Mon Sep 17 00:00:00 2001 From: Kirill Klimenko Date: Wed, 28 Feb 2024 00:56:13 +0100 Subject: [PATCH 01/56] Implement fetching coin prices from CoinGecko --- .env | 2 + .../DAOTreasury/hooks/useFormatCoins.tsx | 74 +++++++++++++------ src/providers/App/hooks/useCoinGeckoAPI.ts | 41 ++++++++++ 3 files changed, 95 insertions(+), 22 deletions(-) create mode 100644 src/providers/App/hooks/useCoinGeckoAPI.ts diff --git a/.env b/.env index af5b17617d..da7dca55c4 100644 --- a/.env +++ b/.env @@ -13,5 +13,7 @@ NEXT_PUBLIC_INFURA_IPFS_API_SECRET="" # site preview links NEXT_PUBLIC_SITE_URL="https://app.dev.fractalframework.xyz/" NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID="" +# CoinGecko API key +NEXT_PUBLIC_COINGECKO_API_KEY="" # Shutter Public Key NEXT_PUBLIC_SHUTTER_EON_PUBKEY=0x0e6493bbb4ee8b19aa9b70367685049ff01dc9382c46aed83f8bc07d2a5ba3e6030bd83b942c1fd3dff5b79bef3b40bf6b666e51e7f0be14ed62daaffad47435265f5c9403b1a801921981f7d8659a9bd91fe92fb1cf9afdb16178a532adfaf51a237103874bb03afafe9cab2118dae1be5f08a0a28bf488c1581e9db4bc23ca \ No newline at end of file diff --git a/src/components/pages/DAOTreasury/hooks/useFormatCoins.tsx b/src/components/pages/DAOTreasury/hooks/useFormatCoins.tsx index 3ea1a5abd2..54eae30ce8 100644 --- a/src/components/pages/DAOTreasury/hooks/useFormatCoins.tsx +++ b/src/components/pages/DAOTreasury/hooks/useFormatCoins.tsx @@ -1,5 +1,7 @@ import { SafeBalanceUsdResponse } from '@safe-global/safe-service-client'; -import { ethers } from 'ethers'; +import { BigNumber, ethers } from 'ethers'; +import { useEffect, useState } from 'react'; +import useCoinGeckoAPI from '../../../../providers/App/hooks/useCoinGeckoAPI'; import { useNetworkConfig } from '../../../../providers/NetworkConfig/NetworkConfigProvider'; import { formatCoin, formatUSD } from '../../../../utils/numberFormats'; @@ -17,27 +19,55 @@ export interface TokenDisplayData { export function useFormatCoins(assets: SafeBalanceUsdResponse[]) { const { nativeTokenSymbol, nativeTokenIcon } = useNetworkConfig(); - let totalFiatValue = 0; - let displayData: TokenDisplayData[] = []; - for (let i = 0; i < assets.length; i++) { - let asset = assets[i]; - if (asset.balance === '0') continue; - totalFiatValue += Number(asset.fiatBalance); - let symbol = asset.token === null ? nativeTokenSymbol : asset.token.symbol; - const formatted: TokenDisplayData = { - iconUri: asset.token === null ? nativeTokenIcon : asset.token.logoUri, - address: asset.tokenAddress === null ? ethers.constants.AddressZero : asset.tokenAddress, - truncatedCoinTotal: formatCoin(asset.balance, true, asset?.token?.decimals, symbol), - fiatValue: Number(asset.fiatBalance), - symbol: symbol, - fiatConversion: `1 ${symbol} = ${formatUSD(Number(asset.fiatConversion))}`, - fullCoinTotal: formatCoin(asset.balance, false, asset?.token?.decimals, symbol), - fiatDisplayValue: formatUSD(Number(asset.fiatBalance)), - rawValue: asset.balance, - }; - displayData.push(formatted); - } - displayData.sort((a, b) => b.fiatValue - a.fiatValue); // sort by USD value + const [totalFiatValue, setTotalFiatValue] = useState(0); + const [displayData, setDisplayData] = useState([]); + const { getTokenPrices } = useCoinGeckoAPI(); + + useEffect(() => { + async function loadDisplayData() { + let newTotalFiatValue = 0; + let newDisplayData = []; + const tokenPrices = await getTokenPrices(assets); + for (let i = 0; i < assets.length; i++) { + let asset = assets[i]; + if (asset.balance === '0') continue; + const tokenPrice = asset.tokenAddress + ? tokenPrices[asset.tokenAddress.toLowerCase()]?.usd + : tokenPrices.ethereum?.usd; + + let tokenFiatBalance = 0; + if (tokenPrice && asset.balance) { + const numerator = 1000000000; + tokenFiatBalance = + BigNumber.from(asset.balance) + .mul(Math.round(tokenPrice * numerator)) // We'll be loosing precision with super small prices like for meme coins. But that shouldn't be awfully off - maybe up to 1% + .div(BigNumber.from(10).pow(asset.token?.decimals || 18)) + .toNumber() / numerator; + newTotalFiatValue += tokenFiatBalance; + } + + let symbol = asset.token === null ? nativeTokenSymbol : asset.token.symbol; + const formatted: TokenDisplayData = { + iconUri: asset.token === null ? nativeTokenIcon : asset.token.logoUri, + address: asset.tokenAddress === null ? ethers.constants.AddressZero : asset.tokenAddress, + truncatedCoinTotal: formatCoin(asset.balance, true, asset?.token?.decimals, symbol), + fiatValue: tokenFiatBalance, + symbol: symbol, + fiatConversion: tokenPrice ? `1 ${symbol} = ${formatUSD(tokenPrice)}` : 'N/A', + fullCoinTotal: formatCoin(asset.balance, false, asset?.token?.decimals, symbol), + fiatDisplayValue: formatUSD(tokenFiatBalance), + rawValue: asset.balance, + }; + newDisplayData.push(formatted); + } + newDisplayData.sort((a, b) => b.fiatValue - a.fiatValue); // sort by USD value + setTotalFiatValue(newTotalFiatValue); + setDisplayData(newDisplayData); + } + + loadDisplayData(); + }, [assets, nativeTokenIcon, nativeTokenSymbol, getTokenPrices]); + return { totalFiatValue, displayData, diff --git a/src/providers/App/hooks/useCoinGeckoAPI.ts b/src/providers/App/hooks/useCoinGeckoAPI.ts new file mode 100644 index 0000000000..8adf9ff096 --- /dev/null +++ b/src/providers/App/hooks/useCoinGeckoAPI.ts @@ -0,0 +1,41 @@ +import { SafeBalanceUsdResponse } from '@safe-global/safe-service-client'; +import { useCallback } from 'react'; +import { useNetworkConfig } from '../../NetworkConfig/NetworkConfigProvider'; + +const PUBLIC_DEMO_API_BASE_URL = 'https://api.coingecko.com/api/v3/'; +const AUTH_QUERY_PARAM = `?x_cg_demo_api_key=${process.env.NEXT_PUBLIC_COINGECKO_API_KEY}`; + +export default function useCoinGeckoAPI() { + const { chainId } = useNetworkConfig(); + + const getTokenPrices = useCallback( + async (tokens: SafeBalanceUsdResponse[]) => { + if (chainId !== 1) { + // Support only mainnet for now. CoinGecko does not support Sepolia (obviously, I guess :D) and we don't want to burn API credits to "simulate" prices display + return; + } + + const tokenPricesUrl = `${PUBLIC_DEMO_API_BASE_URL}simple/token_price/ethereum/${AUTH_QUERY_PARAM}&vs_currencies=usd&contract_addresses=${tokens + .filter(token => token.balance !== '0' && token.tokenAddress) + .map(token => token.tokenAddress) + .join(',')}`; + + const tokenPricesResponse = await fetch(tokenPricesUrl); + + const ethAsset = tokens.find(token => !token.tokenAddress); + if (ethAsset) { + // Unfortunately, there's no way avoiding 2 requests. We either need to fetch asset IDs from CoinGecko for given token contract addresses + // And then use this endpoint to get all the prices. But that brings us way more bandwidth + // Or, we are doing this "hardcoded" call for ETH price. But our request for token prices simpler. + const ethPriceUrl = `${PUBLIC_DEMO_API_BASE_URL}simple/price${AUTH_QUERY_PARAM}&ids=ethereum&vs_currencies=usd`; + const ethPriceResponse = await fetch(ethPriceUrl); + + return { ...(await tokenPricesResponse.json()), ...(await ethPriceResponse.json()) }; + } + + return tokenPricesResponse.json(); + }, + [chainId] + ); + return { getTokenPrices }; +} From 2942280913545e56cb1120d92f56eac2f203e15e Mon Sep 17 00:00:00 2001 From: Kirill Klimenko Date: Wed, 28 Feb 2024 01:00:47 +0100 Subject: [PATCH 02/56] Leverage useFormatCoins for showing treasury total on DAO dashboard page --- .../DAOTreasury/hooks/useTreasuryTotalUSD.tsx | 14 -------------- .../pages/DaoDashboard/Info/InfoTreasury.tsx | 11 ++++++----- 2 files changed, 6 insertions(+), 19 deletions(-) delete mode 100644 src/components/pages/DAOTreasury/hooks/useTreasuryTotalUSD.tsx diff --git a/src/components/pages/DAOTreasury/hooks/useTreasuryTotalUSD.tsx b/src/components/pages/DAOTreasury/hooks/useTreasuryTotalUSD.tsx deleted file mode 100644 index f7e0b711fa..0000000000 --- a/src/components/pages/DAOTreasury/hooks/useTreasuryTotalUSD.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { useMemo } from 'react'; -import { useFractal } from '../../../../providers/App/AppProvider'; -import { formatUSD } from '../../../../utils/numberFormats'; - -export function useTreasuryTotalUSD(): string { - const { - treasury: { assetsFungible }, - } = useFractal(); - return useMemo(() => { - return formatUSD( - assetsFungible.reduce((prev, asset) => (prev += Number(asset.fiatBalance)), 0) - ); - }, [assetsFungible]); -} diff --git a/src/components/pages/DaoDashboard/Info/InfoTreasury.tsx b/src/components/pages/DaoDashboard/Info/InfoTreasury.tsx index 4a5c4a61f3..9ff2cebb6b 100644 --- a/src/components/pages/DaoDashboard/Info/InfoTreasury.tsx +++ b/src/components/pages/DaoDashboard/Info/InfoTreasury.tsx @@ -3,18 +3,19 @@ import { Treasury } from '@decent-org/fractal-ui'; import { useTranslation } from 'react-i18next'; import { useFractal } from '../../../../providers/App/AppProvider'; import { BarLoader } from '../../../ui/loaders/BarLoader'; -import { useTreasuryTotalUSD } from '../../DAOTreasury/hooks/useTreasuryTotalUSD'; +import { useFormatCoins } from '../../DAOTreasury/hooks/useFormatCoins'; interface IDAOGovernance {} export function InfoTreasury({}: IDAOGovernance) { - const { t } = useTranslation('dashboard'); - const totalUSD = useTreasuryTotalUSD(); - const { node: { daoAddress }, + treasury: { assetsFungible }, } = useFractal(); + const { t } = useTranslation('dashboard'); + const { totalFiatValue } = useFormatCoins(assetsFungible); + if (!daoAddress) { return ( - {totalUSD} + {totalFiatValue} ); From 22b4d13a5893cc6cb9e65508915ad6077dba302b Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Wed, 28 Feb 2024 08:59:07 -0500 Subject: [PATCH 03/56] empty commit From 3bc074c2435c0046224baf5e699942134e14bba8 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Fri, 1 Mar 2024 16:55:29 -0500 Subject: [PATCH 04/56] The ProposalActions buttons, which show up in Proposal list cards, no longer needs to be a link --- .../ProposalActions/ProposalAction.tsx | 27 ++----------------- 1 file changed, 2 insertions(+), 25 deletions(-) diff --git a/src/components/Proposals/ProposalActions/ProposalAction.tsx b/src/components/Proposals/ProposalActions/ProposalAction.tsx index 42f372f945..1befef8f54 100644 --- a/src/components/Proposals/ProposalActions/ProposalAction.tsx +++ b/src/components/Proposals/ProposalActions/ProposalAction.tsx @@ -1,9 +1,7 @@ import { Button, Flex, Text } from '@chakra-ui/react'; -import Link from 'next/link'; import { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { BACKGROUND_SEMI_TRANSPARENT } from '../../../constants/common'; -import { DAO_ROUTES } from '../../../constants/routes'; import useSnapshotProposal from '../../../hooks/DAO/loaders/snapshot/useSnapshotProposal'; import { useFractal } from '../../../providers/App/AppProvider'; import { ExtendedSnapshotProposal, FractalProposal, FractalProposalState } from '../../../types'; @@ -52,7 +50,6 @@ export function ProposalAction({ onCastSnapshotVote?: () => Promise; }) { const { - node: { daoAddress }, readOnly: { user, dao }, } = useFractal(); const { t } = useTranslation(); @@ -102,15 +99,7 @@ export function ProposalAction({ if (!showActionButton) { if (!expandedView) { - return ( - - ); + return ; } // This means that Proposal in state where there's no action to perform return null; @@ -139,17 +128,5 @@ export function ProposalAction({ ); } - return ( - - - - ); + return ; } From e1f77e1214762cc787998849d100c747ff72aba3 Mon Sep 17 00:00:00 2001 From: Kirill Klimenko Date: Mon, 4 Mar 2024 17:39:16 +0100 Subject: [PATCH 05/56] Add Netlify function tokenPrices, route request to CoinGecko, cache token prices through Netlify blob --- .env | 2 +- netlify/functions/tokensPrices.mts | 84 +++++++++++++++++++ package-lock.json | 80 ++++++++++++++++++ package.json | 2 + .../DAOTreasury/hooks/useFormatCoins.tsx | 2 +- src/i18n/locales/en/treasury.json | 3 +- src/providers/App/hooks/useCoinGeckoAPI.ts | 41 --------- src/providers/App/hooks/usePriceAPI.ts | 46 ++++++++++ tsconfig.json | 1 + 9 files changed, 217 insertions(+), 44 deletions(-) create mode 100644 netlify/functions/tokensPrices.mts delete mode 100644 src/providers/App/hooks/useCoinGeckoAPI.ts create mode 100644 src/providers/App/hooks/usePriceAPI.ts diff --git a/.env b/.env index 53e8a00d95..c19204e742 100644 --- a/.env +++ b/.env @@ -17,6 +17,6 @@ NEXT_PUBLIC_SITE_URL="https://app.dev.fractalframework.xyz/" # WalletConnect Cloud Project ID NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID="" # CoinGecko API key -NEXT_PUBLIC_COINGECKO_API_KEY="" +COINGECKO_API_KEY="" # Shutter Public Key NEXT_PUBLIC_SHUTTER_EON_PUBKEY=0x0e6493bbb4ee8b19aa9b70367685049ff01dc9382c46aed83f8bc07d2a5ba3e6030bd83b942c1fd3dff5b79bef3b40bf6b666e51e7f0be14ed62daaffad47435265f5c9403b1a801921981f7d8659a9bd91fe92fb1cf9afdb16178a532adfaf51a237103874bb03afafe9cab2118dae1be5f08a0a28bf488c1581e9db4bc23ca \ No newline at end of file diff --git a/netlify/functions/tokensPrices.mts b/netlify/functions/tokensPrices.mts new file mode 100644 index 0000000000..d0980462f4 --- /dev/null +++ b/netlify/functions/tokensPrices.mts @@ -0,0 +1,84 @@ +import { getStore } from '@netlify/blobs'; + +type TokenPriceMetadata = { + expiration: number; +}; + +export default async function getTokenprices(request: Request) { + const store = getStore('fractal-token-prices-store'); + const tokensString = new URL(request.url).searchParams.get('tokens'); + + if (!tokensString) { + Response.json({ error: 'Tokens to request were not provided' }); + } + const tokens = tokensString!.split(','); + try { + const now = new Date().getTime(); + const cachedPrices = await Promise.all( + tokens.map(tokenAddress => store.getWithMetadata(tokenAddress, { type: 'json' })) + ); + const expiredPrices: string[] = []; + const cachedUnexpiredPrices = cachedPrices + .filter(tokenPrice => { + if (tokenPrice && (tokenPrice?.metadata as any as TokenPriceMetadata).expiration <= now) { + expiredPrices.push(tokenPrice.data.tokenAddress); + return false; + } + return tokenPrice; + }) + .map(tokenPrice => ({ + tokenAddress: tokenPrice?.data.tokenAddress, + price: tokenPrice?.data.price, + })); + const nonCachedTokensAddresses = tokens.filter( + address => !cachedUnexpiredPrices.find(tokenPrice => tokenPrice.tokenAddress === address) + ); + const responseBody: Record = {}; + cachedUnexpiredPrices.forEach(tokenPrice => { + responseBody[tokenPrice.tokenAddress] = tokenPrice.price; + }); + if (nonCachedTokensAddresses.length === 0) { + return Response.json({ data: responseBody }); + } + if (!process.env.COINGECKO_API_KEY) { + console.error('CoinGecko API key is missing'); + return { error: 'Unknown error while fetching prices' }; + } + const PUBLIC_DEMO_API_BASE_URL = 'https://api.coingecko.com/api/v3/'; + const AUTH_QUERY_PARAM = `?x_cg_demo_api_key=${process.env.COINGECKO_API_KEY}`; + const tokenPricesUrl = `${PUBLIC_DEMO_API_BASE_URL}simple/token_price/ethereum/${AUTH_QUERY_PARAM}&vs_currencies=usd&contract_addresses=${nonCachedTokensAddresses.join( + ',' + )}`; + + const tokenPricesResponse = await fetch(tokenPricesUrl); + const tokenPricesResponseJson = await tokenPricesResponse.json(); + const tokenPriceMetadata = { metadata: { expiration: now + 1000 * 60 * 30 } }; + Object.keys(tokenPricesResponseJson).forEach(tokenAddress => { + const price = tokenPricesResponseJson[tokenAddress]; + responseBody[tokenAddress] = price; + store.setJSON(tokenAddress, { tokenAddress, price }, tokenPriceMetadata); + }); + + const ethAsset = nonCachedTokensAddresses.find(token => token === 'ethereum'); + if (ethAsset) { + // Unfortunately, there's no way avoiding 2 requests. We either need to fetch asset IDs from CoinGecko for given token contract addresses + // And then use this endpoint to get all the prices. But that brings us way more bandwidth + // Or, we are doing this "hardcoded" call for ETH price. But our request for token prices simpler. + const ethPriceUrl = `${PUBLIC_DEMO_API_BASE_URL}simple/price${AUTH_QUERY_PARAM}&ids=ethereum&vs_currencies=usd`; + const ethPriceResponse = await fetch(ethPriceUrl); + const ethPriceResponseJson = await ethPriceResponse.json(); + store.setJSON( + 'ethereum', + { tokenAddress: 'ethereum', price: ethPriceResponseJson.ethereum }, + tokenPriceMetadata + ); + responseBody.ethereum = ethPriceResponseJson.ethereum; + } + return Response.json({ data: responseBody }); + } catch (e) { + console.error('Error while fetching prices', e); + return Response.json({ + error: 'Unknown error while fetching prices', + }); + } +} diff --git a/package-lock.json b/package-lock.json index 1a43bbbad3..0adc786730 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,8 @@ "@fractal-framework/fractal-contracts": "^0.4.0", "@graphprotocol/client-apollo": "^1.0.16", "@lido-sdk/contracts": "^3.0.2", + "@netlify/blobs": "^6.5.0", + "@netlify/functions": "^2.6.0", "@rainbow-me/rainbowkit": "^1.3.3", "@safe-global/safe-deployments": "^1.23.0", "@safe-global/safe-ethers-lib": "^1.9.2", @@ -8100,6 +8102,50 @@ "tslib": "^2.3.1" } }, + "node_modules/@netlify/blobs": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@netlify/blobs/-/blobs-6.5.0.tgz", + "integrity": "sha512-wRFlNnL/Qv3WNLZd3OT/YYqF1zb6iPSo8T31sl9ccL1ahBxW1fBqKgF4b1XL7Z+6mRIkatvcsVPkWBcO+oJMNA==", + "engines": { + "node": "^14.16.0 || >=16.0.0" + } + }, + "node_modules/@netlify/functions": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@netlify/functions/-/functions-2.6.0.tgz", + "integrity": "sha512-vU20tij0fb4nRGACqb+5SQvKd50JYyTyEhQetCMHdakcJFzjLDivvRR16u1G2Oy4A7xNAtGJF1uz8reeOtTVcQ==", + "dependencies": { + "@netlify/serverless-functions-api": "1.14.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@netlify/node-cookies": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@netlify/node-cookies/-/node-cookies-0.1.0.tgz", + "integrity": "sha512-OAs1xG+FfLX0LoRASpqzVntVV/RpYkgpI0VrUnw2u0Q1qiZUzcPffxRK8HF3gc4GjuhG5ahOEMJ9bswBiZPq0g==", + "engines": { + "node": "^14.16.0 || >=16.0.0" + } + }, + "node_modules/@netlify/serverless-functions-api": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@netlify/serverless-functions-api/-/serverless-functions-api-1.14.0.tgz", + "integrity": "sha512-HUNETLNvNiC2J+SB/YuRwJA9+agPrc0azSoWVk8H85GC+YE114hcS5JW+dstpKwVerp2xILE3vNWN7IMXP5Q5Q==", + "dependencies": { + "@netlify/node-cookies": "^0.1.0", + "urlpattern-polyfill": "8.0.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + } + }, + "node_modules/@netlify/serverless-functions-api/node_modules/urlpattern-polyfill": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-8.0.2.tgz", + "integrity": "sha512-Qp95D4TPJl1kC9SKigDcqgyM2VDVO4RiJc2d4qe5GrYm+zbIQCWWKAFaJNQ4BhdFeDGwBmAxqJBwWSJDb9T3BQ==" + }, "node_modules/@next/env": { "version": "13.3.0", "resolved": "https://registry.npmjs.org/@next/env/-/env-13.3.0.tgz", @@ -34261,6 +34307,40 @@ } } }, + "@netlify/blobs": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@netlify/blobs/-/blobs-6.5.0.tgz", + "integrity": "sha512-wRFlNnL/Qv3WNLZd3OT/YYqF1zb6iPSo8T31sl9ccL1ahBxW1fBqKgF4b1XL7Z+6mRIkatvcsVPkWBcO+oJMNA==" + }, + "@netlify/functions": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@netlify/functions/-/functions-2.6.0.tgz", + "integrity": "sha512-vU20tij0fb4nRGACqb+5SQvKd50JYyTyEhQetCMHdakcJFzjLDivvRR16u1G2Oy4A7xNAtGJF1uz8reeOtTVcQ==", + "requires": { + "@netlify/serverless-functions-api": "1.14.0" + } + }, + "@netlify/node-cookies": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@netlify/node-cookies/-/node-cookies-0.1.0.tgz", + "integrity": "sha512-OAs1xG+FfLX0LoRASpqzVntVV/RpYkgpI0VrUnw2u0Q1qiZUzcPffxRK8HF3gc4GjuhG5ahOEMJ9bswBiZPq0g==" + }, + "@netlify/serverless-functions-api": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@netlify/serverless-functions-api/-/serverless-functions-api-1.14.0.tgz", + "integrity": "sha512-HUNETLNvNiC2J+SB/YuRwJA9+agPrc0azSoWVk8H85GC+YE114hcS5JW+dstpKwVerp2xILE3vNWN7IMXP5Q5Q==", + "requires": { + "@netlify/node-cookies": "^0.1.0", + "urlpattern-polyfill": "8.0.2" + }, + "dependencies": { + "urlpattern-polyfill": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-8.0.2.tgz", + "integrity": "sha512-Qp95D4TPJl1kC9SKigDcqgyM2VDVO4RiJc2d4qe5GrYm+zbIQCWWKAFaJNQ4BhdFeDGwBmAxqJBwWSJDb9T3BQ==" + } + } + }, "@next/env": { "version": "13.3.0", "resolved": "https://registry.npmjs.org/@next/env/-/env-13.3.0.tgz", diff --git a/package.json b/package.json index 636560cb9d..5683cd58a7 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,8 @@ "@fractal-framework/fractal-contracts": "^0.4.0", "@graphprotocol/client-apollo": "^1.0.16", "@lido-sdk/contracts": "^3.0.2", + "@netlify/blobs": "^6.5.0", + "@netlify/functions": "^2.6.0", "@rainbow-me/rainbowkit": "^1.3.3", "@safe-global/safe-deployments": "^1.23.0", "@safe-global/safe-ethers-lib": "^1.9.2", diff --git a/src/components/pages/DAOTreasury/hooks/useFormatCoins.tsx b/src/components/pages/DAOTreasury/hooks/useFormatCoins.tsx index 54eae30ce8..df02f84c12 100644 --- a/src/components/pages/DAOTreasury/hooks/useFormatCoins.tsx +++ b/src/components/pages/DAOTreasury/hooks/useFormatCoins.tsx @@ -1,7 +1,7 @@ import { SafeBalanceUsdResponse } from '@safe-global/safe-service-client'; import { BigNumber, ethers } from 'ethers'; import { useEffect, useState } from 'react'; -import useCoinGeckoAPI from '../../../../providers/App/hooks/useCoinGeckoAPI'; +import useCoinGeckoAPI from '../../../../providers/App/hooks/usePriceAPI'; import { useNetworkConfig } from '../../../../providers/NetworkConfig/NetworkConfigProvider'; import { formatCoin, formatUSD } from '../../../../utils/numberFormats'; diff --git a/src/i18n/locales/en/treasury.json b/src/i18n/locales/en/treasury.json index f32ae46a59..a31ad81fdb 100644 --- a/src/i18n/locales/en/treasury.json +++ b/src/i18n/locales/en/treasury.json @@ -21,5 +21,6 @@ "stake": "Stake", "unstake": "Unstake", "claimUnstakedETH": "Claim ETH", - "nonClaimableYet": "Your ETH cannot yet be claimed from Lido" + "nonClaimableYet": "Your ETH cannot yet be claimed from Lido", + "tokenPriceFetchingError": "There was an error obtaining token prices. Please, try visiting Treasury page later." } \ No newline at end of file diff --git a/src/providers/App/hooks/useCoinGeckoAPI.ts b/src/providers/App/hooks/useCoinGeckoAPI.ts deleted file mode 100644 index 8adf9ff096..0000000000 --- a/src/providers/App/hooks/useCoinGeckoAPI.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { SafeBalanceUsdResponse } from '@safe-global/safe-service-client'; -import { useCallback } from 'react'; -import { useNetworkConfig } from '../../NetworkConfig/NetworkConfigProvider'; - -const PUBLIC_DEMO_API_BASE_URL = 'https://api.coingecko.com/api/v3/'; -const AUTH_QUERY_PARAM = `?x_cg_demo_api_key=${process.env.NEXT_PUBLIC_COINGECKO_API_KEY}`; - -export default function useCoinGeckoAPI() { - const { chainId } = useNetworkConfig(); - - const getTokenPrices = useCallback( - async (tokens: SafeBalanceUsdResponse[]) => { - if (chainId !== 1) { - // Support only mainnet for now. CoinGecko does not support Sepolia (obviously, I guess :D) and we don't want to burn API credits to "simulate" prices display - return; - } - - const tokenPricesUrl = `${PUBLIC_DEMO_API_BASE_URL}simple/token_price/ethereum/${AUTH_QUERY_PARAM}&vs_currencies=usd&contract_addresses=${tokens - .filter(token => token.balance !== '0' && token.tokenAddress) - .map(token => token.tokenAddress) - .join(',')}`; - - const tokenPricesResponse = await fetch(tokenPricesUrl); - - const ethAsset = tokens.find(token => !token.tokenAddress); - if (ethAsset) { - // Unfortunately, there's no way avoiding 2 requests. We either need to fetch asset IDs from CoinGecko for given token contract addresses - // And then use this endpoint to get all the prices. But that brings us way more bandwidth - // Or, we are doing this "hardcoded" call for ETH price. But our request for token prices simpler. - const ethPriceUrl = `${PUBLIC_DEMO_API_BASE_URL}simple/price${AUTH_QUERY_PARAM}&ids=ethereum&vs_currencies=usd`; - const ethPriceResponse = await fetch(ethPriceUrl); - - return { ...(await tokenPricesResponse.json()), ...(await ethPriceResponse.json()) }; - } - - return tokenPricesResponse.json(); - }, - [chainId] - ); - return { getTokenPrices }; -} diff --git a/src/providers/App/hooks/usePriceAPI.ts b/src/providers/App/hooks/usePriceAPI.ts new file mode 100644 index 0000000000..788cb7af90 --- /dev/null +++ b/src/providers/App/hooks/usePriceAPI.ts @@ -0,0 +1,46 @@ +import { SafeBalanceUsdResponse } from '@safe-global/safe-service-client'; +import { useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; +import { toast } from 'react-toastify'; +import { logError } from '../../../helpers/errorLogging'; +import { useNetworkConfig } from '../../NetworkConfig/NetworkConfigProvider'; + +export default function usePriceAPI() { + const { chainId } = useNetworkConfig(); + const { t } = useTranslation('treasury'); + + const getTokenPrices = useCallback( + async (tokens: SafeBalanceUsdResponse[]) => { + if (chainId !== 1) { + // Support only mainnet for now. CoinGecko does not support Sepolia (obviously, I guess :D) and we don't want to burn API credits to "simulate" prices display + return; + } + + try { + const tokensAddresses = tokens + .filter(token => token.balance !== '0' && !!token.tokenAddress) + .map(token => token.tokenAddress); + const ethAsset = tokens.find(token => !token.tokenAddress); + if (ethAsset) { + tokensAddresses.push('ethereum'); + } + const pricesResponse = await fetch( + `/.netlify/functions/tokensPrices?tokens=${tokensAddresses.join(',')}` + ); + + const pricesResponseBody = await pricesResponse.json(); + if (pricesResponseBody.error) { + // We don't need to log error here as it is supposed to be logged through Netlify function anyway + toast.warning(t('tokenPriceFetchingError')); + } else { + return pricesResponseBody.data; + } + } catch (e) { + logError('Error while getting tokens prices', e); + return; + } + }, + [chainId, t] + ); + return { getTokenPrices }; +} diff --git a/tsconfig.json b/tsconfig.json index ca09d9c784..9ee82e7be9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -34,6 +34,7 @@ "tests", "test", "app", + "netlify/functions", ".next/types/**/*.ts", "next-env.d.ts", "next.config.js", From 2d6de296eb844007f8e633d43240615b20a059ea Mon Sep 17 00:00:00 2001 From: Kirill Klimenko Date: Mon, 4 Mar 2024 17:58:25 +0100 Subject: [PATCH 06/56] Refactor and cleanup of storing/getting prices --- netlify/functions/tokensPrices.mts | 16 ++++++---------- .../pages/DAOTreasury/hooks/useFormatCoins.tsx | 14 +++++++------- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/netlify/functions/tokensPrices.mts b/netlify/functions/tokensPrices.mts index d0980462f4..c40d2f7d6a 100644 --- a/netlify/functions/tokensPrices.mts +++ b/netlify/functions/tokensPrices.mts @@ -17,15 +17,11 @@ export default async function getTokenprices(request: Request) { const cachedPrices = await Promise.all( tokens.map(tokenAddress => store.getWithMetadata(tokenAddress, { type: 'json' })) ); - const expiredPrices: string[] = []; const cachedUnexpiredPrices = cachedPrices - .filter(tokenPrice => { - if (tokenPrice && (tokenPrice?.metadata as any as TokenPriceMetadata).expiration <= now) { - expiredPrices.push(tokenPrice.data.tokenAddress); - return false; - } - return tokenPrice; - }) + .filter( + tokenPrice => + tokenPrice && (tokenPrice?.metadata as any as TokenPriceMetadata).expiration <= now + ) .map(tokenPrice => ({ tokenAddress: tokenPrice?.data.tokenAddress, price: tokenPrice?.data.price, @@ -54,7 +50,7 @@ export default async function getTokenprices(request: Request) { const tokenPricesResponseJson = await tokenPricesResponse.json(); const tokenPriceMetadata = { metadata: { expiration: now + 1000 * 60 * 30 } }; Object.keys(tokenPricesResponseJson).forEach(tokenAddress => { - const price = tokenPricesResponseJson[tokenAddress]; + const price = tokenPricesResponseJson[tokenAddress].usd; responseBody[tokenAddress] = price; store.setJSON(tokenAddress, { tokenAddress, price }, tokenPriceMetadata); }); @@ -69,7 +65,7 @@ export default async function getTokenprices(request: Request) { const ethPriceResponseJson = await ethPriceResponse.json(); store.setJSON( 'ethereum', - { tokenAddress: 'ethereum', price: ethPriceResponseJson.ethereum }, + { tokenAddress: 'ethereum', price: ethPriceResponseJson.ethereum.usd }, tokenPriceMetadata ); responseBody.ethereum = ethPriceResponseJson.ethereum; diff --git a/src/components/pages/DAOTreasury/hooks/useFormatCoins.tsx b/src/components/pages/DAOTreasury/hooks/useFormatCoins.tsx index df02f84c12..d05060cd19 100644 --- a/src/components/pages/DAOTreasury/hooks/useFormatCoins.tsx +++ b/src/components/pages/DAOTreasury/hooks/useFormatCoins.tsx @@ -1,7 +1,7 @@ import { SafeBalanceUsdResponse } from '@safe-global/safe-service-client'; import { BigNumber, ethers } from 'ethers'; import { useEffect, useState } from 'react'; -import useCoinGeckoAPI from '../../../../providers/App/hooks/usePriceAPI'; +import usePriceAPI from '../../../../providers/App/hooks/usePriceAPI'; import { useNetworkConfig } from '../../../../providers/NetworkConfig/NetworkConfigProvider'; import { formatCoin, formatUSD } from '../../../../utils/numberFormats'; @@ -21,7 +21,7 @@ export function useFormatCoins(assets: SafeBalanceUsdResponse[]) { const { nativeTokenSymbol, nativeTokenIcon } = useNetworkConfig(); const [totalFiatValue, setTotalFiatValue] = useState(0); const [displayData, setDisplayData] = useState([]); - const { getTokenPrices } = useCoinGeckoAPI(); + const { getTokenPrices } = usePriceAPI(); useEffect(() => { async function loadDisplayData() { @@ -32,17 +32,17 @@ export function useFormatCoins(assets: SafeBalanceUsdResponse[]) { let asset = assets[i]; if (asset.balance === '0') continue; const tokenPrice = asset.tokenAddress - ? tokenPrices[asset.tokenAddress.toLowerCase()]?.usd - : tokenPrices.ethereum?.usd; + ? tokenPrices[asset.tokenAddress.toLowerCase()] + : tokenPrices.ethereum; let tokenFiatBalance = 0; if (tokenPrice && asset.balance) { - const numerator = 1000000000; + const multiplicator = 1000000000; tokenFiatBalance = BigNumber.from(asset.balance) - .mul(Math.round(tokenPrice * numerator)) // We'll be loosing precision with super small prices like for meme coins. But that shouldn't be awfully off - maybe up to 1% + .mul(Math.round(tokenPrice * multiplicator)) // We'll be loosing precision with super small prices like for meme coins. But that shouldn't be awfully off - maybe up to 1% .div(BigNumber.from(10).pow(asset.token?.decimals || 18)) - .toNumber() / numerator; + .toNumber() / multiplicator; newTotalFiatValue += tokenFiatBalance; } From ea7aa8f49fb8e6f369aad2d665194a5b3485dd1e Mon Sep 17 00:00:00 2001 From: Kirill Klimenko Date: Mon, 4 Mar 2024 18:10:36 +0100 Subject: [PATCH 07/56] Fix returningprice for ETH --- netlify/functions/tokensPrices.mts | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/netlify/functions/tokensPrices.mts b/netlify/functions/tokensPrices.mts index c40d2f7d6a..3c6fc40dd4 100644 --- a/netlify/functions/tokensPrices.mts +++ b/netlify/functions/tokensPrices.mts @@ -62,13 +62,9 @@ export default async function getTokenprices(request: Request) { // Or, we are doing this "hardcoded" call for ETH price. But our request for token prices simpler. const ethPriceUrl = `${PUBLIC_DEMO_API_BASE_URL}simple/price${AUTH_QUERY_PARAM}&ids=ethereum&vs_currencies=usd`; const ethPriceResponse = await fetch(ethPriceUrl); - const ethPriceResponseJson = await ethPriceResponse.json(); - store.setJSON( - 'ethereum', - { tokenAddress: 'ethereum', price: ethPriceResponseJson.ethereum.usd }, - tokenPriceMetadata - ); - responseBody.ethereum = ethPriceResponseJson.ethereum; + const price = (await ethPriceResponse.json()).ethereum.usd; + store.setJSON('ethereum', { tokenAddress: 'ethereum', price }, tokenPriceMetadata); + responseBody.ethereum = price; } return Response.json({ data: responseBody }); } catch (e) { From c5b477af3fc8d93c9d7dc68d3613ed9d2bea3ccf Mon Sep 17 00:00:00 2001 From: Kirill Klimenko Date: Mon, 4 Mar 2024 19:00:46 +0100 Subject: [PATCH 08/56] Try fixing token fiat balance overflow --- .../pages/DAOTreasury/hooks/useFormatCoins.tsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/components/pages/DAOTreasury/hooks/useFormatCoins.tsx b/src/components/pages/DAOTreasury/hooks/useFormatCoins.tsx index d05060cd19..ea5d47f739 100644 --- a/src/components/pages/DAOTreasury/hooks/useFormatCoins.tsx +++ b/src/components/pages/DAOTreasury/hooks/useFormatCoins.tsx @@ -38,11 +38,12 @@ export function useFormatCoins(assets: SafeBalanceUsdResponse[]) { let tokenFiatBalance = 0; if (tokenPrice && asset.balance) { const multiplicator = 1000000000; - tokenFiatBalance = - BigNumber.from(asset.balance) - .mul(Math.round(tokenPrice * multiplicator)) // We'll be loosing precision with super small prices like for meme coins. But that shouldn't be awfully off - maybe up to 1% - .div(BigNumber.from(10).pow(asset.token?.decimals || 18)) - .toNumber() / multiplicator; + const tokenFiatBalanceBn = BigNumber.from(asset.balance) + .mul(Math.round(tokenPrice * multiplicator)) // We'll be loosing precision with super small prices like for meme coins. But that shouldn't be awfully off - maybe up to 1% + .div(BigNumber.from(10).pow(asset.token?.decimals || 18)); + tokenFiatBalance = tokenFiatBalanceBn.gte(Number.MAX_SAFE_INTEGER) + ? tokenFiatBalanceBn.div(multiplicator).toNumber() + : tokenFiatBalanceBn.toNumber() / multiplicator; newTotalFiatValue += tokenFiatBalance; } From 88f356a146b2e0b36a3d867b838878e2c2c84c7b Mon Sep 17 00:00:00 2001 From: Kirill Klimenko Date: Mon, 4 Mar 2024 19:31:16 +0100 Subject: [PATCH 09/56] Use ethers.constants.MaxUint256 instead of Number max value --- .../DAOTreasury/hooks/useFormatCoins.tsx | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/components/pages/DAOTreasury/hooks/useFormatCoins.tsx b/src/components/pages/DAOTreasury/hooks/useFormatCoins.tsx index ea5d47f739..213a89b83b 100644 --- a/src/components/pages/DAOTreasury/hooks/useFormatCoins.tsx +++ b/src/components/pages/DAOTreasury/hooks/useFormatCoins.tsx @@ -1,6 +1,7 @@ import { SafeBalanceUsdResponse } from '@safe-global/safe-service-client'; -import { BigNumber, ethers } from 'ethers'; +import { BigNumber, constants } from 'ethers'; import { useEffect, useState } from 'react'; +import { logError } from '../../../../helpers/errorLogging'; import usePriceAPI from '../../../../providers/App/hooks/usePriceAPI'; import { useNetworkConfig } from '../../../../providers/NetworkConfig/NetworkConfigProvider'; import { formatCoin, formatUSD } from '../../../../utils/numberFormats'; @@ -37,20 +38,24 @@ export function useFormatCoins(assets: SafeBalanceUsdResponse[]) { let tokenFiatBalance = 0; if (tokenPrice && asset.balance) { - const multiplicator = 1000000000; - const tokenFiatBalanceBn = BigNumber.from(asset.balance) - .mul(Math.round(tokenPrice * multiplicator)) // We'll be loosing precision with super small prices like for meme coins. But that shouldn't be awfully off - maybe up to 1% - .div(BigNumber.from(10).pow(asset.token?.decimals || 18)); - tokenFiatBalance = tokenFiatBalanceBn.gte(Number.MAX_SAFE_INTEGER) - ? tokenFiatBalanceBn.div(multiplicator).toNumber() - : tokenFiatBalanceBn.toNumber() / multiplicator; - newTotalFiatValue += tokenFiatBalance; + try { + const multiplicator = 1000000000; + const tokenFiatBalanceBn = BigNumber.from(asset.balance) + .mul(Math.round(tokenPrice * multiplicator)) // We'll be loosing precision with super small prices like for meme coins. But that shouldn't be awfully off - maybe up to 1% + .div(BigNumber.from(10).pow(asset.token?.decimals || 18)); + tokenFiatBalance = tokenFiatBalanceBn.gte(constants.MaxUint256) + ? tokenFiatBalanceBn.div(multiplicator).toNumber() + : tokenFiatBalanceBn.toNumber() / multiplicator; + newTotalFiatValue += tokenFiatBalance; + } catch (e) { + logError('Error while calculating token fiat balance', e); + } } let symbol = asset.token === null ? nativeTokenSymbol : asset.token.symbol; const formatted: TokenDisplayData = { iconUri: asset.token === null ? nativeTokenIcon : asset.token.logoUri, - address: asset.tokenAddress === null ? ethers.constants.AddressZero : asset.tokenAddress, + address: asset.tokenAddress === null ? constants.AddressZero : asset.tokenAddress, truncatedCoinTotal: formatCoin(asset.balance, true, asset?.token?.decimals, symbol), fiatValue: tokenFiatBalance, symbol: symbol, From 3e5f170e24ec8e8719f977c884e777aa54e39429 Mon Sep 17 00:00:00 2001 From: Kirill Klimenko Date: Mon, 4 Mar 2024 21:10:16 +0100 Subject: [PATCH 10/56] Use smaller multiplicator --- src/components/pages/DAOTreasury/hooks/useFormatCoins.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/pages/DAOTreasury/hooks/useFormatCoins.tsx b/src/components/pages/DAOTreasury/hooks/useFormatCoins.tsx index 213a89b83b..593dbcb8fe 100644 --- a/src/components/pages/DAOTreasury/hooks/useFormatCoins.tsx +++ b/src/components/pages/DAOTreasury/hooks/useFormatCoins.tsx @@ -39,9 +39,9 @@ export function useFormatCoins(assets: SafeBalanceUsdResponse[]) { let tokenFiatBalance = 0; if (tokenPrice && asset.balance) { try { - const multiplicator = 1000000000; + const multiplicator = 10000; const tokenFiatBalanceBn = BigNumber.from(asset.balance) - .mul(Math.round(tokenPrice * multiplicator)) // We'll be loosing precision with super small prices like for meme coins. But that shouldn't be awfully off - maybe up to 1% + .mul(parseFloat(tokenPrice.toFixed(5)) * multiplicator) // We'll be loosing precision with super small prices like for meme coins. But that shouldn't be awfully off .div(BigNumber.from(10).pow(asset.token?.decimals || 18)); tokenFiatBalance = tokenFiatBalanceBn.gte(constants.MaxUint256) ? tokenFiatBalanceBn.div(multiplicator).toNumber() From 59dd0678b096abc33bf03323660b8cf6c2ef3f1a Mon Sep 17 00:00:00 2001 From: Kirill Klimenko Date: Mon, 4 Mar 2024 21:17:00 +0100 Subject: [PATCH 11/56] Add math.round --- src/components/pages/DAOTreasury/hooks/useFormatCoins.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/pages/DAOTreasury/hooks/useFormatCoins.tsx b/src/components/pages/DAOTreasury/hooks/useFormatCoins.tsx index 593dbcb8fe..e0f1f8b526 100644 --- a/src/components/pages/DAOTreasury/hooks/useFormatCoins.tsx +++ b/src/components/pages/DAOTreasury/hooks/useFormatCoins.tsx @@ -41,7 +41,7 @@ export function useFormatCoins(assets: SafeBalanceUsdResponse[]) { try { const multiplicator = 10000; const tokenFiatBalanceBn = BigNumber.from(asset.balance) - .mul(parseFloat(tokenPrice.toFixed(5)) * multiplicator) // We'll be loosing precision with super small prices like for meme coins. But that shouldn't be awfully off + .mul(Math.round(parseFloat(tokenPrice.toFixed(5)) * multiplicator)) // We'll be loosing precision with super small prices like for meme coins. But that shouldn't be awfully off .div(BigNumber.from(10).pow(asset.token?.decimals || 18)); tokenFiatBalance = tokenFiatBalanceBn.gte(constants.MaxUint256) ? tokenFiatBalanceBn.div(multiplicator).toNumber() From 969f712b96735105b4d1ad2d22ef9d96a99456f8 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Wed, 6 Mar 2024 10:04:38 -0500 Subject: [PATCH 12/56] Delete the RELEASES.md doc, it's not relevant any longer --- docs/RELEASES.md | 31 ------------------------------- 1 file changed, 31 deletions(-) delete mode 100644 docs/RELEASES.md diff --git a/docs/RELEASES.md b/docs/RELEASES.md deleted file mode 100644 index a011aa4f4f..0000000000 --- a/docs/RELEASES.md +++ /dev/null @@ -1,31 +0,0 @@ -# Fractal Release Cycle -This document details the release cycle process followed by the Fractal frontend engineering team. - -## Development environments -Fractal has three application environments: - -- [Development (dev)](https://app.dev.fractalframework.xyz/) - for daily coding PRs and immediate deployment upon a code merge. -- [Staging](https://app.staging.fractalframework.xyz/) - a more stable environment for QA engineer testing and demoing upcoming features. -- [Production (prod)](https://app.fractalframework.xyz/) - The user facing application, which requires a version tag to publish a new release. - -## Release preparation and testing -Every 2nd Thursday morning all code currently merged in the development environment is included in a pull request into staging. - -Included in this PR is an update to `version` in the app's `package.json`, following the release conventions detailed below. - -The engineering team performs a quick scan of the pull request Thursday morning, and given 2 approvals, the code is merged into staging as a potential release candidate. - -The QA team performs manual and automated regression testing on Thursday and Friday, with any regressions logged as Github issue tickets, to be fixed by the engineering team prior to the end of the week. - -Assuming the testing process has passed and all blocking issues have been fixed, on Monday morning the staging environment is merged into production, and a release tag is created, triggering a production build. - -The QA team performs a quick manual test of the app in production immediately following the release, and monitors Sentry and user feedback via email or Twitter throughout Monday, in case of a missed regression. - -## Versioning -Fractal follows semantic versioning (https://semver.org/). - -There are three types of releases: - -- **MAJOR**: Rare and usually aligns with a version upgrade to the [underlying smart contracts](https://github.com/decent-dao/fractal-contracts). These are changes incompatible with prior front end versions. -- **MINOR**: Adds backwards-compatible manner functionalities and bug fixes. -- **PATCH**: Adds backwards-compatible bug and/or security fixes and can be deployed instantaneously, bypassing a full release cycle. No new functionality will be introduced. From b7ef35f2ce6a8558061dfc45aff0197b680f126d Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Wed, 6 Mar 2024 10:05:28 -0500 Subject: [PATCH 13/56] Remove windows specific scripts from package.json --- package.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/package.json b/package.json index 0aae53eb67..fe8addc0ff 100644 --- a/package.json +++ b/package.json @@ -57,9 +57,7 @@ "dev": "npm run set:env & next dev", "start": "npm run set:env & next start", "start:https": "npm run set:env & HTTPS=true next start", - "start:windows": "set \"GENERATE_SOURCEMAP=false\" && for /F \"delims=\" %I in ('git rev-parse HEAD') do set \"NEXT_PUBLIC_GIT_HASH=%I\" && next start", "build": "npm run graphql:build && NEXT_PUBLIC_GIT_HASH=`git rev-parse HEAD` next build", - "build:windows": "for /F \"delims=\" %I in ('git rev-parse HEAD') do set \"NEXT_PUBLIC_GIT_HASH=%I\" && next build", "graphql:build": "graphclient build", "graphql:dev-server": "graphclient serve-dev", "test": "vitest --dir=test", From 4f19bf3b3247300f275c46e8c4e13d03aeb9ed5b Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Wed, 6 Mar 2024 10:07:09 -0500 Subject: [PATCH 14/56] set:env script wasn't working, so inlined into dev, start, start:https --- package.json | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index fe8addc0ff..a3110fe17b 100644 --- a/package.json +++ b/package.json @@ -53,10 +53,9 @@ "scripts": { "lint": "next lint", "lint:fix": "next lint --fix", - "set:env": "GENERATE_SOURCEMAP=false NEXT_PUBLIC_GIT_HASH=`git rev-parse HEAD`", - "dev": "npm run set:env & next dev", - "start": "npm run set:env & next start", - "start:https": "npm run set:env & HTTPS=true next start", + "dev": "GENERATE_SOURCEMAP=false NEXT_PUBLIC_GIT_HASH=`git rev-parse HEAD` next dev", + "start": "GENERATE_SOURCEMAP=false NEXT_PUBLIC_GIT_HASH=`git rev-parse HEAD` next start", + "start:https": "GENERATE_SOURCEMAP=false NEXT_PUBLIC_GIT_HASH=`git rev-parse HEAD` HTTPS=true next start", "build": "npm run graphql:build && NEXT_PUBLIC_GIT_HASH=`git rev-parse HEAD` next build", "graphql:build": "graphclient build", "graphql:dev-server": "graphclient serve-dev", From 8926efb27d8f567c6b058d3a1bc13087c10b60d5 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Wed, 6 Mar 2024 10:07:32 -0500 Subject: [PATCH 15/56] Use the git shorthash as version identifier in Footer --- src/components/pages/AppHome/AppFooter.tsx | 39 ++++++++++++---------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/src/components/pages/AppHome/AppFooter.tsx b/src/components/pages/AppHome/AppFooter.tsx index 2e3035b49b..0ea4238b39 100644 --- a/src/components/pages/AppHome/AppFooter.tsx +++ b/src/components/pages/AppHome/AppFooter.tsx @@ -1,6 +1,5 @@ import { Box, BoxProps, Divider, Flex, Link, Spacer, Text } from '@chakra-ui/react'; import { Trans, useTranslation } from 'react-i18next'; -import packageJson from '../../../../package.json'; import { URL_DECENT } from '../../../constants/url'; import ExternalLink from '../../ui/links/ExternalLink'; @@ -42,23 +41,27 @@ export function AppFooter({ ...rest }: BoxProps) { > {t('audit')} - - | - - - v{packageJson.version} - + {process.env.NEXT_PUBLIC_GIT_HASH !== undefined && ( + <> + + | + + + {process.env.NEXT_PUBLIC_GIT_HASH.substring(0, 7)} + + + )} ); From 2befe6330eaa8e6a611d0e7ab933a4be9875aff4 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Wed, 6 Mar 2024 10:17:06 -0500 Subject: [PATCH 16/56] Remove version property from package.json --- package-lock.json | 1 - package.json | 1 - 2 files changed, 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index b28277fdf9..2668a872e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,6 @@ "packages": { "": { "name": "fractal-interface", - "version": "0.1.8", "dependencies": { "@apollo/client": "^3.7.10", "@chakra-ui/next-js": "^2.2.0", diff --git a/package.json b/package.json index a3110fe17b..758d6f65a5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,5 @@ { "name": "fractal-interface", - "version": "0.1.8", "private": true, "dependencies": { "@apollo/client": "^3.7.10", From 49d744be17ec6927408eb9a27546009a57fbdd48 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Wed, 6 Mar 2024 10:17:41 -0500 Subject: [PATCH 17/56] Remove GENERATE_SOURCEMAP=false from some npm scripts --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 758d6f65a5..9c80410053 100644 --- a/package.json +++ b/package.json @@ -52,9 +52,9 @@ "scripts": { "lint": "next lint", "lint:fix": "next lint --fix", - "dev": "GENERATE_SOURCEMAP=false NEXT_PUBLIC_GIT_HASH=`git rev-parse HEAD` next dev", - "start": "GENERATE_SOURCEMAP=false NEXT_PUBLIC_GIT_HASH=`git rev-parse HEAD` next start", - "start:https": "GENERATE_SOURCEMAP=false NEXT_PUBLIC_GIT_HASH=`git rev-parse HEAD` HTTPS=true next start", + "dev": "NEXT_PUBLIC_GIT_HASH=`git rev-parse HEAD` next dev", + "start": "NEXT_PUBLIC_GIT_HASH=`git rev-parse HEAD` next start", + "start:https": "NEXT_PUBLIC_GIT_HASH=`git rev-parse HEAD` HTTPS=true next start", "build": "npm run graphql:build && NEXT_PUBLIC_GIT_HASH=`git rev-parse HEAD` next build", "graphql:build": "graphclient build", "graphql:dev-server": "graphclient serve-dev", From a7b7890a92520c742c58c773bdc522d027c27f58 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Wed, 6 Mar 2024 10:20:15 -0500 Subject: [PATCH 18/56] Remove the start and start:https scripts --- package.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/package.json b/package.json index 9c80410053..cd94a35d2a 100644 --- a/package.json +++ b/package.json @@ -53,8 +53,6 @@ "lint": "next lint", "lint:fix": "next lint --fix", "dev": "NEXT_PUBLIC_GIT_HASH=`git rev-parse HEAD` next dev", - "start": "NEXT_PUBLIC_GIT_HASH=`git rev-parse HEAD` next start", - "start:https": "NEXT_PUBLIC_GIT_HASH=`git rev-parse HEAD` HTTPS=true next start", "build": "npm run graphql:build && NEXT_PUBLIC_GIT_HASH=`git rev-parse HEAD` next build", "graphql:build": "graphclient build", "graphql:dev-server": "graphclient serve-dev", From 0f31fd6f385f0e6f22b3d6f094dee88215953eec Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Wed, 6 Mar 2024 10:21:10 -0500 Subject: [PATCH 19/56] Build the graphql client when `npm run dev` --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index cd94a35d2a..0df7c4b153 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "scripts": { "lint": "next lint", "lint:fix": "next lint --fix", - "dev": "NEXT_PUBLIC_GIT_HASH=`git rev-parse HEAD` next dev", + "dev": "npm run graphql:build && NEXT_PUBLIC_GIT_HASH=`git rev-parse HEAD` next dev", "build": "npm run graphql:build && NEXT_PUBLIC_GIT_HASH=`git rev-parse HEAD` next build", "graphql:build": "graphclient build", "graphql:dev-server": "graphclient serve-dev", From 6781722a8cbf2b1443ff3249291c191fabb1fa8b Mon Sep 17 00:00:00 2001 From: Kirill Klimenko Date: Wed, 6 Mar 2024 16:39:05 +0100 Subject: [PATCH 20/56] Rename netlify function, fix returning response error, better typing within netlify function --- .../{tokensPrices.mts => tokenPrices.mts} | 38 +++++++++++-------- src/providers/App/hooks/usePriceAPI.ts | 2 +- 2 files changed, 24 insertions(+), 16 deletions(-) rename netlify/functions/{tokensPrices.mts => tokenPrices.mts} (76%) diff --git a/netlify/functions/tokensPrices.mts b/netlify/functions/tokenPrices.mts similarity index 76% rename from netlify/functions/tokensPrices.mts rename to netlify/functions/tokenPrices.mts index 3c6fc40dd4..922cd22b90 100644 --- a/netlify/functions/tokensPrices.mts +++ b/netlify/functions/tokenPrices.mts @@ -1,30 +1,38 @@ import { getStore } from '@netlify/blobs'; -type TokenPriceMetadata = { - expiration: number; +type TokenPriceWithMetadata = { + data: { + tokenAddress: string; + price: number; + }; + metadata: { + expiration: number; + }; }; -export default async function getTokenprices(request: Request) { - const store = getStore('fractal-token-prices-store'); +export default async function getTokenPrices(request: Request) { + const store = getStore('token-prices'); const tokensString = new URL(request.url).searchParams.get('tokens'); if (!tokensString) { - Response.json({ error: 'Tokens to request were not provided' }); + return Response.json({ error: 'Tokens to request were not provided' }); } - const tokens = tokensString!.split(','); + const tokens = tokensString.split(','); try { - const now = new Date().getTime(); + const now = Date.now(); const cachedPrices = await Promise.all( - tokens.map(tokenAddress => store.getWithMetadata(tokenAddress, { type: 'json' })) + tokens.map( + tokenAddress => + store.getWithMetadata(tokenAddress, { + type: 'json', + }) as Promise | null + ) ); const cachedUnexpiredPrices = cachedPrices - .filter( - tokenPrice => - tokenPrice && (tokenPrice?.metadata as any as TokenPriceMetadata).expiration <= now - ) + .filter(tokenPrice => tokenPrice && tokenPrice.metadata.expiration <= now) .map(tokenPrice => ({ - tokenAddress: tokenPrice?.data.tokenAddress, - price: tokenPrice?.data.price, + tokenAddress: tokenPrice!.data.tokenAddress, + price: tokenPrice!.data.price, })); const nonCachedTokensAddresses = tokens.filter( address => !cachedUnexpiredPrices.find(tokenPrice => tokenPrice.tokenAddress === address) @@ -38,7 +46,7 @@ export default async function getTokenprices(request: Request) { } if (!process.env.COINGECKO_API_KEY) { console.error('CoinGecko API key is missing'); - return { error: 'Unknown error while fetching prices' }; + return Response.json({ error: 'Unknown error while fetching prices' }); } const PUBLIC_DEMO_API_BASE_URL = 'https://api.coingecko.com/api/v3/'; const AUTH_QUERY_PARAM = `?x_cg_demo_api_key=${process.env.COINGECKO_API_KEY}`; diff --git a/src/providers/App/hooks/usePriceAPI.ts b/src/providers/App/hooks/usePriceAPI.ts index 788cb7af90..1e20353f61 100644 --- a/src/providers/App/hooks/usePriceAPI.ts +++ b/src/providers/App/hooks/usePriceAPI.ts @@ -25,7 +25,7 @@ export default function usePriceAPI() { tokensAddresses.push('ethereum'); } const pricesResponse = await fetch( - `/.netlify/functions/tokensPrices?tokens=${tokensAddresses.join(',')}` + `/.netlify/functions/tokenPrices?tokens=${tokensAddresses.join(',')}` ); const pricesResponseBody = await pricesResponse.json(); From 86335357de66e6bd736d8a2ac2dfa07b439ae906 Mon Sep 17 00:00:00 2001 From: Kirill Klimenko Date: Wed, 6 Mar 2024 18:06:40 +0100 Subject: [PATCH 21/56] Handle better CoinGecko requesting error and return cached prices --- netlify/functions/tokenPrices.mts | 58 ++++++++++++++++++------------- 1 file changed, 34 insertions(+), 24 deletions(-) diff --git a/netlify/functions/tokenPrices.mts b/netlify/functions/tokenPrices.mts index 922cd22b90..60ac58c204 100644 --- a/netlify/functions/tokenPrices.mts +++ b/netlify/functions/tokenPrices.mts @@ -48,33 +48,43 @@ export default async function getTokenPrices(request: Request) { console.error('CoinGecko API key is missing'); return Response.json({ error: 'Unknown error while fetching prices' }); } - const PUBLIC_DEMO_API_BASE_URL = 'https://api.coingecko.com/api/v3/'; - const AUTH_QUERY_PARAM = `?x_cg_demo_api_key=${process.env.COINGECKO_API_KEY}`; - const tokenPricesUrl = `${PUBLIC_DEMO_API_BASE_URL}simple/token_price/ethereum/${AUTH_QUERY_PARAM}&vs_currencies=usd&contract_addresses=${nonCachedTokensAddresses.join( - ',' - )}`; + try { + const PUBLIC_DEMO_API_BASE_URL = 'https://api.coingecko.com/api/v3/'; + const AUTH_QUERY_PARAM = `?x_cg_demo_api_key=${process.env.COINGECKO_API_KEY}`; + const tokenPricesUrl = `${PUBLIC_DEMO_API_BASE_URL}simple/token_price/ethereum/${AUTH_QUERY_PARAM}&vs_currencies=usd&contract_addresses=${nonCachedTokensAddresses.join( + ',' + )}`; - const tokenPricesResponse = await fetch(tokenPricesUrl); - const tokenPricesResponseJson = await tokenPricesResponse.json(); - const tokenPriceMetadata = { metadata: { expiration: now + 1000 * 60 * 30 } }; - Object.keys(tokenPricesResponseJson).forEach(tokenAddress => { - const price = tokenPricesResponseJson[tokenAddress].usd; - responseBody[tokenAddress] = price; - store.setJSON(tokenAddress, { tokenAddress, price }, tokenPriceMetadata); - }); + const tokenPricesResponse = await fetch(tokenPricesUrl); + const tokenPricesResponseJson = await tokenPricesResponse.json(); + const tokenPriceMetadata = { metadata: { expiration: now + 1000 * 60 * 30 } }; + Object.keys(tokenPricesResponseJson).forEach(tokenAddress => { + const price = tokenPricesResponseJson[tokenAddress].usd; + responseBody[tokenAddress] = price; + store.setJSON(tokenAddress, { tokenAddress, price }, tokenPriceMetadata); + }); - const ethAsset = nonCachedTokensAddresses.find(token => token === 'ethereum'); - if (ethAsset) { - // Unfortunately, there's no way avoiding 2 requests. We either need to fetch asset IDs from CoinGecko for given token contract addresses - // And then use this endpoint to get all the prices. But that brings us way more bandwidth - // Or, we are doing this "hardcoded" call for ETH price. But our request for token prices simpler. - const ethPriceUrl = `${PUBLIC_DEMO_API_BASE_URL}simple/price${AUTH_QUERY_PARAM}&ids=ethereum&vs_currencies=usd`; - const ethPriceResponse = await fetch(ethPriceUrl); - const price = (await ethPriceResponse.json()).ethereum.usd; - store.setJSON('ethereum', { tokenAddress: 'ethereum', price }, tokenPriceMetadata); - responseBody.ethereum = price; + const ethAsset = nonCachedTokensAddresses.find(token => token === 'ethereum'); + if (ethAsset) { + // Unfortunately, there's no way avoiding 2 requests. We either need to fetch asset IDs from CoinGecko for given token contract addresses + // And then use this endpoint to get all the prices. But that brings us way more bandwidth + // Or, we are doing this "hardcoded" call for ETH price. But our request for token prices simpler. + const ethPriceUrl = `${PUBLIC_DEMO_API_BASE_URL}simple/price${AUTH_QUERY_PARAM}&ids=ethereum&vs_currencies=usd`; + const ethPriceResponse = await fetch(ethPriceUrl); + const price = (await ethPriceResponse.json()).ethereum.usd; + store.setJSON('ethereum', { tokenAddress: 'ethereum', price }, tokenPriceMetadata); + responseBody.ethereum = price; + } + return Response.json({ data: responseBody }); + } catch (e) { + console.error('Error while querying CoinGecko', e); + cachedPrices.forEach(tokenPrice => { + if (tokenPrice && !responseBody[tokenPrice.data.tokenAddress]) { + responseBody[tokenPrice.data.tokenAddress] = tokenPrice.data.price; + } + }); + return Response.json({ error: 'Error while fetching prices', data: responseBody }); } - return Response.json({ data: responseBody }); } catch (e) { console.error('Error while fetching prices', e); return Response.json({ From a3783d4df824371efa3914bba0aeec4411025c69 Mon Sep 17 00:00:00 2001 From: Kirill Klimenko Date: Wed, 6 Mar 2024 18:13:52 +0100 Subject: [PATCH 22/56] Show cached prices on FE if request to CoinGecko failed --- src/components/pages/DAOTreasury/hooks/useFormatCoins.tsx | 8 +++++--- src/providers/App/hooks/usePriceAPI.ts | 4 ++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/components/pages/DAOTreasury/hooks/useFormatCoins.tsx b/src/components/pages/DAOTreasury/hooks/useFormatCoins.tsx index e0f1f8b526..3944108ff9 100644 --- a/src/components/pages/DAOTreasury/hooks/useFormatCoins.tsx +++ b/src/components/pages/DAOTreasury/hooks/useFormatCoins.tsx @@ -32,9 +32,11 @@ export function useFormatCoins(assets: SafeBalanceUsdResponse[]) { for (let i = 0; i < assets.length; i++) { let asset = assets[i]; if (asset.balance === '0') continue; - const tokenPrice = asset.tokenAddress - ? tokenPrices[asset.tokenAddress.toLowerCase()] - : tokenPrices.ethereum; + const tokenPrice = tokenPrices + ? asset.tokenAddress + ? tokenPrices[asset.tokenAddress.toLowerCase()] + : tokenPrices.ethereum + : 0; let tokenFiatBalance = 0; if (tokenPrice && asset.balance) { diff --git a/src/providers/App/hooks/usePriceAPI.ts b/src/providers/App/hooks/usePriceAPI.ts index 1e20353f61..c407fec6a7 100644 --- a/src/providers/App/hooks/usePriceAPI.ts +++ b/src/providers/App/hooks/usePriceAPI.ts @@ -32,6 +32,10 @@ export default function usePriceAPI() { if (pricesResponseBody.error) { // We don't need to log error here as it is supposed to be logged through Netlify function anyway toast.warning(t('tokenPriceFetchingError')); + if (pricesResponseBody.data) { + // Netlify function might fail due to rate limit of CoinGecko error, but it still will return cached prices. + return pricesResponseBody.data; + } } else { return pricesResponseBody.data; } From effde4216665f27c198f33ea18c8fe5dc7360674 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Wed, 6 Mar 2024 12:30:08 -0500 Subject: [PATCH 23/56] Add start and start:https scripts back --- package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.json b/package.json index 0df7c4b153..fb3203f312 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,8 @@ "lint": "next lint", "lint:fix": "next lint --fix", "dev": "npm run graphql:build && NEXT_PUBLIC_GIT_HASH=`git rev-parse HEAD` next dev", + "start": "NEXT_PUBLIC_GIT_HASH=`git rev-parse HEAD` next start", + "start:https": "NEXT_PUBLIC_GIT_HASH=`git rev-parse HEAD` HTTPS=true next start", "build": "npm run graphql:build && NEXT_PUBLIC_GIT_HASH=`git rev-parse HEAD` next build", "graphql:build": "graphclient build", "graphql:dev-server": "graphclient serve-dev", From e22463ed42ade5946ae959bfbf817c6ab3ea1cc8 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Wed, 6 Mar 2024 13:19:57 -0500 Subject: [PATCH 24/56] Remove unused import --- app/page.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/app/page.tsx b/app/page.tsx index 666bf3e65f..9cba45c4cd 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -14,7 +14,6 @@ import ExternalLink from '../src/components/ui/links/ExternalLink'; import ClientOnly from '../src/components/ui/utils/ClientOnly'; import { BASE_ROUTES } from '../src/constants/routes'; import { URL_DOCS } from '../src/constants/url'; -import ethLizardsLogo from '../src/metadata/lizzardsDAO/assets/logo.png'; import { useFractal } from '../src/providers/App/AppProvider'; import { disconnectedChain } from '../src/providers/NetworkConfig/NetworkConfigProvider'; From d25deadce75240269cde30244772dd49d73057d6 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Wed, 6 Mar 2024 13:20:27 -0500 Subject: [PATCH 25/56] Delete unnecessary commands from start scripts --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index fb3203f312..745b5843c5 100644 --- a/package.json +++ b/package.json @@ -53,8 +53,8 @@ "lint": "next lint", "lint:fix": "next lint --fix", "dev": "npm run graphql:build && NEXT_PUBLIC_GIT_HASH=`git rev-parse HEAD` next dev", - "start": "NEXT_PUBLIC_GIT_HASH=`git rev-parse HEAD` next start", - "start:https": "NEXT_PUBLIC_GIT_HASH=`git rev-parse HEAD` HTTPS=true next start", + "start": "next start", + "start:https": "HTTPS=true next start", "build": "npm run graphql:build && NEXT_PUBLIC_GIT_HASH=`git rev-parse HEAD` next build", "graphql:build": "graphclient build", "graphql:dev-server": "graphclient serve-dev", From 8ad97005a679b8094e60ea73bb49b778858c0528 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Wed, 6 Mar 2024 13:20:43 -0500 Subject: [PATCH 26/56] temp - remove start scripts --- package.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/package.json b/package.json index 745b5843c5..0df7c4b153 100644 --- a/package.json +++ b/package.json @@ -53,8 +53,6 @@ "lint": "next lint", "lint:fix": "next lint --fix", "dev": "npm run graphql:build && NEXT_PUBLIC_GIT_HASH=`git rev-parse HEAD` next dev", - "start": "next start", - "start:https": "HTTPS=true next start", "build": "npm run graphql:build && NEXT_PUBLIC_GIT_HASH=`git rev-parse HEAD` next build", "graphql:build": "graphclient build", "graphql:dev-server": "graphclient serve-dev", From 6cfbe6665fa2a112cee092b479181cbb4be78b64 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Wed, 6 Mar 2024 14:37:52 -0500 Subject: [PATCH 27/56] Adding the `start` script back --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 0df7c4b153..4b8e2006f9 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "lint": "next lint", "lint:fix": "next lint --fix", "dev": "npm run graphql:build && NEXT_PUBLIC_GIT_HASH=`git rev-parse HEAD` next dev", + "start": "next start", "build": "npm run graphql:build && NEXT_PUBLIC_GIT_HASH=`git rev-parse HEAD` next build", "graphql:build": "graphclient build", "graphql:dev-server": "graphclient serve-dev", From 97930a9bce8160e7a2ac084fb2ce7cbef0afad82 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Wed, 6 Mar 2024 17:29:21 -0500 Subject: [PATCH 28/56] Added some more explicit code and comments --- netlify/functions/tokenPrices.mts | 153 ++++++++++++++++++++++-------- 1 file changed, 116 insertions(+), 37 deletions(-) diff --git a/netlify/functions/tokenPrices.mts b/netlify/functions/tokenPrices.mts index 60ac58c204..029e910c69 100644 --- a/netlify/functions/tokenPrices.mts +++ b/netlify/functions/tokenPrices.mts @@ -1,4 +1,5 @@ import { getStore } from '@netlify/blobs'; +import { ethers } from 'ethers'; type TokenPriceWithMetadata = { data: { @@ -11,16 +12,46 @@ type TokenPriceWithMetadata = { }; export default async function getTokenPrices(request: Request) { - const store = getStore('token-prices'); - const tokensString = new URL(request.url).searchParams.get('tokens'); + if (!process.env.COINGECKO_API_KEY) { + console.error('CoinGecko API key is missing'); + return Response.json({ error: 'Unknown error while fetching prices' }, { status: 503 }); + } + + const now = Date.now(); - if (!tokensString) { - return Response.json({ error: 'Tokens to request were not provided' }); + // First we want to pull the tokens off of the request's query param. + const tokensStringParam = new URL(request.url).searchParams.get('tokens'); + if (!tokensStringParam) { + return Response.json({ error: 'Tokens to request were not provided' }, { status: 400 }); } - const tokens = tokensString.split(','); + + // These are the token addresses from the client, split up. + const rawTokenAddresses = tokensStringParam.split(','); + + // Let's make sure all of these given addresses are valid. + const anyInvalidTokens = rawTokenAddresses.some(address => !ethers.utils.isAddress(address)); + if (!anyInvalidTokens) { + return Response.json({ error: 'One or more token addresses is invalid' }, { status: 400 }); + } + + // Next we want to standardize them all by making them lowercase. + const lowerCaseTokens = rawTokenAddresses.map(address => address.toLowerCase()); + + // Finally, make sure we're dealing with a unique set of token addresses. + const tokens = [...new Set(lowerCaseTokens)]; + + const store = getStore('token-prices'); + + // Let's immediately build up our repsonse object, containing each + // token address and an value of 0. We'll modify this along the way + // populating it with more relevant prices. + const responseBody: Record = tokens.reduce((p, c) => ({ ...p, [c]: 0 }), {}); + try { - const now = Date.now(); - const cachedPrices = await Promise.all( + // Try to get all of the tokens from our store. + // Any token address that we don't have a record for will + // come back as null. + const possibleCachedTokenPrices = await Promise.all( tokens.map( tokenAddress => store.getWithMetadata(tokenAddress, { @@ -28,67 +59,115 @@ export default async function getTokenPrices(request: Request) { }) as Promise | null ) ); - const cachedUnexpiredPrices = cachedPrices - .filter(tokenPrice => tokenPrice && tokenPrice.metadata.expiration <= now) - .map(tokenPrice => ({ - tokenAddress: tokenPrice!.data.tokenAddress, - price: tokenPrice!.data.price, - })); - const nonCachedTokensAddresses = tokens.filter( - address => !cachedUnexpiredPrices.find(tokenPrice => tokenPrice.tokenAddress === address) + + // Filter out the null values, leaving us with an array of + // TokenPricesWithMetadata. All of these TokenPrices will be either + // expired or unexpired. + const allCachedTokenPrices = possibleCachedTokenPrices.filter( + (possible): possible is TokenPriceWithMetadata => possible !== null + ); + + // Let's pull out all of the unexpired TokenPrices from our cache. + const cachedUnexpiredTokenPrices = allCachedTokenPrices.filter( + tokenPrice => tokenPrice.metadata.expiration <= now + ); + + // We'll update our response with those unexpired cached prices. + cachedUnexpiredTokenPrices.forEach(tokenPrice => { + responseBody[tokenPrice.data.tokenAddress] = tokenPrice.data.price; + }); + + // Let's pull out all of the expired TokenPrices from our cache. + const cachedExpiredTokenPrices = allCachedTokenPrices.filter( + tokenPrice => tokenPrice.metadata.expiration > now ); - const responseBody: Record = {}; - cachedUnexpiredPrices.forEach(tokenPrice => { - responseBody[tokenPrice.tokenAddress] = tokenPrice.price; + + // We'll update our response with those expired cached prices. + // This is done now in the offchance that we won't be able to contact + // CoinGecko later. Ideally these will be updated after getting + // fresher data from CoinGecko + cachedExpiredTokenPrices.forEach(tokenPrice => { + responseBody[tokenPrice.data.tokenAddress] = tokenPrice.data.price; }); - if (nonCachedTokensAddresses.length === 0) { + + // Finally let's get a list of all of the token addresses that + // we don't have any price for in our cache, expired or not. + const allUncachedTokenPrices = possibleCachedTokenPrices.filter( + (possible): possible is TokenPriceWithMetadata => possible === null + ); + + // If there are no expired token prices, and no token addresses that we + // don't have a cached value for at all, we can early return! + if (allUncachedTokenPrices.length === 0 && cachedExpiredTokenPrices.length === 0) { return Response.json({ data: responseBody }); } - if (!process.env.COINGECKO_API_KEY) { - console.error('CoinGecko API key is missing'); - return Response.json({ error: 'Unknown error while fetching prices' }); - } + + // If we got here, then we have either some expired prices for given tokens, + // or no prices at all for given tokens. + + // First, let's build up our list of token addresses to query CoinGecko with, + // which is all uncached tokens and tokens that have expired. + const needPrices = [...allUncachedTokenPrices, ...cachedExpiredTokenPrices]; + try { + // Build up the request URL. const PUBLIC_DEMO_API_BASE_URL = 'https://api.coingecko.com/api/v3/'; const AUTH_QUERY_PARAM = `?x_cg_demo_api_key=${process.env.COINGECKO_API_KEY}`; - const tokenPricesUrl = `${PUBLIC_DEMO_API_BASE_URL}simple/token_price/ethereum/${AUTH_QUERY_PARAM}&vs_currencies=usd&contract_addresses=${nonCachedTokensAddresses.join( + const tokenPricesUrl = `${PUBLIC_DEMO_API_BASE_URL}simple/token_price/ethereum/${AUTH_QUERY_PARAM}&vs_currencies=usd&contract_addresses=${needPrices.join( ',' )}`; + // Make the request. const tokenPricesResponse = await fetch(tokenPricesUrl); const tokenPricesResponseJson = await tokenPricesResponse.json(); + + // Create the metadata for our new token prices, with an + // expiration in the future. const tokenPriceMetadata = { metadata: { expiration: now + 1000 * 60 * 30 } }; + + // With our response... Object.keys(tokenPricesResponseJson).forEach(tokenAddress => { const price = tokenPricesResponseJson[tokenAddress].usd; - responseBody[tokenAddress] = price; - store.setJSON(tokenAddress, { tokenAddress, price }, tokenPriceMetadata); + const sanitizedAddress = tokenAddress.toLowerCase(); + // 1. Replace the token addresses of our existing response object with the new prices. + responseBody[sanitizedAddress] = price; + // 2. Store these fresh prices in our Blob store. + store.setJSON( + sanitizedAddress, + { tokenAddress: sanitizedAddress, price }, + tokenPriceMetadata + ); }); - const ethAsset = nonCachedTokensAddresses.find(token => token === 'ethereum'); + // Do we need to get the price of our chain's gas token (ethereum)? + const ethAsset = needPrices.find(token => token.data.tokenAddress === 'ethereum'); if (ethAsset) { - // Unfortunately, there's no way avoiding 2 requests. We either need to fetch asset IDs from CoinGecko for given token contract addresses - // And then use this endpoint to get all the prices. But that brings us way more bandwidth - // Or, we are doing this "hardcoded" call for ETH price. But our request for token prices simpler. + // Build up the request URL. const ethPriceUrl = `${PUBLIC_DEMO_API_BASE_URL}simple/price${AUTH_QUERY_PARAM}&ids=ethereum&vs_currencies=usd`; + + // Make the request. const ethPriceResponse = await fetch(ethPriceUrl); - const price = (await ethPriceResponse.json()).ethereum.usd; - store.setJSON('ethereum', { tokenAddress: 'ethereum', price }, tokenPriceMetadata); + const ethPriceResponseJson = await ethPriceResponse.json(); + + // Get the price data. + const price = ethPriceResponseJson.ethereum.usd; + + // 1. Replace the token addresses of our existing response object with the new prices. responseBody.ethereum = price; + + // 2. Store this fresh prices in our Blob store. + store.setJSON('ethereum', { tokenAddress: 'ethereum', price }, tokenPriceMetadata); } return Response.json({ data: responseBody }); } catch (e) { console.error('Error while querying CoinGecko', e); - cachedPrices.forEach(tokenPrice => { - if (tokenPrice && !responseBody[tokenPrice.data.tokenAddress]) { - responseBody[tokenPrice.data.tokenAddress] = tokenPrice.data.price; - } - }); return Response.json({ error: 'Error while fetching prices', data: responseBody }); } } catch (e) { console.error('Error while fetching prices', e); return Response.json({ error: 'Unknown error while fetching prices', + data: responseBody, }); } } From 9252d6e8a263db91f5d66585f772403f11f0cbd0 Mon Sep 17 00:00:00 2001 From: Kirill Klimenko Date: Wed, 6 Mar 2024 23:57:01 +0100 Subject: [PATCH 29/56] Do not execute request to netlify function if tokensAddresses is empty. Bump Node.js version to match required version by Netlify functions. --- .gitignore | 3 +++ .nvmrc | 2 +- package-lock.json | 4 ++-- package.json | 4 ++-- src/providers/App/hooks/usePriceAPI.ts | 24 +++++++++++++----------- 5 files changed, 21 insertions(+), 16 deletions(-) diff --git a/.gitignore b/.gitignore index ea11b4cd41..888bc818a6 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,6 @@ yarn-error.log* # GraphQL generated files .graphclient/ + +# Local Netlify folder +.netlify diff --git a/.nvmrc b/.nvmrc index 72c7744b30..741b4916ea 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -18.12.1 \ No newline at end of file +18.14.0 \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 2d5394b5c1..387b0ec794 100644 --- a/package-lock.json +++ b/package-lock.json @@ -81,8 +81,8 @@ "vitest": "^1.2.2" }, "engines": { - "node": "18.12.1", - "npm": "8.19.2" + "node": "18.14.0", + "npm": "9.3.1" } }, "node_modules/@adobe/css-tools": { diff --git a/package.json b/package.json index 8bf6cb5749..44dea924d4 100644 --- a/package.json +++ b/package.json @@ -68,8 +68,8 @@ "tests": "playwright test" }, "engines": { - "node": "18.12.1", - "npm": "8.19.2" + "node": "18.14.0", + "npm": "9.3.1" }, "browserslist": { "production": [ diff --git a/src/providers/App/hooks/usePriceAPI.ts b/src/providers/App/hooks/usePriceAPI.ts index c407fec6a7..c88cce0985 100644 --- a/src/providers/App/hooks/usePriceAPI.ts +++ b/src/providers/App/hooks/usePriceAPI.ts @@ -24,20 +24,22 @@ export default function usePriceAPI() { if (ethAsset) { tokensAddresses.push('ethereum'); } - const pricesResponse = await fetch( - `/.netlify/functions/tokenPrices?tokens=${tokensAddresses.join(',')}` - ); + if (tokensAddresses.length > 0) { + const pricesResponse = await fetch( + `/.netlify/functions/tokenPrices?tokens=${tokensAddresses.join(',')}` + ); - const pricesResponseBody = await pricesResponse.json(); - if (pricesResponseBody.error) { - // We don't need to log error here as it is supposed to be logged through Netlify function anyway - toast.warning(t('tokenPriceFetchingError')); - if (pricesResponseBody.data) { - // Netlify function might fail due to rate limit of CoinGecko error, but it still will return cached prices. + const pricesResponseBody = await pricesResponse.json(); + if (pricesResponseBody.error) { + // We don't need to log error here as it is supposed to be logged through Netlify function anyway + toast.warning(t('tokenPriceFetchingError')); + if (pricesResponseBody.data) { + // Netlify function might fail due to rate limit of CoinGecko error, but it still will return cached prices. + return pricesResponseBody.data; + } + } else { return pricesResponseBody.data; } - } else { - return pricesResponseBody.data; } } catch (e) { logError('Error while getting tokens prices', e); From 2c92f7b9802ea2754aec77f1d4c2359c77037155 Mon Sep 17 00:00:00 2001 From: Kirill Klimenko Date: Thu, 7 Mar 2024 00:28:04 +0100 Subject: [PATCH 30/56] Trying changing possibleCachedTokenPrices filtering null values --- netlify/functions/tokenPrices.mts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netlify/functions/tokenPrices.mts b/netlify/functions/tokenPrices.mts index 029e910c69..50a40e622c 100644 --- a/netlify/functions/tokenPrices.mts +++ b/netlify/functions/tokenPrices.mts @@ -64,7 +64,7 @@ export default async function getTokenPrices(request: Request) { // TokenPricesWithMetadata. All of these TokenPrices will be either // expired or unexpired. const allCachedTokenPrices = possibleCachedTokenPrices.filter( - (possible): possible is TokenPriceWithMetadata => possible !== null + (possible): possible is TokenPriceWithMetadata => !!possible ); // Let's pull out all of the unexpired TokenPrices from our cache. From 873df06901b8081511be6cb4e15612e470377430 Mon Sep 17 00:00:00 2001 From: Kirill Klimenko Date: Thu, 7 Mar 2024 00:38:09 +0100 Subject: [PATCH 31/56] Fix filtering logic of uncached token prices --- netlify/functions/tokenPrices.mts | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/netlify/functions/tokenPrices.mts b/netlify/functions/tokenPrices.mts index 50a40e622c..099269836a 100644 --- a/netlify/functions/tokenPrices.mts +++ b/netlify/functions/tokenPrices.mts @@ -92,13 +92,18 @@ export default async function getTokenPrices(request: Request) { // Finally let's get a list of all of the token addresses that // we don't have any price for in our cache, expired or not. - const allUncachedTokenPrices = possibleCachedTokenPrices.filter( - (possible): possible is TokenPriceWithMetadata => possible === null - ); + const allUncachedTokenAddresses = tokensStringParam + .split(',') + .filter( + address => + !allCachedTokenPrices.find( + cachedTokenPrice => cachedTokenPrice.data.tokenAddress === address + ) + ); // If there are no expired token prices, and no token addresses that we // don't have a cached value for at all, we can early return! - if (allUncachedTokenPrices.length === 0 && cachedExpiredTokenPrices.length === 0) { + if (allUncachedTokenAddresses.length === 0 && cachedExpiredTokenPrices.length === 0) { return Response.json({ data: responseBody }); } @@ -107,13 +112,16 @@ export default async function getTokenPrices(request: Request) { // First, let's build up our list of token addresses to query CoinGecko with, // which is all uncached tokens and tokens that have expired. - const needPrices = [...allUncachedTokenPrices, ...cachedExpiredTokenPrices]; + const needPricesTokenAddresses = [ + ...allUncachedTokenAddresses, + ...cachedExpiredTokenPrices.map(tokenPrice => tokenPrice.data.tokenAddress), + ]; try { // Build up the request URL. const PUBLIC_DEMO_API_BASE_URL = 'https://api.coingecko.com/api/v3/'; const AUTH_QUERY_PARAM = `?x_cg_demo_api_key=${process.env.COINGECKO_API_KEY}`; - const tokenPricesUrl = `${PUBLIC_DEMO_API_BASE_URL}simple/token_price/ethereum/${AUTH_QUERY_PARAM}&vs_currencies=usd&contract_addresses=${needPrices.join( + const tokenPricesUrl = `${PUBLIC_DEMO_API_BASE_URL}simple/token_price/ethereum/${AUTH_QUERY_PARAM}&vs_currencies=usd&contract_addresses=${needPricesTokenAddresses.join( ',' )}`; @@ -140,7 +148,7 @@ export default async function getTokenPrices(request: Request) { }); // Do we need to get the price of our chain's gas token (ethereum)? - const ethAsset = needPrices.find(token => token.data.tokenAddress === 'ethereum'); + const ethAsset = needPricesTokenAddresses.find(address => address === 'ethereum'); if (ethAsset) { // Build up the request URL. const ethPriceUrl = `${PUBLIC_DEMO_API_BASE_URL}simple/price${AUTH_QUERY_PARAM}&ids=ethereum&vs_currencies=usd`; From 15e1564b0a806bf8951accde9a4d99d138e86525 Mon Sep 17 00:00:00 2001 From: Kirill Klimenko Date: Thu, 7 Mar 2024 00:45:18 +0100 Subject: [PATCH 32/56] Use rawTokenAddresses for allUncachedTokenAddresses instead of splitting tokensStringParam once more, move const now down closer to the try...catch block of validating/setting cache --- netlify/functions/tokenPrices.mts | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/netlify/functions/tokenPrices.mts b/netlify/functions/tokenPrices.mts index 099269836a..3a04f931a0 100644 --- a/netlify/functions/tokenPrices.mts +++ b/netlify/functions/tokenPrices.mts @@ -17,8 +17,6 @@ export default async function getTokenPrices(request: Request) { return Response.json({ error: 'Unknown error while fetching prices' }, { status: 503 }); } - const now = Date.now(); - // First we want to pull the tokens off of the request's query param. const tokensStringParam = new URL(request.url).searchParams.get('tokens'); if (!tokensStringParam) { @@ -48,6 +46,8 @@ export default async function getTokenPrices(request: Request) { const responseBody: Record = tokens.reduce((p, c) => ({ ...p, [c]: 0 }), {}); try { + const now = Date.now(); + // Try to get all of the tokens from our store. // Any token address that we don't have a record for will // come back as null. @@ -92,14 +92,12 @@ export default async function getTokenPrices(request: Request) { // Finally let's get a list of all of the token addresses that // we don't have any price for in our cache, expired or not. - const allUncachedTokenAddresses = tokensStringParam - .split(',') - .filter( - address => - !allCachedTokenPrices.find( - cachedTokenPrice => cachedTokenPrice.data.tokenAddress === address - ) - ); + const allUncachedTokenAddresses = rawTokenAddresses.filter( + address => + !allCachedTokenPrices.find( + cachedTokenPrice => cachedTokenPrice.data.tokenAddress === address + ) + ); // If there are no expired token prices, and no token addresses that we // don't have a cached value for at all, we can early return! From 7c038884677fada0d86ca1fa7ce7c8f7ec5d9290 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Wed, 6 Mar 2024 22:09:29 -0500 Subject: [PATCH 33/56] Handle a lot of cases: - Don't throw error when query param is not a valid address, just skip it. - Don't send "ethereum" string as a token to CG token API endpoint. - Return ethereum price in response if all data can be found in cache. - Fix expiration conditionals - Handle case when CoinGecko responds to an address but there's no usd price - Add entries to blob store (with long expiration) for token addresses that CoinGecko doesn't track, to avoid constantly asking about these tokens --- netlify/functions/tokenPrices.mts | 133 ++++++++++++++++++++++-------- 1 file changed, 98 insertions(+), 35 deletions(-) diff --git a/netlify/functions/tokenPrices.mts b/netlify/functions/tokenPrices.mts index 3a04f931a0..7338220442 100644 --- a/netlify/functions/tokenPrices.mts +++ b/netlify/functions/tokenPrices.mts @@ -23,30 +23,23 @@ export default async function getTokenPrices(request: Request) { return Response.json({ error: 'Tokens to request were not provided' }, { status: 400 }); } - // These are the token addresses from the client, split up. + // Sanitize user input const rawTokenAddresses = tokensStringParam.split(','); - - // Let's make sure all of these given addresses are valid. - const anyInvalidTokens = rawTokenAddresses.some(address => !ethers.utils.isAddress(address)); - if (!anyInvalidTokens) { - return Response.json({ error: 'One or more token addresses is invalid' }, { status: 400 }); - } - - // Next we want to standardize them all by making them lowercase. - const lowerCaseTokens = rawTokenAddresses.map(address => address.toLowerCase()); - - // Finally, make sure we're dealing with a unique set of token addresses. - const tokens = [...new Set(lowerCaseTokens)]; - - const store = getStore('token-prices'); + const needEthereum = rawTokenAddresses.map(address => address.toLowerCase()).includes('ethereum'); + const validTokenAddresses = rawTokenAddresses.filter(address => ethers.utils.isAddress(address)); + const lowerCaseTokenAddresses = validTokenAddresses.map(address => address.toLowerCase()); + const tokens = [...new Set(lowerCaseTokenAddresses)]; + if (needEthereum) tokens.push('ethereum'); // Let's immediately build up our repsonse object, containing each // token address and an value of 0. We'll modify this along the way // populating it with more relevant prices. const responseBody: Record = tokens.reduce((p, c) => ({ ...p, [c]: 0 }), {}); + const store = getStore('token-prices'); + try { - const now = Date.now(); + const now = Math.floor(Date.now() / 1000); // Try to get all of the tokens from our store. // Any token address that we don't have a record for will @@ -64,12 +57,30 @@ export default async function getTokenPrices(request: Request) { // TokenPricesWithMetadata. All of these TokenPrices will be either // expired or unexpired. const allCachedTokenPrices = possibleCachedTokenPrices.filter( - (possible): possible is TokenPriceWithMetadata => !!possible + (possible): possible is TokenPriceWithMetadata => possible !== null + ); + + console.log('allCachedTokenPrices'); + console.log( + allCachedTokenPrices.map(a => ({ + address: a.data.tokenAddress, + price: a.data.price, + now: ' ' + now, + expiration: a.metadata.expiration, + expired: a.metadata.expiration < now, + })) ); // Let's pull out all of the unexpired TokenPrices from our cache. const cachedUnexpiredTokenPrices = allCachedTokenPrices.filter( - tokenPrice => tokenPrice.metadata.expiration <= now + tokenPrice => tokenPrice.metadata.expiration >= now + ); + + console.log('cachedUnexpiredTokenPrices'); + console.log( + cachedUnexpiredTokenPrices.map(a => ({ + address: a.data.tokenAddress, + })) ); // We'll update our response with those unexpired cached prices. @@ -79,7 +90,14 @@ export default async function getTokenPrices(request: Request) { // Let's pull out all of the expired TokenPrices from our cache. const cachedExpiredTokenPrices = allCachedTokenPrices.filter( - tokenPrice => tokenPrice.metadata.expiration > now + tokenPrice => tokenPrice.metadata.expiration < now + ); + + console.log('cachedExpiredTokenPrices'); + console.log( + cachedExpiredTokenPrices.map(a => ({ + address: a.data.tokenAddress, + })) ); // We'll update our response with those expired cached prices. @@ -92,16 +110,25 @@ export default async function getTokenPrices(request: Request) { // Finally let's get a list of all of the token addresses that // we don't have any price for in our cache, expired or not. - const allUncachedTokenAddresses = rawTokenAddresses.filter( + const allUncachedTokenAddresses = tokens.filter( address => !allCachedTokenPrices.find( cachedTokenPrice => cachedTokenPrice.data.tokenAddress === address ) ); + console.log('allUncachedTokenAddresses'); + console.log( + allUncachedTokenAddresses.map(a => ({ + address: a, + })) + ); + // If there are no expired token prices, and no token addresses that we // don't have a cached value for at all, we can early return! if (allUncachedTokenAddresses.length === 0 && cachedExpiredTokenPrices.length === 0) { + console.log('early exit'); + console.log({ responseBody }); return Response.json({ data: responseBody }); } @@ -110,10 +137,11 @@ export default async function getTokenPrices(request: Request) { // First, let's build up our list of token addresses to query CoinGecko with, // which is all uncached tokens and tokens that have expired. + // Remove "ethereum" if it's in this list const needPricesTokenAddresses = [ ...allUncachedTokenAddresses, ...cachedExpiredTokenPrices.map(tokenPrice => tokenPrice.data.tokenAddress), - ]; + ].filter(address => address !== 'ethereum'); try { // Build up the request URL. @@ -123,18 +151,33 @@ export default async function getTokenPrices(request: Request) { ',' )}`; + console.log('making CoinGecko API call:', tokenPricesUrl); + // Make the request. const tokenPricesResponse = await fetch(tokenPricesUrl); const tokenPricesResponseJson = await tokenPricesResponse.json(); // Create the metadata for our new token prices, with an // expiration in the future. - const tokenPriceMetadata = { metadata: { expiration: now + 1000 * 60 * 30 } }; + const tokenPriceMetadata = { metadata: { expiration: now + 48 } }; // With our response... - Object.keys(tokenPricesResponseJson).forEach(tokenAddress => { - const price = tokenPricesResponseJson[tokenAddress].usd; + const coinGeckoResponseAddresses = Object.keys(tokenPricesResponseJson); + coinGeckoResponseAddresses.forEach(tokenAddress => { + const price: number | undefined = tokenPricesResponseJson[tokenAddress].usd; + const sanitizedAddress = tokenAddress.toLowerCase(); + + // Sometimes no USD price is returned + if (price === undefined) { + store.setJSON( + sanitizedAddress, + { tokenAddress: sanitizedAddress, price: 0 }, + { metadata: { expiration: now + 60 * 10 } } + ); + return; + } + // 1. Replace the token addresses of our existing response object with the new prices. responseBody[sanitizedAddress] = price; // 2. Store these fresh prices in our Blob store. @@ -145,25 +188,48 @@ export default async function getTokenPrices(request: Request) { ); }); + // CoinGecko will only respond back with prices for token addresses + // that it knows about. We should store a price of 0 in our store with + // a long expiration for all addresses that CoinGecko isn't tracking + // (likely spam tokens), so as to not continually query CoinGecko with + // these addresses + const likelySpamAddresses = needPricesTokenAddresses.filter( + x => !coinGeckoResponseAddresses.includes(x) + ); + console.log({ likelySpamAddresses }); + likelySpamAddresses.forEach(tokenAddress => { + const sanitizedAddress = tokenAddress.toLowerCase(); + store.setJSON( + sanitizedAddress, + { tokenAddress: sanitizedAddress, price: 0 }, + { metadata: { expiration: now + 60 * 10 } } + ); + }); + // Do we need to get the price of our chain's gas token (ethereum)? - const ethAsset = needPricesTokenAddresses.find(address => address === 'ethereum'); - if (ethAsset) { + if (needEthereum) { // Build up the request URL. const ethPriceUrl = `${PUBLIC_DEMO_API_BASE_URL}simple/price${AUTH_QUERY_PARAM}&ids=ethereum&vs_currencies=usd`; + console.log('making CoinGecko API call:', ethPriceUrl); + // Make the request. const ethPriceResponse = await fetch(ethPriceUrl); const ethPriceResponseJson = await ethPriceResponse.json(); // Get the price data. - const price = ethPriceResponseJson.ethereum.usd; + const price: number | undefined = ethPriceResponseJson.ethereum.usd; - // 1. Replace the token addresses of our existing response object with the new prices. - responseBody.ethereum = price; + if (price !== undefined) { + // 1. Replace the token addresses of our existing response object with the new prices. + responseBody.ethereum = price; - // 2. Store this fresh prices in our Blob store. - store.setJSON('ethereum', { tokenAddress: 'ethereum', price }, tokenPriceMetadata); + // 2. Store this fresh prices in our Blob store. + store.setJSON('ethereum', { tokenAddress: 'ethereum', price }, tokenPriceMetadata); + } } + + console.log({ responseBody }); return Response.json({ data: responseBody }); } catch (e) { console.error('Error while querying CoinGecko', e); @@ -171,9 +237,6 @@ export default async function getTokenPrices(request: Request) { } } catch (e) { console.error('Error while fetching prices', e); - return Response.json({ - error: 'Unknown error while fetching prices', - data: responseBody, - }); + return Response.json({ error: 'Error while fetching prices', data: responseBody }); } } From b207d663735f3aa78b7d0ec01077c0b8bac3ea19 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Thu, 7 Mar 2024 01:14:18 -0500 Subject: [PATCH 34/56] Refactor to compartmentalize code --- .env | 4 + netlify/functions/tokenPrices.mts | 412 ++++++++++++++++-------------- 2 files changed, 222 insertions(+), 194 deletions(-) diff --git a/.env b/.env index 977667056e..c36bb2b353 100644 --- a/.env +++ b/.env @@ -16,5 +16,9 @@ NEXT_PUBLIC_SITE_URL="https://app.dev.fractalframework.xyz/" NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID="" # CoinGecko API key COINGECKO_API_KEY="" +# Minutes to cache prices for valid token addresses +TOKEN_PRICE_VALID_CACHE_MINUTES="" +# Minutes to cache "0" for invalid token addresses +TOKEN_PRICE_INVALID_CACHE_MINUTES="" # Shutter Public Key NEXT_PUBLIC_SHUTTER_EON_PUBKEY=0x0e6493bbb4ee8b19aa9b70367685049ff01dc9382c46aed83f8bc07d2a5ba3e6030bd83b942c1fd3dff5b79bef3b40bf6b666e51e7f0be14ed62daaffad47435265f5c9403b1a801921981f7d8659a9bd91fe92fb1cf9afdb16178a532adfaf51a237103874bb03afafe9cab2118dae1be5f08a0a28bf488c1581e9db4bc23ca \ No newline at end of file diff --git a/netlify/functions/tokenPrices.mts b/netlify/functions/tokenPrices.mts index 7338220442..ccaf81d4cb 100644 --- a/netlify/functions/tokenPrices.mts +++ b/netlify/functions/tokenPrices.mts @@ -1,6 +1,10 @@ -import { getStore } from '@netlify/blobs'; +// eslint-disable-next-line import/named +import { Store, getStore } from '@netlify/blobs'; import { ethers } from 'ethers'; +const PUBLIC_DEMO_API_BASE_URL = 'https://api.coingecko.com/api/v3/'; +const AUTH_QUERY_PARAM = `?x_cg_demo_api_key=${process.env.COINGECKO_API_KEY}`; + type TokenPriceWithMetadata = { data: { tokenAddress: string; @@ -11,232 +15,252 @@ type TokenPriceWithMetadata = { }; }; -export default async function getTokenPrices(request: Request) { - if (!process.env.COINGECKO_API_KEY) { - console.error('CoinGecko API key is missing'); - return Response.json({ error: 'Unknown error while fetching prices' }, { status: 503 }); - } - - // First we want to pull the tokens off of the request's query param. - const tokensStringParam = new URL(request.url).searchParams.get('tokens'); - if (!tokensStringParam) { - return Response.json({ error: 'Tokens to request were not provided' }, { status: 400 }); - } +type Config = { + store: Store; + now: number; + invalidAddressCacheTime: number; + validAddressCacheTime: number; +}; - // Sanitize user input - const rawTokenAddresses = tokensStringParam.split(','); +function sanitizeUserInput(tokensString: string) { + const rawTokenAddresses = tokensString.split(','); const needEthereum = rawTokenAddresses.map(address => address.toLowerCase()).includes('ethereum'); const validTokenAddresses = rawTokenAddresses.filter(address => ethers.utils.isAddress(address)); const lowerCaseTokenAddresses = validTokenAddresses.map(address => address.toLowerCase()); const tokens = [...new Set(lowerCaseTokenAddresses)]; if (needEthereum) tokens.push('ethereum'); + return { tokens, needEthereum }; +} - // Let's immediately build up our repsonse object, containing each - // token address and an value of 0. We'll modify this along the way - // populating it with more relevant prices. - const responseBody: Record = tokens.reduce((p, c) => ({ ...p, [c]: 0 }), {}); +async function splitData( + config: Config, + tokens: string[], + responseBodyCallback: (address: string, price: number) => void +) { + // Try to get all of the tokens from our store. + // Any token address that we don't have a record for will + // come back as null. + const possibleCachedTokenPrices = await Promise.all( + tokens.map( + tokenAddress => + config.store.getWithMetadata(tokenAddress, { + type: 'json', + }) as Promise | null + ) + ); + + // Filter out the null values, leaving us with an array of + // TokenPricesWithMetadata. All of these TokenPrices will be either + // expired or unexpired. + const cachedTokenPrices = possibleCachedTokenPrices.filter( + (possible): possible is TokenPriceWithMetadata => possible !== null + ); + + // Let's pull out all of the expired addresses from our cache. + const expiredCachedTokenAddresses = cachedTokenPrices + .filter(tokenPrice => tokenPrice.metadata.expiration < config.now) + .map(tokenPrice => tokenPrice.data.tokenAddress); + + // Finally let's get a list of all of the token addresses that + // we don't have any record of in our cache. + const uncachedTokenAddresses = tokens.filter( + address => + !cachedTokenPrices.find(cachedTokenPrice => cachedTokenPrice.data.tokenAddress === address) + ); + + // We'll update our response with those cached expired and + // unexpired prices. + cachedTokenPrices.forEach(tokenPrice => { + responseBodyCallback(tokenPrice.data.tokenAddress, tokenPrice.data.price); + }); + + return { expiredCachedTokenAddresses, uncachedTokenAddresses }; +} - const store = getStore('token-prices'); +function getTokenPricesUrl(tokens: string[]) { + const tokenPricesUrl = `${PUBLIC_DEMO_API_BASE_URL}simple/token_price/ethereum/${AUTH_QUERY_PARAM}&vs_currencies=usd&contract_addresses=${tokens.join( + ',' + )}`; + return tokenPricesUrl; +} - try { - const now = Math.floor(Date.now() / 1000); - - // Try to get all of the tokens from our store. - // Any token address that we don't have a record for will - // come back as null. - const possibleCachedTokenPrices = await Promise.all( - tokens.map( - tokenAddress => - store.getWithMetadata(tokenAddress, { - type: 'json', - }) as Promise | null - ) - ); +function getEthereumPriceUrl() { + const ethPriceUrl = `${PUBLIC_DEMO_API_BASE_URL}simple/price${AUTH_QUERY_PARAM}&ids=ethereum&vs_currencies=usd`; + return ethPriceUrl; +} - // Filter out the null values, leaving us with an array of - // TokenPricesWithMetadata. All of these TokenPrices will be either - // expired or unexpired. - const allCachedTokenPrices = possibleCachedTokenPrices.filter( - (possible): possible is TokenPriceWithMetadata => possible !== null - ); +async function storeTokenPrice( + config: Config, + tokenAddress: string, + price: number, + expiration: number +) { + await config.store.setJSON( + tokenAddress, + { tokenAddress, price }, + { metadata: { expiration: config.now + expiration } } + ); +} - console.log('allCachedTokenPrices'); - console.log( - allCachedTokenPrices.map(a => ({ - address: a.data.tokenAddress, - price: a.data.price, - now: ' ' + now, - expiration: a.metadata.expiration, - expired: a.metadata.expiration < now, - })) - ); +async function processTokenPricesResponse( + config: Config, + tokenPricesResponseJson: Record, + responseBodyCallback: (address: string, price: number) => void +) { + const coinGeckoResponseAddresses = Object.keys(tokenPricesResponseJson); + for await (const tokenAddress of coinGeckoResponseAddresses) { + const price = tokenPricesResponseJson[tokenAddress].usd; + const sanitizedAddress = tokenAddress.toLowerCase(); + + // Sometimes no USD price is returned. If this happens, + // we should consider it as though CoinGecko doesn't support + // this address and not query it again for a while. + if (price === undefined) { + await storeTokenPrice(config, sanitizedAddress, 0, config.invalidAddressCacheTime); + } else { + // Otherwise, update the cache with the new price and update + // the response object. + responseBodyCallback(sanitizedAddress, price); + await storeTokenPrice(config, sanitizedAddress, price, config.validAddressCacheTime); + } + } - // Let's pull out all of the unexpired TokenPrices from our cache. - const cachedUnexpiredTokenPrices = allCachedTokenPrices.filter( - tokenPrice => tokenPrice.metadata.expiration >= now - ); + return coinGeckoResponseAddresses; +} - console.log('cachedUnexpiredTokenPrices'); - console.log( - cachedUnexpiredTokenPrices.map(a => ({ - address: a.data.tokenAddress, - })) - ); +async function processUnknownAddresses( + config: Config, + needPricesTokenAddresses: string[], + responseAddresses: string[] +) { + const unknownAddresses = needPricesTokenAddresses + .filter(x => !responseAddresses.includes(x)) + .map(address => address.toLowerCase()); + for await (const tokenAddress of unknownAddresses) { + await storeTokenPrice(config, tokenAddress, 0, config.invalidAddressCacheTime); + } +} - // We'll update our response with those unexpired cached prices. - cachedUnexpiredTokenPrices.forEach(tokenPrice => { - responseBody[tokenPrice.data.tokenAddress] = tokenPrice.data.price; - }); +async function coinGeckoRequestAndResponse( + config: Config, + url: string, + responseBodyCallback: (address: string, price: number) => void +) { + // Make the request to CoinGecko. + // Response is of shape: + // { + // [tokenAddress]: { usd: 1234 }, + // } + let ethPriceResponseJson: Record; + try { + const ethPriceResponse = await fetch(url); + ethPriceResponseJson = await ethPriceResponse.json(); + } catch (e) { + throw e; + } - // Let's pull out all of the expired TokenPrices from our cache. - const cachedExpiredTokenPrices = allCachedTokenPrices.filter( - tokenPrice => tokenPrice.metadata.expiration < now - ); + // Update the cache with the new price and update + // the response object. + const responseAddresses = processTokenPricesResponse( + config, + ethPriceResponseJson, + responseBodyCallback + ); - console.log('cachedExpiredTokenPrices'); - console.log( - cachedExpiredTokenPrices.map(a => ({ - address: a.data.tokenAddress, - })) - ); - - // We'll update our response with those expired cached prices. - // This is done now in the offchance that we won't be able to contact - // CoinGecko later. Ideally these will be updated after getting - // fresher data from CoinGecko - cachedExpiredTokenPrices.forEach(tokenPrice => { - responseBody[tokenPrice.data.tokenAddress] = tokenPrice.data.price; - }); - - // Finally let's get a list of all of the token addresses that - // we don't have any price for in our cache, expired or not. - const allUncachedTokenAddresses = tokens.filter( - address => - !allCachedTokenPrices.find( - cachedTokenPrice => cachedTokenPrice.data.tokenAddress === address - ) - ); + return responseAddresses; +} - console.log('allUncachedTokenAddresses'); - console.log( - allUncachedTokenAddresses.map(a => ({ - address: a, - })) - ); +export default async function getTokenPrices(request: Request) { + if (!process.env.COINGECKO_API_KEY) { + console.error('CoinGecko API key is missing'); + return Response.json({ error: 'Error while fetching prices' }, { status: 503 }); + } - // If there are no expired token prices, and no token addresses that we - // don't have a cached value for at all, we can early return! - if (allUncachedTokenAddresses.length === 0 && cachedExpiredTokenPrices.length === 0) { - console.log('early exit'); - console.log({ responseBody }); - return Response.json({ data: responseBody }); - } + if (!process.env.TOKEN_PRICE_INVALID_CACHE_MINUTES) { + console.error('TOKEN_PRICE_INVALID_CACHE_MINUTES is not set'); + return Response.json({ error: 'Error while fetching prices' }, { status: 503 }); + } - // If we got here, then we have either some expired prices for given tokens, - // or no prices at all for given tokens. + if (!process.env.TOKEN_PRICE_VALID_CACHE_MINUTES) { + console.error('TOKEN_PRICE_VALID_CACHE_MINUTES is not set'); + return Response.json({ error: 'Error while fetching prices' }, { status: 503 }); + } - // First, let's build up our list of token addresses to query CoinGecko with, - // which is all uncached tokens and tokens that have expired. - // Remove "ethereum" if it's in this list - const needPricesTokenAddresses = [ - ...allUncachedTokenAddresses, - ...cachedExpiredTokenPrices.map(tokenPrice => tokenPrice.data.tokenAddress), - ].filter(address => address !== 'ethereum'); + const tokensStringParam = new URL(request.url).searchParams.get('tokens'); + if (!tokensStringParam) { + return Response.json({ error: 'Tokens missing from request' }, { status: 400 }); + } - try { - // Build up the request URL. - const PUBLIC_DEMO_API_BASE_URL = 'https://api.coingecko.com/api/v3/'; - const AUTH_QUERY_PARAM = `?x_cg_demo_api_key=${process.env.COINGECKO_API_KEY}`; - const tokenPricesUrl = `${PUBLIC_DEMO_API_BASE_URL}simple/token_price/ethereum/${AUTH_QUERY_PARAM}&vs_currencies=usd&contract_addresses=${needPricesTokenAddresses.join( - ',' - )}`; - - console.log('making CoinGecko API call:', tokenPricesUrl); - - // Make the request. - const tokenPricesResponse = await fetch(tokenPricesUrl); - const tokenPricesResponseJson = await tokenPricesResponse.json(); - - // Create the metadata for our new token prices, with an - // expiration in the future. - const tokenPriceMetadata = { metadata: { expiration: now + 48 } }; - - // With our response... - const coinGeckoResponseAddresses = Object.keys(tokenPricesResponseJson); - coinGeckoResponseAddresses.forEach(tokenAddress => { - const price: number | undefined = tokenPricesResponseJson[tokenAddress].usd; - - const sanitizedAddress = tokenAddress.toLowerCase(); - - // Sometimes no USD price is returned - if (price === undefined) { - store.setJSON( - sanitizedAddress, - { tokenAddress: sanitizedAddress, price: 0 }, - { metadata: { expiration: now + 60 * 10 } } - ); - return; - } - - // 1. Replace the token addresses of our existing response object with the new prices. - responseBody[sanitizedAddress] = price; - // 2. Store these fresh prices in our Blob store. - store.setJSON( - sanitizedAddress, - { tokenAddress: sanitizedAddress, price }, - tokenPriceMetadata - ); - }); + const store = getStore('token-prices'); + const now = Math.floor(Date.now() / 1000); + const invalidAddressCacheTime = parseInt(process.env.TOKEN_PRICE_INVALID_CACHE_MINUTES); + const validAddressCacheTime = parseInt(process.env.TOKEN_PRICE_VALID_CACHE_MINUTES); + const config = { store, now, invalidAddressCacheTime, validAddressCacheTime }; - // CoinGecko will only respond back with prices for token addresses - // that it knows about. We should store a price of 0 in our store with - // a long expiration for all addresses that CoinGecko isn't tracking - // (likely spam tokens), so as to not continually query CoinGecko with - // these addresses - const likelySpamAddresses = needPricesTokenAddresses.filter( - x => !coinGeckoResponseAddresses.includes(x) - ); - console.log({ likelySpamAddresses }); - likelySpamAddresses.forEach(tokenAddress => { - const sanitizedAddress = tokenAddress.toLowerCase(); - store.setJSON( - sanitizedAddress, - { tokenAddress: sanitizedAddress, price: 0 }, - { metadata: { expiration: now + 60 * 10 } } - ); - }); + const { tokens, needEthereum } = sanitizeUserInput(tokensStringParam); - // Do we need to get the price of our chain's gas token (ethereum)? - if (needEthereum) { - // Build up the request URL. - const ethPriceUrl = `${PUBLIC_DEMO_API_BASE_URL}simple/price${AUTH_QUERY_PARAM}&ids=ethereum&vs_currencies=usd`; + // Let's immediately build up our repsonse object, containing each + // token address and an value of 0. We'll modify this along the way + // populating it with more relevant prices. + const responseBody: Record = tokens.reduce((p, c) => ({ ...p, [c]: 0 }), {}); - console.log('making CoinGecko API call:', ethPriceUrl); + const { expiredCachedTokenAddresses, uncachedTokenAddresses } = await splitData( + config, + tokens, + (address, price) => { + responseBody[address] = price; + } + ); - // Make the request. - const ethPriceResponse = await fetch(ethPriceUrl); - const ethPriceResponseJson = await ethPriceResponse.json(); + // If there are no expired token prices, and no token addresses that we + // don't have a cached value for at all, we can early return! + if (expiredCachedTokenAddresses.length === 0 && uncachedTokenAddresses.length === 0) { + return Response.json({ data: responseBody }); + } - // Get the price data. - const price: number | undefined = ethPriceResponseJson.ethereum.usd; + // If we got here, then we have either some expired prices for given tokens, + // or no prices at all for given tokens. - if (price !== undefined) { - // 1. Replace the token addresses of our existing response object with the new prices. - responseBody.ethereum = price; + // First, let's build up our list of token addresses to query CoinGecko with, + // which is all uncached tokens and tokens that have expired. + // Remove "ethereum" if it's in this list. + const needPricesTokenAddresses = [ + ...uncachedTokenAddresses, + ...expiredCachedTokenAddresses, + ].filter(address => address !== 'ethereum'); - // 2. Store this fresh prices in our Blob store. - store.setJSON('ethereum', { tokenAddress: 'ethereum', price }, tokenPriceMetadata); - } + let responseAddresses: string[]; + try { + responseAddresses = await coinGeckoRequestAndResponse( + config, + getTokenPricesUrl(needPricesTokenAddresses), + (address, price) => { + responseBody[address] = price; } + ); + } catch (e) { + console.error('Error while querying CoinGecko', e); + return Response.json({ error: 'Error while fetching prices', data: responseBody }); + } - console.log({ responseBody }); - return Response.json({ data: responseBody }); + // In the previous request, CoinGecko will only respond back with prices + // for token addresses that it knows about. We should store a price of 0 + // in our store with a long expiration for all addresses that CoinGecko + // isn't tracking (likely spam tokens), so as to not continually query + // CoinGecko with these addresses + await processUnknownAddresses(config, needPricesTokenAddresses, responseAddresses); + + // Do we need to get the price of our chain's gas token (ethereum)? + if (needEthereum) { + try { + await coinGeckoRequestAndResponse(config, getEthereumPriceUrl(), (address, price) => { + responseBody[address] = price; + }); } catch (e) { console.error('Error while querying CoinGecko', e); return Response.json({ error: 'Error while fetching prices', data: responseBody }); } - } catch (e) { - console.error('Error while fetching prices', e); - return Response.json({ error: 'Error while fetching prices', data: responseBody }); } + + return Response.json({ data: responseBody }); } From e70037d2806dcb7f5dc2bc0c8715640919a6f411 Mon Sep 17 00:00:00 2001 From: Kirill Klimenko Date: Thu, 7 Mar 2024 15:24:30 +0100 Subject: [PATCH 35/56] Split stores per network --- src/providers/App/hooks/usePriceAPI.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/providers/App/hooks/usePriceAPI.ts b/src/providers/App/hooks/usePriceAPI.ts index c88cce0985..59fb5c4091 100644 --- a/src/providers/App/hooks/usePriceAPI.ts +++ b/src/providers/App/hooks/usePriceAPI.ts @@ -26,7 +26,7 @@ export default function usePriceAPI() { } if (tokensAddresses.length > 0) { const pricesResponse = await fetch( - `/.netlify/functions/tokenPrices?tokens=${tokensAddresses.join(',')}` + `/.netlify/functions/tokenPrices?tokens=${tokensAddresses.join(',')}&network=ethereum` ); const pricesResponseBody = await pricesResponse.json(); From 91c03261e7edd321436eff1662f3a4efd613bd4c Mon Sep 17 00:00:00 2001 From: Kirill Klimenko Date: Thu, 7 Mar 2024 15:37:27 +0100 Subject: [PATCH 36/56] Add network param to the tokenPrices function --- netlify/functions/tokenPrices.mts | 53 +++++++++++++++++++------------ 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/netlify/functions/tokenPrices.mts b/netlify/functions/tokenPrices.mts index ccaf81d4cb..bb9ae05894 100644 --- a/netlify/functions/tokenPrices.mts +++ b/netlify/functions/tokenPrices.mts @@ -22,14 +22,17 @@ type Config = { validAddressCacheTime: number; }; -function sanitizeUserInput(tokensString: string) { +const SUPPORTED_NETWORKS = ['ethereum'] as const; +type SupportedNetworks = typeof SUPPORTED_NETWORKS[number]; + +function sanitizeUserInput(tokensString: string, network: SupportedNetworks) { const rawTokenAddresses = tokensString.split(','); - const needEthereum = rawTokenAddresses.map(address => address.toLowerCase()).includes('ethereum'); + const needNativeAsset = rawTokenAddresses.map(address => address.toLowerCase()).includes(network); const validTokenAddresses = rawTokenAddresses.filter(address => ethers.utils.isAddress(address)); const lowerCaseTokenAddresses = validTokenAddresses.map(address => address.toLowerCase()); const tokens = [...new Set(lowerCaseTokenAddresses)]; - if (needEthereum) tokens.push('ethereum'); - return { tokens, needEthereum }; + if (needNativeAsset) tokens.push(network); + return { tokens, needNativeAsset }; } async function splitData( @@ -77,16 +80,16 @@ async function splitData( return { expiredCachedTokenAddresses, uncachedTokenAddresses }; } -function getTokenPricesUrl(tokens: string[]) { - const tokenPricesUrl = `${PUBLIC_DEMO_API_BASE_URL}simple/token_price/ethereum/${AUTH_QUERY_PARAM}&vs_currencies=usd&contract_addresses=${tokens.join( +function getTokenPricesUrl(tokens: string[], network: SupportedNetworks) { + const tokenPricesUrl = `${PUBLIC_DEMO_API_BASE_URL}simple/token_price/${network}/${AUTH_QUERY_PARAM}&vs_currencies=usd&contract_addresses=${tokens.join( ',' )}`; return tokenPricesUrl; } -function getEthereumPriceUrl() { - const ethPriceUrl = `${PUBLIC_DEMO_API_BASE_URL}simple/price${AUTH_QUERY_PARAM}&ids=ethereum&vs_currencies=usd`; - return ethPriceUrl; +function getNativeAssetPriceUrl(network: SupportedNetworks) { + const nativeAssetPriceUrl = `${PUBLIC_DEMO_API_BASE_URL}simple/price${AUTH_QUERY_PARAM}&ids=${network}&vs_currencies=usd`; + return nativeAssetPriceUrl; } async function storeTokenPrice( @@ -186,18 +189,24 @@ export default async function getTokenPrices(request: Request) { return Response.json({ error: 'Error while fetching prices' }, { status: 503 }); } - const tokensStringParam = new URL(request.url).searchParams.get('tokens'); + const requestSearchParams = new URL(request.url).searchParams; + const tokensStringParam = requestSearchParams.get('tokens'); if (!tokensStringParam) { return Response.json({ error: 'Tokens missing from request' }, { status: 400 }); } - const store = getStore('token-prices'); + const networkParam = requestSearchParams.get('network') as SupportedNetworks; + if (!networkParam || !SUPPORTED_NETWORKS.includes(networkParam)) { + return Response.json({ error: 'Requested network is not supported' }, { status: 400 }); + } + + const store = getStore(`${networkParam}-token-prices`); const now = Math.floor(Date.now() / 1000); const invalidAddressCacheTime = parseInt(process.env.TOKEN_PRICE_INVALID_CACHE_MINUTES); const validAddressCacheTime = parseInt(process.env.TOKEN_PRICE_VALID_CACHE_MINUTES); const config = { store, now, invalidAddressCacheTime, validAddressCacheTime }; - const { tokens, needEthereum } = sanitizeUserInput(tokensStringParam); + const { tokens, needNativeAsset } = sanitizeUserInput(tokensStringParam, networkParam); // Let's immediately build up our repsonse object, containing each // token address and an value of 0. We'll modify this along the way @@ -223,17 +232,17 @@ export default async function getTokenPrices(request: Request) { // First, let's build up our list of token addresses to query CoinGecko with, // which is all uncached tokens and tokens that have expired. - // Remove "ethereum" if it's in this list. + // Remove native asset name if it's in this list. const needPricesTokenAddresses = [ ...uncachedTokenAddresses, ...expiredCachedTokenAddresses, - ].filter(address => address !== 'ethereum'); + ].filter(address => address !== networkParam); let responseAddresses: string[]; try { responseAddresses = await coinGeckoRequestAndResponse( config, - getTokenPricesUrl(needPricesTokenAddresses), + getTokenPricesUrl(needPricesTokenAddresses, networkParam), (address, price) => { responseBody[address] = price; } @@ -250,12 +259,16 @@ export default async function getTokenPrices(request: Request) { // CoinGecko with these addresses await processUnknownAddresses(config, needPricesTokenAddresses, responseAddresses); - // Do we need to get the price of our chain's gas token (ethereum)? - if (needEthereum) { + // Do we need to get the price of our chain's gas token? + if (needNativeAsset) { try { - await coinGeckoRequestAndResponse(config, getEthereumPriceUrl(), (address, price) => { - responseBody[address] = price; - }); + await coinGeckoRequestAndResponse( + config, + getNativeAssetPriceUrl(networkParam), + (address, price) => { + responseBody[address] = price; + } + ); } catch (e) { console.error('Error while querying CoinGecko', e); return Response.json({ error: 'Error while fetching prices', data: responseBody }); From 4cdd6f18a0bf363287f52b37b786d545f0dbfba8 Mon Sep 17 00:00:00 2001 From: Kirill Klimenko Date: Thu, 7 Mar 2024 15:51:16 +0100 Subject: [PATCH 37/56] Refactor useEthersProvider and useEthersSigner to return singleton instance from EthersContext --- .../formComponents/AzoriusNFTDetail.tsx | 2 +- .../formComponents/AzoriusTokenDetails.tsx | 2 +- .../DaoCreator/hooks/usePrepareFormData.ts | 4 +- .../pages/DAOTreasury/components/Assets.tsx | 6 +- .../DaoDashboard/Info/InfoGovernance.tsx | 2 +- .../Signers/modals/AddSignerModal.tsx | 2 +- .../Signers/modals/RemoveSignerModal.tsx | 2 +- src/components/ui/modals/DelegateModal.tsx | 2 +- .../ui/modals/ForkProposalTemplateModal.tsx | 4 +- src/components/ui/modals/UnwrapToken.tsx | 2 +- src/components/ui/modals/WrapToken.tsx | 2 +- .../ui/proposal/useProposalCountdown.tsx | 2 +- src/graphql/utils/index.ts | 2 +- .../loaders/governance/useAzoriusProposals.ts | 8 ++- .../governance/useERC20LinearStrategy.ts | 2 +- .../governance/useERC721LinearStrategy.ts | 2 +- src/hooks/DAO/loaders/useFractalFreeze.ts | 2 +- .../DAO/loaders/useFractalGuardContracts.ts | 10 +++- src/hooks/DAO/loaders/useFractalNode.ts | 2 +- src/hooks/DAO/loaders/useFractalTreasury.ts | 2 +- .../DAO/loaders/useGovernanceContracts.ts | 3 +- src/hooks/DAO/proposal/useCastVote.ts | 2 +- src/hooks/DAO/proposal/usePrepareProposal.ts | 2 +- src/hooks/DAO/proposal/useSubmitProposal.ts | 4 +- src/hooks/DAO/useClawBack.ts | 2 +- src/hooks/DAO/useDAOName.ts | 2 +- src/hooks/safe/useSafeContracts.ts | 2 +- .../schemas/DAOCreate/useDAOCreateTests.ts | 2 +- .../schemas/common/useValidationAddress.tsx | 6 +- src/hooks/stake/lido/useLidoStaking.ts | 6 +- src/hooks/utils/useAddress.ts | 2 +- src/hooks/utils/useAvatar.ts | 2 +- src/hooks/utils/useBlockTimestamp.ts | 2 +- src/hooks/utils/useCurrentBlockNumber.ts | 2 +- src/hooks/utils/useDisplayName.ts | 2 +- src/hooks/utils/useEthersProvider.ts | 26 --------- src/hooks/utils/useEthersSigner.ts | 21 ------- src/hooks/utils/useSafeTransactions.ts | 2 +- src/hooks/utils/useSignerOrProvider.ts | 4 +- src/providers/App/hooks/useSafeAPI.ts | 5 +- src/providers/App/useReadOnlyValues.ts | 2 +- .../Ethers/hooks/useEthersProvider.ts | 7 +++ src/providers/Ethers/hooks/useEthersSigner.ts | 7 +++ src/providers/Ethers/index.tsx | 57 +++++++++++++++++++ src/providers/Providers.tsx | 26 +++++---- 45 files changed, 151 insertions(+), 109 deletions(-) delete mode 100644 src/hooks/utils/useEthersProvider.ts delete mode 100644 src/hooks/utils/useEthersSigner.ts create mode 100644 src/providers/Ethers/hooks/useEthersProvider.ts create mode 100644 src/providers/Ethers/hooks/useEthersSigner.ts create mode 100644 src/providers/Ethers/index.tsx diff --git a/src/components/DaoCreator/formComponents/AzoriusNFTDetail.tsx b/src/components/DaoCreator/formComponents/AzoriusNFTDetail.tsx index e422d285ea..7347420cf5 100644 --- a/src/components/DaoCreator/formComponents/AzoriusNFTDetail.tsx +++ b/src/components/DaoCreator/formComponents/AzoriusNFTDetail.tsx @@ -4,7 +4,7 @@ import { useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { erc721ABI } from 'wagmi'; import useDisplayName from '../../../hooks/utils/useDisplayName'; -import { useEthersProvider } from '../../../hooks/utils/useEthersProvider'; +import { useEthersProvider } from '../../../providers/Ethers/hooks/useEthersProvider'; import { BigNumberValuePair, ERC721TokenConfig } from '../../../types'; import { BarLoader } from '../../ui/loaders/BarLoader'; diff --git a/src/components/DaoCreator/formComponents/AzoriusTokenDetails.tsx b/src/components/DaoCreator/formComponents/AzoriusTokenDetails.tsx index 6ebd0fc126..5f3c2b775a 100644 --- a/src/components/DaoCreator/formComponents/AzoriusTokenDetails.tsx +++ b/src/components/DaoCreator/formComponents/AzoriusTokenDetails.tsx @@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next'; import { erc20ABI } from 'wagmi'; import { BACKGROUND_SEMI_TRANSPARENT } from '../../../constants/common'; import { createAccountSubstring } from '../../../hooks/utils/useDisplayName'; -import { useEthersProvider } from '../../../hooks/utils/useEthersProvider'; +import { useEthersProvider } from '../../../providers/Ethers/hooks/useEthersProvider'; import { TokenCreationType, ICreationStepProps } from '../../../types'; import SupportTooltip from '../../ui/badges/SupportTooltip'; import ContentBoxTitle from '../../ui/containers/ContentBox/ContentBoxTitle'; diff --git a/src/components/DaoCreator/hooks/usePrepareFormData.ts b/src/components/DaoCreator/hooks/usePrepareFormData.ts index 9e283280ed..54bece8582 100644 --- a/src/components/DaoCreator/hooks/usePrepareFormData.ts +++ b/src/components/DaoCreator/hooks/usePrepareFormData.ts @@ -1,8 +1,8 @@ import { IVotes__factory } from '@fractal-framework/fractal-contracts'; import { useCallback } from 'react'; -import { useEthersProvider } from '../../../hooks/utils/useEthersProvider'; -import { useEthersSigner } from '../../../hooks/utils/useEthersSigner'; +import { useEthersProvider } from '../../../providers/Ethers/hooks/useEthersProvider'; +import { useEthersSigner } from '../../../providers/Ethers/hooks/useEthersSigner'; import { SafeMultisigDAO, DAOFreezeGuardConfig, diff --git a/src/components/pages/DAOTreasury/components/Assets.tsx b/src/components/pages/DAOTreasury/components/Assets.tsx index 5df12b13a4..89e0522f1f 100644 --- a/src/components/pages/DAOTreasury/components/Assets.tsx +++ b/src/components/pages/DAOTreasury/components/Assets.tsx @@ -248,7 +248,11 @@ export function Assets() { const showClaimETHButton = canUserCreateProposal && staking.lido && lidoWithdrawelNFT; useEffect(() => { const getLidoClaimableStatus = async () => { - if (!staking.lido?.withdrawalQueueContractAddress || !lidoWithdrawelNFT) { + if ( + !staking.lido?.withdrawalQueueContractAddress || + !lidoWithdrawelNFT || + !signerOrProvider + ) { return; } const withdrawalQueueContract = getWithdrawalQueueContract( diff --git a/src/components/pages/DaoDashboard/Info/InfoGovernance.tsx b/src/components/pages/DaoDashboard/Info/InfoGovernance.tsx index 6603d28907..b954621778 100644 --- a/src/components/pages/DaoDashboard/Info/InfoGovernance.tsx +++ b/src/components/pages/DaoDashboard/Info/InfoGovernance.tsx @@ -3,7 +3,7 @@ import { Govern } from '@decent-org/fractal-ui'; import { MultisigFreezeGuard } from '@fractal-framework/fractal-contracts'; import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { useEthersProvider } from '../../../../hooks/utils/useEthersProvider'; +import { useEthersProvider } from '../../../../providers/Ethers/hooks/useEthersProvider'; import { useTimeHelpers } from '../../../../hooks/utils/useTimeHelpers'; import { useFractal } from '../../../../providers/App/AppProvider'; import { AzoriusGovernance, FreezeGuardType } from '../../../../types'; diff --git a/src/components/pages/DaoSettings/components/Signers/modals/AddSignerModal.tsx b/src/components/pages/DaoSettings/components/Signers/modals/AddSignerModal.tsx index 16877e04f1..55d7eb12cb 100644 --- a/src/components/pages/DaoSettings/components/Signers/modals/AddSignerModal.tsx +++ b/src/components/pages/DaoSettings/components/Signers/modals/AddSignerModal.tsx @@ -16,7 +16,7 @@ import { useCallback, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import * as Yup from 'yup'; import { useValidationAddress } from '../../../../../../hooks/schemas/common/useValidationAddress'; -import { useEthersSigner } from '../../../../../../hooks/utils/useEthersSigner'; +import { useEthersSigner } from '../../../../../../providers/Ethers/hooks/useEthersSigner'; import { useFractal } from '../../../../../../providers/App/AppProvider'; import { couldBeENS } from '../../../../../../utils/url'; import SupportTooltip from '../../../../../ui/badges/SupportTooltip'; diff --git a/src/components/pages/DaoSettings/components/Signers/modals/RemoveSignerModal.tsx b/src/components/pages/DaoSettings/components/Signers/modals/RemoveSignerModal.tsx index 7212e5a099..4cbf5454f8 100644 --- a/src/components/pages/DaoSettings/components/Signers/modals/RemoveSignerModal.tsx +++ b/src/components/pages/DaoSettings/components/Signers/modals/RemoveSignerModal.tsx @@ -13,7 +13,7 @@ import { import { useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Address, useEnsName } from 'wagmi'; -import { useEthersProvider } from '../../../../../../hooks/utils/useEthersProvider'; +import { useEthersProvider } from '../../../../../../providers/Ethers/hooks/useEthersProvider'; import { useFractal } from '../../../../../../providers/App/AppProvider'; import SupportTooltip from '../../../../../ui/badges/SupportTooltip'; import { CustomNonceInput } from '../../../../../ui/forms/CustomNonceInput'; diff --git a/src/components/ui/modals/DelegateModal.tsx b/src/components/ui/modals/DelegateModal.tsx index 9cff91ec09..db041c035e 100644 --- a/src/components/ui/modals/DelegateModal.tsx +++ b/src/components/ui/modals/DelegateModal.tsx @@ -7,7 +7,7 @@ import * as Yup from 'yup'; import useDelegateVote from '../../../hooks/DAO/useDelegateVote'; import { useValidationAddress } from '../../../hooks/schemas/common/useValidationAddress'; import useDisplayName from '../../../hooks/utils/useDisplayName'; -import { useEthersSigner } from '../../../hooks/utils/useEthersSigner'; +import { useEthersSigner } from '../../../providers/Ethers/hooks/useEthersSigner'; import { useFractal } from '../../../providers/App/AppProvider'; import { AzoriusGovernance, DecentGovernance } from '../../../types'; import { formatCoin } from '../../../utils/numberFormats'; diff --git a/src/components/ui/modals/ForkProposalTemplateModal.tsx b/src/components/ui/modals/ForkProposalTemplateModal.tsx index 21ebdb0af2..9a94189acd 100644 --- a/src/components/ui/modals/ForkProposalTemplateModal.tsx +++ b/src/components/ui/modals/ForkProposalTemplateModal.tsx @@ -7,8 +7,8 @@ import { DAO_ROUTES } from '../../../constants/routes'; import useSubmitProposal from '../../../hooks/DAO/proposal/useSubmitProposal'; import { useIsSafe } from '../../../hooks/safe/useIsSafe'; import { validateAddress } from '../../../hooks/schemas/common/useValidationAddress'; -import { useEthersProvider } from '../../../hooks/utils/useEthersProvider'; -import { useEthersSigner } from '../../../hooks/utils/useEthersSigner'; +import { useEthersProvider } from '../../../providers/Ethers/hooks/useEthersProvider'; +import { useEthersSigner } from '../../../providers/Ethers/hooks/useEthersSigner'; import { useFractal } from '../../../providers/App/AppProvider'; import { disconnectedChain } from '../../../providers/NetworkConfig/NetworkConfigProvider'; import { ProposalTemplate } from '../../../types/createProposalTemplate'; diff --git a/src/components/ui/modals/UnwrapToken.tsx b/src/components/ui/modals/UnwrapToken.tsx index 72d5c562f4..f88a5db66f 100644 --- a/src/components/ui/modals/UnwrapToken.tsx +++ b/src/components/ui/modals/UnwrapToken.tsx @@ -9,7 +9,7 @@ import { useAccount } from 'wagmi'; import * as Yup from 'yup'; import { useERC20LinearToken } from '../../../hooks/DAO/loaders/governance/useERC20LinearToken'; import useApproval from '../../../hooks/utils/useApproval'; -import { useEthersSigner } from '../../../hooks/utils/useEthersSigner'; +import { useEthersSigner } from '../../../providers/Ethers/hooks/useEthersSigner'; import { useFormHelpers } from '../../../hooks/utils/useFormHelpers'; import { useTransaction } from '../../../hooks/utils/useTransaction'; import { useFractal } from '../../../providers/App/AppProvider'; diff --git a/src/components/ui/modals/WrapToken.tsx b/src/components/ui/modals/WrapToken.tsx index 52320b578b..ed502c7a3e 100644 --- a/src/components/ui/modals/WrapToken.tsx +++ b/src/components/ui/modals/WrapToken.tsx @@ -10,7 +10,7 @@ import * as Yup from 'yup'; import { logError } from '../../../helpers/errorLogging'; import { useERC20LinearToken } from '../../../hooks/DAO/loaders/governance/useERC20LinearToken'; import useApproval from '../../../hooks/utils/useApproval'; -import { useEthersSigner } from '../../../hooks/utils/useEthersSigner'; +import { useEthersSigner } from '../../../providers/Ethers/hooks/useEthersSigner'; import { useFormHelpers } from '../../../hooks/utils/useFormHelpers'; import { useTransaction } from '../../../hooks/utils/useTransaction'; import { useFractal } from '../../../providers/App/AppProvider'; diff --git a/src/components/ui/proposal/useProposalCountdown.tsx b/src/components/ui/proposal/useProposalCountdown.tsx index 32759d4227..a8d6ae538b 100644 --- a/src/components/ui/proposal/useProposalCountdown.tsx +++ b/src/components/ui/proposal/useProposalCountdown.tsx @@ -5,7 +5,7 @@ import { logError } from '../../../helpers/errorLogging'; import useSnapshotProposal from '../../../hooks/DAO/loaders/snapshot/useSnapshotProposal'; import { useDAOProposals } from '../../../hooks/DAO/loaders/useProposals'; import useUpdateProposalState from '../../../hooks/DAO/proposal/useUpdateProposalState'; -import { useEthersProvider } from '../../../hooks/utils/useEthersProvider'; +import { useEthersProvider } from '../../../providers/Ethers/hooks/useEthersProvider'; import { useFractal } from '../../../providers/App/AppProvider'; import { AzoriusGovernance, diff --git a/src/graphql/utils/index.ts b/src/graphql/utils/index.ts index 06aba49426..f0df4aa7e6 100644 --- a/src/graphql/utils/index.ts +++ b/src/graphql/utils/index.ts @@ -1,5 +1,5 @@ import { useMemo } from 'react'; -import { useEthersProvider } from '../../hooks/utils/useEthersProvider'; +import { useEthersProvider } from '../../providers/Ethers/hooks/useEthersProvider'; import { supportedChains } from '../../providers/NetworkConfig/NetworkConfigProvider'; export const useSubgraphChainName = () => { diff --git a/src/hooks/DAO/loaders/governance/useAzoriusProposals.ts b/src/hooks/DAO/loaders/governance/useAzoriusProposals.ts index 3f8c43390a..df4c52ca4f 100644 --- a/src/hooks/DAO/loaders/governance/useAzoriusProposals.ts +++ b/src/hooks/DAO/loaders/governance/useAzoriusProposals.ts @@ -12,11 +12,11 @@ import { useCallback, useEffect, useMemo } from 'react'; import { getEventRPC } from '../../../../helpers'; import { useFractal } from '../../../../providers/App/AppProvider'; import { FractalGovernanceAction } from '../../../../providers/App/governance/action'; +import { useEthersProvider } from '../../../../providers/Ethers/hooks/useEthersProvider'; import { ProposalMetadata, MetaTransaction, VotingStrategyType } from '../../../../types'; import { AzoriusProposal, ProposalData } from '../../../../types/daoProposal'; import { mapProposalCreatedEventToProposal, getProposalVotesSummary } from '../../../../utils'; import { useAsyncRetry } from '../../../utils/useAsyncRetry'; -import { useEthersProvider } from '../../../utils/useEthersProvider'; import { useSafeDecoder } from '../../../utils/useSafeDecoder'; export const useAzoriusProposals = () => { @@ -51,7 +51,8 @@ export const useAzoriusProposals = () => { if ( !azoriusContract || !(ozLinearVotingContract || erc721LinearVotingContract) || - !strategyType + !strategyType || + !provider ) { return []; } @@ -108,7 +109,8 @@ export const useAzoriusProposals = () => { if ( !azoriusContract || !(ozLinearVotingContract || erc721LinearVotingContract) || - !strategyType + !strategyType || + !provider ) { return; } diff --git a/src/hooks/DAO/loaders/governance/useERC20LinearStrategy.ts b/src/hooks/DAO/loaders/governance/useERC20LinearStrategy.ts index 4909d9e6df..28c8db99a2 100644 --- a/src/hooks/DAO/loaders/governance/useERC20LinearStrategy.ts +++ b/src/hooks/DAO/loaders/governance/useERC20LinearStrategy.ts @@ -8,9 +8,9 @@ import { useCallback, useEffect } from 'react'; import { getEventRPC } from '../../../../helpers'; import { useFractal } from '../../../../providers/App/AppProvider'; import { FractalGovernanceAction } from '../../../../providers/App/governance/action'; +import { useEthersProvider } from '../../../../providers/Ethers/hooks/useEthersProvider'; import { VotingStrategyType } from '../../../../types'; import { blocksToSeconds } from '../../../../utils/contract'; -import { useEthersProvider } from '../../../utils/useEthersProvider'; import { useTimeHelpers } from '../../../utils/useTimeHelpers'; export const useERC20LinearStrategy = () => { diff --git a/src/hooks/DAO/loaders/governance/useERC721LinearStrategy.ts b/src/hooks/DAO/loaders/governance/useERC721LinearStrategy.ts index aac113ee03..0ba0a1a0fc 100644 --- a/src/hooks/DAO/loaders/governance/useERC721LinearStrategy.ts +++ b/src/hooks/DAO/loaders/governance/useERC721LinearStrategy.ts @@ -10,9 +10,9 @@ import { useCallback, useEffect } from 'react'; import { getEventRPC } from '../../../../helpers'; import { useFractal } from '../../../../providers/App/AppProvider'; import { FractalGovernanceAction } from '../../../../providers/App/governance/action'; +import { useEthersProvider } from '../../../../providers/Ethers/hooks/useEthersProvider'; import { VotingStrategyType } from '../../../../types'; import { blocksToSeconds } from '../../../../utils/contract'; -import { useEthersProvider } from '../../../utils/useEthersProvider'; import { useTimeHelpers } from '../../../utils/useTimeHelpers'; export const useERC721LinearStrategy = () => { diff --git a/src/hooks/DAO/loaders/useFractalFreeze.ts b/src/hooks/DAO/loaders/useFractalFreeze.ts index 4f8bdff7a9..c8c39f4489 100644 --- a/src/hooks/DAO/loaders/useFractalFreeze.ts +++ b/src/hooks/DAO/loaders/useFractalFreeze.ts @@ -15,9 +15,9 @@ import { } from '../../../helpers/freezePeriodHelpers'; import { useFractal } from '../../../providers/App/AppProvider'; import { FractalGuardAction } from '../../../providers/App/guard/action'; +import { useEthersProvider } from '../../../providers/Ethers/hooks/useEthersProvider'; import { ContractConnection, FractalGuardContracts, FreezeVotingType } from '../../../types'; import { blocksToSeconds, getTimeStamp } from '../../../utils/contract'; -import { useEthersProvider } from '../../utils/useEthersProvider'; import useUserERC721VotingTokens from '../proposal/useUserERC721VotingTokens'; import { FreezeGuard } from './../../../types/fractal'; diff --git a/src/hooks/DAO/loaders/useFractalGuardContracts.ts b/src/hooks/DAO/loaders/useFractalGuardContracts.ts index bf4281ab94..b01033aef6 100644 --- a/src/hooks/DAO/loaders/useFractalGuardContracts.ts +++ b/src/hooks/DAO/loaders/useFractalGuardContracts.ts @@ -3,6 +3,7 @@ import { constants } from 'ethers'; import { useCallback, useEffect, useRef } from 'react'; import { useFractal } from '../../../providers/App/AppProvider'; import { GuardContractAction } from '../../../providers/App/guardContracts/action'; +import { useEthersProvider } from '../../../providers/Ethers/hooks/useEthersProvider'; import { ContractConnection, SafeInfoResponseWithGuard, @@ -27,9 +28,8 @@ export const useFractalGuardContracts = ({ loadOnMount = true }: { loadOnMount?: action, } = useFractal(); - const { - network: { chainId }, - } = useEthersProvider(); + const provider = useEthersProvider(); + const chainId = provider?.network.chainId; const { getZodiacModuleProxyMasterCopyData } = useMasterCopy(); @@ -122,6 +122,10 @@ export const useFractalGuardContracts = ({ loadOnMount = true }: { loadOnMount?: }, [action, daoAddress, safe, fractalModules, loadFractalGuardContracts]); useEffect(() => { + if (chainId === undefined) { + return; + } + if (daoAddress && chainId + daoAddress !== loadKey.current && loadOnMount && isModulesLoaded) { loadKey.current = chainId + daoAddress; setGuardContracts(); diff --git a/src/hooks/DAO/loaders/useFractalNode.ts b/src/hooks/DAO/loaders/useFractalNode.ts index 5bd8cd4b9c..71b47e37ee 100644 --- a/src/hooks/DAO/loaders/useFractalNode.ts +++ b/src/hooks/DAO/loaders/useFractalNode.ts @@ -79,7 +79,7 @@ export const useFractalNode = ({ daoAddress }: { daoAddress?: string }) => { }); const fetchSafeInfo = useCallback(async () => { - if (daoAddress) { + if (daoAddress && safeAPI) { const safeInfo = await safeAPI.getSafeInfo(utils.getAddress(daoAddress)); return safeInfo; } diff --git a/src/hooks/DAO/loaders/useFractalTreasury.ts b/src/hooks/DAO/loaders/useFractalTreasury.ts index 92c56f66aa..2ddc1e3f99 100644 --- a/src/hooks/DAO/loaders/useFractalTreasury.ts +++ b/src/hooks/DAO/loaders/useFractalTreasury.ts @@ -4,9 +4,9 @@ import { logError } from '../../../helpers/errorLogging'; import { useFractal } from '../../../providers/App/AppProvider'; import { useSafeAPI } from '../../../providers/App/hooks/useSafeAPI'; import { TreasuryAction } from '../../../providers/App/treasury/action'; +import { useEthersProvider } from '../../../providers/Ethers/hooks/useEthersProvider'; import { useNetworkConfig } from '../../../providers/NetworkConfig/NetworkConfigProvider'; import { buildSafeApiUrl } from '../../../utils'; -import { useEthersProvider } from '../../utils/useEthersProvider'; import { useUpdateTimer } from './../../utils/useUpdateTimer'; export const useFractalTreasury = () => { diff --git a/src/hooks/DAO/loaders/useGovernanceContracts.ts b/src/hooks/DAO/loaders/useGovernanceContracts.ts index cc8999e124..3e1e8be23e 100644 --- a/src/hooks/DAO/loaders/useGovernanceContracts.ts +++ b/src/hooks/DAO/loaders/useGovernanceContracts.ts @@ -10,6 +10,7 @@ import { useCallback, useEffect, useRef } from 'react'; import { LockRelease, LockRelease__factory } from '../../../assets/typechain-types/dcnt'; import { useFractal } from '../../../providers/App/AppProvider'; import { GovernanceContractAction } from '../../../providers/App/governanceContracts/action'; +import { useEthersProvider } from '../../../providers/Ethers/hooks/useEthersProvider'; import { ContractConnection } from '../../../types'; import { getAzoriusModuleFromModules } from '../../../utils'; import { useEthersProvider } from '../../utils/useEthersProvider'; @@ -108,7 +109,7 @@ export const useGovernanceContracts = () => { return undefined; }); - if (lockedToken) { + if (lockedToken && provider) { lockReleaseContract = { asSigner: LockRelease__factory.connect(govTokenAddress, signerOrProvider), asProvider: LockRelease__factory.connect(govTokenAddress, provider), diff --git a/src/hooks/DAO/proposal/useCastVote.ts b/src/hooks/DAO/proposal/useCastVote.ts index bd2090960c..af853c3255 100644 --- a/src/hooks/DAO/proposal/useCastVote.ts +++ b/src/hooks/DAO/proposal/useCastVote.ts @@ -6,6 +6,7 @@ import { toast } from 'react-toastify'; import { useVoteContext } from '../../../components/Proposals/ProposalVotes/context/VoteContext'; import { logError } from '../../../helpers/errorLogging'; import { useFractal } from '../../../providers/App/AppProvider'; +import { useEthersSigner } from '../../../providers/Ethers/hooks/useEthersSigner'; import { AzoriusGovernance, GovernanceType, @@ -13,7 +14,6 @@ import { ExtendedSnapshotProposal, } from '../../../types'; import encryptWithShutter from '../../../utils/shutter'; -import { useEthersSigner } from '../../utils/useEthersSigner'; import { useTransaction } from '../../utils/useTransaction'; import useSnapshotSpaceName from '../loaders/snapshot/useSnapshotSpaceName'; import useUserERC721VotingTokens from './useUserERC721VotingTokens'; diff --git a/src/hooks/DAO/proposal/usePrepareProposal.ts b/src/hooks/DAO/proposal/usePrepareProposal.ts index 245d81062c..2f660d31c5 100644 --- a/src/hooks/DAO/proposal/usePrepareProposal.ts +++ b/src/hooks/DAO/proposal/usePrepareProposal.ts @@ -1,8 +1,8 @@ import { useCallback } from 'react'; +import { useEthersSigner } from '../../../providers/Ethers/hooks/useEthersSigner'; import { CreateProposalForm } from '../../../types'; import { encodeFunction } from '../../../utils/crypto'; import { couldBeENS } from '../../../utils/url'; -import { useEthersSigner } from '../../utils/useEthersSigner'; export function usePrepareProposal() { const signer = useEthersSigner(); diff --git a/src/hooks/DAO/proposal/useSubmitProposal.ts b/src/hooks/DAO/proposal/useSubmitProposal.ts index cdc9c067b3..452f5c251b 100644 --- a/src/hooks/DAO/proposal/useSubmitProposal.ts +++ b/src/hooks/DAO/proposal/useSubmitProposal.ts @@ -12,6 +12,8 @@ import { logError } from '../../../helpers/errorLogging'; import { useFractal } from '../../../providers/App/AppProvider'; import useIPFSClient from '../../../providers/App/hooks/useIPFSClient'; import { useSafeAPI } from '../../../providers/App/hooks/useSafeAPI'; +import { useEthersProvider } from '../../../providers/Ethers/hooks/useEthersProvider'; +import { useEthersSigner } from '../../../providers/Ethers/hooks/useEthersSigner'; import { useNetworkConfig } from '../../../providers/NetworkConfig/NetworkConfigProvider'; import { MetaTransaction, @@ -21,8 +23,6 @@ import { } from '../../../types'; import { buildSafeApiUrl, getAzoriusModuleFromModules } from '../../../utils'; import { getAverageBlockTime } from '../../../utils/contract'; -import { useEthersProvider } from '../../utils/useEthersProvider'; -import { useEthersSigner } from '../../utils/useEthersSigner'; import useSignerOrProvider from '../../utils/useSignerOrProvider'; import { useFractalModules } from '../loaders/useFractalModules'; import { useDAOProposals } from '../loaders/useProposals'; diff --git a/src/hooks/DAO/useClawBack.ts b/src/hooks/DAO/useClawBack.ts index 312dc8f388..46177d04af 100644 --- a/src/hooks/DAO/useClawBack.ts +++ b/src/hooks/DAO/useClawBack.ts @@ -3,8 +3,8 @@ import { ethers, utils } from 'ethers'; import { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { useSafeAPI } from '../../providers/App/hooks/useSafeAPI'; +import { useEthersProvider } from '../../providers/Ethers/hooks/useEthersProvider'; import { FractalModuleType, FractalNode } from '../../types'; -import { useEthersProvider } from '../utils/useEthersProvider'; import useSubmitProposal from './proposal/useSubmitProposal'; interface IUseClawBack { diff --git a/src/hooks/DAO/useDAOName.ts b/src/hooks/DAO/useDAOName.ts index 8b22092a6e..816821dfae 100644 --- a/src/hooks/DAO/useDAOName.ts +++ b/src/hooks/DAO/useDAOName.ts @@ -3,10 +3,10 @@ import { useCallback, useEffect, useState } from 'react'; import { Address, useEnsName } from 'wagmi'; import { getEventRPC } from '../../helpers'; import { useFractal } from '../../providers/App/AppProvider'; +import { useEthersProvider } from '../../providers/Ethers/hooks/useEthersProvider'; import { CacheKeys } from '../utils/cache/cacheDefaults'; import { useLocalStorage } from '../utils/cache/useLocalStorage'; import { createAccountSubstring } from '../utils/useDisplayName'; -import { useEthersProvider } from '../utils/useEthersProvider'; /** * Gets the 'display name' for a Fractal DAO, in the following order of preference: diff --git a/src/hooks/safe/useSafeContracts.ts b/src/hooks/safe/useSafeContracts.ts index affd66fa51..b280c0e5dd 100644 --- a/src/hooks/safe/useSafeContracts.ts +++ b/src/hooks/safe/useSafeContracts.ts @@ -19,8 +19,8 @@ import { import { useMemo } from 'react'; import { MultiSend__factory } from '../../assets/typechain-types/usul'; import { GnosisSafeL2__factory } from '../../assets/typechain-types/usul/factories/@gnosis.pm/safe-contracts/contracts'; +import { useEthersProvider } from '../../providers/Ethers/hooks/useEthersProvider'; import { useNetworkConfig } from '../../providers/NetworkConfig/NetworkConfigProvider'; -import { useEthersProvider } from '../utils/useEthersProvider'; import useSignerOrProvider from '../utils/useSignerOrProvider'; export default function useSafeContracts() { diff --git a/src/hooks/schemas/DAOCreate/useDAOCreateTests.ts b/src/hooks/schemas/DAOCreate/useDAOCreateTests.ts index e5e3debbd4..3313d4c95c 100644 --- a/src/hooks/schemas/DAOCreate/useDAOCreateTests.ts +++ b/src/hooks/schemas/DAOCreate/useDAOCreateTests.ts @@ -4,9 +4,9 @@ import { useTranslation } from 'react-i18next'; import { erc20ABI } from 'wagmi'; import { AnyObject } from 'yup'; import { logError } from '../../../helpers/errorLogging'; +import { useEthersProvider } from '../../../providers/Ethers/hooks/useEthersProvider'; import { AddressValidationMap, CreatorFormState, TokenAllocation } from '../../../types'; import { couldBeENS } from '../../../utils/url'; -import { useEthersProvider } from '../../utils/useEthersProvider'; import useSignerOrProvider from '../../utils/useSignerOrProvider'; import { validateAddress } from '../common/useValidationAddress'; diff --git a/src/hooks/schemas/common/useValidationAddress.tsx b/src/hooks/schemas/common/useValidationAddress.tsx index 66438fcaba..8a788260f6 100644 --- a/src/hooks/schemas/common/useValidationAddress.tsx +++ b/src/hooks/schemas/common/useValidationAddress.tsx @@ -3,10 +3,10 @@ import { useMemo, useRef, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { AnyObject } from 'yup'; import { useFractal } from '../../../providers/App/AppProvider'; +import { useEthersSigner } from '../../../providers/Ethers/hooks/useEthersSigner'; import { AddressValidationMap, ERC721TokenConfig } from '../../../types'; import { Providers } from '../../../types/network'; import { couldBeENS } from '../../../utils/url'; -import { useEthersSigner } from '../../utils/useEthersSigner'; import useSignerOrProvider from '../../utils/useSignerOrProvider'; export function validateENSName({ ensName }: { ensName: string }) { @@ -33,11 +33,11 @@ export async function validateAddress({ address, checkENS = true, }: { - signerOrProvider: Signer | Providers; + signerOrProvider?: Signer | Providers; address: string; checkENS?: boolean; }) { - if (couldBeENS(address) && checkENS) { + if (couldBeENS(address) && checkENS && signerOrProvider) { const resolvedAddress = await signerOrProvider.resolveName(address).catch(); if (resolvedAddress) { return { diff --git a/src/hooks/stake/lido/useLidoStaking.ts b/src/hooks/stake/lido/useLidoStaking.ts index 5b853f4c82..8fef66ded3 100644 --- a/src/hooks/stake/lido/useLidoStaking.ts +++ b/src/hooks/stake/lido/useLidoStaking.ts @@ -21,7 +21,7 @@ export default function useLidoStaking() { const handleStake = useCallback( async (value: BigNumber) => { - if (!lido || !daoAddress) { + if (!lido || !daoAddress || !signerOrProvider) { // Means it is not supported on current network return; } @@ -51,7 +51,7 @@ export default function useLidoStaking() { const handleUnstake = useCallback( async (value: string) => { - if (!lido || !daoAddress) { + if (!lido || !daoAddress || !signerOrProvider) { // Means it is not supported on current network return; } @@ -96,7 +96,7 @@ export default function useLidoStaking() { const handleClaimUnstakedETH = useCallback( async (nftId: BigNumber) => { - if (!lido || !daoAddress) { + if (!lido || !daoAddress || !signerOrProvider) { // Means it is not supported on current network return; } diff --git a/src/hooks/utils/useAddress.ts b/src/hooks/utils/useAddress.ts index 90b95fa9fa..6eab22627d 100644 --- a/src/hooks/utils/useAddress.ts +++ b/src/hooks/utils/useAddress.ts @@ -2,10 +2,10 @@ import { ethers } from 'ethers'; import { useEffect, useState } from 'react'; import { useNetwork } from 'wagmi'; import { supportsENS } from '../../helpers'; +import { useEthersProvider } from '../../providers/Ethers/hooks/useEthersProvider'; import { couldBeENS } from '../../utils/url'; import { CacheKeys, CacheExpiry } from './cache/cacheDefaults'; import { useLocalStorage } from './cache/useLocalStorage'; -import { useEthersProvider } from './useEthersProvider'; const useAddress = (addressInput: string | undefined) => { const provider = useEthersProvider(); diff --git a/src/hooks/utils/useAvatar.ts b/src/hooks/utils/useAvatar.ts index 2275e6dffd..54d3245f58 100644 --- a/src/hooks/utils/useAvatar.ts +++ b/src/hooks/utils/useAvatar.ts @@ -1,5 +1,5 @@ import { useEnsAvatar } from 'wagmi'; -import { useEthersProvider } from './useEthersProvider'; +import { useEthersProvider } from '../../providers/Ethers/hooks/useEthersProvider'; const useAvatar = (account: string | null) => { const provider = useEthersProvider(); diff --git a/src/hooks/utils/useBlockTimestamp.ts b/src/hooks/utils/useBlockTimestamp.ts index 47b07b35a7..2e57cb02e6 100644 --- a/src/hooks/utils/useBlockTimestamp.ts +++ b/src/hooks/utils/useBlockTimestamp.ts @@ -1,6 +1,6 @@ import { useEffect, useState } from 'react'; import { logError } from '../../helpers/errorLogging'; -import { useEthersProvider } from './useEthersProvider'; +import { useEthersProvider } from '../../providers/Ethers/hooks/useEthersProvider'; const useBlockTimestamp = (blockNumber?: number) => { const [timestamp, setTimestamp] = useState(Math.floor(Date.now() / 1000)); diff --git a/src/hooks/utils/useCurrentBlockNumber.ts b/src/hooks/utils/useCurrentBlockNumber.ts index f2c9cee9f1..a8bed53dff 100644 --- a/src/hooks/utils/useCurrentBlockNumber.ts +++ b/src/hooks/utils/useCurrentBlockNumber.ts @@ -1,5 +1,5 @@ import { useEffect, useState, useCallback } from 'react'; -import { useEthersProvider } from './useEthersProvider'; +import { useEthersProvider } from '../../providers/Ethers/hooks/useEthersProvider'; const useCurrentBlockNumber = () => { const [currentBlockNumber, setCurrentBlockNumber] = useState(); diff --git a/src/hooks/utils/useDisplayName.ts b/src/hooks/utils/useDisplayName.ts index 325fab1043..68e40c1385 100644 --- a/src/hooks/utils/useDisplayName.ts +++ b/src/hooks/utils/useDisplayName.ts @@ -1,6 +1,6 @@ import { useEffect, useState } from 'react'; import { Address, useEnsName } from 'wagmi'; -import { useEthersProvider } from './useEthersProvider'; +import { useEthersProvider } from '../../providers/Ethers/hooks/useEthersProvider'; export const createAccountSubstring = (account: string) => { return `${account.substring(0, 6)}...${account.slice(-4)}`; diff --git a/src/hooks/utils/useEthersProvider.ts b/src/hooks/utils/useEthersProvider.ts deleted file mode 100644 index c041144bbf..0000000000 --- a/src/hooks/utils/useEthersProvider.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { providers } from 'ethers'; -import { useMemo } from 'react'; -import type { Transport } from 'viem'; -import { usePublicClient } from 'wagmi'; - -/** Hook to convert a viem Client to an ethers.js Provider. */ -export function useEthersProvider() { - const client = usePublicClient(); - const provider = useMemo(() => { - const { chain, transport } = client; - const network = { - chainId: chain.id, - name: chain.name, - ensAddress: chain.contracts?.ensRegistry?.address, - }; - if (transport.type === 'fallback') { - return new providers.FallbackProvider( - (transport.transports as ReturnType[]).map( - ({ value }) => new providers.JsonRpcProvider(value?.url, network) - ) - ); - } - return new providers.JsonRpcProvider(transport.url, network); - }, [client]); - return provider; -} diff --git a/src/hooks/utils/useEthersSigner.ts b/src/hooks/utils/useEthersSigner.ts deleted file mode 100644 index 278bca5ba1..0000000000 --- a/src/hooks/utils/useEthersSigner.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { providers } from 'ethers'; -import { useMemo } from 'react'; -import { useWalletClient } from 'wagmi'; - -/** Hook to convert a Viem Client to an ethers.js Signer. */ -export function useEthersSigner() { - const { data: client } = useWalletClient(); - const signer = useMemo(() => { - if (client) { - const { account, chain, transport } = client; - const network = { - chainId: chain.id, - name: chain.name, - ensAddress: chain.contracts?.ensRegistry?.address, - }; - const provider = new providers.Web3Provider(transport, network); - return provider.getSigner(account.address); - } - }, [client]); - return signer; -} diff --git a/src/hooks/utils/useSafeTransactions.ts b/src/hooks/utils/useSafeTransactions.ts index 657f9b663e..b7dea61012 100644 --- a/src/hooks/utils/useSafeTransactions.ts +++ b/src/hooks/utils/useSafeTransactions.ts @@ -9,6 +9,7 @@ import { constants, BigNumber, ethers } from 'ethers'; import { useCallback } from 'react'; import { isApproved, isRejected } from '../../helpers/activity'; import { useFractal } from '../../providers/App/AppProvider'; +import { useEthersProvider } from '../../providers/Ethers/hooks/useEthersProvider'; import { useNetworkConfig } from '../../providers/NetworkConfig/NetworkConfigProvider'; import { AssetTotals, @@ -20,7 +21,6 @@ import { import { formatWeiToValue, isModuleTx, isMultiSigTx, parseDecodedData } from '../../utils'; import { getAverageBlockTime } from '../../utils/contract'; import { getTxTimelockedTimestamp } from '../../utils/guard'; -import { useEthersProvider } from './useEthersProvider'; import { useSafeDecoder } from './useSafeDecoder'; type FreezeGuardData = { diff --git a/src/hooks/utils/useSignerOrProvider.ts b/src/hooks/utils/useSignerOrProvider.ts index f49f868622..b180b73e12 100644 --- a/src/hooks/utils/useSignerOrProvider.ts +++ b/src/hooks/utils/useSignerOrProvider.ts @@ -1,6 +1,6 @@ import { useMemo } from 'react'; -import { useEthersProvider } from './useEthersProvider'; -import { useEthersSigner } from './useEthersSigner'; +import { useEthersProvider } from '../../providers/Ethers/hooks/useEthersProvider'; +import { useEthersSigner } from '../../providers/Ethers/hooks/useEthersSigner'; export default function useSignerOrProvider() { const signer = useEthersSigner(); diff --git a/src/providers/App/hooks/useSafeAPI.ts b/src/providers/App/hooks/useSafeAPI.ts index cdcdf47f8d..f15119a941 100644 --- a/src/providers/App/hooks/useSafeAPI.ts +++ b/src/providers/App/hooks/useSafeAPI.ts @@ -229,7 +229,10 @@ export function useSafeAPI() { const { safeBaseURL, chainId } = useNetworkConfig(); const signerOrProvider = useSignerOrProvider(); - const safeAPI: SafeServiceClient = useMemo(() => { + const safeAPI: SafeServiceClient | undefined = useMemo(() => { + if (!signerOrProvider) { + return undefined; + } const ethAdapter = new EthersAdapter({ ethers, signerOrProvider, diff --git a/src/providers/App/useReadOnlyValues.ts b/src/providers/App/useReadOnlyValues.ts index f965d07189..1defb10925 100644 --- a/src/providers/App/useReadOnlyValues.ts +++ b/src/providers/App/useReadOnlyValues.ts @@ -39,7 +39,7 @@ export const useReadOnlyValues = ({ node, governance }: Fractal, _account?: stri const tokenWeight = azoriusGovernance.votesToken?.votingWeight || BigNumber.from(0); return lockedTokenWeight || tokenWeight; case GovernanceType.AZORIUS_ERC721: - if (!_account || !azoriusGovernance.erc721Tokens) { + if (!_account || !azoriusGovernance.erc721Tokens || !signerOrProvider) { return BigNumber.from(0); } const userVotingWeight = ( diff --git a/src/providers/Ethers/hooks/useEthersProvider.ts b/src/providers/Ethers/hooks/useEthersProvider.ts new file mode 100644 index 0000000000..8cf195e078 --- /dev/null +++ b/src/providers/Ethers/hooks/useEthersProvider.ts @@ -0,0 +1,7 @@ +import { useContext } from 'react'; +import { EthersContext } from '..'; + +export function useEthersProvider() { + const { provider } = useContext(EthersContext); + return provider; +} diff --git a/src/providers/Ethers/hooks/useEthersSigner.ts b/src/providers/Ethers/hooks/useEthersSigner.ts new file mode 100644 index 0000000000..2d2b395d6d --- /dev/null +++ b/src/providers/Ethers/hooks/useEthersSigner.ts @@ -0,0 +1,7 @@ +import { useContext } from 'react'; +import { EthersContext } from '..'; + +export function useEthersSigner() { + const { signer } = useContext(EthersContext); + return signer; +} diff --git a/src/providers/Ethers/index.tsx b/src/providers/Ethers/index.tsx new file mode 100644 index 0000000000..0d4f5cf1f2 --- /dev/null +++ b/src/providers/Ethers/index.tsx @@ -0,0 +1,57 @@ +'use client'; + +import { Signer, providers } from 'ethers'; +import { ReactNode, createContext, useMemo } from 'react'; +import type { Transport } from 'viem'; +import { usePublicClient, useWalletClient } from 'wagmi'; + +interface IEthersContext { + provider?: providers.JsonRpcProvider | providers.FallbackProvider; + signer?: Signer; +} + +export const EthersContext = createContext({ + provider: undefined, + signer: undefined, +}); + +/** + * Provider to convert a viem Clients to an ethers.js Provider and Signer + * and supply "singleton" instances of it to underlying components. + */ +export default function EthersContextProvider({ children }: { children: ReactNode }) { + const publicClient = usePublicClient(); + const { data: walletClient } = useWalletClient(); + + const provider = useMemo(() => { + const { chain, transport } = publicClient; + const network = { + chainId: chain.id, + name: chain.name, + ensAddress: chain.contracts?.ensRegistry?.address, + }; + if (transport.type === 'fallback') { + return new providers.FallbackProvider( + (transport.transports as ReturnType[]).map( + ({ value }) => new providers.JsonRpcProvider(value?.url, network) + ) + ); + } + return new providers.JsonRpcProvider(transport.url, network); + }, [publicClient]); + + const signer = useMemo(() => { + if (walletClient) { + const { account, chain, transport } = walletClient; + const network = { + chainId: chain.id, + name: chain.name, + ensAddress: chain.contracts?.ensRegistry?.address, + }; + const publicProvider = new providers.Web3Provider(transport, network); + return publicProvider.getSigner(account.address); + } + }, [walletClient]); + + return {children}; +} diff --git a/src/providers/Providers.tsx b/src/providers/Providers.tsx index 2968441281..def0190b26 100644 --- a/src/providers/Providers.tsx +++ b/src/providers/Providers.tsx @@ -12,6 +12,7 @@ import { ErrorFallback } from '../components/ui/utils/ErrorFallback'; import graphQLClient from '../graphql'; import { FractalErrorBoundary, initErrorLogging } from '../helpers/errorLogging'; import { AppProvider } from './App/AppProvider'; +import EthersContextProvider from './Ethers'; import { NetworkConfigProvider } from './NetworkConfig/NetworkConfigProvider'; import { wagmiConfig, chains } from './NetworkConfig/rainbow-kit.config'; import '@fontsource/ibm-plex-mono'; @@ -21,6 +22,7 @@ export default function Providers({ children }: { children: ReactNode }) { useEffect(() => { initErrorLogging(); }, []); + return ( - - - - {children} - - + + + + + {children} + + + From 52135947cd5b49f038fa2eb593e4e635099afdb4 Mon Sep 17 00:00:00 2001 From: Kirill Klimenko Date: Thu, 7 Mar 2024 16:15:18 +0100 Subject: [PATCH 38/56] Reduce usages of useEthersProvider, fix lint errors, add more conditional logic whenever applicable to check that provider is not undefined --- .../DaoCreator/hooks/usePrepareFormData.ts | 215 ++++++++++-------- .../DaoDashboard/Info/InfoGovernance.tsx | 6 +- .../Signers/modals/AddSignerModal.tsx | 2 +- .../Signers/modals/RemoveSignerModal.tsx | 8 +- src/components/ui/modals/DelegateModal.tsx | 2 +- .../ui/modals/ForkProposalTemplateModal.tsx | 7 +- src/components/ui/modals/UnwrapToken.tsx | 2 +- src/components/ui/modals/WrapToken.tsx | 2 +- .../ui/proposal/useProposalCountdown.tsx | 11 +- src/graphql/utils/index.ts | 14 +- .../governance/useERC20LinearStrategy.ts | 2 +- .../governance/useERC721LinearStrategy.ts | 2 +- src/hooks/DAO/loaders/useFractalFreeze.ts | 4 +- .../DAO/loaders/useFractalGuardContracts.ts | 17 +- .../DAO/loaders/useGovernanceContracts.ts | 3 +- src/hooks/DAO/proposal/useSubmitProposal.ts | 6 +- src/hooks/DAO/useClawBack.ts | 2 +- src/hooks/DAO/useDAOName.ts | 19 +- src/hooks/safe/useSafeContracts.ts | 3 + src/hooks/utils/useAvatar.ts | 8 +- src/hooks/utils/useDisplayName.ts | 8 +- src/hooks/utils/useSafeTransactions.ts | 4 +- 22 files changed, 178 insertions(+), 169 deletions(-) diff --git a/src/components/DaoCreator/hooks/usePrepareFormData.ts b/src/components/DaoCreator/hooks/usePrepareFormData.ts index 54bece8582..0b4e2ac2ce 100644 --- a/src/components/DaoCreator/hooks/usePrepareFormData.ts +++ b/src/components/DaoCreator/hooks/usePrepareFormData.ts @@ -24,39 +24,43 @@ export function usePrepareFormData() { const prepareFreezeGuardData = useCallback( async ( freezeGuard: DAOFreezeGuardConfig - ): Promise => { - return { - executionPeriod: await getEstimatedNumberOfBlocks( - freezeGuard.executionPeriod.bigNumberValue!, - provider - ), - timelockPeriod: await getEstimatedNumberOfBlocks( - freezeGuard.timelockPeriod.bigNumberValue!, - provider - ), - freezeVotesThreshold: freezeGuard.freezeVotesThreshold.bigNumberValue!, - freezeProposalPeriod: await getEstimatedNumberOfBlocks( - freezeGuard.freezeProposalPeriod.bigNumberValue!, - provider - ), - freezePeriod: await getEstimatedNumberOfBlocks( - freezeGuard.freezePeriod.bigNumberValue!, - provider - ), - }; + ): Promise => { + if (provider) { + return { + executionPeriod: await getEstimatedNumberOfBlocks( + freezeGuard.executionPeriod.bigNumberValue!, + provider + ), + timelockPeriod: await getEstimatedNumberOfBlocks( + freezeGuard.timelockPeriod.bigNumberValue!, + provider + ), + freezeVotesThreshold: freezeGuard.freezeVotesThreshold.bigNumberValue!, + freezeProposalPeriod: await getEstimatedNumberOfBlocks( + freezeGuard.freezeProposalPeriod.bigNumberValue!, + provider + ), + freezePeriod: await getEstimatedNumberOfBlocks( + freezeGuard.freezePeriod.bigNumberValue!, + provider + ), + }; + } }, [provider] ); const checkVotesToken = useCallback( async (address: string) => { - try { - const votesContract = IVotes__factory.connect(address, provider); - await votesContract.delegates('0x0000000000000000000000000000000000000001'); - await votesContract.getVotes('0x0000000000000000000000000000000000000001'); - return true; - } catch (error) { - return false; + if (provider) { + try { + const votesContract = IVotes__factory.connect(address, provider); + await votesContract.delegates('0x0000000000000000000000000000000000000001'); + await votesContract.getVotes('0x0000000000000000000000000000000000000001'); + return true; + } catch (error) { + return false; + } } }, [provider] @@ -77,15 +81,17 @@ export function usePrepareFormData() { return inputValue; }) ); - let freezeGuardData: Partial = {}; + let freezeGuardData; if (freezeGuard) { freezeGuardData = await prepareFreezeGuardData(freezeGuard); } - return { - trustedAddresses: resolvedAddresses, - ...freezeGuardData, - ...rest, - }; + if (freezeGuardData) { + return { + trustedAddresses: resolvedAddresses, + ...freezeGuardData, + ...rest, + }; + } }, [signer, prepareFreezeGuardData] ); @@ -103,44 +109,48 @@ export function usePrepareFormData() { tokenImportAddress, tokenCreationType, ...rest - }: AzoriusERC20DAO & FreezeGuardConfigParam): Promise => { - const resolvedTokenAllocations = await Promise.all( - tokenAllocations.map(async allocation => { - let address = allocation.address; - if (couldBeENS(address)) { - address = await signer!.resolveName(allocation.address); - } - return { amount: allocation.amount.bigNumberValue!, address: address }; - }) - ); - let freezeGuardData: Partial = {}; - if (freezeGuard) { - freezeGuardData = await prepareFreezeGuardData(freezeGuard); - } - const isTokenImported = - tokenCreationType === TokenCreationType.IMPORTED && !!tokenImportAddress; - let isVotesToken = false; - if (isTokenImported) { - isVotesToken = await checkVotesToken(tokenImportAddress); + }: AzoriusERC20DAO & FreezeGuardConfigParam): Promise< + AzoriusERC20DAO | undefined + > => { + if (provider) { + const resolvedTokenAllocations = await Promise.all( + tokenAllocations.map(async allocation => { + let address = allocation.address; + if (couldBeENS(address)) { + address = await signer!.resolveName(allocation.address); + } + return { amount: allocation.amount.bigNumberValue!, address: address }; + }) + ); + let freezeGuardData; + if (freezeGuard) { + freezeGuardData = await prepareFreezeGuardData(freezeGuard); + } + const isTokenImported = + tokenCreationType === TokenCreationType.IMPORTED && !!tokenImportAddress; + let isVotesToken: boolean | undefined = false; + if (isTokenImported) { + isVotesToken = await checkVotesToken(tokenImportAddress); + } + return { + tokenSupply: tokenSupply.bigNumberValue!, + parentAllocationAmount: parentAllocationAmount?.bigNumberValue!, + quorumPercentage: quorumPercentage.bigNumberValue!, + timelock: await getEstimatedNumberOfBlocks(timelock.bigNumberValue!, provider), + executionPeriod: await getEstimatedNumberOfBlocks( + executionPeriod.bigNumberValue!, + provider + ), + votingPeriod: await getEstimatedNumberOfBlocks(votingPeriod.bigNumberValue!, provider), + tokenAllocations: resolvedTokenAllocations, + tokenImportAddress, + tokenCreationType, + isTokenImported, + isVotesToken, + ...freezeGuardData, + ...rest, + }; } - return { - tokenSupply: tokenSupply.bigNumberValue!, - parentAllocationAmount: parentAllocationAmount?.bigNumberValue!, - quorumPercentage: quorumPercentage.bigNumberValue!, - timelock: await getEstimatedNumberOfBlocks(timelock.bigNumberValue!, provider), - executionPeriod: await getEstimatedNumberOfBlocks( - executionPeriod.bigNumberValue!, - provider - ), - votingPeriod: await getEstimatedNumberOfBlocks(votingPeriod.bigNumberValue!, provider), - tokenAllocations: resolvedTokenAllocations, - tokenImportAddress, - tokenCreationType, - isTokenImported, - isVotesToken, - ...freezeGuardData, - ...rest, - }; }, [signer, checkVotesToken, provider, prepareFreezeGuardData] ); @@ -155,39 +165,42 @@ export function usePrepareFormData() { nfts, quorumThreshold, ...rest - }: AzoriusERC721DAO & - FreezeGuardConfigParam): Promise => { - let freezeGuardData: Partial = {}; - if (freezeGuard) { - freezeGuardData = await prepareFreezeGuardData(freezeGuard); - } + }: AzoriusERC721DAO & FreezeGuardConfigParam): Promise< + AzoriusERC721DAO | undefined + > => { + if (provider) { + let freezeGuardData; + if (freezeGuard) { + freezeGuardData = await prepareFreezeGuardData(freezeGuard); + } - const resolvedNFTs = await Promise.all( - nfts.map(async nft => { - let address = nft.tokenAddress; - if (couldBeENS(address)) { - address = await signer!.resolveName(nft.tokenAddress); - } - return { - tokenAddress: address, - tokenWeight: nft.tokenWeight.bigNumberValue!, - }; - }) - ); + const resolvedNFTs = await Promise.all( + nfts.map(async nft => { + let address = nft.tokenAddress; + if (couldBeENS(address)) { + address = await signer!.resolveName(nft.tokenAddress); + } + return { + tokenAddress: address, + tokenWeight: nft.tokenWeight.bigNumberValue!, + }; + }) + ); - return { - quorumPercentage: quorumPercentage.bigNumberValue!, - timelock: await getEstimatedNumberOfBlocks(timelock.bigNumberValue!, provider), - executionPeriod: await getEstimatedNumberOfBlocks( - executionPeriod.bigNumberValue!, - provider - ), - votingPeriod: await getEstimatedNumberOfBlocks(votingPeriod.bigNumberValue!, provider), - nfts: resolvedNFTs, - quorumThreshold: quorumThreshold.bigNumberValue!, - ...freezeGuardData, - ...rest, - }; + return { + quorumPercentage: quorumPercentage.bigNumberValue!, + timelock: await getEstimatedNumberOfBlocks(timelock.bigNumberValue!, provider), + executionPeriod: await getEstimatedNumberOfBlocks( + executionPeriod.bigNumberValue!, + provider + ), + votingPeriod: await getEstimatedNumberOfBlocks(votingPeriod.bigNumberValue!, provider), + nfts: resolvedNFTs, + quorumThreshold: quorumThreshold.bigNumberValue!, + ...freezeGuardData, + ...rest, + }; + } }, [prepareFreezeGuardData, provider, signer] ); diff --git a/src/components/pages/DaoDashboard/Info/InfoGovernance.tsx b/src/components/pages/DaoDashboard/Info/InfoGovernance.tsx index b954621778..611b02fbad 100644 --- a/src/components/pages/DaoDashboard/Info/InfoGovernance.tsx +++ b/src/components/pages/DaoDashboard/Info/InfoGovernance.tsx @@ -3,9 +3,9 @@ import { Govern } from '@decent-org/fractal-ui'; import { MultisigFreezeGuard } from '@fractal-framework/fractal-contracts'; import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { useEthersProvider } from '../../../../providers/Ethers/hooks/useEthersProvider'; import { useTimeHelpers } from '../../../../hooks/utils/useTimeHelpers'; import { useFractal } from '../../../../providers/App/AppProvider'; +import { useEthersProvider } from '../../../../providers/Ethers/hooks/useEthersProvider'; import { AzoriusGovernance, FreezeGuardType } from '../../../../types'; import { blocksToSeconds } from '../../../../utils/contract'; import { BarLoader } from '../../../ui/loaders/BarLoader'; @@ -25,7 +25,9 @@ export function InfoGovernance() { useEffect(() => { const setTimelockInfo = async () => { const formatBlocks = async (blocks: number): Promise => { - return getTimeDuration(await blocksToSeconds(blocks, provider)); + if (provider) { + return getTimeDuration(await blocksToSeconds(blocks, provider)); + } }; if (freezeGuardType == FreezeGuardType.MULTISIG) { if (freezeGuardContract) { diff --git a/src/components/pages/DaoSettings/components/Signers/modals/AddSignerModal.tsx b/src/components/pages/DaoSettings/components/Signers/modals/AddSignerModal.tsx index 55d7eb12cb..5f2ef2e9e4 100644 --- a/src/components/pages/DaoSettings/components/Signers/modals/AddSignerModal.tsx +++ b/src/components/pages/DaoSettings/components/Signers/modals/AddSignerModal.tsx @@ -16,8 +16,8 @@ import { useCallback, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import * as Yup from 'yup'; import { useValidationAddress } from '../../../../../../hooks/schemas/common/useValidationAddress'; -import { useEthersSigner } from '../../../../../../providers/Ethers/hooks/useEthersSigner'; import { useFractal } from '../../../../../../providers/App/AppProvider'; +import { useEthersSigner } from '../../../../../../providers/Ethers/hooks/useEthersSigner'; import { couldBeENS } from '../../../../../../utils/url'; import SupportTooltip from '../../../../../ui/badges/SupportTooltip'; import { CustomNonceInput } from '../../../../../ui/forms/CustomNonceInput'; diff --git a/src/components/pages/DaoSettings/components/Signers/modals/RemoveSignerModal.tsx b/src/components/pages/DaoSettings/components/Signers/modals/RemoveSignerModal.tsx index 4cbf5454f8..2831fd4772 100644 --- a/src/components/pages/DaoSettings/components/Signers/modals/RemoveSignerModal.tsx +++ b/src/components/pages/DaoSettings/components/Signers/modals/RemoveSignerModal.tsx @@ -12,8 +12,7 @@ import { } from '@chakra-ui/react'; import { useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { Address, useEnsName } from 'wagmi'; -import { useEthersProvider } from '../../../../../../providers/Ethers/hooks/useEthersProvider'; +import { Address, useEnsName, usePublicClient } from 'wagmi'; import { useFractal } from '../../../../../../providers/App/AppProvider'; import SupportTooltip from '../../../../../ui/badges/SupportTooltip'; import { CustomNonceInput } from '../../../../../ui/forms/CustomNonceInput'; @@ -37,11 +36,10 @@ function RemoveSignerModal({ const [prevSigner, setPrevSigner] = useState(''); const [threshold, setThreshold] = useState(currentThreshold); const [nonce, setNonce] = useState(safe!.nonce); - const provider = useEthersProvider(); - const networkId = provider.network.chainId; + const { chain } = usePublicClient(); const { data: ensName } = useEnsName({ address: selectedSigner as Address, - chainId: networkId, + chainId: chain.id, cacheTime: 1000 * 60 * 30, // 30 min }); const { t } = useTranslation(['modals', 'common']); diff --git a/src/components/ui/modals/DelegateModal.tsx b/src/components/ui/modals/DelegateModal.tsx index db041c035e..4cecf59d7d 100644 --- a/src/components/ui/modals/DelegateModal.tsx +++ b/src/components/ui/modals/DelegateModal.tsx @@ -7,8 +7,8 @@ import * as Yup from 'yup'; import useDelegateVote from '../../../hooks/DAO/useDelegateVote'; import { useValidationAddress } from '../../../hooks/schemas/common/useValidationAddress'; import useDisplayName from '../../../hooks/utils/useDisplayName'; -import { useEthersSigner } from '../../../providers/Ethers/hooks/useEthersSigner'; import { useFractal } from '../../../providers/App/AppProvider'; +import { useEthersSigner } from '../../../providers/Ethers/hooks/useEthersSigner'; import { AzoriusGovernance, DecentGovernance } from '../../../types'; import { formatCoin } from '../../../utils/numberFormats'; import { couldBeENS } from '../../../utils/url'; diff --git a/src/components/ui/modals/ForkProposalTemplateModal.tsx b/src/components/ui/modals/ForkProposalTemplateModal.tsx index 9a94189acd..60616de28d 100644 --- a/src/components/ui/modals/ForkProposalTemplateModal.tsx +++ b/src/components/ui/modals/ForkProposalTemplateModal.tsx @@ -7,8 +7,7 @@ import { DAO_ROUTES } from '../../../constants/routes'; import useSubmitProposal from '../../../hooks/DAO/proposal/useSubmitProposal'; import { useIsSafe } from '../../../hooks/safe/useIsSafe'; import { validateAddress } from '../../../hooks/schemas/common/useValidationAddress'; -import { useEthersProvider } from '../../../providers/Ethers/hooks/useEthersProvider'; -import { useEthersSigner } from '../../../providers/Ethers/hooks/useEthersSigner'; +import useSignerOrProvider from '../../../hooks/utils/useSignerOrProvider'; import { useFractal } from '../../../providers/App/AppProvider'; import { disconnectedChain } from '../../../providers/NetworkConfig/NetworkConfigProvider'; import { ProposalTemplate } from '../../../types/createProposalTemplate'; @@ -31,9 +30,7 @@ export default function ForkProposalTemplateModal({ const { t } = useTranslation('proposalTemplate'); const { push } = useRouter(); - const provider = useEthersProvider(); - const signer = useEthersSigner(); - const signerOrProvider = signer || provider; + const signerOrProvider = useSignerOrProvider(); const { chain } = useNetwork(); const { node: { proposalTemplatesHash }, diff --git a/src/components/ui/modals/UnwrapToken.tsx b/src/components/ui/modals/UnwrapToken.tsx index f88a5db66f..8fa4bdb1cd 100644 --- a/src/components/ui/modals/UnwrapToken.tsx +++ b/src/components/ui/modals/UnwrapToken.tsx @@ -9,10 +9,10 @@ import { useAccount } from 'wagmi'; import * as Yup from 'yup'; import { useERC20LinearToken } from '../../../hooks/DAO/loaders/governance/useERC20LinearToken'; import useApproval from '../../../hooks/utils/useApproval'; -import { useEthersSigner } from '../../../providers/Ethers/hooks/useEthersSigner'; import { useFormHelpers } from '../../../hooks/utils/useFormHelpers'; import { useTransaction } from '../../../hooks/utils/useTransaction'; import { useFractal } from '../../../providers/App/AppProvider'; +import { useEthersSigner } from '../../../providers/Ethers/hooks/useEthersSigner'; import { AzoriusGovernance, BigNumberValuePair } from '../../../types'; import { formatCoin } from '../../../utils'; import { BigNumberInput } from '../forms/BigNumberInput'; diff --git a/src/components/ui/modals/WrapToken.tsx b/src/components/ui/modals/WrapToken.tsx index ed502c7a3e..0d3eac4331 100644 --- a/src/components/ui/modals/WrapToken.tsx +++ b/src/components/ui/modals/WrapToken.tsx @@ -10,10 +10,10 @@ import * as Yup from 'yup'; import { logError } from '../../../helpers/errorLogging'; import { useERC20LinearToken } from '../../../hooks/DAO/loaders/governance/useERC20LinearToken'; import useApproval from '../../../hooks/utils/useApproval'; -import { useEthersSigner } from '../../../providers/Ethers/hooks/useEthersSigner'; import { useFormHelpers } from '../../../hooks/utils/useFormHelpers'; import { useTransaction } from '../../../hooks/utils/useTransaction'; import { useFractal } from '../../../providers/App/AppProvider'; +import { useEthersSigner } from '../../../providers/Ethers/hooks/useEthersSigner'; import { AzoriusGovernance, BigNumberValuePair } from '../../../types'; import { formatCoin } from '../../../utils'; import { BigNumberInput } from '../forms/BigNumberInput'; diff --git a/src/components/ui/proposal/useProposalCountdown.tsx b/src/components/ui/proposal/useProposalCountdown.tsx index a8d6ae538b..254cd1867d 100644 --- a/src/components/ui/proposal/useProposalCountdown.tsx +++ b/src/components/ui/proposal/useProposalCountdown.tsx @@ -5,8 +5,8 @@ import { logError } from '../../../helpers/errorLogging'; import useSnapshotProposal from '../../../hooks/DAO/loaders/snapshot/useSnapshotProposal'; import { useDAOProposals } from '../../../hooks/DAO/loaders/useProposals'; import useUpdateProposalState from '../../../hooks/DAO/proposal/useUpdateProposalState'; -import { useEthersProvider } from '../../../providers/Ethers/hooks/useEthersProvider'; import { useFractal } from '../../../providers/App/AppProvider'; +import { useEthersProvider } from '../../../providers/Ethers/hooks/useEthersProvider'; import { AzoriusGovernance, FractalProposal, @@ -119,7 +119,12 @@ export function useProposalCountdown(proposal: FractalProposal) { ) { startCountdown(votingDeadlineMs + Number(timeLockPeriod.value) * 1000); // If the proposal is timelocked start the countdown (for safe multisig proposals with guards) - } else if (proposal.state === FractalProposalState.TIMELOCKED && freezeGuard && isSafeGuard) { + } else if ( + proposal.state === FractalProposalState.TIMELOCKED && + freezeGuard && + isSafeGuard && + provider + ) { const safeGuard = freezeGuard as MultisigFreezeGuard; const timelockedTimestamp = await getTxTimelockedTimestamp(proposal, safeGuard, provider); const guardTimeLockPeriod = await blocksToSeconds( @@ -130,7 +135,7 @@ export function useProposalCountdown(proposal: FractalProposal) { // If the proposal is executable start the countdown (for safe multisig proposals with guards) } else if (proposal.state === FractalProposalState.EXECUTABLE && freezeGuard) { let guardTimelockPeriod: number = 0; - if (isSafeGuard) { + if (isSafeGuard && provider) { const safeGuard = freezeGuard as MultisigFreezeGuard; const timelockedTimestamp = (await getTxTimelockedTimestamp(proposal, safeGuard, provider)) * 1000; diff --git a/src/graphql/utils/index.ts b/src/graphql/utils/index.ts index f0df4aa7e6..7ce2f56bef 100644 --- a/src/graphql/utils/index.ts +++ b/src/graphql/utils/index.ts @@ -1,19 +1,19 @@ import { useMemo } from 'react'; -import { useEthersProvider } from '../../providers/Ethers/hooks/useEthersProvider'; +import { usePublicClient } from 'wagmi'; import { supportedChains } from '../../providers/NetworkConfig/NetworkConfigProvider'; export const useSubgraphChainName = () => { - const provider = useEthersProvider(); + const { chain } = usePublicClient(); const subgraphChainName = useMemo(() => { - let chainName = provider.network.name; - supportedChains.forEach(chain => { - if (chain.chainId === provider.network.chainId) { - chainName = chain.subgraphChainName; + let chainName = chain.name; + supportedChains.forEach(supportedChain => { + if (supportedChain.chainId === chain.id) { + chainName = supportedChain.subgraphChainName; } }); return chainName; - }, [provider]); + }, [chain.id, chain.name]); return subgraphChainName; }; diff --git a/src/hooks/DAO/loaders/governance/useERC20LinearStrategy.ts b/src/hooks/DAO/loaders/governance/useERC20LinearStrategy.ts index 28c8db99a2..061bcf4714 100644 --- a/src/hooks/DAO/loaders/governance/useERC20LinearStrategy.ts +++ b/src/hooks/DAO/loaders/governance/useERC20LinearStrategy.ts @@ -22,7 +22,7 @@ export const useERC20LinearStrategy = () => { const { getTimeDuration } = useTimeHelpers(); const loadERC20Strategy = useCallback(async () => { - if (!ozLinearVotingContract || !azoriusContract) { + if (!ozLinearVotingContract || !azoriusContract || !provider) { return {}; } const [votingPeriodBlocks, quorumNumerator, quorumDenominator, timeLockPeriod] = diff --git a/src/hooks/DAO/loaders/governance/useERC721LinearStrategy.ts b/src/hooks/DAO/loaders/governance/useERC721LinearStrategy.ts index 0ba0a1a0fc..915506d284 100644 --- a/src/hooks/DAO/loaders/governance/useERC721LinearStrategy.ts +++ b/src/hooks/DAO/loaders/governance/useERC721LinearStrategy.ts @@ -24,7 +24,7 @@ export const useERC721LinearStrategy = () => { const { getTimeDuration } = useTimeHelpers(); const loadERC721Strategy = useCallback(async () => { - if (!erc721LinearVotingContract || !azoriusContract) { + if (!erc721LinearVotingContract || !azoriusContract || !provider) { return {}; } const [votingPeriodBlocks, quorumThreshold, timeLockPeriod] = await Promise.all([ diff --git a/src/hooks/DAO/loaders/useFractalFreeze.ts b/src/hooks/DAO/loaders/useFractalFreeze.ts index c8c39f4489..997626f904 100644 --- a/src/hooks/DAO/loaders/useFractalFreeze.ts +++ b/src/hooks/DAO/loaders/useFractalFreeze.ts @@ -49,7 +49,7 @@ export const useFractalFreeze = ({ const loadFractalFreezeGuard = useCallback( async ({ freezeVotingContract, freezeVotingType: freezeVotingType }: FractalGuardContracts) => { - if (freezeVotingType == null || !freezeVotingContract || !account) return; + if (freezeVotingType == null || !freezeVotingContract || !account || !provider) return; let userHasVotes: boolean = false; const freezeCreatedBlock = await ( freezeVotingContract!.asProvider as @@ -164,7 +164,7 @@ export const useFractalFreeze = ({ }, [setFractalFreezeGuard, guardContracts, daoAddress, loadOnMount]); useEffect(() => { - if (!loadOnMount) return; + if (!loadOnMount || !provider) return; const { freezeVotingContract, freezeVotingType: freezeVotingType } = guardContracts; let votingRPC: MultisigFreezeVoting | ERC20FreezeVoting | ERC721FreezeVoting; const listenerCallback: TypedListener = async ( diff --git a/src/hooks/DAO/loaders/useFractalGuardContracts.ts b/src/hooks/DAO/loaders/useFractalGuardContracts.ts index b01033aef6..777f68ada6 100644 --- a/src/hooks/DAO/loaders/useFractalGuardContracts.ts +++ b/src/hooks/DAO/loaders/useFractalGuardContracts.ts @@ -1,18 +1,18 @@ import { AzoriusFreezeGuard, MultisigFreezeGuard } from '@fractal-framework/fractal-contracts'; import { constants } from 'ethers'; import { useCallback, useEffect, useRef } from 'react'; +import { usePublicClient } from 'wagmi'; import { useFractal } from '../../../providers/App/AppProvider'; import { GuardContractAction } from '../../../providers/App/guardContracts/action'; -import { useEthersProvider } from '../../../providers/Ethers/hooks/useEthersProvider'; import { ContractConnection, SafeInfoResponseWithGuard, FreezeGuardType, FreezeVotingType, } from '../../../types'; -import { useEthersProvider } from '../../utils/useEthersProvider'; import { useMasterCopy } from '../../utils/useMasterCopy'; import { FractalModuleData, FractalModuleType } from './../../../types/fractal'; + export const useFractalGuardContracts = ({ loadOnMount = true }: { loadOnMount?: boolean }) => { // load key for component; helps prevent unnecessary calls const loadKey = useRef(); @@ -28,8 +28,7 @@ export const useFractalGuardContracts = ({ loadOnMount = true }: { loadOnMount?: action, } = useFractal(); - const provider = useEthersProvider(); - const chainId = provider?.network.chainId; + const { chain } = usePublicClient(); const { getZodiacModuleProxyMasterCopyData } = useMasterCopy(); @@ -122,14 +121,10 @@ export const useFractalGuardContracts = ({ loadOnMount = true }: { loadOnMount?: }, [action, daoAddress, safe, fractalModules, loadFractalGuardContracts]); useEffect(() => { - if (chainId === undefined) { - return; - } - - if (daoAddress && chainId + daoAddress !== loadKey.current && loadOnMount && isModulesLoaded) { - loadKey.current = chainId + daoAddress; + if (daoAddress && chain.id + daoAddress !== loadKey.current && loadOnMount && isModulesLoaded) { + loadKey.current = chain.id + daoAddress; setGuardContracts(); } - }, [setGuardContracts, isModulesLoaded, daoAddress, loadOnMount, chainId]); + }, [setGuardContracts, isModulesLoaded, daoAddress, loadOnMount, chain.id]); return loadFractalGuardContracts; }; diff --git a/src/hooks/DAO/loaders/useGovernanceContracts.ts b/src/hooks/DAO/loaders/useGovernanceContracts.ts index 3e1e8be23e..509f86a96a 100644 --- a/src/hooks/DAO/loaders/useGovernanceContracts.ts +++ b/src/hooks/DAO/loaders/useGovernanceContracts.ts @@ -13,7 +13,6 @@ import { GovernanceContractAction } from '../../../providers/App/governanceContr import { useEthersProvider } from '../../../providers/Ethers/hooks/useEthersProvider'; import { ContractConnection } from '../../../types'; import { getAzoriusModuleFromModules } from '../../../utils'; -import { useEthersProvider } from '../../utils/useEthersProvider'; import { useMasterCopy } from '../../utils/useMasterCopy'; import useSignerOrProvider from '../../utils/useSignerOrProvider'; @@ -109,7 +108,7 @@ export const useGovernanceContracts = () => { return undefined; }); - if (lockedToken && provider) { + if (lockedToken && provider && signerOrProvider) { lockReleaseContract = { asSigner: LockRelease__factory.connect(govTokenAddress, signerOrProvider), asProvider: LockRelease__factory.connect(govTokenAddress, provider), diff --git a/src/hooks/DAO/proposal/useSubmitProposal.ts b/src/hooks/DAO/proposal/useSubmitProposal.ts index 452f5c251b..8e705965a9 100644 --- a/src/hooks/DAO/proposal/useSubmitProposal.ts +++ b/src/hooks/DAO/proposal/useSubmitProposal.ts @@ -97,7 +97,7 @@ export default function useSubmitProposal() { */ const getCanUserCreateProposal = useCallback( async (safeAddress?: string): Promise => { - if (!user.address) { + if (!user.address || !safeAPI || !signerOrProvider) { return false; } @@ -282,7 +282,7 @@ export default function useSubmitProposal() { failedToastMessage, safeAddress, }: ISubmitAzoriusProposal) => { - if (!proposalData) { + if (!proposalData || !provider) { return; } const toastId = toast(pendingToastMessage, { @@ -352,7 +352,7 @@ export default function useSubmitProposal() { successCallback, safeAddress, }: ISubmitProposal) => { - if (!proposalData) { + if (!proposalData || !safeAPI) { return; } diff --git a/src/hooks/DAO/useClawBack.ts b/src/hooks/DAO/useClawBack.ts index 46177d04af..41d504f56c 100644 --- a/src/hooks/DAO/useClawBack.ts +++ b/src/hooks/DAO/useClawBack.ts @@ -19,7 +19,7 @@ export default function useClawBack({ childSafeInfo, parentAddress }: IUseClawBa const { submitProposal, canUserCreateProposal } = useSubmitProposal(); const handleClawBack = useCallback(async () => { - if (childSafeInfo && childSafeInfo.daoAddress && parentAddress) { + if (childSafeInfo && childSafeInfo.daoAddress && parentAddress && safeAPI && provider) { const childSafeBalance = await safeAPI.getBalances( utils.getAddress(childSafeInfo.daoAddress) ); diff --git a/src/hooks/DAO/useDAOName.ts b/src/hooks/DAO/useDAOName.ts index 816821dfae..b0a7921f93 100644 --- a/src/hooks/DAO/useDAOName.ts +++ b/src/hooks/DAO/useDAOName.ts @@ -1,6 +1,6 @@ import { FractalRegistry } from '@fractal-framework/fractal-contracts'; import { useCallback, useEffect, useState } from 'react'; -import { Address, useEnsName } from 'wagmi'; +import { Address, useEnsName, usePublicClient } from 'wagmi'; import { getEventRPC } from '../../helpers'; import { useFractal } from '../../providers/App/AppProvider'; import { useEthersProvider } from '../../providers/Ethers/hooks/useEthersProvider'; @@ -26,12 +26,11 @@ export default function useDAOName({ baseContracts: { fractalRegistryContract }, } = useFractal(); const [daoRegistryName, setDAORegistryName] = useState(''); - const provider = useEthersProvider(); - const networkId = provider.network.chainId; + const { chain } = usePublicClient(); const { data: ensName } = useEnsName({ address: address as Address, - chainId: networkId, + chainId: chain.id, cacheTime: 1000 * 60 * 30, // 30 min }); const { setValue, getValue } = useLocalStorage(); @@ -102,11 +101,13 @@ export function useLazyDAOName() { if (cachedName) { return cachedName; } - // check if ens name resolves - const ensName = await provider.lookupAddress(_address).catch(() => null); - if (ensName) { - setValue(CacheKeys.DAO_NAME_PREFIX + _address, ensName, 5); - return ensName; + if (provider) { + // check if ens name resolves + const ensName = await provider.lookupAddress(_address).catch(() => null); + if (ensName) { + setValue(CacheKeys.DAO_NAME_PREFIX + _address, ensName, 5); + return ensName; + } } if (_registryName) { diff --git a/src/hooks/safe/useSafeContracts.ts b/src/hooks/safe/useSafeContracts.ts index b280c0e5dd..6ef749ad1a 100644 --- a/src/hooks/safe/useSafeContracts.ts +++ b/src/hooks/safe/useSafeContracts.ts @@ -51,6 +51,9 @@ export default function useSafeContracts() { } = useNetworkConfig(); const daoContracts = useMemo(() => { + if (!signerOrProvider || !provider) { + return; + } const multiSendContract = { asSigner: MultiSend__factory.connect(multisend, signerOrProvider), asProvider: MultiSend__factory.connect(multisend, provider), diff --git a/src/hooks/utils/useAvatar.ts b/src/hooks/utils/useAvatar.ts index 54d3245f58..d8fbd9a5f8 100644 --- a/src/hooks/utils/useAvatar.ts +++ b/src/hooks/utils/useAvatar.ts @@ -1,12 +1,10 @@ -import { useEnsAvatar } from 'wagmi'; -import { useEthersProvider } from '../../providers/Ethers/hooks/useEthersProvider'; +import { useEnsAvatar, usePublicClient } from 'wagmi'; const useAvatar = (account: string | null) => { - const provider = useEthersProvider(); - const networkId = provider.network.chainId; + const { chain } = usePublicClient(); const { data: avatarURL } = useEnsAvatar({ name: account, - chainId: networkId, + chainId: chain.id, }); return avatarURL; diff --git a/src/hooks/utils/useDisplayName.ts b/src/hooks/utils/useDisplayName.ts index 68e40c1385..628530c654 100644 --- a/src/hooks/utils/useDisplayName.ts +++ b/src/hooks/utils/useDisplayName.ts @@ -1,6 +1,5 @@ import { useEffect, useState } from 'react'; -import { Address, useEnsName } from 'wagmi'; -import { useEthersProvider } from '../../providers/Ethers/hooks/useEthersProvider'; +import { Address, useEnsName, usePublicClient } from 'wagmi'; export const createAccountSubstring = (account: string) => { return `${account.substring(0, 6)}...${account.slice(-4)}`; @@ -16,11 +15,10 @@ export const createAccountSubstring = (account: string) => { */ const useDisplayName = (account?: string | null, truncate?: boolean) => { if (truncate === undefined) truncate = true; - const provider = useEthersProvider(); - const networkId = provider.network.chainId; + const { chain } = usePublicClient(); const { data: ensName } = useEnsName({ address: account as Address, - chainId: networkId, + chainId: chain.id, cacheTime: 1000 * 60 * 30, // 30 min }); diff --git a/src/hooks/utils/useSafeTransactions.ts b/src/hooks/utils/useSafeTransactions.ts index b7dea61012..cf21818b41 100644 --- a/src/hooks/utils/useSafeTransactions.ts +++ b/src/hooks/utils/useSafeTransactions.ts @@ -41,7 +41,7 @@ export const useSafeTransactions = () => { freezeGuard?: MultisigFreezeGuard, freezeGuardData?: FreezeGuardData ) => { - if (freezeGuard && freezeGuardData) { + if (freezeGuard && freezeGuardData && provider) { return Promise.all( activities.map(async (activity, _, activityArr) => { if (activity.eventType !== ActivityEventType.Governance || !activity.transaction) { @@ -189,7 +189,7 @@ export const useSafeTransactions = () => { const parseTransactions = useCallback( async (transactions: AllTransactionsListResponse, daoAddress: string) => { - if (!transactions.results.length) { + if (!transactions.results.length || !provider) { return []; } From 0d5bfe876e2fa88d03646985b169f8d12b6d6425 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Thu, 7 Mar 2024 12:58:18 -0500 Subject: [PATCH 39/56] Update prettier, make it play nicer with eslint, fix the package.json scripts --- .eslintrc.json | 146 ++++++++++++++++++++-------------------------- .prettierignore | 1 + .prettierrc | 10 ++++ .prettierrc.json | 10 ---- package-lock.json | 102 +++++++++----------------------- package.json | 9 +-- 6 files changed, 107 insertions(+), 171 deletions(-) create mode 100644 .prettierignore create mode 100644 .prettierrc delete mode 100644 .prettierrc.json diff --git a/.eslintrc.json b/.eslintrc.json index 2a6aeaf8a3..320c6f1a4e 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,89 +1,67 @@ { - "env": { - "browser": true, - "es2021": true + "env": { + "browser": true, + "es2021": true + }, + "extends": [ + "next/core-web-vitals", + "plugin:import/recommended", + "airbnb-typescript/base", + "prettier" + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + // required for "type-aware linting" + "project": ["./tsconfig.json"], + "ecmaFeatures": { + "jsx": true }, - "extends": [ - "next/core-web-vitals", - "plugin:import/recommended", - "airbnb-typescript/base" - ], - "parser": "@typescript-eslint/parser", - "parserOptions": { - // required for "type-aware linting" - "project": [ - "./tsconfig.json" - ], - "ecmaFeatures": { - "jsx": true - }, - "ecmaVersion": "latest", - "sourceType": "module" + "ecmaVersion": "latest", + "sourceType": "module" + }, + "plugins": ["import", "@typescript-eslint"], + "settings": { + // resolves imports without file extensions for listed extensions + "import/resolver": { + "node": { + "extensions": [".ts", ".tsx"] + } }, - "plugins": [ - "import", - "@typescript-eslint", - "prettier" + "react": { + "version": "detect" + } + }, + "rules": { + // ignores need for "react" import + "react/react-in-jsx-scope": "off", + // rule for function component to be framed as function + "react/function-component-definition": [ + 2, + { + "namedComponents": "function-declaration" + } ], - "settings": { - // resolves imports without file extensions for listed extensions - "import/resolver": { - "node": { - "extensions": [ - ".ts", - ".tsx" - ] - } - }, - "react": { - "version": "detect" + "comma-dangle": "off", + "@typescript-eslint/comma-dangle": "off", + "indent": "off", + "@typescript-eslint/indent": "off", + "import/order": [ + "error", + { + "alphabetize": { + "order": "asc", + "caseInsensitive": false } - }, - "rules": { - // ignores need for "react" import - "react/react-in-jsx-scope": "off", - // rule for function component to be framed as function - "react/function-component-definition": [ - 2, - { - "namedComponents": "function-declaration" - } - ], - "comma-dangle": "off", - "@typescript-eslint/comma-dangle": "off", - "indent": "off", - "@typescript-eslint/indent": "off", - // prettier config - "prettier/prettier": [ - 1, - { - "printWidth": 100, - "endOfLine": "auto", - "tabWidth": 2, - "useTabs": false, - "singleQuote": true, - "semi": true, - "arrowParens": "avoid", - "singleAttributePerLine": true - } - ], - "import/order": [ - "error", - { - "alphabetize": { - "order": "asc", - "caseInsensitive": false - } - } - ], - // react hook rules - "react-hooks/rules-of-hooks": "error", - "react-hooks/exhaustive-deps": "warn", - // var rules - "no-use-before-define": "off", - "@typescript-eslint/no-use-before-define": "error", - "@typescript-eslint/no-unused-vars": "warn", - // class rules - "@typescript-eslint/lines-between-class-members": "off" - } -} \ No newline at end of file + } + ], + // react hook rules + "react-hooks/rules-of-hooks": "error", + "react-hooks/exhaustive-deps": "warn", + // var rules + "no-use-before-define": "off", + "@typescript-eslint/no-use-before-define": "error", + "@typescript-eslint/no-unused-vars": "warn", + // class rules + "@typescript-eslint/lines-between-class-members": "off" + } +} diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000000..8e7db9cfde --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +src/assets/typechain-types/ diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000000..90a9a13893 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,10 @@ +{ + "printWidth": 100, + "endOfLine": "auto", + "tabWidth": 2, + "useTabs": false, + "singleQuote": true, + "semi": true, + "arrowParens": "avoid", + "singleAttributePerLine": true +} diff --git a/.prettierrc.json b/.prettierrc.json deleted file mode 100644 index 949647aaa5..0000000000 --- a/.prettierrc.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "printWidth": 100, - "endOfLine": "auto", - "tabWidth": 2, - "useTabs": false, - "singleQuote": true, - "semi": true, - "arrowParens": "avoid", - "singleAttributePerLine": true -} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 2668a872e6..c69ef1ce99 100644 --- a/package-lock.json +++ b/package-lock.json @@ -68,11 +68,11 @@ "eslint": "^8.22.0", "eslint-config-airbnb-typescript": "^17.1.0", "eslint-config-next": "^13.2.4", + "eslint-config-prettier": "^9.1.0", "eslint-plugin-import": "^2.26.0", - "eslint-plugin-prettier": "^4.2.1", "jsdom": "^24.0.0", "playwright": "^1.32.3", - "prettier": "^2.7.1", + "prettier": "^3.2.5", "typescript": "^5.0.4", "url": "^0.11.0", "vitest": "^1.2.2" @@ -15504,6 +15504,18 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/eslint-config-prettier": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", + "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, "node_modules/eslint-import-resolver-node": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", @@ -15698,27 +15710,6 @@ "semver": "bin/semver.js" } }, - "node_modules/eslint-plugin-prettier": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz", - "integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==", - "dev": true, - "dependencies": { - "prettier-linter-helpers": "^1.0.0" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "eslint": ">=7.28.0", - "prettier": ">=2.0.0" - }, - "peerDependenciesMeta": { - "eslint-config-prettier": { - "optional": true - } - } - }, "node_modules/eslint-plugin-react": { "version": "7.32.2", "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.32.2.tgz", @@ -16554,12 +16545,6 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, - "node_modules/fast-diff": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", - "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", - "dev": true - }, "node_modules/fast-glob": { "version": "3.2.11", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", @@ -23264,32 +23249,20 @@ } }, "node_modules/prettier": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", - "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", "dev": true, "bin": { - "prettier": "bin-prettier.js" + "prettier": "bin/prettier.cjs" }, "engines": { - "node": ">=10.13.0" + "node": ">=14" }, "funding": { "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "node_modules/prettier-linter-helpers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", - "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", - "dev": true, - "dependencies": { - "fast-diff": "^1.1.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/pretty-format": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", @@ -39978,6 +39951,13 @@ } } }, + "eslint-config-prettier": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", + "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", + "dev": true, + "requires": {} + }, "eslint-import-resolver-node": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", @@ -40134,15 +40114,6 @@ } } }, - "eslint-plugin-prettier": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz", - "integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==", - "dev": true, - "requires": { - "prettier-linter-helpers": "^1.0.0" - } - }, "eslint-plugin-react": { "version": "7.32.2", "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.32.2.tgz", @@ -40802,12 +40773,6 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, - "fast-diff": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", - "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", - "dev": true - }, "fast-glob": { "version": "3.2.11", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", @@ -45886,20 +45851,11 @@ "dev": true }, "prettier": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", - "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", "dev": true }, - "prettier-linter-helpers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", - "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", - "dev": true, - "requires": { - "fast-diff": "^1.1.2" - } - }, "pretty-format": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", diff --git a/package.json b/package.json index 4b8e2006f9..2088ddcca9 100644 --- a/package.json +++ b/package.json @@ -50,8 +50,9 @@ "yup": "^1" }, "scripts": { - "lint": "next lint", - "lint:fix": "next lint --fix", + "lint": "eslint . --ext .ts,.tsx", + "pretty": "prettier . --write", + "pretty:check": "prettier . --check", "dev": "npm run graphql:build && NEXT_PUBLIC_GIT_HASH=`git rev-parse HEAD` next dev", "start": "next start", "build": "npm run graphql:build && NEXT_PUBLIC_GIT_HASH=`git rev-parse HEAD` next build", @@ -90,11 +91,11 @@ "eslint": "^8.22.0", "eslint-config-airbnb-typescript": "^17.1.0", "eslint-config-next": "^13.2.4", + "eslint-config-prettier": "^9.1.0", "eslint-plugin-import": "^2.26.0", - "eslint-plugin-prettier": "^4.2.1", "jsdom": "^24.0.0", "playwright": "^1.32.3", - "prettier": "^2.7.1", + "prettier": "^3.2.5", "typescript": "^5.0.4", "url": "^0.11.0", "vitest": "^1.2.2" From b025ac5b0893ad4936a1b5146f56a9f871951c1c Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Thu, 7 Mar 2024 12:59:02 -0500 Subject: [PATCH 40/56] Auto fix prettier problems --- .github/CODE_OF_CONDUCT.md | 22 +- .github/CONTRIBUTING.md | 2 - .github/SECURITY.md | 2 +- .github/workflows/e2e-tests.yaml | 1 - .graphclientrc.yml | 2 +- README.md | 7 +- app/create/page.tsx | 4 +- .../[daoAddress]/edit/governance/page.tsx | 2 +- .../proposal-templates/new/page.tsx | 6 +- app/daos/[daoAddress]/proposals/new/page.tsx | 2 +- docker-compose.yml | 6 +- docker/blockchain/hardhat.config.js | 10 +- docker/blockchain/hardhat.package.json | 2 +- docs/NETWORK_SUPPORT.md | 2 +- docs/TRANSLATIONS.md | 10 +- playwright.config.ts | 27 +- src/assets/css/SnapshotProposalMarkdown.css | 953 +++++++++--------- .../Activity/ActivityDescriptionTreasury.tsx | 12 +- .../Activity/ActivityGovernance.tsx | 2 +- src/components/Activity/ActivityTreasury.tsx | 2 +- src/components/Activity/FreezeButton.tsx | 2 +- .../ProposalTemplateTransaction.tsx | 14 +- .../ProposalTemplateTransactions.tsx | 2 +- .../ProposalTemplateTransactionsForm.tsx | 2 +- src/components/DaoCreator/StepButtons.tsx | 4 +- src/components/DaoCreator/StepWrapper.tsx | 2 +- .../formComponents/AzoriusNFTDetails.tsx | 2 +- .../formComponents/AzoriusTokenDetails.tsx | 2 +- .../formComponents/EstablishEssentials.tsx | 4 +- .../formComponents/GuardDetails.tsx | 6 +- .../DaoCreator/hooks/usePrepareFormData.ts | 30 +- .../MultisigProposalDetails/TxActions.tsx | 10 +- .../Proposals/ProposalActions/CastVote.tsx | 6 +- .../ProposalActions/ProposalAction.tsx | 2 +- src/components/Proposals/ProposalSummary.tsx | 22 +- .../ProposalVotes/context/VoteContext.tsx | 10 +- .../Proposals/ProposalVotes/index.tsx | 6 +- .../SnapshotProposalDescription.tsx | 2 +- src/components/Proposals/index.tsx | 8 +- .../pages/DAOTreasury/components/Assets.tsx | 6 +- .../DAOTreasury/components/Transactions.tsx | 2 +- .../DAOTreasury/hooks/useFormatTransfers.tsx | 2 +- .../pages/DAOTreasury/hooks/useSendAssets.ts | 2 +- .../DAOTreasury/hooks/useTreasuryTotalUSD.tsx | 2 +- .../Activities/ActivityFreeze.tsx | 4 +- .../pages/DaoDashboard/ERC20Claim.tsx | 4 +- .../pages/DaoDashboard/Info/InfoProposals.tsx | 4 +- src/components/pages/DaoHierarchy/DaoNode.tsx | 4 +- .../pages/DaoHierarchy/useFetchNodes.tsx | 6 +- .../components/ERC20Token/index.tsx | 2 +- .../DaoSettings/components/Modules/index.tsx | 4 +- .../components/Signers/hooks/useAddSigner.ts | 2 +- .../Signers/modals/AddSignerModal.tsx | 4 +- .../Signers/modals/RemoveSignerModal.tsx | 4 +- src/components/ui/forms/ABISelector.tsx | 8 +- src/components/ui/forms/BigNumberInput.tsx | 10 +- src/components/ui/icons/FavoriteIcon.tsx | 2 +- .../ui/menus/ManageDAO/ManageDAOMenu.tsx | 14 +- src/components/ui/modals/DelegateModal.tsx | 4 +- .../ui/modals/ForkProposalTemplateModal.tsx | 4 +- .../ui/modals/ProposalTemplateModal.tsx | 10 +- src/components/ui/modals/SendAssetsModal.tsx | 4 +- src/components/ui/modals/UnwrapToken.tsx | 6 +- src/components/ui/modals/WrapToken.tsx | 8 +- .../ui/proposal/ProposalCountdown.tsx | 18 +- .../ui/proposal/useProposalCountdown.tsx | 6 +- src/graphql/DAO.graphql | 56 +- src/helpers/activity.ts | 2 +- src/helpers/crypto.ts | 22 +- src/helpers/errorLogging.ts | 2 +- src/helpers/freezePeriodHelpers.ts | 12 +- .../loaders/governance/useAzoriusProposals.ts | 24 +- .../DAO/loaders/governance/useERC20Claim.ts | 2 +- .../loaders/governance/useERC20LinearToken.ts | 2 +- .../DAO/loaders/governance/useERC721Tokens.ts | 6 +- .../DAO/loaders/governance/useLockRelease.ts | 2 +- .../governance/useSafeMultisigProposals.ts | 2 +- .../loaders/snapshot/useSnapshotProposal.ts | 6 +- .../loaders/snapshot/useSnapshotProposals.ts | 4 +- src/hooks/DAO/loaders/useFavorites.ts | 4 +- src/hooks/DAO/loaders/useFractalFreeze.ts | 28 +- .../DAO/loaders/useFractalGuardContracts.ts | 26 +- src/hooks/DAO/loaders/useFractalModules.ts | 4 +- src/hooks/DAO/loaders/useFractalNode.ts | 2 +- .../DAO/loaders/useGovernanceContracts.ts | 8 +- src/hooks/DAO/loaders/useLoadDAONode.ts | 4 +- src/hooks/DAO/proposal/useCastVote.ts | 12 +- .../DAO/proposal/useCreateProposalTemplate.ts | 4 +- src/hooks/DAO/proposal/useExecuteProposal.ts | 4 +- src/hooks/DAO/proposal/useGetMetadata.ts | 4 +- src/hooks/DAO/proposal/usePrepareProposal.ts | 6 +- src/hooks/DAO/proposal/useProposals.ts | 2 +- .../DAO/proposal/useRemoveProposalTemplate.ts | 4 +- src/hooks/DAO/proposal/useSubmitProposal.ts | 18 +- .../DAO/proposal/useUpdateProposalState.ts | 2 +- .../DAO/proposal/useUserERC721VotingTokens.ts | 24 +- src/hooks/DAO/useBuildDAOTx.ts | 8 +- src/hooks/DAO/useCastFreezeVote.ts | 2 +- src/hooks/DAO/useClawBack.ts | 14 +- src/hooks/DAO/useCreateSubDAOProposal.ts | 4 +- src/hooks/DAO/useDAOData.ts | 2 +- src/hooks/DAO/useDAOName.ts | 2 +- src/hooks/DAO/useDelegateVote.ts | 2 +- src/hooks/DAO/useDeployAzorius.ts | 6 +- src/hooks/DAO/useDeployDAO.ts | 4 +- src/hooks/DAO/useSearchDao.ts | 2 +- src/hooks/safe/useSafeContracts.ts | 4 +- .../schemas/DAOCreate/useDAOCreateSchema.ts | 8 +- .../schemas/DAOCreate/useDAOCreateTests.ts | 10 +- .../schemas/common/useValidationAddress.tsx | 8 +- .../useCreateProposalTemplateSchema.ts | 8 +- .../proposalCreate/useCreateProposalSchema.ts | 6 +- src/hooks/stake/lido/useLidoStaking.ts | 14 +- src/hooks/toasts/useActionToast.tsx | 2 +- src/hooks/utils/cache/useLocalDB.ts | 8 +- src/hooks/utils/cache/useLocalStorage.ts | 6 +- src/hooks/utils/useAddress.ts | 2 +- src/hooks/utils/useApproval.tsx | 2 +- src/hooks/utils/useAsyncRequest.ts | 2 +- src/hooks/utils/useAsyncRetry.ts | 4 +- src/hooks/utils/useCurrentBlockNumber.ts | 2 +- src/hooks/utils/useEthersProvider.ts | 4 +- src/hooks/utils/useMasterCopy.ts | 20 +- src/hooks/utils/useSafeDecoder.tsx | 6 +- src/hooks/utils/useSafeTransactions.ts | 34 +- src/hooks/utils/useTimeHelpers.tsx | 2 +- src/hooks/utils/useTransaction.ts | 2 +- src/hooks/utils/useUpdateTimer.tsx | 2 +- src/i18n/locales/en/breadcrumbs.json | 26 +- src/i18n/locales/en/common.json | 212 ++-- src/i18n/locales/en/daoCreate.json | 204 ++-- src/i18n/locales/en/daoEdit.json | 4 +- src/i18n/locales/en/dashboard.json | 58 +- src/i18n/locales/en/languages.json | 8 +- src/i18n/locales/en/menu.json | 40 +- src/i18n/locales/en/modals.json | 132 +-- src/i18n/locales/en/navigation.json | 34 +- src/i18n/locales/en/proposal.json | 266 ++--- src/i18n/locales/en/proposalMetadata.json | 38 +- src/i18n/locales/en/proposalTemplate.json | 58 +- src/i18n/locales/en/settings.json | 44 +- src/i18n/locales/en/stake.json | 6 +- src/i18n/locales/en/transaction.json | 48 +- src/i18n/locales/en/treasury.json | 48 +- src/i18n/locales/uk/breadcrumbs.json | 12 +- src/i18n/locales/uk/common.json | 126 +-- src/i18n/locales/uk/daoCreate.json | 104 +- src/i18n/locales/uk/daoEdit.json | 4 +- src/i18n/locales/uk/dashboard.json | 44 +- src/i18n/locales/uk/home.json | 4 +- src/i18n/locales/uk/languages.json | 8 +- src/i18n/locales/uk/menu.json | 26 +- src/i18n/locales/uk/modals.json | 42 +- src/i18n/locales/uk/navigation.json | 20 +- src/i18n/locales/uk/proposal.json | 186 ++-- src/i18n/locales/uk/proposalMetadata.json | 8 +- src/i18n/locales/uk/settings.json | 2 +- src/i18n/locales/uk/transaction.json | 32 +- src/i18n/locales/uk/treasury.json | 30 +- src/models/AzoriusTxBuilder.ts | 88 +- src/models/BaseTxBuilder.ts | 2 +- src/models/DaoTxBuilder.ts | 20 +- src/models/FreezeGuardTxBuilder.ts | 30 +- src/models/MultisigTxBuilder.ts | 2 +- src/models/TxBuilderFactory.ts | 18 +- src/models/helpers/fractalModuleData.ts | 12 +- src/models/helpers/safeData.ts | 10 +- src/models/helpers/utils.ts | 6 +- src/providers/App/combinedReducer.ts | 2 +- src/providers/App/governance/reducer.ts | 4 +- .../App/governanceContracts/reducer.ts | 2 +- src/providers/App/guardContracts/reducer.ts | 2 +- src/providers/App/hooks/useIPFSClient.ts | 4 +- src/providers/App/hooks/useSafeAPI.ts | 26 +- src/providers/App/treasury/reducer.ts | 2 +- src/providers/App/useReadOnlyValues.ts | 2 +- .../NetworkConfig/NetworkConfigProvider.tsx | 2 +- .../NetworkConfig/rainbow-kit.config.ts | 4 +- src/types/createDAO.ts | 4 +- src/types/daoProposal.ts | 2 +- src/types/votingFungibleToken.ts | 6 +- src/utils/api.spec.ts | 4 +- src/utils/api.ts | 2 +- src/utils/azorius.ts | 21 +- src/utils/contract.ts | 4 +- src/utils/crypto.ts | 6 +- src/utils/guard.ts | 6 +- src/utils/numberFormats.ts | 6 +- src/utils/percentFormat.spec.ts | 6 +- src/utils/safeData.ts | 2 +- src/utils/shutter.ts | 2 +- src/utils/signatures.ts | 6 +- test/encodeFunction.test.ts | 59 +- tests/QA_TESTING.md | 67 +- tests/home/connect-wallet.spec.ts | 2 +- tests/models/mock/SafeMocker.ts | 2 +- tests/models/mock/data/creation.ts | 2 +- tsconfig.json | 12 +- vitest.config.ts | 8 +- 199 files changed, 2116 insertions(+), 2083 deletions(-) diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md index 89681fdee6..fe654804d2 100644 --- a/.github/CODE_OF_CONDUCT.md +++ b/.github/CODE_OF_CONDUCT.md @@ -17,23 +17,23 @@ diverse, inclusive, and healthy community. Examples of behavior that contributes to a positive environment for our community include: -* Demonstrating empathy and kindness toward other people -* Being respectful of differing opinions, viewpoints, and experiences -* Giving and gracefully accepting constructive feedback -* Accepting responsibility and apologizing to those affected by our mistakes, +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience -* Focusing on what is best not just for us as individuals, but for the +- Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: -* The use of sexualized language or imagery, and sexual attention or +- The use of sexualized language or imagery, and sexual attention or advances of any kind -* Trolling, insulting or derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or email +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email address, without their explicit permission -* Other conduct which could reasonably be considered inappropriate in a +- Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities @@ -106,7 +106,7 @@ Violating these terms may lead to a permanent ban. ### 4. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an +standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 434113119f..73b6e3e6dc 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -20,12 +20,10 @@ - If possible, please provide unit or integration tests for your changes. You can see example automation tests in the [test directory](https://github.com/decent-dao/fractal-interface/tree/HEAD/tests). - - All of your components should be composed of [Chakra UI](https://chakra-ui.com/) base components, using our custom theme. - A member of the Fractal engineering team will respond with any further discussion or requested changes and once approved you are free to merge. - - We appreciate the collaboration! ### Submitting Language Translations diff --git a/.github/SECURITY.md b/.github/SECURITY.md index ccbbecc226..3bf9e5d8b5 100644 --- a/.github/SECURITY.md +++ b/.github/SECURITY.md @@ -19,4 +19,4 @@ Please include the following information in your email: If you prefer secure communication, please use the following GPG key: -https://keys.openpgp.org/search?q=security%40decent-dao.org \ No newline at end of file +https://keys.openpgp.org/search?q=security%40decent-dao.org diff --git a/.github/workflows/e2e-tests.yaml b/.github/workflows/e2e-tests.yaml index 784676845c..00eb0d7b76 100644 --- a/.github/workflows/e2e-tests.yaml +++ b/.github/workflows/e2e-tests.yaml @@ -143,4 +143,3 @@ jobs: docker rm webapp docker stop blockchain docker rm blockchain - diff --git a/.graphclientrc.yml b/.graphclientrc.yml index 0feff206a0..06bfdca034 100644 --- a/.graphclientrc.yml +++ b/.graphclientrc.yml @@ -5,4 +5,4 @@ sources: endpoint: https://api.studio.thegraph.com/query/43215/fractal-{context.chainName:sepolia}/version/latest documents: - - ./src/graphql/DAO.graphql \ No newline at end of file + - ./src/graphql/DAO.graphql diff --git a/README.md b/README.md index 1ac8ba529b..92528592f9 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ $ npm run graphql:build # For UNIX ``` Running development environment + ```shell $ npm run dev ``` @@ -81,8 +82,8 @@ The "dev" environment tracks the `develop` branch, "staging" tracks `staging`, a So at any given time, there are effectively three builds out there, and they are publicly accessible and privately configurable within Github: 1. dev site - - url: https://app.dev.fractalframework.xyz + - url: https://app.dev.fractalframework.xyz 2. staging site - - url: https://app.staging.fractalframework.xyz + - url: https://app.staging.fractalframework.xyz 3. prod site - - url: https://app.fractalframework.xyz + - url: https://app.fractalframework.xyz diff --git a/app/create/page.tsx b/app/create/page.tsx index 2fe1bd9925..9a358ff7c3 100644 --- a/app/create/page.tsx +++ b/app/create/page.tsx @@ -29,7 +29,7 @@ export default function DaoCreatePage() { const { getAddress } = ethers.utils; const daoFound = await requestWithRetries( () => safeAPI.getSafeCreationInfo(getAddress(daoAddress)), - 8 + 8, ); toggleFavorite(daoAddress); if (daoFound) { @@ -45,7 +45,7 @@ export default function DaoCreatePage() { push(BASE_ROUTES.landing); } }, - [safeAPI, requestWithRetries, toggleFavorite, push, t] + [safeAPI, requestWithRetries, toggleFavorite, push, t], ); const [deploy, pending] = useDeployDAO(); diff --git a/app/daos/[daoAddress]/edit/governance/page.tsx b/app/daos/[daoAddress]/edit/governance/page.tsx index d712090f23..e2a13fc07f 100644 --- a/app/daos/[daoAddress]/edit/governance/page.tsx +++ b/app/daos/[daoAddress]/edit/governance/page.tsx @@ -35,7 +35,7 @@ export default function ModifyGovernancePage() { deployAzorius( daoData as AzoriusERC20DAO | AzoriusERC721DAO, !daoName || createAccountSubstring(daoAddress!) === daoName, - !daoSnapshotURL && !!daoData.snapshotURL + !daoSnapshotURL && !!daoData.snapshotURL, ); }; diff --git a/app/daos/[daoAddress]/proposal-templates/new/page.tsx b/app/daos/[daoAddress]/proposal-templates/new/page.tsx index 87bfd7d7b8..ddc876f15e 100644 --- a/app/daos/[daoAddress]/proposal-templates/new/page.tsx +++ b/app/daos/[daoAddress]/proposal-templates/new/page.tsx @@ -41,11 +41,11 @@ export default function CreateProposalTemplatePage() { const searchParams = useSearchParams(); const defaultProposalTemplatesHash = useMemo( () => searchParams?.get('templatesHash'), - [searchParams] + [searchParams], ); const defaultProposalTemplateIndex = useMemo( () => searchParams?.get('templateIndex'), - [searchParams] + [searchParams], ); const { @@ -141,7 +141,7 @@ export default function CreateProposalTemplatePage() { push( daoAddress ? DAO_ROUTES.proposalTemplates.relative(daoAddress) - : BASE_ROUTES.landing + : BASE_ROUTES.landing, ) } isButtonDisabled={pendingCreateTx} diff --git a/app/daos/[daoAddress]/proposals/new/page.tsx b/app/daos/[daoAddress]/proposals/new/page.tsx index dc79b44ba5..1d259c6ed1 100644 --- a/app/daos/[daoAddress]/proposals/new/page.tsx +++ b/app/daos/[daoAddress]/proposals/new/page.tsx @@ -41,7 +41,7 @@ export default function ProposalCreatePage() { const [formState, setFormState] = useState(CreateProposalState.METADATA_FORM); const isAzorius = useMemo( () => type === GovernanceType.AZORIUS_ERC20 || type === GovernanceType.AZORIUS_ERC721, - [type] + [type], ); const successCallback = () => { diff --git a/docker-compose.yml b/docker-compose.yml index 1f6d7305ce..60f8a1d47e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,9 +1,9 @@ -version: "3" +version: '3' services: blockchain: build: docker/blockchain ports: - - "8545:8545" + - '8545:8545' env_file: - ./docker/.env.tests.local @@ -16,7 +16,7 @@ services: NEXT_PUBLIC_LOCAL_CHAIN_ID: '31337' NEXT_PUBLIC_LOCAL_PROVIDER_URL: 'http://127.0.0.1:8545' ports: - - "3000:3000" + - '3000:3000' environment: - NEXT_PUBLIC_TESTING_ENVIRONMENT=true - NEXT_PUBLIC_LOCAL_CHAIN_ID=31337 diff --git a/docker/blockchain/hardhat.config.js b/docker/blockchain/hardhat.config.js index d3fe4ddd27..39702cac26 100644 --- a/docker/blockchain/hardhat.config.js +++ b/docker/blockchain/hardhat.config.js @@ -1,16 +1,16 @@ -require('dotenv').config() +require('dotenv').config(); /** * @type import('hardhat/config').HardhatUserConfig */ - module.exports = { - solidity: "0.8.4", - networks: { +module.exports = { + solidity: '0.8.4', + networks: { hardhat: { chainId: 31337, forking: { url: `https://eth-sepolia.g.alchemy.com/v2/${process.env.NEXT_PUBLIC_ALCHEMY_TESTING_API_KEY}`, - } + }, }, }, }; diff --git a/docker/blockchain/hardhat.package.json b/docker/blockchain/hardhat.package.json index f31abded1c..e0ac1af9da 100644 --- a/docker/blockchain/hardhat.package.json +++ b/docker/blockchain/hardhat.package.json @@ -7,4 +7,4 @@ "hardhat": "^2.6.4", "dotenv": "16.0.2" } -} \ No newline at end of file +} diff --git a/docs/NETWORK_SUPPORT.md b/docs/NETWORK_SUPPORT.md index 88e37a2556..83153cc1b3 100644 --- a/docs/NETWORK_SUPPORT.md +++ b/docs/NETWORK_SUPPORT.md @@ -15,4 +15,4 @@ The steps to add support for additional EVM networks include: 1. Deploy the [Fractal contracts](https://github.com/decent-dao/fractal-contracts) to the new EVM chain and publish the contract addresses to our NPM package. 2. Update to this new NPM package version in the `package.json` file. 3. Add a `NetworkConfig` file under `src/providers/NetworkConfig/networks/{your new network}.ts`. -4. Add the network to the `supportedChains` array in `src/providers/NetworkConfig/NetworkConfigProvider.tsx`. \ No newline at end of file +4. Add the network to the `supportedChains` array in `src/providers/NetworkConfig/NetworkConfigProvider.tsx`. diff --git a/docs/TRANSLATIONS.md b/docs/TRANSLATIONS.md index d2e5ff5895..855fb2067d 100644 --- a/docs/TRANSLATIONS.md +++ b/docs/TRANSLATIONS.md @@ -3,16 +3,18 @@ ### Submitting Language Translations - Languages we are actively seeking translations for will appear as a [Github Issue](https://github.com/decent-dao/fractal-interface/labels/translation), though we always appreciate other - submissions. If we don't currently support your language, please help us out! + submissions. If we don't currently support your language, please help us out! - Run the `1_json_to_csv.py` [Python 3](https://www.python.org/downloads/) script with your [2 character ISO language code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) to get a helpful CSV file of missing strings in your language: + ```console python3 1_json_to_csv.py ``` -- Add your translations in the last column of the output CSV file. Feel free to improve existing translations! +- Add your translations in the last column of the output CSV file. Feel free to improve existing translations! - When finished translating, run `2_csv_to_json.py` to generate the required `.json` files for the Fractal app: + ```console python3 2_csv_to_json.py ``` @@ -23,7 +25,7 @@ ### Translation Guidelines -- The app name is Fractal, which is a project of Decent DAO. Unless used as their non product specific meanings (e.g. John is a decent fellow who loves fractals.), these words should not be translated. +- The app name is Fractal, which is a project of Decent DAO. Unless used as their non product specific meanings (e.g. John is a decent fellow who loves fractals.), these words should not be translated. - Content within curly brackets (e.g. `{{count}}`), indicates a variable that is replaced within the app logic, and neither the brackets, nor the content within should be altered. _You may place these bracketed variables anywhere in your translation text_. @@ -37,4 +39,4 @@ - The words _block_, _token_, _chain_, and _transaction_ have specific meanings within the Ethereum and web3 space. Please be sure to understand their contextual meaning, if you have any doubts please raise a question in the Github issue! -- If you have ANY other questions or would like more context on specific text, feel free to reach out! \ No newline at end of file +- If you have ANY other questions or would like more context on specific text, feel free to reach out! diff --git a/playwright.config.ts b/playwright.config.ts index af0a843bfd..2ea3a75287 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -17,11 +17,14 @@ const config: PlaywrightTestConfig = { /* List reporter for getting updates */ [process.env.CI ? 'list' : 'line'], /* HTML output - unzip(open) videos and images before referencing them on the html report */ - ['html', { - /* Output HTML files to playwright-report folder */ - /* Never open a server - important for CI since it doesn't close automatically */ - open: 'never', - }], + [ + 'html', + { + /* Output HTML files to playwright-report folder */ + /* Never open a server - important for CI since it doesn't close automatically */ + open: 'never', + }, + ], ], /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { @@ -30,17 +33,21 @@ const config: PlaywrightTestConfig = { viewport: { width: 1280, height: 1020 }, ignoreHTTPSErrors: false, /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ - trace: 'on-first-retry', /* Tracks exactly what is going on in the test. Saved as a zip file in test-results folder. Can be viewed at 'trace.playwright.dev' in a browser. To use 'npx playwright test --trace on' */ + trace: + 'on-first-retry' /* Tracks exactly what is going on in the test. Saved as a zip file in test-results folder. Can be viewed at 'trace.playwright.dev' in a browser. To use 'npx playwright test --trace on' */, screenshot: 'on', launchOptions: { - slowMo: !process.env.CI ? 100 : 0, /* Adjusts tests' run speed to aid in video report visualization reports as well as mimic user input/action speed */ - devtools: false, /* When tests are ran locally with the '--headed' flag devtools will appear for debugging purposes. */ + slowMo: !process.env.CI + ? 100 + : 0 /* Adjusts tests' run speed to aid in video report visualization reports as well as mimic user input/action speed */, + devtools: + false /* When tests are ran locally with the '--headed' flag devtools will appear for debugging purposes. */, }, video: !process.env.CI ? 'on' : 'off', contextOptions: { recordVideo: { - dir: './playwright-report' - } /* Or wherever you want the videos to be saved. */ + dir: './playwright-report', + } /* Or wherever you want the videos to be saved. */, }, }, diff --git a/src/assets/css/SnapshotProposalMarkdown.css b/src/assets/css/SnapshotProposalMarkdown.css index f86c6feb0d..b4ce42947b 100644 --- a/src/assets/css/SnapshotProposalMarkdown.css +++ b/src/assets/css/SnapshotProposalMarkdown.css @@ -5,481 +5,482 @@ .markdown-body { min-width: 100%; } -.markdown-body, .markdown-body > * { - font-size: 16px; - line-height: 1.3; - word-wrap: break-word; -} - - .markdown-body::before { - display: table; - content: ''; - } - - .markdown-body blockquote { - color: var(--text-color); - border-left-color: var(--text-color); - } - - .markdown-body::after { - display: table; - clear: both; - content: ''; - } - - .markdown-body > *:first-child { - margin-top: 0 !important; - } - - .markdown-body > *:last-child { - margin-bottom: 0 !important; - } - - .markdown-body a:not([href]) { - color: inherit; - text-decoration: none; - } +.markdown-body, +.markdown-body > * { + font-size: 16px; + line-height: 1.3; + word-wrap: break-word; +} - .markdown-body a { - color: #fabd2e; - } - - .markdown-body .absent { - color: #cb2431; - } - - .markdown-body .anchor { - float: left; - padding-right: 4px; - margin-left: -20px; - line-height: 1; - } - - .markdown-body .anchor:focus { - outline: none; - } - - .markdown-body p, - .markdown-body blockquote, - .markdown-body ul, - .markdown-body ol, - .markdown-body dl, - .markdown-body table, - .markdown-body pre { - margin-top: 0; - margin-bottom: 16px; - } - - .markdown-body hr { - height: 0.25em; - padding: 0; - margin: 24px 0; - background-color: #e1e4e8; - border: 0; - } - - .markdown-body blockquote { - padding: 0 1em; - color: #6a737d; - border-left: 0.25em solid #dfe2e5; - } - - .markdown-body blockquote > :first-child { - margin-top: 0; - } - - .markdown-body blockquote > :last-child { - margin-bottom: 0; - } - - .markdown-body kbd { - display: inline-block; - padding: 3px 5px; - font-size: 11px; - line-height: 10px; - color: #444d56; - vertical-align: middle; - background-color: #fafbfc; - border: solid 1px #c6cbd1; - border-bottom-color: #959da5; - border-radius: 3px; - box-shadow: inset 0 -1px 0 #959da5; - } - - .markdown-body h1, - .markdown-body h2, - .markdown-body h3, - .markdown-body h4, - .markdown-body h5, - .markdown-body h6 { - margin-top: 24px; - margin-bottom: 16px; - font-weight: 500; - line-height: 1.4 !important; - } - - .markdown-body h1 .octicon-link, - .markdown-body h2 .octicon-link, - .markdown-body h3 .octicon-link, - .markdown-body h4 .octicon-link, - .markdown-body h5 .octicon-link, - .markdown-body h6 .octicon-link { - color: #1b1f23; - vertical-align: middle; - visibility: hidden; - } - - .markdown-body h1:hover .anchor, - .markdown-body h2:hover .anchor, - .markdown-body h3:hover .anchor, - .markdown-body h4:hover .anchor, - .markdown-body h5:hover .anchor, - .markdown-body h6:hover .anchor { - text-decoration: none; - } - - .markdown-body h1:hover .anchor .octicon-link, - .markdown-body h2:hover .anchor .octicon-link, - .markdown-body h3:hover .anchor .octicon-link, - .markdown-body h4:hover .anchor .octicon-link, - .markdown-body h5:hover .anchor .octicon-link, - .markdown-body h6:hover .anchor .octicon-link { - visibility: visible; - } - - .markdown-body h1 tt, - .markdown-body h1 code, - .markdown-body h2 tt, - .markdown-body h2 code, - .markdown-body h3 tt, - .markdown-body h3 code, - .markdown-body h4 tt, - .markdown-body h4 code, - .markdown-body h5 tt, - .markdown-body h5 code, - .markdown-body h6 tt, - .markdown-body h6 code { - font-size: inherit; - } - - .markdown-body h1 { - font-size: 1.5em; - } - - .markdown-body h2 { - font-size: 1.25em; - } - - .markdown-body h3 { - font-size: 1em; - } - - .markdown-body h4 { - font-size: 0.875em; - } - - .markdown-body h5 { - font-size: 0.85em; - } - - .markdown-body h6 { - font-size: 0.8em; - } - - .markdown-body ul, - .markdown-body ol { - padding-left: 2em; - } - - .markdown-body ul.no-list, - .markdown-body ol.no-list { - padding: 0; - list-style-type: none; - } - - .markdown-body ul { - list-style-type: disc; - } - - .markdown-body ol { - list-style-type: decimal; - } - - .markdown-body ul ul, - .markdown-body ul ol, - .markdown-body ol ol, - .markdown-body ol ul { - margin-top: 0; - margin-bottom: 0; - } - - .markdown-body li { - word-wrap: break-all; - } - - .markdown-body li > p { - margin-top: 16px; - } - - .markdown-body li + li { - margin-top: 0.25em; - } - - .markdown-body dl { - padding: 0; - } - - .markdown-body dl dt { - padding: 0; - margin-top: 16px; - font-size: 1em; - font-style: italic; - font-weight: 500; - } - - .markdown-body dl dd { - padding: 0 16px; - margin-bottom: 16px; - } - - .markdown-body table { - display: block; - width: 100%; - overflow: auto; - } - - .markdown-body table th { - font-weight: 500; - } - - .markdown-body table th, - .markdown-body table td { - padding: 6px 13px; - border: 1px solid var(--border-color); - } - - .markdown-body table thead tr, - .markdown-body table tbody tr:nth-child(2n) { - background-color: var(--bg-color); - border-top: 1px solid #c6cbd1; - } - - .markdown-body table tbody tr { - background-color: var(--bg-color); - } - - .markdown-body table img { - background-color: transparent; - } - - .markdown-body img { - max-width: 100%; - box-sizing: content-box; - background-color: #fff; - cursor: pointer; - } - - .markdown-body img[align='right'] { - padding-left: 20px; - } - - .markdown-body img[align='left'] { - padding-right: 20px; - } - - .markdown-body .emoji { - max-width: none; - vertical-align: text-top; - background-color: transparent; - } - - .markdown-body span.frame { - display: block; - overflow: hidden; - } - - .markdown-body span.frame > span { - display: block; - float: left; - width: auto; - padding: 7px; - margin: 13px 0 0; - overflow: hidden; - border: 1px solid #dfe2e5; - } - - .markdown-body span.frame span img { - display: block; - float: left; - } - - .markdown-body span.frame span span { - display: block; - padding: 5px 0 0; - clear: both; - color: #24292e; - } - - .markdown-body span.align-center { - display: block; - overflow: hidden; - clear: both; - } - - .markdown-body span.align-center > span { - display: block; - margin: 13px auto 0; - overflow: hidden; - text-align: center; - } - - .markdown-body span.align-center span img { - margin: 0 auto; - text-align: center; - } - - .markdown-body span.align-right { - display: block; - overflow: hidden; - clear: both; - } - - .markdown-body span.align-right > span { - display: block; - margin: 13px 0 0; - overflow: hidden; - text-align: right; - } - - .markdown-body span.align-right span img { - margin: 0; - text-align: right; - } - - .markdown-body span.float-left { - display: block; - float: left; - margin-right: 13px; - overflow: hidden; - } - - .markdown-body span.float-left span { - margin: 13px 0 0; - } - - .markdown-body span.float-right { - display: block; - float: right; - margin-left: 13px; - overflow: hidden; - } - - .markdown-body span.float-right > span { - display: block; - margin: 13px auto 0; - overflow: hidden; - text-align: right; - } - - .markdown-body code, - .markdown-body tt { - padding: 0.2em 0.4em; - margin: 0; - background-color: rgba(27, 31, 35, 0.05); - border-radius: 3px; - font-size: 16px; - } - - .markdown-body code br, - .markdown-body tt br { - display: none; - } - - .markdown-body del code { - text-decoration: inherit; - } - - .markdown-body pre { - font-size: 16px; - word-wrap: normal; - } - - .markdown-body pre { - color: var(--link-color); - background-color: var(--border-color); - position: relative; - } - - .markdown-body pre > code { - .copy { - font-size: 24px; - line-height: 24px; - position: absolute; - right: 1.2rem; - top: 1.2rem; - } - } - - .markdown-body pre > code { - padding: 0; - margin: 0; - word-break: normal; - white-space: pre; - background: transparent; - border: 0; - } - - .markdown-body .highlight { - margin-bottom: 16px; - } - - .markdown-body .highlight pre { - margin-bottom: 0; - word-break: normal; - } - - .markdown-body .highlight pre, - .markdown-body pre { - padding: 16px; - overflow: auto; - } - - .markdown-body pre code, - .markdown-body pre tt { - display: inline; - max-width: auto; - padding: 0; - margin: 0; - overflow: visible; - line-height: inherit; - word-wrap: normal; - background-color: transparent; - border: 0; - } - - .markdown-body .csv-data td, - .markdown-body .csv-data th { - padding: 5px; - overflow: hidden; - font-size: 12px; - line-height: 1; - text-align: left; - white-space: nowrap; - } - - .markdown-body .csv-data .blob-num { - padding: 10px 8px 9px; - text-align: right; - background: #fff; - border: 0; - } - - .markdown-body .csv-data tr { - border-top: 0; +.markdown-body::before { + display: table; + content: ''; +} + +.markdown-body blockquote { + color: var(--text-color); + border-left-color: var(--text-color); +} + +.markdown-body::after { + display: table; + clear: both; + content: ''; +} + +.markdown-body > *:first-child { + margin-top: 0 !important; +} + +.markdown-body > *:last-child { + margin-bottom: 0 !important; +} + +.markdown-body a:not([href]) { + color: inherit; + text-decoration: none; +} + +.markdown-body a { + color: #fabd2e; +} + +.markdown-body .absent { + color: #cb2431; +} + +.markdown-body .anchor { + float: left; + padding-right: 4px; + margin-left: -20px; + line-height: 1; +} + +.markdown-body .anchor:focus { + outline: none; +} + +.markdown-body p, +.markdown-body blockquote, +.markdown-body ul, +.markdown-body ol, +.markdown-body dl, +.markdown-body table, +.markdown-body pre { + margin-top: 0; + margin-bottom: 16px; +} + +.markdown-body hr { + height: 0.25em; + padding: 0; + margin: 24px 0; + background-color: #e1e4e8; + border: 0; +} + +.markdown-body blockquote { + padding: 0 1em; + color: #6a737d; + border-left: 0.25em solid #dfe2e5; +} + +.markdown-body blockquote > :first-child { + margin-top: 0; +} + +.markdown-body blockquote > :last-child { + margin-bottom: 0; +} + +.markdown-body kbd { + display: inline-block; + padding: 3px 5px; + font-size: 11px; + line-height: 10px; + color: #444d56; + vertical-align: middle; + background-color: #fafbfc; + border: solid 1px #c6cbd1; + border-bottom-color: #959da5; + border-radius: 3px; + box-shadow: inset 0 -1px 0 #959da5; +} + +.markdown-body h1, +.markdown-body h2, +.markdown-body h3, +.markdown-body h4, +.markdown-body h5, +.markdown-body h6 { + margin-top: 24px; + margin-bottom: 16px; + font-weight: 500; + line-height: 1.4 !important; +} + +.markdown-body h1 .octicon-link, +.markdown-body h2 .octicon-link, +.markdown-body h3 .octicon-link, +.markdown-body h4 .octicon-link, +.markdown-body h5 .octicon-link, +.markdown-body h6 .octicon-link { + color: #1b1f23; + vertical-align: middle; + visibility: hidden; +} + +.markdown-body h1:hover .anchor, +.markdown-body h2:hover .anchor, +.markdown-body h3:hover .anchor, +.markdown-body h4:hover .anchor, +.markdown-body h5:hover .anchor, +.markdown-body h6:hover .anchor { + text-decoration: none; +} + +.markdown-body h1:hover .anchor .octicon-link, +.markdown-body h2:hover .anchor .octicon-link, +.markdown-body h3:hover .anchor .octicon-link, +.markdown-body h4:hover .anchor .octicon-link, +.markdown-body h5:hover .anchor .octicon-link, +.markdown-body h6:hover .anchor .octicon-link { + visibility: visible; +} + +.markdown-body h1 tt, +.markdown-body h1 code, +.markdown-body h2 tt, +.markdown-body h2 code, +.markdown-body h3 tt, +.markdown-body h3 code, +.markdown-body h4 tt, +.markdown-body h4 code, +.markdown-body h5 tt, +.markdown-body h5 code, +.markdown-body h6 tt, +.markdown-body h6 code { + font-size: inherit; +} + +.markdown-body h1 { + font-size: 1.5em; +} + +.markdown-body h2 { + font-size: 1.25em; +} + +.markdown-body h3 { + font-size: 1em; +} + +.markdown-body h4 { + font-size: 0.875em; +} + +.markdown-body h5 { + font-size: 0.85em; +} + +.markdown-body h6 { + font-size: 0.8em; +} + +.markdown-body ul, +.markdown-body ol { + padding-left: 2em; +} + +.markdown-body ul.no-list, +.markdown-body ol.no-list { + padding: 0; + list-style-type: none; +} + +.markdown-body ul { + list-style-type: disc; +} + +.markdown-body ol { + list-style-type: decimal; +} + +.markdown-body ul ul, +.markdown-body ul ol, +.markdown-body ol ol, +.markdown-body ol ul { + margin-top: 0; + margin-bottom: 0; +} + +.markdown-body li { + word-wrap: break-all; +} + +.markdown-body li > p { + margin-top: 16px; +} + +.markdown-body li + li { + margin-top: 0.25em; +} + +.markdown-body dl { + padding: 0; +} + +.markdown-body dl dt { + padding: 0; + margin-top: 16px; + font-size: 1em; + font-style: italic; + font-weight: 500; +} + +.markdown-body dl dd { + padding: 0 16px; + margin-bottom: 16px; +} + +.markdown-body table { + display: block; + width: 100%; + overflow: auto; +} + +.markdown-body table th { + font-weight: 500; +} + +.markdown-body table th, +.markdown-body table td { + padding: 6px 13px; + border: 1px solid var(--border-color); +} + +.markdown-body table thead tr, +.markdown-body table tbody tr:nth-child(2n) { + background-color: var(--bg-color); + border-top: 1px solid #c6cbd1; +} + +.markdown-body table tbody tr { + background-color: var(--bg-color); +} + +.markdown-body table img { + background-color: transparent; +} + +.markdown-body img { + max-width: 100%; + box-sizing: content-box; + background-color: #fff; + cursor: pointer; +} + +.markdown-body img[align='right'] { + padding-left: 20px; +} + +.markdown-body img[align='left'] { + padding-right: 20px; +} + +.markdown-body .emoji { + max-width: none; + vertical-align: text-top; + background-color: transparent; +} + +.markdown-body span.frame { + display: block; + overflow: hidden; +} + +.markdown-body span.frame > span { + display: block; + float: left; + width: auto; + padding: 7px; + margin: 13px 0 0; + overflow: hidden; + border: 1px solid #dfe2e5; +} + +.markdown-body span.frame span img { + display: block; + float: left; +} + +.markdown-body span.frame span span { + display: block; + padding: 5px 0 0; + clear: both; + color: #24292e; +} + +.markdown-body span.align-center { + display: block; + overflow: hidden; + clear: both; +} + +.markdown-body span.align-center > span { + display: block; + margin: 13px auto 0; + overflow: hidden; + text-align: center; +} + +.markdown-body span.align-center span img { + margin: 0 auto; + text-align: center; +} + +.markdown-body span.align-right { + display: block; + overflow: hidden; + clear: both; +} + +.markdown-body span.align-right > span { + display: block; + margin: 13px 0 0; + overflow: hidden; + text-align: right; +} + +.markdown-body span.align-right span img { + margin: 0; + text-align: right; +} + +.markdown-body span.float-left { + display: block; + float: left; + margin-right: 13px; + overflow: hidden; +} + +.markdown-body span.float-left span { + margin: 13px 0 0; +} + +.markdown-body span.float-right { + display: block; + float: right; + margin-left: 13px; + overflow: hidden; +} + +.markdown-body span.float-right > span { + display: block; + margin: 13px auto 0; + overflow: hidden; + text-align: right; +} + +.markdown-body code, +.markdown-body tt { + padding: 0.2em 0.4em; + margin: 0; + background-color: rgba(27, 31, 35, 0.05); + border-radius: 3px; + font-size: 16px; +} + +.markdown-body code br, +.markdown-body tt br { + display: none; +} + +.markdown-body del code { + text-decoration: inherit; +} + +.markdown-body pre { + font-size: 16px; + word-wrap: normal; +} + +.markdown-body pre { + color: var(--link-color); + background-color: var(--border-color); + position: relative; +} + +.markdown-body pre > code { + .copy { + font-size: 24px; + line-height: 24px; + position: absolute; + right: 1.2rem; + top: 1.2rem; } - - .markdown-body .csv-data th { - font-weight: 500; - background: #f6f8fa; - border-top: 0; - } \ No newline at end of file +} + +.markdown-body pre > code { + padding: 0; + margin: 0; + word-break: normal; + white-space: pre; + background: transparent; + border: 0; +} + +.markdown-body .highlight { + margin-bottom: 16px; +} + +.markdown-body .highlight pre { + margin-bottom: 0; + word-break: normal; +} + +.markdown-body .highlight pre, +.markdown-body pre { + padding: 16px; + overflow: auto; +} + +.markdown-body pre code, +.markdown-body pre tt { + display: inline; + max-width: auto; + padding: 0; + margin: 0; + overflow: visible; + line-height: inherit; + word-wrap: normal; + background-color: transparent; + border: 0; +} + +.markdown-body .csv-data td, +.markdown-body .csv-data th { + padding: 5px; + overflow: hidden; + font-size: 12px; + line-height: 1; + text-align: left; + white-space: nowrap; +} + +.markdown-body .csv-data .blob-num { + padding: 10px 8px 9px; + text-align: right; + background: #fff; + border: 0; +} + +.markdown-body .csv-data tr { + border-top: 0; +} + +.markdown-body .csv-data th { + font-weight: 500; + background: #f6f8fa; + border-top: 0; +} diff --git a/src/components/Activity/ActivityDescriptionTreasury.tsx b/src/components/Activity/ActivityDescriptionTreasury.tsx index 6f167a71f6..9866ab31d7 100644 --- a/src/components/Activity/ActivityDescriptionTreasury.tsx +++ b/src/components/Activity/ActivityDescriptionTreasury.tsx @@ -15,16 +15,16 @@ export function ActivityDescriptionTreasury({ activity }: { activity: Activity } const transferTypeStr = !hasTransfers ? undefined : !isGovernanceActivity && isDeposit - ? t('received') - : isGovernanceActivity && !isDeposit - ? t('transfer') - : t('send'); + ? t('received') + : isGovernanceActivity && !isDeposit + ? t('transfer') + : t('send'); const transferDirStr = !hasTransfers ? undefined : treasuryActivity.isDeposit && activity.eventType !== ActivityEventType.Governance - ? t('from') - : t('to'); + ? t('from') + : t('to'); return ( <> diff --git a/src/components/Activity/ActivityGovernance.tsx b/src/components/Activity/ActivityGovernance.tsx index 9e194388bc..1d166b4e6d 100644 --- a/src/components/Activity/ActivityGovernance.tsx +++ b/src/components/Activity/ActivityGovernance.tsx @@ -25,7 +25,7 @@ export function ActivityGovernance({ activity }: { activity: FractalProposal }) ? activity.transaction?.to === safe?.address ? 'received' : 'sent' - : 'created' + : 'created', ); const { diff --git a/src/components/Activity/ActivityTreasury.tsx b/src/components/Activity/ActivityTreasury.tsx index 3408a2bb00..390ad80293 100644 --- a/src/components/Activity/ActivityTreasury.tsx +++ b/src/components/Activity/ActivityTreasury.tsx @@ -19,7 +19,7 @@ export function ActivityTreasury({ activity }: { activity: TreasuryActivity }) { ? activity.transaction?.to === daoAddress ? 'received' : 'sent' - : 'created' + : 'created', ); return ( diff --git a/src/components/Activity/FreezeButton.tsx b/src/components/Activity/FreezeButton.tsx index 46fa810c79..73104d9391 100644 --- a/src/components/Activity/FreezeButton.tsx +++ b/src/components/Activity/FreezeButton.tsx @@ -27,7 +27,7 @@ export function FreezeButton() { isDisabled={disabled} > {t( - !userHasVotes ? 'noVotesButton' : userHasFreezeVoted ? 'freezeVotedButton' : 'freezeButton' + !userHasVotes ? 'noVotesButton' : userHasFreezeVoted ? 'freezeVotedButton' : 'freezeButton', )} ); diff --git a/src/components/CreateProposalTemplate/ProposalTemplateTransaction.tsx b/src/components/CreateProposalTemplate/ProposalTemplateTransaction.tsx index cb521a251a..2fe576db21 100644 --- a/src/components/CreateProposalTemplate/ProposalTemplateTransaction.tsx +++ b/src/components/CreateProposalTemplate/ProposalTemplateTransaction.tsx @@ -35,10 +35,10 @@ export default function ProposalTemplateTransaction({ signature: `${abiInput.type} ${abiInput.name}`, label: '', value: '', - })) + })), ); }, - [setFieldValue, transactionIndex] + [setFieldValue, transactionIndex], ); return ( @@ -151,7 +151,7 @@ export default function ProposalTemplateTransaction({ onChange={e => setFieldValue( `transactions.${transactionIndex}.parameters.${i}.signature`, - e.target.value + e.target.value, ) } disabled={transactionPending} @@ -178,7 +178,7 @@ export default function ProposalTemplateTransaction({ onChange={e => setFieldValue( `transactions.${transactionIndex}.parameters.${i}.label`, - e.target.value + e.target.value, ) } disabled={transactionPending || !!parameter.value} @@ -206,7 +206,7 @@ export default function ProposalTemplateTransaction({ onChange={e => setFieldValue( `transactions.${transactionIndex}.parameters.${i}.value`, - e.target.value + e.target.value, ) } disabled={transactionPending || !!parameter.label} @@ -245,8 +245,8 @@ export default function ProposalTemplateTransaction({ setFieldValue( `transactions.${transactionIndex}.parameters`, transaction.parameters.filter( - (parameterToRemove, parameterToRemoveIndex) => parameterToRemoveIndex !== i - ) + (parameterToRemove, parameterToRemoveIndex) => parameterToRemoveIndex !== i, + ), ) } > diff --git a/src/components/CreateProposalTemplate/ProposalTemplateTransactions.tsx b/src/components/CreateProposalTemplate/ProposalTemplateTransactions.tsx index 2b83e6b4ce..1b4c5b4849 100644 --- a/src/components/CreateProposalTemplate/ProposalTemplateTransactions.tsx +++ b/src/components/CreateProposalTemplate/ProposalTemplateTransactions.tsx @@ -36,7 +36,7 @@ export default function ProposalTemplateTransactions({ const removeTransaction = (transactionIndex: number) => { setFieldValue( 'transactions', - transactions.filter((tx, i) => i !== transactionIndex) + transactions.filter((tx, i) => i !== transactionIndex), ); }; return ( diff --git a/src/components/CreateProposalTemplate/ProposalTemplateTransactionsForm.tsx b/src/components/CreateProposalTemplate/ProposalTemplateTransactionsForm.tsx index 8f795a0d76..080570b312 100644 --- a/src/components/CreateProposalTemplate/ProposalTemplateTransactionsForm.tsx +++ b/src/components/CreateProposalTemplate/ProposalTemplateTransactionsForm.tsx @@ -19,7 +19,7 @@ interface ProposalTemplateTransactionsFormProps extends FormikProps push( - !isSubDAO || !daoAddress ? BASE_ROUTES.landing : DAO_ROUTES.dao.relative(daoAddress) + !isSubDAO || !daoAddress ? BASE_ROUTES.landing : DAO_ROUTES.dao.relative(daoAddress), ) } /> diff --git a/src/components/DaoCreator/formComponents/AzoriusNFTDetails.tsx b/src/components/DaoCreator/formComponents/AzoriusNFTDetails.tsx index 2f4e3b522a..fb4b62ec8c 100644 --- a/src/components/DaoCreator/formComponents/AzoriusNFTDetails.tsx +++ b/src/components/DaoCreator/formComponents/AzoriusNFTDetails.tsx @@ -35,7 +35,7 @@ export default function AzoriusNFTDetails(props: ICreationStepProps) { const handleRemoveNFT = (indexToRemove: number) => { setFieldValue( 'erc721Token.nfts', - values.erc721Token.nfts.filter((_, i) => i !== indexToRemove) + values.erc721Token.nfts.filter((_, i) => i !== indexToRemove), ); }; diff --git a/src/components/DaoCreator/formComponents/AzoriusTokenDetails.tsx b/src/components/DaoCreator/formComponents/AzoriusTokenDetails.tsx index 6ebd0fc126..73e0355548 100644 --- a/src/components/DaoCreator/formComponents/AzoriusTokenDetails.tsx +++ b/src/components/DaoCreator/formComponents/AzoriusTokenDetails.tsx @@ -62,7 +62,7 @@ export function AzoriusTokenDetails(props: ICreationStepProps) { value: totalSupply, bigNumberValue: BigNumber.from(totalSupply), }, - true + true, ); if (!isVotesToken) { setFieldValue('erc20Token.tokenName', 'Wrapped ' + name, true); diff --git a/src/components/DaoCreator/formComponents/EstablishEssentials.tsx b/src/components/DaoCreator/formComponents/EstablishEssentials.tsx index 335a80e2f3..89de33253d 100644 --- a/src/components/DaoCreator/formComponents/EstablishEssentials.tsx +++ b/src/components/DaoCreator/formComponents/EstablishEssentials.tsx @@ -187,8 +187,8 @@ export function EstablishEssentials(props: ICreationStepProps) { values.essentials.governance === GovernanceType.MULTISIG ? CreatorSteps.MULTISIG_DETAILS : values.azorius.votingStrategyType === VotingStrategyType.LINEAR_ERC20 - ? CreatorSteps.ERC20_DETAILS - : CreatorSteps.ERC721_DETAILS + ? CreatorSteps.ERC20_DETAILS + : CreatorSteps.ERC721_DETAILS } /> diff --git a/src/components/DaoCreator/formComponents/GuardDetails.tsx b/src/components/DaoCreator/formComponents/GuardDetails.tsx index 68ec41ac56..2091fa8d8c 100644 --- a/src/components/DaoCreator/formComponents/GuardDetails.tsx +++ b/src/components/DaoCreator/formComponents/GuardDetails.tsx @@ -47,7 +47,7 @@ function GuardDetails(props: ICreationStepProps) { (nonce?: number) => { setFieldValue('multisig.customNonce', nonce ? parseInt(nonce.toString(), 10) : undefined); }, - [setFieldValue] + [setFieldValue], ); useEffect(() => { @@ -76,14 +76,14 @@ function GuardDetails(props: ICreationStepProps) { if (azoriusGovernance.votesToken) { const normalized = ethers.utils.formatUnits( azoriusGovernance.votesToken.totalSupply, - azoriusGovernance.votesToken.decimals + azoriusGovernance.votesToken.decimals, ); parentVotes = BigNumber.from(normalized.substring(0, normalized.indexOf('.'))); } else if (azoriusGovernance.erc721Tokens) { parentVotes = azoriusGovernance.erc721Tokens!.reduce( (prev, curr) => curr.votingWeight.mul(curr.totalSupply || 1).add(prev), - BigNumber.from(0) + BigNumber.from(0), ); } else { parentVotes = BigNumber.from(1); diff --git a/src/components/DaoCreator/hooks/usePrepareFormData.ts b/src/components/DaoCreator/hooks/usePrepareFormData.ts index 9e283280ed..3d7529bc0e 100644 --- a/src/components/DaoCreator/hooks/usePrepareFormData.ts +++ b/src/components/DaoCreator/hooks/usePrepareFormData.ts @@ -23,29 +23,29 @@ export function usePrepareFormData() { // Helper function to prepare freezeGuard data const prepareFreezeGuardData = useCallback( async ( - freezeGuard: DAOFreezeGuardConfig + freezeGuard: DAOFreezeGuardConfig, ): Promise => { return { executionPeriod: await getEstimatedNumberOfBlocks( freezeGuard.executionPeriod.bigNumberValue!, - provider + provider, ), timelockPeriod: await getEstimatedNumberOfBlocks( freezeGuard.timelockPeriod.bigNumberValue!, - provider + provider, ), freezeVotesThreshold: freezeGuard.freezeVotesThreshold.bigNumberValue!, freezeProposalPeriod: await getEstimatedNumberOfBlocks( freezeGuard.freezeProposalPeriod.bigNumberValue!, - provider + provider, ), freezePeriod: await getEstimatedNumberOfBlocks( freezeGuard.freezePeriod.bigNumberValue!, - provider + provider, ), }; }, - [provider] + [provider], ); const checkVotesToken = useCallback( @@ -59,7 +59,7 @@ export function usePrepareFormData() { return false; } }, - [provider] + [provider], ); const prepareMultisigFormData = useCallback( @@ -75,7 +75,7 @@ export function usePrepareFormData() { return resolvedAddress; } return inputValue; - }) + }), ); let freezeGuardData: Partial = {}; if (freezeGuard) { @@ -87,7 +87,7 @@ export function usePrepareFormData() { ...rest, }; }, - [signer, prepareFreezeGuardData] + [signer, prepareFreezeGuardData], ); const prepareAzoriusERC20FormData = useCallback( @@ -111,7 +111,7 @@ export function usePrepareFormData() { address = await signer!.resolveName(allocation.address); } return { amount: allocation.amount.bigNumberValue!, address: address }; - }) + }), ); let freezeGuardData: Partial = {}; if (freezeGuard) { @@ -130,7 +130,7 @@ export function usePrepareFormData() { timelock: await getEstimatedNumberOfBlocks(timelock.bigNumberValue!, provider), executionPeriod: await getEstimatedNumberOfBlocks( executionPeriod.bigNumberValue!, - provider + provider, ), votingPeriod: await getEstimatedNumberOfBlocks(votingPeriod.bigNumberValue!, provider), tokenAllocations: resolvedTokenAllocations, @@ -142,7 +142,7 @@ export function usePrepareFormData() { ...rest, }; }, - [signer, checkVotesToken, provider, prepareFreezeGuardData] + [signer, checkVotesToken, provider, prepareFreezeGuardData], ); const prepareAzoriusERC721FormData = useCallback( @@ -172,7 +172,7 @@ export function usePrepareFormData() { tokenAddress: address, tokenWeight: nft.tokenWeight.bigNumberValue!, }; - }) + }), ); return { @@ -180,7 +180,7 @@ export function usePrepareFormData() { timelock: await getEstimatedNumberOfBlocks(timelock.bigNumberValue!, provider), executionPeriod: await getEstimatedNumberOfBlocks( executionPeriod.bigNumberValue!, - provider + provider, ), votingPeriod: await getEstimatedNumberOfBlocks(votingPeriod.bigNumberValue!, provider), nfts: resolvedNFTs, @@ -189,7 +189,7 @@ export function usePrepareFormData() { ...rest, }; }, - [prepareFreezeGuardData, provider, signer] + [prepareFreezeGuardData, provider, signer], ); return { prepareMultisigFormData, diff --git a/src/components/Proposals/MultisigProposalDetails/TxActions.tsx b/src/components/Proposals/MultisigProposalDetails/TxActions.tsx index 52df951948..1c1d6a4297 100644 --- a/src/components/Proposals/MultisigProposalDetails/TxActions.tsx +++ b/src/components/Proposals/MultisigProposalDetails/TxActions.tsx @@ -61,7 +61,7 @@ export function TxActions({ (signerOrProvider as Signer & TypedDataSigner)._signTypedData( { verifyingContract: safe.address, chainId: chainId }, EIP712_SAFE_TX_TYPE, - safeTx + safeTx, ), failedMessage: t('failedSign'), pendingMessage: t('pendingSign'), @@ -88,7 +88,7 @@ export function TxActions({ multisigTx.confirmations.map(confirmation => ({ signer: confirmation.owner, data: confirmation.signature, - })) + })), ); contractCall({ contractFn: () => @@ -103,7 +103,7 @@ export function TxActions({ safeTx.gasToken, safeTx.refundReceiver, signatures, - safeTx.nonce + safeTx.nonce, ), failedMessage: t('failedExecute', { ns: 'transaction' }), pendingMessage: t('pendingExecute', { ns: 'transaction' }), @@ -130,7 +130,7 @@ export function TxActions({ multisigTx.confirmations.map(confirmation => ({ signer: confirmation.owner, data: confirmation.signature, - })) + })), ); contractCall({ contractFn: () => @@ -144,7 +144,7 @@ export function TxActions({ safeTx.gasPrice, safeTx.gasToken, safeTx.refundReceiver, - signatures + signatures, ), failedMessage: t('failedExecute', { ns: 'transaction' }), pendingMessage: t('pendingExecute', { ns: 'transaction' }), diff --git a/src/components/Proposals/ProposalActions/CastVote.tsx b/src/components/Proposals/ProposalActions/CastVote.tsx index e454872c5b..635ce912b2 100644 --- a/src/components/Proposals/ProposalActions/CastVote.tsx +++ b/src/components/Proposals/ProposalActions/CastVote.tsx @@ -59,7 +59,7 @@ function Vote({ !isSnapshotProposal && isCurrentBlockLoaded && currentBlockNumber && - azoriusProposal.startBlock.gte(currentBlockNumber) + azoriusProposal.startBlock.gte(currentBlockNumber), ); const disabled = @@ -160,8 +160,8 @@ function Vote({ proposalStartBlockNotFinalized ? t('proposalStartBlockNotFinalized', { ns: 'proposal' }) : hasVoted - ? t('currentUserAlreadyVoted', { ns: 'proposal' }) - : undefined + ? t('currentUserAlreadyVoted', { ns: 'proposal' }) + : undefined } > <> diff --git a/src/components/Proposals/ProposalActions/ProposalAction.tsx b/src/components/Proposals/ProposalActions/ProposalAction.tsx index 1befef8f54..1779600201 100644 --- a/src/components/Proposals/ProposalActions/ProposalAction.tsx +++ b/src/components/Proposals/ProposalActions/ProposalAction.tsx @@ -58,7 +58,7 @@ export function ProposalAction({ const isActiveProposal = useMemo( () => proposal.state === FractalProposalState.ACTIVE, - [proposal.state] + [proposal.state], ); const showActionButton = diff --git a/src/components/Proposals/ProposalSummary.tsx b/src/components/Proposals/ProposalSummary.tsx index bfc3dff82f..4177de8040 100644 --- a/src/components/Proposals/ProposalSummary.tsx +++ b/src/components/Proposals/ProposalSummary.tsx @@ -47,13 +47,13 @@ export default function ProposalSummary({ () => erc721Tokens?.reduce( (prev, curr) => prev.add(curr.totalSupply?.mul(curr.votingWeight) || BigNumber.from(0)), - BigNumber.from(0) + BigNumber.from(0), ), - [erc721Tokens] + [erc721Tokens], ); const votesTokenDecimalsDenominator = useMemo( () => BigNumber.from(10).pow(votesToken?.decimals || 0), - [votesToken?.decimals] + [votesToken?.decimals], ); const [showVotingPower, setShowVotingPower] = useState(false); @@ -64,7 +64,7 @@ export default function ProposalSummary({ if (tokenContract && address) { const pastVotingWeight = await tokenContract.asProvider.getPastVotes(address, startBlock); setProposalsERC20VotingWeight( - pastVotingWeight.div(votesTokenDecimalsDenominator).toString() + pastVotingWeight.div(votesTokenDecimalsDenominator).toString(), ); } } @@ -89,13 +89,13 @@ export default function ProposalSummary({ votesToken && isERC20 ? votingStrategy.quorumPercentage!.value.toNumber() : isERC721 - ? votingStrategy.quorumThreshold!.value.toNumber() - : 1; + ? votingStrategy.quorumThreshold!.value.toNumber() + : 1; const reachedQuorum = isERC721 ? totalVotesCasted.sub(no).toString() : votesToken - ? totalVotesCasted.sub(no).div(votesTokenDecimalsDenominator).toString() - : '0'; + ? totalVotesCasted.sub(no).div(votesTokenDecimalsDenominator).toString() + : '0'; const totalQuorum = isERC721 ? strategyQuorum.toString() : votesToken?.totalSupply @@ -207,14 +207,14 @@ export default function ProposalSummary({ isERC20 ? 'proposalSupportERC20SummaryHelper' : isERC721 - ? 'proposalSupportERC721SummaryHelper' - : '', + ? 'proposalSupportERC721SummaryHelper' + : '', { quorum: strategyQuorum, total: isERC721 ? totalVotingWeight?.toString() : votesToken?.totalSupply.div(votesTokenDecimalsDenominator).toString(), - } + }, )} reachedQuorum={reachedQuorum} totalQuorum={totalQuorum} diff --git a/src/components/Proposals/ProposalVotes/context/VoteContext.tsx b/src/components/Proposals/ProposalVotes/context/VoteContext.tsx index 2acb83b1c1..aeab0cee8a 100644 --- a/src/components/Proposals/ProposalVotes/context/VoteContext.tsx +++ b/src/components/Proposals/ProposalVotes/context/VoteContext.tsx @@ -58,7 +58,7 @@ export function VoteContextProvider({ const { remainingTokenIds, getUserERC721VotingTokens } = useUserERC721VotingTokens( proposal.proposalId, undefined, - true + true, ); const { isSnapshotProposal } = useSnapshotProposal(proposal); @@ -67,7 +67,7 @@ export function VoteContextProvider({ if (isSnapshotProposal) { setHasVoted( !!extendedSnapshotProposal && - !!extendedSnapshotProposal.votes.find(vote => vote.voter === user.address) + !!extendedSnapshotProposal.votes.find(vote => vote.voter === user.address), ); } else if (dao?.isAzorius) { const azoriusProposal = proposal as AzoriusProposal; @@ -77,7 +77,7 @@ export function VoteContextProvider({ } else { const safeProposal = proposal as MultisigProposal; setHasVoted( - !!safeProposal.confirmations.find(confirmation => confirmation.owner === user.address) + !!safeProposal.confirmations.find(confirmation => confirmation.owner === user.address), ); } setHasVotedLoading(false); @@ -96,7 +96,7 @@ export function VoteContextProvider({ ( await ozLinearVotingContract!.asProvider.getVotingWeight( user.address, - proposal.proposalId + proposal.proposalId, ) )?.gt(0) && !hasVoted; } else if (type === GovernanceType.AZORIUS_ERC721) { @@ -128,7 +128,7 @@ export function VoteContextProvider({ loadVotingWeight, ozLinearVotingContract, proposal?.proposalId, - ] + ], ); useEffect(() => { getCanVote(); diff --git a/src/components/Proposals/ProposalVotes/index.tsx b/src/components/Proposals/ProposalVotes/index.tsx index 1b5be572ff..b0ca2b9f3c 100644 --- a/src/components/Proposals/ProposalVotes/index.tsx +++ b/src/components/Proposals/ProposalVotes/index.tsx @@ -74,11 +74,11 @@ function ProposalVotes({ const isERC20 = useMemo( () => azoriusGovernance.type === GovernanceType.AZORIUS_ERC20, - [azoriusGovernance.type] + [azoriusGovernance.type], ); const isERC721 = useMemo( () => azoriusGovernance.type === GovernanceType.AZORIUS_ERC721, - [azoriusGovernance.type] + [azoriusGovernance.type], ); const getVotesPercentage = useCallback( @@ -88,7 +88,7 @@ function ProposalVotes({ } return voteTotal.mul(100).div(totalVotesCasted).toNumber(); }, - [totalVotesCasted] + [totalVotesCasted], ); if ((isERC20 && !azoriusGovernance.votesToken) || (isERC721 && !azoriusGovernance.erc721Tokens)) { diff --git a/src/components/Proposals/SnapshotProposalDetails/SnapshotProposalDescription.tsx b/src/components/Proposals/SnapshotProposalDetails/SnapshotProposalDescription.tsx index a45cb4a7f1..bc2ae95b47 100644 --- a/src/components/Proposals/SnapshotProposalDetails/SnapshotProposalDescription.tsx +++ b/src/components/Proposals/SnapshotProposalDetails/SnapshotProposalDescription.tsx @@ -58,7 +58,7 @@ export default function SnapshotProposalDescription({ ) { const divHeight = markdownTextContainerRef.current.scrollHeight; const lineHeight = parseInt( - document.defaultView.getComputedStyle(markdownTextContainerRef.current, null).lineHeight + document.defaultView.getComputedStyle(markdownTextContainerRef.current, null).lineHeight, ); if (isNaN(lineHeight)) { setCollapsed(false); diff --git a/src/components/Proposals/index.tsx b/src/components/Proposals/index.tsx index d755fa324c..44b5c09686 100644 --- a/src/components/Proposals/index.tsx +++ b/src/components/Proposals/index.tsx @@ -101,10 +101,10 @@ export default function Proposals() { filters.length === 1 ? t(filters[0]) : filters.length === allOptions.length - ? t('filterProposalsAllSelected') - : filters.length === 0 // No filters selected means no filtering applied - ? t('filterProposalsNoneSelected') - : t('filterProposalsNSelected', { count: filters.length }); + ? t('filterProposalsAllSelected') + : filters.length === 0 // No filters selected means no filtering applied + ? t('filterProposalsNoneSelected') + : t('filterProposalsNSelected', { count: filters.length }); return ( <> diff --git a/src/components/pages/DAOTreasury/components/Assets.tsx b/src/components/pages/DAOTreasury/components/Assets.tsx index 5df12b13a4..7f23686655 100644 --- a/src/components/pages/DAOTreasury/components/Assets.tsx +++ b/src/components/pages/DAOTreasury/components/Assets.tsx @@ -232,7 +232,7 @@ export function Assets() { // --- Lido Unstake button setup --- const stETHAsset = coinDisplay.displayData.find( - asset => asset.address === staking?.lido?.stETHContractAddress + asset => asset.address === staking?.lido?.stETHContractAddress, ); const showUnstakeButton = canUserCreateProposal && staking.lido && stETHAsset; const handleUnstakeButtonClick = () => { @@ -243,7 +243,7 @@ export function Assets() { const signerOrProvider = useSignerOrProvider(); const [isLidoClaimable, setIsLidoClaimable] = useState(false); const lidoWithdrawelNFT = assetsNonFungible.find( - asset => asset.address === staking.lido?.withdrawalQueueContractAddress + asset => asset.address === staking.lido?.withdrawalQueueContractAddress, ); const showClaimETHButton = canUserCreateProposal && staking.lido && lidoWithdrawelNFT; useEffect(() => { @@ -253,7 +253,7 @@ export function Assets() { } const withdrawalQueueContract = getWithdrawalQueueContract( staking.lido.withdrawalQueueContractAddress, - signerOrProvider + signerOrProvider, ); const claimableStatus = ( await withdrawalQueueContract.getWithdrawalStatus([lidoWithdrawelNFT!.id]) diff --git a/src/components/pages/DAOTreasury/components/Transactions.tsx b/src/components/pages/DAOTreasury/components/Transactions.tsx index e1d200d715..0f5ccd08ff 100644 --- a/src/components/pages/DAOTreasury/components/Transactions.tsx +++ b/src/components/pages/DAOTreasury/components/Transactions.tsx @@ -144,7 +144,7 @@ export function Transactions() { const displayData: TransferDisplayData[] = useFormatTransfers( transfers ? (transfers.results as AssetTransfer[]) : [], - daoAddress! + daoAddress!, ); if (!transfers || transfers.results.length === 0) return ; diff --git a/src/components/pages/DAOTreasury/hooks/useFormatTransfers.tsx b/src/components/pages/DAOTreasury/hooks/useFormatTransfers.tsx index 4af2787dc1..49bd666d08 100644 --- a/src/components/pages/DAOTreasury/hooks/useFormatTransfers.tsx +++ b/src/components/pages/DAOTreasury/hooks/useFormatTransfers.tsx @@ -23,7 +23,7 @@ export interface TransferDisplayData { export function useFormatTransfers( transfers: AssetTransfer[], - safeAddress: string + safeAddress: string, ): TransferDisplayData[] { let displayData: TransferDisplayData[] = new Array(transfers.length); const { nativeTokenSymbol, nativeTokenIcon } = useNetworkConfig(); diff --git a/src/components/pages/DAOTreasury/hooks/useSendAssets.ts b/src/components/pages/DAOTreasury/hooks/useSendAssets.ts index 2478d68ab0..1935c84bec 100644 --- a/src/components/pages/DAOTreasury/hooks/useSendAssets.ts +++ b/src/components/pages/DAOTreasury/hooks/useSendAssets.ts @@ -27,7 +27,7 @@ const useSendAssets = ({ transferAmount, false, asset?.token?.decimals, - asset?.token?.symbol + asset?.token?.symbol, ); const funcSignature = 'function transfer(address to, uint256 value)'; diff --git a/src/components/pages/DAOTreasury/hooks/useTreasuryTotalUSD.tsx b/src/components/pages/DAOTreasury/hooks/useTreasuryTotalUSD.tsx index f7e0b711fa..c00c09d0c3 100644 --- a/src/components/pages/DAOTreasury/hooks/useTreasuryTotalUSD.tsx +++ b/src/components/pages/DAOTreasury/hooks/useTreasuryTotalUSD.tsx @@ -8,7 +8,7 @@ export function useTreasuryTotalUSD(): string { } = useFractal(); return useMemo(() => { return formatUSD( - assetsFungible.reduce((prev, asset) => (prev += Number(asset.fiatBalance)), 0) + assetsFungible.reduce((prev, asset) => (prev += Number(asset.fiatBalance)), 0), ); }, [assetsFungible]); } diff --git a/src/components/pages/DaoDashboard/Activities/ActivityFreeze.tsx b/src/components/pages/DaoDashboard/Activities/ActivityFreeze.tsx index 3b0e98d5ab..b76fe5a3ec 100644 --- a/src/components/pages/DaoDashboard/Activities/ActivityFreeze.tsx +++ b/src/components/pages/DaoDashboard/Activities/ActivityFreeze.tsx @@ -33,10 +33,10 @@ export function ActivityFreeze() { } = useFractal(); const { t } = useTranslation('dashboard'); const freezeProposalDeadlineDate = new Date( - freezeProposalCreatedTime!.add(freezeProposalPeriod!).mul(1000).toNumber() + freezeProposalCreatedTime!.add(freezeProposalPeriod!).mul(1000).toNumber(), ); const freezeDeadlineDate = new Date( - freezeProposalCreatedTime!.add(freezePeriod!).mul(1000).toNumber() + freezeProposalCreatedTime!.add(freezePeriod!).mul(1000).toNumber(), ); const now = new Date(); diff --git a/src/components/pages/DaoDashboard/ERC20Claim.tsx b/src/components/pages/DaoDashboard/ERC20Claim.tsx index 1fee39e608..79f2af7927 100644 --- a/src/components/pages/DaoDashboard/ERC20Claim.tsx +++ b/src/components/pages/DaoDashboard/ERC20Claim.tsx @@ -40,7 +40,7 @@ export function ERCO20Claim() { userClaimable, false, azoriusGovernance.votesToken.decimals, - azoriusGovernance.votesToken.symbol + azoriusGovernance.votesToken.symbol, ); contractCall({ contractFn: () => tokenClaimContract.claimTokens(account), @@ -66,7 +66,7 @@ export function ERCO20Claim() { userClaimable, false, azoriusGovernance.votesToken.decimals, - azoriusGovernance.votesToken.symbol + azoriusGovernance.votesToken.symbol, ); return ( (proposal.state === FractalProposalState.EXECUTED ? prev + 1 : prev), - 0 + 0, ); const active = !proposals @@ -41,7 +41,7 @@ export function InfoProposals({}: IDAOGovernance) { proposal.state === FractalProposalState.EXECUTABLE ? prev + 1 : prev, - 0 + 0, ); return ( diff --git a/src/components/pages/DaoHierarchy/DaoNode.tsx b/src/components/pages/DaoHierarchy/DaoNode.tsx index 8f01453a78..211cdbfc9b 100644 --- a/src/components/pages/DaoHierarchy/DaoNode.tsx +++ b/src/components/pages/DaoHierarchy/DaoNode.tsx @@ -63,9 +63,9 @@ export function DaoNode({ ? getTotalDescendants( fnode.nodeHierarchy.childNodes[ fnode.nodeHierarchy.childNodes.length - 1 - ] as FractalNode + ] as FractalNode, ) - : 0 + : 0, ); } else if (errorNode.error === 'errorFailedSearch') { setNode({ diff --git a/src/components/pages/DaoHierarchy/useFetchNodes.tsx b/src/components/pages/DaoHierarchy/useFetchNodes.tsx index 2e93232e74..9158b3762b 100644 --- a/src/components/pages/DaoHierarchy/useFetchNodes.tsx +++ b/src/components/pages/DaoHierarchy/useFetchNodes.tsx @@ -54,7 +54,7 @@ export function useFetchNodes(address?: string) { fractalAzoriusMasterCopyContract ) { const azoriusContract = fractalAzoriusMasterCopyContract?.asProvider.attach( - azoriusModule.moduleAddress + azoriusModule.moduleAddress, ); const azoriusGuardAddress = await azoriusContract.getGuard(); if (azoriusGuardAddress !== ethers.constants.AddressZero) { @@ -75,7 +75,7 @@ export function useFetchNodes(address?: string) { azoriusFreezeGuardMasterCopyContract, fractalAzoriusMasterCopyContract, lookupModules, - ] + ], ); const fetchDAOInfo = useCallback( @@ -83,7 +83,7 @@ export function useFetchNodes(address?: string) { const { getAddress } = ethers.utils; return (await safeAPI.getSafeInfo(getAddress(safeAddress))) as SafeInfoResponseWithGuard; }, - [safeAPI] + [safeAPI], ); const fetchSubDAOs = useCallback(async () => { diff --git a/src/components/pages/DaoSettings/components/ERC20Token/index.tsx b/src/components/pages/DaoSettings/components/ERC20Token/index.tsx index c91c163553..b3408cb9b2 100644 --- a/src/components/pages/DaoSettings/components/ERC20Token/index.tsx +++ b/src/components/pages/DaoSettings/components/ERC20Token/index.tsx @@ -68,7 +68,7 @@ export default function ERC20TokenContainer() { false, votesToken.decimals, votesToken.symbol, - false + false, )} diff --git a/src/components/pages/DaoSettings/components/Modules/index.tsx b/src/components/pages/DaoSettings/components/Modules/index.tsx index 353d073ff0..1f5cd5c3a3 100644 --- a/src/components/pages/DaoSettings/components/Modules/index.tsx +++ b/src/components/pages/DaoSettings/components/Modules/index.tsx @@ -71,8 +71,8 @@ export function ModulesContainer() { {moduleType === FractalModuleType.AZORIUS ? ' (Azorius Module)' : moduleType === FractalModuleType.FRACTAL - ? ' (Fractal Module)' - : ''} + ? ' (Fractal Module)' + : ''} )) diff --git a/src/components/pages/DaoSettings/components/Signers/hooks/useAddSigner.ts b/src/components/pages/DaoSettings/components/Signers/hooks/useAddSigner.ts index 5c22f89455..ed19d1a394 100644 --- a/src/components/pages/DaoSettings/components/Signers/hooks/useAddSigner.ts +++ b/src/components/pages/DaoSettings/components/Signers/hooks/useAddSigner.ts @@ -54,7 +54,7 @@ const useAddSigner = () => { failedToastMessage: t('addSignerFailureToastMessage'), }); }, - [safeSingletonContract.asSigner.interface, submitProposal, t] + [safeSingletonContract.asSigner.interface, submitProposal, t], ); return addSigner; diff --git a/src/components/pages/DaoSettings/components/Signers/modals/AddSignerModal.tsx b/src/components/pages/DaoSettings/components/Signers/modals/AddSignerModal.tsx index 16877e04f1..c8ad8ea8ac 100644 --- a/src/components/pages/DaoSettings/components/Signers/modals/AddSignerModal.tsx +++ b/src/components/pages/DaoSettings/components/Signers/modals/AddSignerModal.tsx @@ -59,7 +59,7 @@ function AddSignerModal({ close: close, }); }, - [addSigner, close, daoAddress, signer] + [addSigner, close, daoAddress, signer], ); const addSignerValidationSchema = Yup.object().shape({ @@ -150,7 +150,7 @@ function AddSignerModal({ ml={2} >{`${t('signersRequired1', { ns: 'modals' })} ${signers.length + 1} ${t( 'signersRequired2', - { ns: 'modals' } + { ns: 'modals' }, )}`} diff --git a/src/components/pages/DaoSettings/components/Signers/modals/RemoveSignerModal.tsx b/src/components/pages/DaoSettings/components/Signers/modals/RemoveSignerModal.tsx index 7212e5a099..7af160476c 100644 --- a/src/components/pages/DaoSettings/components/Signers/modals/RemoveSignerModal.tsx +++ b/src/components/pages/DaoSettings/components/Signers/modals/RemoveSignerModal.tsx @@ -67,7 +67,7 @@ function RemoveSignerModal({ useEffect(() => { const signerIndex = signers.findIndex(signer => signer === selectedSigner); setPrevSigner( - signerIndex > 0 ? signers[signerIndex - 1] : '0x0000000000000000000000000000000000000001' + signerIndex > 0 ? signers[signerIndex - 1] : '0x0000000000000000000000000000000000000001', ); }, [selectedSigner, signers]); @@ -138,7 +138,7 @@ function RemoveSignerModal({ ml={2} >{`${t('signersRequired1', { ns: 'modals' })} ${signers.length - 1} ${t( 'signersRequired2', - { ns: 'modals' } + { ns: 'modals' }, )}`} diff --git a/src/components/ui/forms/ABISelector.tsx b/src/components/ui/forms/ABISelector.tsx index e7ee44c602..cd83d863b9 100644 --- a/src/components/ui/forms/ABISelector.tsx +++ b/src/components/ui/forms/ABISelector.tsx @@ -44,7 +44,7 @@ export default function ABISelector({ target, onChange, onFetchABI }: IABISelect const response = await axios.get( `${etherscanAPIBaseUrl}/api?module=contract&action=getabi&address=${ proxy || ensAddress || target // Proxy detection might not always work - }&apikey=${process.env.NEXT_PUBLIC_ETHERSCAN_API_KEY}` + }&apikey=${process.env.NEXT_PUBLIC_ETHERSCAN_API_KEY}`, ); const responseData = response.data; @@ -83,9 +83,9 @@ export default function ABISelector({ target, onChange, onFetchABI }: IABISelect (abiElement: ABIElement) => abiElement.type === 'function' && abiElement.stateMutability !== 'pure' && - abiElement.stateMutability !== 'view' + abiElement.stateMutability !== 'view', ), - [abi] + [abi], ); if (!abiFunctions || !abiFunctions.length) { @@ -108,7 +108,7 @@ export default function ABISelector({ target, onChange, onFetchABI }: IABISelect color="white" onChange={e => { const selectedFunction = abiFunctions.find( - (abiFunction: ABIElement) => abiFunction.name === e.target.value + (abiFunction: ABIElement) => abiFunction.name === e.target.value, ); onChange(selectedFunction!); }} diff --git a/src/components/ui/forms/BigNumberInput.tsx b/src/components/ui/forms/BigNumberInput.tsx index 2317114304..89467ed155 100644 --- a/src/components/ui/forms/BigNumberInput.tsx +++ b/src/components/ui/forms/BigNumberInput.tsx @@ -61,7 +61,7 @@ export function BigNumberInput({ ? !value.isZero() ? removeTrailingZeros(utils.formatUnits(value, decimalPlaces)) : '0' - : '' + : '', ); }, [value, decimalPlaces, inputValue]); @@ -89,7 +89,7 @@ export function BigNumberInput({ } return eventValue; }, - [decimalPlaces] + [decimalPlaces], ); const processValue = useCallback( @@ -137,14 +137,14 @@ export function BigNumberInput({ } setInputValue(newValue); }, - [decimalPlaces, max, onChange, truncateDecimalPlaces] + [decimalPlaces, max, onChange, truncateDecimalPlaces], ); const onChangeInput = useCallback( (event: React.ChangeEvent) => { processValue(event); }, - [processValue] + [processValue], ); //set value to min if less than min, when focus is lost @@ -168,7 +168,7 @@ export function BigNumberInput({ } } }, - [decimalPlaces, min, onChange] + [decimalPlaces, min, onChange], ); // if the decimalPlaces change, need to update the value diff --git a/src/components/ui/icons/FavoriteIcon.tsx b/src/components/ui/icons/FavoriteIcon.tsx index 527aeef563..30be0950de 100644 --- a/src/components/ui/icons/FavoriteIcon.tsx +++ b/src/components/ui/icons/FavoriteIcon.tsx @@ -13,7 +13,7 @@ export default function FavoriteIcon({ safeAddress, ...rest }: Props) { const { favoritesList, toggleFavorite } = useAccountFavorites(); const isFavorite = useMemo( () => (!!safeAddress ? favoritesList.includes(utils.getAddress(safeAddress)) : false), - [favoritesList, safeAddress] + [favoritesList, safeAddress], ); const { t } = useTranslation(); return ( diff --git a/src/components/ui/menus/ManageDAO/ManageDAOMenu.tsx b/src/components/ui/menus/ManageDAO/ManageDAOMenu.tsx index 154e3bca26..2e79b408cf 100644 --- a/src/components/ui/menus/ManageDAO/ManageDAOMenu.tsx +++ b/src/components/ui/menus/ManageDAO/ManageDAOMenu.tsx @@ -99,10 +99,10 @@ export function ManageDAOMenu({ if (!!azoriusModule) { const azoriusContract = { asProvider: fractalAzoriusMasterCopyContract.asProvider.attach( - azoriusModule.moduleAddress + azoriusModule.moduleAddress, ), asSigner: fractalAzoriusMasterCopyContract.asSigner.attach( - azoriusModule.moduleAddress + azoriusModule.moduleAddress, ), }; @@ -110,7 +110,7 @@ export function ManageDAOMenu({ const votingContractAddress = ( await azoriusContract.asProvider.getStrategies( '0x0000000000000000000000000000000000000001', - 0 + 0, ) )[1]; const masterCopyData = await getZodiacModuleProxyMasterCopyData(votingContractAddress); @@ -142,7 +142,7 @@ export function ManageDAOMenu({ const handleNavigateToSettings = useCallback( () => push(DAO_ROUTES.settings.relative(safeAddress)), - [push, safeAddress] + [push, safeAddress], ); const handleModifyGovernance = useFractalModal(ModalType.CONFIRM_MODIFY_GOVERNANCE); @@ -198,12 +198,12 @@ export function ManageDAOMenu({ !isWithinFreezeProposalPeriod( freezeGuard.freezeProposalCreatedTime, freezeGuard.freezeProposalPeriod, - currentTime + currentTime, ) && !isWithinFreezePeriod( freezeGuard.freezeProposalCreatedTime, freezeGuard.freezePeriod, - currentTime + currentTime, ) && freezeGuard.userHasVotes ) { @@ -219,7 +219,7 @@ export function ManageDAOMenu({ isWithinFreezePeriod( freezeGuard.freezeProposalCreatedTime, freezeGuard.freezePeriod, - currentTime + currentTime, ) && freezeGuard.isFrozen && freezeGuard.userHasVotes diff --git a/src/components/ui/modals/DelegateModal.tsx b/src/components/ui/modals/DelegateModal.tsx index 9cff91ec09..2d7750ee03 100644 --- a/src/components/ui/modals/DelegateModal.tsx +++ b/src/components/ui/modals/DelegateModal.tsx @@ -89,7 +89,7 @@ export function DelegateModal({ close }: { close: Function }) { azoriusGovernance.votesToken.balance || BigNumber.from(0), false, azoriusGovernance.votesToken.decimals, - azoriusGovernance.votesToken.symbol + azoriusGovernance.votesToken.symbol, )} { push( `${DAO_ROUTES.proposalTemplateNew.relative( - targetDAOAddress - )}?templatesHash=${proposalTemplatesHash}&templateIndex=${templateIndex}` + targetDAOAddress, + )}?templatesHash=${proposalTemplatesHash}&templateIndex=${templateIndex}`, ); onClose(); }; diff --git a/src/components/ui/modals/ProposalTemplateModal.tsx b/src/components/ui/modals/ProposalTemplateModal.tsx index 5ee47bb308..57747fadfb 100644 --- a/src/components/ui/modals/ProposalTemplateModal.tsx +++ b/src/components/ui/modals/ProposalTemplateModal.tsx @@ -73,7 +73,7 @@ export default function ProposalTemplateModal({ }; } return transaction; - }) + }), ); }; @@ -102,11 +102,11 @@ export default function ProposalTemplateModal({ .map(parameter => isValidUrl(parameter.value!.trim()) ? encodeURIComponent(parameter.value!.trim()) // If parameter.value is valid URL with special symbols like ":" or "?" - decoding might fail, thus we need to encode URL - : parameter.value!.trim() + : parameter.value!.trim(), ) .join(', '), }; - } + }, ); try { const proposalData = await prepareProposal({ @@ -129,7 +129,7 @@ export default function ProposalTemplateModal({ // The form is valid when there's no parameter without filled value const isValid = !filledProposalTransactions.find( - transaction => !!transaction.parameters.find(parameter => !parameter.value) + transaction => !!transaction.parameters.find(parameter => !parameter.value), ); return ( @@ -176,7 +176,7 @@ export default function ProposalTemplateModal({ } /> - ) + ), )} {transaction.parameters.length > 0 && } diff --git a/src/components/ui/modals/SendAssetsModal.tsx b/src/components/ui/modals/SendAssetsModal.tsx index e626b74d1b..212791f78c 100644 --- a/src/components/ui/modals/SendAssetsModal.tsx +++ b/src/components/ui/modals/SendAssetsModal.tsx @@ -27,7 +27,7 @@ export function SendAssetsModal({ close }: { close: () => void }) { const fungibleAssetsWithBalance = assetsFungible.filter(asset => parseFloat(asset.balance) > 0); const [selectedAsset, setSelectedAsset] = useState( - fungibleAssetsWithBalance[0] + fungibleAssetsWithBalance[0], ); const [inputAmount, setInputAmount] = useState(); const [nonceInput, setNonceInput] = useState(safe!.nonce); @@ -37,7 +37,7 @@ export function SendAssetsModal({ close }: { close: () => void }) { const hasFiatBalance = Number(selectedAsset.fiatBalance) > 0; const convertedTotal = formatUSD( - Number(inputAmount?.value || '0') * Number(selectedAsset.fiatConversion) + Number(inputAmount?.value || '0') * Number(selectedAsset.fiatConversion), ); const sendAssets = useSendAssets({ diff --git a/src/components/ui/modals/UnwrapToken.tsx b/src/components/ui/modals/UnwrapToken.tsx index 72d5c562f4..2a02cfc529 100644 --- a/src/components/ui/modals/UnwrapToken.tsx +++ b/src/components/ui/modals/UnwrapToken.tsx @@ -32,7 +32,7 @@ export function UnwrapToken({ close }: { close: () => void }) { pending: approvalPending, } = useApproval( governanceContracts.tokenContract?.asSigner.attach(governanceContracts.underlyingTokenAddress!), - azoriusGovernance.votesToken?.address + azoriusGovernance.votesToken?.address, ); const { t } = useTranslation(['modals', 'treasury']); @@ -56,7 +56,7 @@ export function UnwrapToken({ close }: { close: () => void }) { }, }); }, - [account, contractCall, governanceContracts, signer, close, t, loadERC20TokenAccountData] + [account, contractCall, governanceContracts, signer, close, t, loadERC20TokenAccountData], ); if ( @@ -119,7 +119,7 @@ export function UnwrapToken({ close }: { close: () => void }) { azoriusGovernance.votesToken?.balance!, false, azoriusGovernance.votesToken?.decimals!, - azoriusGovernance.votesToken?.symbol + azoriusGovernance.votesToken?.symbol, ), })} > diff --git a/src/components/ui/modals/WrapToken.tsx b/src/components/ui/modals/WrapToken.tsx index 52320b578b..fb92607c6f 100644 --- a/src/components/ui/modals/WrapToken.tsx +++ b/src/components/ui/modals/WrapToken.tsx @@ -37,7 +37,7 @@ export function WrapToken({ close }: { close: () => void }) { } = useApproval( governanceContracts.tokenContract?.asSigner.attach(governanceContracts.underlyingTokenAddress!), azoriusGovernance.votesToken?.address, - userBalance.bigNumberValue + userBalance.bigNumberValue, ); const { t } = useTranslation(['modals', 'treasury']); @@ -54,7 +54,7 @@ export function WrapToken({ close }: { close: () => void }) { const baseTokenContract = new Contract( azoriusGovernance.votesToken.underlyingTokenData.address, erc20ABI, - signer + signer, ); try { const [balance, decimals]: [BigNumber, number] = await Promise.all([ @@ -66,7 +66,7 @@ export function WrapToken({ close }: { close: () => void }) { balance, false, decimals, - azoriusGovernance.votesToken?.underlyingTokenData?.symbol + azoriusGovernance.votesToken?.underlyingTokenData?.symbol, ), bigNumberValue: balance, }); @@ -98,7 +98,7 @@ export function WrapToken({ close }: { close: () => void }) { }, }); }, - [account, contractCall, governanceContracts, signer, close, t, loadERC20TokenAccountData] + [account, contractCall, governanceContracts, signer, close, t, loadERC20TokenAccountData], ); if ( diff --git a/src/components/ui/proposal/ProposalCountdown.tsx b/src/components/ui/proposal/ProposalCountdown.tsx index 692444fd56..f73e7456eb 100644 --- a/src/components/ui/proposal/ProposalCountdown.tsx +++ b/src/components/ui/proposal/ProposalCountdown.tsx @@ -39,7 +39,7 @@ export function ProposalCountdown({ state === FractalProposalState.TIMELOCKED || state === FractalProposalState.EXECUTABLE || isSnapshotProposal), - [state, secondsLeft, isSnapshotProposal] + [state, secondsLeft, isSnapshotProposal], ); if (!showCountdown) return null; @@ -48,20 +48,20 @@ export function ProposalCountdown({ state === FractalProposalState.ACTIVE ? 'votingTooltip' : state === FractalProposalState.TIMELOCKED - ? 'timeLockedTooltip' - : state === FractalProposalState.EXECUTABLE - ? 'executableTooltip' - : '' + ? 'timeLockedTooltip' + : state === FractalProposalState.EXECUTABLE + ? 'executableTooltip' + : '', ); const Icon: ComponentWithAs<'svg', IconProps> | null = state === FractalProposalState.ACTIVE || isSnapshotProposal ? Vote : state === FractalProposalState.TIMELOCKED - ? Lock - : state === FractalProposalState.EXECUTABLE - ? Execute - : null; + ? Lock + : state === FractalProposalState.EXECUTABLE + ? Execute + : null; const daysLeft = Math.floor(secondsLeft! / (60 * 60 * 24)); const hoursLeft = Math.floor((secondsLeft! / (60 * 60)) % 24); diff --git a/src/components/ui/proposal/useProposalCountdown.tsx b/src/components/ui/proposal/useProposalCountdown.tsx index 32759d4227..79e0d09b80 100644 --- a/src/components/ui/proposal/useProposalCountdown.tsx +++ b/src/components/ui/proposal/useProposalCountdown.tsx @@ -99,8 +99,8 @@ export function useProposalCountdown(proposal: FractalProposal) { freezeGuardType === FreezeGuardType.MULTISIG ? (freezeGuardContract?.asProvider as MultisigFreezeGuard) : freezeGuardType === FreezeGuardType.AZORIUS - ? (freezeGuardContract?.asProvider as AzoriusFreezeGuard) - : undefined; + ? (freezeGuardContract?.asProvider as AzoriusFreezeGuard) + : undefined; const isSafeGuard = freezeGuardType === FreezeGuardType.MULTISIG; const isAzoriusGuard = freezeGuardType === FreezeGuardType.AZORIUS; @@ -124,7 +124,7 @@ export function useProposalCountdown(proposal: FractalProposal) { const timelockedTimestamp = await getTxTimelockedTimestamp(proposal, safeGuard, provider); const guardTimeLockPeriod = await blocksToSeconds( await safeGuard.timelockPeriod(), - provider + provider, ); startCountdown(timelockedTimestamp * 1000 + guardTimeLockPeriod * 1000); // If the proposal is executable start the countdown (for safe multisig proposals with guards) diff --git a/src/graphql/DAO.graphql b/src/graphql/DAO.graphql index 140e233111..2fd59c7715 100644 --- a/src/graphql/DAO.graphql +++ b/src/graphql/DAO.graphql @@ -1,42 +1,42 @@ query DAOQuery($daoAddress: Bytes) { - daos(where: {id: $daoAddress}) { + daos(where: { id: $daoAddress }) { + id + address + parentAddress + name + snapshotURL + hierarchy { + id + address + parentAddress + name + snapshotURL + # There's probably a better way to organize this with Fragments + # However, current GraphQL spec does not allow recursive fragment. + # Though this might be improved via generating GraphQL typings from fractal-subgraph repository + # Gonna keep it this "dumb" way for PoC purpose only + hierarchy { id address parentAddress name snapshotURL hierarchy { + id + address + parentAddress + name + snapshotURL + hierarchy { id address parentAddress name snapshotURL - # There's probably a better way to organize this with Fragments - # However, current GraphQL spec does not allow recursive fragment. - # Though this might be improved via generating GraphQL typings from fractal-subgraph repository - # Gonna keep it this "dumb" way for PoC purpose only - hierarchy { - id - address - parentAddress - name - snapshotURL - hierarchy { - id - address - parentAddress - name - snapshotURL - hierarchy { - id - address - parentAddress - name - snapshotURL - } - } - } + } } - proposalTemplatesHash + } } -} \ No newline at end of file + proposalTemplatesHash + } +} diff --git a/src/helpers/activity.ts b/src/helpers/activity.ts index 1fadb6b766..acba01776c 100644 --- a/src/helpers/activity.ts +++ b/src/helpers/activity.ts @@ -4,7 +4,7 @@ import { isMultiSigTx } from '../utils'; export const isRejected = ( activityArr: Activity[], - multiSigTransaction: SafeMultisigTransactionWithTransfersResponse + multiSigTransaction: SafeMultisigTransactionWithTransfersResponse, ) => { return ( isMultiSigTx(multiSigTransaction) && diff --git a/src/helpers/crypto.ts b/src/helpers/crypto.ts index 7ce6b98168..6476230c2e 100644 --- a/src/helpers/crypto.ts +++ b/src/helpers/crypto.ts @@ -35,18 +35,18 @@ export function getRandomBytes() { export const calculateSafeTransactionHash = ( safe: Contract, safeTx: SafeTransaction, - chainId: BigNumberish + chainId: BigNumberish, ): string => { return utils._TypedDataEncoder.hash( { verifyingContract: safe.address, chainId }, EIP712_SAFE_TX_TYPE, - safeTx + safeTx, ); }; export const buildSignatureBytes = (signatures: SafeSignature[]): string => { signatures.sort((left, right) => - left.signer.toLowerCase().localeCompare(right.signer.toLowerCase()) + left.signer.toLowerCase().localeCompare(right.signer.toLowerCase()), ); let signatureBytes = '0x'; for (const sig of signatures) { @@ -85,7 +85,7 @@ export const safeSignTypedData = async ( signer: Signer & TypedDataSigner, safe: Contract, safeTx: SafeTransaction, - chainId?: BigNumberish + chainId?: BigNumberish, ): Promise => { if (!chainId && !signer.provider) throw Error('Provider required to retrieve chainId'); const cid = chainId || (await signer.provider!.getNetwork()).chainId; @@ -95,7 +95,7 @@ export const safeSignTypedData = async ( data: await signer._signTypedData( { verifyingContract: safe.address, chainId: cid }, EIP712_SAFE_TX_TYPE, - safeTx + safeTx, ), }; }; @@ -115,7 +115,7 @@ export const buildSafeAPIPost = async ( gasToken?: string; refundReceiver?: string; nonce: number; - } + }, ): Promise => { const safeTx = buildSafeTransaction(template); @@ -125,7 +125,7 @@ export const buildSafeAPIPost = async ( signerOrProvider as Signer & TypedDataSigner, safeContract, safeTx, - chainId + chainId, ), ]; const signatureBytes = buildSignatureBytes(sig); @@ -153,7 +153,7 @@ export const buildContractCall = ( params: any[], nonce: number, delegateCall?: boolean, - overrides?: Partial + overrides?: Partial, ): SafeTransaction => { const data = contract.interface.encodeFunctionData(method, params); return buildSafeTransaction( @@ -164,8 +164,8 @@ export const buildContractCall = ( operation: delegateCall ? 1 : 0, nonce, }, - overrides - ) + overrides, + ), ); }; @@ -173,7 +173,7 @@ const encodeMetaTransaction = (tx: MetaTransaction): string => { const data = utils.arrayify(tx.data); const encoded = utils.solidityPack( ['uint8', 'address', 'uint256', 'uint256', 'bytes'], - [tx.operation, tx.to, tx.value, data.length, data] + [tx.operation, tx.to, tx.value, data.length, data], ); return encoded.slice(2); }; diff --git a/src/helpers/errorLogging.ts b/src/helpers/errorLogging.ts index 1c59058c1f..3562298400 100644 --- a/src/helpers/errorLogging.ts +++ b/src/helpers/errorLogging.ts @@ -91,7 +91,7 @@ export class FractalErrorBoundary extends Sentry.ErrorBoundary { error: Error & { cause?: Error; }, - errorInfo: React.ErrorInfo + errorInfo: React.ErrorInfo, ) { logError(error, errorInfo); if (isProd()) return; diff --git a/src/helpers/freezePeriodHelpers.ts b/src/helpers/freezePeriodHelpers.ts index d759ce7c9b..99e775a24d 100644 --- a/src/helpers/freezePeriodHelpers.ts +++ b/src/helpers/freezePeriodHelpers.ts @@ -3,7 +3,7 @@ import { BigNumber } from 'ethers'; export const secondsLeftInFreezePeriod = ( freezeProposalCreatedTime: BigNumber, freezePeriod: BigNumber, - currentTime: BigNumber + currentTime: BigNumber, ): BigNumber => { const secondsLeft = freezeProposalCreatedTime.add(freezePeriod).sub(currentTime); @@ -13,7 +13,7 @@ export const secondsLeftInFreezePeriod = ( export const secondsLeftInFreezeProposalPeriod = ( freezeProposalCreatedTime: BigNumber, freezeProposalPeriod: BigNumber, - currentTime: BigNumber + currentTime: BigNumber, ): BigNumber => { const secondsLeft = freezeProposalCreatedTime.add(freezeProposalPeriod).sub(currentTime); @@ -23,12 +23,12 @@ export const secondsLeftInFreezeProposalPeriod = ( export const isWithinFreezePeriod = ( freezeProposalCreatedTime: BigNumber, freezePeriod: BigNumber, - currentTime: BigNumber + currentTime: BigNumber, ): boolean => { const secondsLeft = secondsLeftInFreezePeriod( freezeProposalCreatedTime, freezePeriod, - currentTime + currentTime, ); return secondsLeft.gt(0); @@ -37,12 +37,12 @@ export const isWithinFreezePeriod = ( export const isWithinFreezeProposalPeriod = ( freezeProposalCreatedTime: BigNumber, freezeProposalPeriod: BigNumber, - currentTime: BigNumber + currentTime: BigNumber, ): boolean => { const secondsLeft = secondsLeftInFreezeProposalPeriod( freezeProposalCreatedTime, freezeProposalPeriod, - currentTime + currentTime, ); return secondsLeft.gt(0); diff --git a/src/hooks/DAO/loaders/governance/useAzoriusProposals.ts b/src/hooks/DAO/loaders/governance/useAzoriusProposals.ts index 3f8c43390a..43ce0bc6bf 100644 --- a/src/hooks/DAO/loaders/governance/useAzoriusProposals.ts +++ b/src/hooks/DAO/loaders/governance/useAzoriusProposals.ts @@ -40,11 +40,11 @@ export const useAzoriusProposals = () => { const decodedTransactions = await Promise.all( transactions.map(async tx => { return decode(tx.value.toString(), tx.to, tx.data); - }) + }), ); return decodedTransactions.flat(); }, - [decode] + [decode], ); const loadAzoriusProposals = useCallback(async (): Promise => { @@ -61,7 +61,7 @@ export const useAzoriusProposals = () => { const proposalCreatedEvents = await rpc.queryFilter(proposalCreatedFilter); const strategyContract = getEventRPC( - ozLinearVotingContract ?? erc721LinearVotingContract! + ozLinearVotingContract ?? erc721LinearVotingContract!, ); const proposals = await Promise.all( @@ -87,9 +87,9 @@ export const useAzoriusProposals = () => { args.proposer, azoriusContract, provider, - proposalData + proposalData, ); - }) + }), ); return proposals; }, [ @@ -127,7 +127,7 @@ export const useAzoriusProposals = () => { }; } const strategyContract = getEventRPC( - ozLinearVotingContract ?? erc721LinearVotingContract! + ozLinearVotingContract ?? erc721LinearVotingContract!, ).attach(strategyAddress); const func = async () => { return mapProposalCreatedEventToProposal( @@ -137,7 +137,7 @@ export const useAzoriusProposals = () => { proposer, azoriusContract, provider, - proposalData + proposalData, ); }; const proposal = await requestWithRetries(func, 5, 7000); @@ -155,7 +155,7 @@ export const useAzoriusProposals = () => { action, requestWithRetries, strategyType, - ] + ], ); const erc20ProposalVotedEventListener: TypedListener = useCallback( @@ -167,7 +167,7 @@ export const useAzoriusProposals = () => { const votesSummary = await getProposalVotesSummary( strategyContract, strategyType, - BigNumber.from(proposalId) + BigNumber.from(proposalId), ); action.dispatch({ @@ -181,7 +181,7 @@ export const useAzoriusProposals = () => { }, }); }, - [ozLinearVotingContract, action, strategyType] + [ozLinearVotingContract, action, strategyType], ); const erc721ProposalVotedEventListener: TypedListener = useCallback( @@ -193,7 +193,7 @@ export const useAzoriusProposals = () => { const votesSummary = await getProposalVotesSummary( strategyContract, strategyType, - BigNumber.from(proposalId) + BigNumber.from(proposalId), ); action.dispatch({ @@ -208,7 +208,7 @@ export const useAzoriusProposals = () => { }, }); }, - [erc721LinearVotingContract, action, strategyType] + [erc721LinearVotingContract, action, strategyType], ); useEffect(() => { diff --git a/src/hooks/DAO/loaders/governance/useERC20Claim.ts b/src/hooks/DAO/loaders/governance/useERC20Claim.ts index a9fc4e0424..de6625a863 100644 --- a/src/hooks/DAO/loaders/governance/useERC20Claim.ts +++ b/src/hooks/DAO/loaders/governance/useERC20Claim.ts @@ -20,7 +20,7 @@ export function useERC20Claim() { const approvals = await tokenContract.asProvider.queryFilter(approvalFilter); if (!approvals.length) return; const possibleTokenClaimContract = claimingMasterCopyContract.asProvider.attach( - approvals[0].args[1] + approvals[0].args[1], ); const tokenClaimFilter = possibleTokenClaimContract.filters.ERC20ClaimCreated(); const tokenClaimArray = await possibleTokenClaimContract diff --git a/src/hooks/DAO/loaders/governance/useERC20LinearToken.ts b/src/hooks/DAO/loaders/governance/useERC20LinearToken.ts index c254ff9d04..c01dc5713d 100644 --- a/src/hooks/DAO/loaders/governance/useERC20LinearToken.ts +++ b/src/hooks/DAO/loaders/governance/useERC20LinearToken.ts @@ -75,7 +75,7 @@ export const useERC20LinearToken = ({ onMount = true }: { onMount?: boolean }) = let delegateChangeEvents: DelegateChangedEvent[]; try { delegateChangeEvents = await tokenContract.asProvider.queryFilter( - tokenContract.asProvider.filters.DelegateChanged() + tokenContract.asProvider.filters.DelegateChanged(), ); } catch (e) { delegateChangeEvents = []; diff --git a/src/hooks/DAO/loaders/governance/useERC721Tokens.ts b/src/hooks/DAO/loaders/governance/useERC721Tokens.ts index ec2c48857b..5046e6007b 100644 --- a/src/hooks/DAO/loaders/governance/useERC721Tokens.ts +++ b/src/hooks/DAO/loaders/governance/useERC721Tokens.ts @@ -29,17 +29,17 @@ export default function useERC721Tokens() { let totalSupply = undefined; try { const tokenMintEvents = await tokenContract.queryFilter( - tokenContract.filters.Transfer(constants.AddressZero, null) + tokenContract.filters.Transfer(constants.AddressZero, null), ); const tokenBurnEvents = await tokenContract.queryFilter( - tokenContract.filters.Transfer(null, constants.AddressZero) + tokenContract.filters.Transfer(null, constants.AddressZero), ); totalSupply = BigNumber.from(tokenMintEvents.length - tokenBurnEvents.length); } catch (e) { logError('Error while getting ERC721 total supply'); } return { name, symbol, address, votingWeight, totalSupply }; - }) + }), ); action.dispatch({ diff --git a/src/hooks/DAO/loaders/governance/useLockRelease.ts b/src/hooks/DAO/loaders/governance/useLockRelease.ts index 546f959b89..8e0472f9e8 100644 --- a/src/hooks/DAO/loaders/governance/useLockRelease.ts +++ b/src/hooks/DAO/loaders/governance/useLockRelease.ts @@ -36,7 +36,7 @@ export const useLockRelease = ({ onMount = true }: { onMount?: boolean }) => { let delegateChangeEvents: DelegateChangedEvent[]; try { delegateChangeEvents = await lockReleaseContract.asProvider.queryFilter( - lockReleaseContract.asProvider.filters.DelegateChanged() + lockReleaseContract.asProvider.filters.DelegateChanged(), ); } catch (e) { delegateChangeEvents = []; diff --git a/src/hooks/DAO/loaders/governance/useSafeMultisigProposals.ts b/src/hooks/DAO/loaders/governance/useSafeMultisigProposals.ts index 50498c3299..a62b9fcc30 100644 --- a/src/hooks/DAO/loaders/governance/useSafeMultisigProposals.ts +++ b/src/hooks/DAO/loaders/governance/useSafeMultisigProposals.ts @@ -23,7 +23,7 @@ export const useSafeMultisigProposals = () => { const transactions = await safeAPI.getAllTransactions(daoAddress); const activities = await parseTransactions(transactions, daoAddress); const multisendProposals = activities.filter( - activity => activity.eventType !== ActivityEventType.Treasury + activity => activity.eventType !== ActivityEventType.Treasury, ); action.dispatch({ type: FractalGovernanceAction.SET_PROPOSALS, diff --git a/src/hooks/DAO/loaders/snapshot/useSnapshotProposal.ts b/src/hooks/DAO/loaders/snapshot/useSnapshotProposal.ts index f1d7f86a01..24ea78a238 100644 --- a/src/hooks/DAO/loaders/snapshot/useSnapshotProposal.ts +++ b/src/hooks/DAO/loaders/snapshot/useSnapshotProposal.ts @@ -32,7 +32,7 @@ export default function useSnapshotProposal(proposal: FractalProposal | null | u const snapshotProposal = proposal as SnapshotProposal; const isSnapshotProposal = useMemo( () => !!snapshotProposal?.snapshotProposalId, - [snapshotProposal] + [snapshotProposal], ); const loadProposal = useCallback(async () => { @@ -74,7 +74,7 @@ export default function useSnapshotProposal(proposal: FractalProposal | null | u privacy, ipfs, }; - } + }, ); const votesQueryResult = await client @@ -153,7 +153,7 @@ export default function useSnapshotProposal(proposal: FractalProposal | null | u } else { let totalVotingWeightDistributon = 0; Object.keys(voteChoices).forEach( - (choice: any) => (totalVotingWeightDistributon += voteChoices[choice]) + (choice: any) => (totalVotingWeightDistributon += voteChoices[choice]), ); Object.keys(voteChoices).forEach((choiceIndex: any) => { const voteChoiceValue = voteChoices[choiceIndex] / totalVotingWeightDistributon; diff --git a/src/hooks/DAO/loaders/snapshot/useSnapshotProposals.ts b/src/hooks/DAO/loaders/snapshot/useSnapshotProposals.ts index 45a4d6bd92..080709777b 100644 --- a/src/hooks/DAO/loaders/snapshot/useSnapshotProposals.ts +++ b/src/hooks/DAO/loaders/snapshot/useSnapshotProposals.ts @@ -60,8 +60,8 @@ export const useSnapshotProposals = () => { proposal.state === 'active' ? FractalProposalState.ACTIVE : proposal.state === 'closed' - ? FractalProposalState.CLOSED - : FractalProposalState.PENDING, + ? FractalProposalState.CLOSED + : FractalProposalState.PENDING, proposalId: proposal.id, snapshotProposalId: proposal.id, diff --git a/src/hooks/DAO/loaders/useFavorites.ts b/src/hooks/DAO/loaders/useFavorites.ts index 52302e7a08..2886223595 100644 --- a/src/hooks/DAO/loaders/useFavorites.ts +++ b/src/hooks/DAO/loaders/useFavorites.ts @@ -24,7 +24,7 @@ export const useAccountFavorites = () => { */ const isConnectedFavorited = useMemo( () => (!daoAddress ? false : favoritesList.includes(daoAddress)), - [daoAddress, favoritesList] + [daoAddress, favoritesList], ); /** @@ -46,7 +46,7 @@ export const useAccountFavorites = () => { setFavoritesList(updatedFavorites); setValue(CacheKeys.FAVORITES, updatedFavorites, CacheExpiry.NEVER); }, - [favoritesList, setValue] + [favoritesList, setValue], ); return { favoritesList, isConnectedFavorited, toggleFavorite }; diff --git a/src/hooks/DAO/loaders/useFractalFreeze.ts b/src/hooks/DAO/loaders/useFractalFreeze.ts index 4f8bdff7a9..2640e1207d 100644 --- a/src/hooks/DAO/loaders/useFractalFreeze.ts +++ b/src/hooks/DAO/loaders/useFractalFreeze.ts @@ -42,7 +42,7 @@ export const useFractalFreeze = ({ const { getUserERC721VotingTokens } = useUserERC721VotingTokens( undefined, parentSafeAddress, - loadOnMount + loadOnMount, ); const provider = useEthersProvider(); @@ -73,7 +73,7 @@ export const useFractalFreeze = ({ const userHasFreezeVoted = await freezeVotingContract!.asProvider.userHasFreezeVoted( account || constants.AddressZero, - freezeCreatedBlock + freezeCreatedBlock, ); const isFrozen = await freezeVotingContract!.asProvider.isFrozen(); @@ -89,25 +89,25 @@ export const useFractalFreeze = ({ if (freezeVotingType === FreezeVotingType.MULTISIG) { const safeContract = safeSingletonContract!.asProvider.attach( - await (freezeVotingContract!.asProvider as MultisigFreezeVoting).parentGnosisSafe() + await (freezeVotingContract!.asProvider as MultisigFreezeVoting).parentGnosisSafe(), ); const owners = await safeContract.getOwners(); userHasVotes = owners.find(owner => owner === account) !== undefined; } else if (freezeVotingType === FreezeVotingType.ERC20) { const votesTokenContract = votesTokenMasterCopyContract!.asProvider.attach( - await (freezeVotingContract!.asProvider as ERC20FreezeVoting).votesERC20() + await (freezeVotingContract!.asProvider as ERC20FreezeVoting).votesERC20(), ); const currentTimestamp = await getTimeStamp('latest', provider); const isFreezeActive = isWithinFreezeProposalPeriod( BigNumber.from(freezeGuard.freezeProposalCreatedTime), BigNumber.from(freezeGuard.freezeProposalPeriod), - BigNumber.from(currentTimestamp) + BigNumber.from(currentTimestamp), ) || isWithinFreezePeriod( BigNumber.from(freezeGuard.freezeProposalCreatedTime), BigNumber.from(freezeGuard.freezePeriod), - BigNumber.from(currentTimestamp) + BigNumber.from(currentTimestamp), ); userHasVotes = ( !isFreezeActive @@ -119,7 +119,7 @@ export const useFractalFreeze = ({ } else if (freezeVotingType === FreezeVotingType.ERC721) { const { totalVotingTokenAddresses } = await getUserERC721VotingTokens( undefined, - parentSafeAddress + parentSafeAddress, ); userHasVotes = totalVotingTokenAddresses.length > 0; } @@ -138,7 +138,7 @@ export const useFractalFreeze = ({ votesTokenMasterCopyContract, getUserERC721VotingTokens, parentSafeAddress, - ] + ], ); const setFractalFreezeGuard = useCallback( @@ -148,7 +148,7 @@ export const useFractalFreeze = ({ action.dispatch({ type: FractalGuardAction.SET_FREEZE_GUARD, payload: freezeGuard }); } }, - [action, loadFractalFreezeGuard] + [action, loadFractalFreezeGuard], ); useEffect(() => { @@ -169,7 +169,7 @@ export const useFractalFreeze = ({ let votingRPC: MultisigFreezeVoting | ERC20FreezeVoting | ERC721FreezeVoting; const listenerCallback: TypedListener = async ( voter: string, - votesCast + votesCast, ) => { const freezeProposalCreatedBlock = await votingRPC.freezeProposalCreatedBlock(); action.dispatch({ @@ -177,7 +177,7 @@ export const useFractalFreeze = ({ payload: { isVoter: voter === account, freezeProposalCreatedTime: BigNumber.from( - await getTimeStamp(freezeProposalCreatedBlock, provider) + await getTimeStamp(freezeProposalCreatedBlock, provider), ), votesCast, }, @@ -187,19 +187,19 @@ export const useFractalFreeze = ({ if (isFreezeSet.current && freezeVotingType !== null && freezeVotingContract) { if (freezeVotingType === FreezeVotingType.MULTISIG) { votingRPC = getEventRPC( - freezeVotingContract as ContractConnection + freezeVotingContract as ContractConnection, ); const filter = votingRPC.filters.FreezeVoteCast(); votingRPC.on(filter, listenerCallback); } else if (freezeVotingType === FreezeVotingType.ERC20) { votingRPC = getEventRPC( - freezeVotingContract as ContractConnection + freezeVotingContract as ContractConnection, ); const filter = votingRPC.filters.FreezeVoteCast(); votingRPC.on(filter, listenerCallback); } else if (freezeVotingType === FreezeVotingType.ERC721) { votingRPC = getEventRPC( - freezeVotingContract as ContractConnection + freezeVotingContract as ContractConnection, ); const filter = votingRPC.filters.FreezeVoteCast(); votingRPC.on(filter, listenerCallback); diff --git a/src/hooks/DAO/loaders/useFractalGuardContracts.ts b/src/hooks/DAO/loaders/useFractalGuardContracts.ts index bf4281ab94..b5b12615e3 100644 --- a/src/hooks/DAO/loaders/useFractalGuardContracts.ts +++ b/src/hooks/DAO/loaders/useFractalGuardContracts.ts @@ -37,7 +37,7 @@ export const useFractalGuardContracts = ({ loadOnMount = true }: { loadOnMount?: async ( _daoAddress: string, _safe: SafeInfoResponseWithGuard, - _fractalModules: FractalModuleData[] + _fractalModules: FractalModuleData[], ) => { const { guard } = _safe; @@ -47,7 +47,7 @@ export const useFractalGuardContracts = ({ loadOnMount = true }: { loadOnMount?: let freezeGuardType: FreezeGuardType | null = null; const azoriusModule = _fractalModules?.find( - module => module.moduleType === FractalModuleType.AZORIUS + module => module.moduleType === FractalModuleType.AZORIUS, ); if (!!azoriusModule && azoriusModule.moduleContract) { const azoriusGuardAddress = await azoriusModule.moduleContract.getGuard(); @@ -75,8 +75,8 @@ export const useFractalGuardContracts = ({ loadOnMount = true }: { loadOnMount?: const freezeVotingType = masterCopyData.isMultisigFreezeVoting ? FreezeVotingType.MULTISIG : masterCopyData.isERC721FreezeVoting - ? FreezeVotingType.ERC721 - : FreezeVotingType.ERC20; + ? FreezeVotingType.ERC721 + : FreezeVotingType.ERC20; const freezeVotingContract = freezeVotingType === FreezeVotingType.MULTISIG @@ -85,14 +85,14 @@ export const useFractalGuardContracts = ({ loadOnMount = true }: { loadOnMount?: asProvider: freezeMultisigVotingMasterCopyContract.asProvider.attach(votingAddress), } : freezeVotingType === FreezeVotingType.ERC721 - ? { - asSigner: freezeERC721VotingMasterCopyContract.asSigner.attach(votingAddress), - asProvider: freezeERC721VotingMasterCopyContract.asProvider.attach(votingAddress), - } - : { - asSigner: freezeERC20VotingMasterCopyContract.asSigner.attach(votingAddress), - asProvider: freezeERC20VotingMasterCopyContract.asProvider.attach(votingAddress), - }; + ? { + asSigner: freezeERC721VotingMasterCopyContract.asSigner.attach(votingAddress), + asProvider: freezeERC721VotingMasterCopyContract.asProvider.attach(votingAddress), + } + : { + asSigner: freezeERC20VotingMasterCopyContract.asSigner.attach(votingAddress), + asProvider: freezeERC20VotingMasterCopyContract.asProvider.attach(votingAddress), + }; const contracts = { freezeGuardContract, @@ -111,7 +111,7 @@ export const useFractalGuardContracts = ({ loadOnMount = true }: { loadOnMount?: azoriusFreezeGuardMasterCopyContract, multisigFreezeGuardMasterCopyContract, getZodiacModuleProxyMasterCopyData, - ] + ], ); const setGuardContracts = useCallback(async () => { diff --git a/src/hooks/DAO/loaders/useFractalModules.ts b/src/hooks/DAO/loaders/useFractalModules.ts index a09d54c46b..cb9a3c9c19 100644 --- a/src/hooks/DAO/loaders/useFractalModules.ts +++ b/src/hooks/DAO/loaders/useFractalModules.ts @@ -37,7 +37,7 @@ export const useFractalModules = () => { } return safeModule; - }) + }), ); return modules; }, @@ -45,7 +45,7 @@ export const useFractalModules = () => { fractalAzoriusMasterCopyContract, fractalModuleMasterCopyContract, getZodiacModuleProxyMasterCopyData, - ] + ], ); return lookupModules; }; diff --git a/src/hooks/DAO/loaders/useFractalNode.ts b/src/hooks/DAO/loaders/useFractalNode.ts index 5bd8cd4b9c..3c6739686f 100644 --- a/src/hooks/DAO/loaders/useFractalNode.ts +++ b/src/hooks/DAO/loaders/useFractalNode.ts @@ -122,7 +122,7 @@ export const useFractalNode = ({ daoAddress }: { daoAddress?: string }) => { } setNodeLoading(false); }, - [action, safeAPI, lookupModules, fetchSafeInfo, requestWithRetries] + [action, safeAPI, lookupModules, fetchSafeInfo, requestWithRetries], ); const { chain } = useNetwork(); diff --git a/src/hooks/DAO/loaders/useGovernanceContracts.ts b/src/hooks/DAO/loaders/useGovernanceContracts.ts index cc8999e124..8e7481d548 100644 --- a/src/hooks/DAO/loaders/useGovernanceContracts.ts +++ b/src/hooks/DAO/loaders/useGovernanceContracts.ts @@ -43,7 +43,7 @@ export const useGovernanceContracts = () => { if (!!azoriusModuleContract) { const azoriusContract = { asProvider: fractalAzoriusMasterCopyContract.asProvider.attach( - azoriusModuleContract.address + azoriusModuleContract.address, ), asSigner: fractalAzoriusMasterCopyContract.asSigner.attach(azoriusModuleContract.address), }; @@ -61,7 +61,7 @@ export const useGovernanceContracts = () => { const votingContractAddress = ( await azoriusContract.asProvider.getStrategies( '0x0000000000000000000000000000000000000001', - 0 + 0, ) )[1]; @@ -82,7 +82,7 @@ export const useGovernanceContracts = () => { erc721LinearVotingContract = { asSigner: linearVotingERC721MasterCopyContract.asSigner.attach(votingContractAddress!), asProvider: linearVotingERC721MasterCopyContract.asProvider.attach( - votingContractAddress! + votingContractAddress!, ), }; } @@ -99,7 +99,7 @@ export const useGovernanceContracts = () => { const possibleLockRelease = new ethers.Contract( govTokenAddress, LockRelease__factory.abi, - provider + provider, ) as LockRelease; const lockedToken = await possibleLockRelease.token().catch(() => { diff --git a/src/hooks/DAO/loaders/useLoadDAONode.ts b/src/hooks/DAO/loaders/useLoadDAONode.ts index 3dd8cba499..9220fd4426 100644 --- a/src/hooks/DAO/loaders/useLoadDAONode.ts +++ b/src/hooks/DAO/loaders/useLoadDAONode.ts @@ -46,7 +46,7 @@ export const useLoadDAONode = () => { try { const graphNodeInfo = formatDAOQuery( await getDAOInfo({ variables: { daoAddress: _daoAddress } }), - _daoAddress + _daoAddress, ); if (!graphNodeInfo) { logError('graphQL query failed'); @@ -75,7 +75,7 @@ export const useLoadDAONode = () => { return { error: 'errorFailedSearch' }; } }, - [safeAPI, lookupModules, formatDAOQuery, getDAOInfo, getDaoName] + [safeAPI, lookupModules, formatDAOQuery, getDAOInfo, getDaoName], ); return { loadDao }; diff --git a/src/hooks/DAO/proposal/useCastVote.ts b/src/hooks/DAO/proposal/useCastVote.ts index bd2090960c..2c7d7f9a79 100644 --- a/src/hooks/DAO/proposal/useCastVote.ts +++ b/src/hooks/DAO/proposal/useCastVote.ts @@ -57,7 +57,7 @@ const useCastVote = ({ const [contractCallCastVote, contractCallPending] = useTransaction(); const { remainingTokenIds, remainingTokenAddresses } = useUserERC721VotingTokens( - proposal.proposalId + proposal.proposalId, ); const { getCanVote, getHasVoted } = useVoteContext(); @@ -81,7 +81,7 @@ const useCastVote = ({ const handleChangeSnapshotWeightedChoice = useCallback((choiceIndex: number, value: number) => { setSnapshotWeightedChoice(prevState => - prevState.map((choiceValue, index) => (index === choiceIndex ? value : choiceValue)) + prevState.map((choiceValue, index) => (index === choiceIndex ? value : choiceValue)), ); }, []); @@ -96,7 +96,7 @@ const useCastVote = ({ proposal.proposalId, vote, remainingTokenAddresses, - remainingTokenIds + remainingTokenIds, ); } @@ -126,7 +126,7 @@ const useCastVote = ({ remainingTokenIds, getCanVote, getHasVoted, - ] + ], ); const castSnapshotVote = useCallback( @@ -163,7 +163,7 @@ const useCastVote = ({ if (extendedSnapshotProposal.privacy === 'shutter') { const encryptedChoice = await encryptWithShutter( JSON.stringify(choice), - extendedSnapshotProposal.proposalId + extendedSnapshotProposal.proposalId, ); await client.vote(signer.provider as ethers.providers.Web3Provider, address, { space: daoSnapshotSpaceName, @@ -205,7 +205,7 @@ const useCastVote = ({ selectedChoice, snapshotWeightedChoice, client, - ] + ], ); return { diff --git a/src/hooks/DAO/proposal/useCreateProposalTemplate.ts b/src/hooks/DAO/proposal/useCreateProposalTemplate.ts index 6ab5965f88..c39e954220 100644 --- a/src/hooks/DAO/proposal/useCreateProposalTemplate.ts +++ b/src/hooks/DAO/proposal/useCreateProposalTemplate.ts @@ -46,7 +46,7 @@ export default function useCreateProposalTemplate() { } }) .filter(param => param), - })) + })), ), }; @@ -69,7 +69,7 @@ export default function useCreateProposalTemplate() { return proposal; } }, - [proposalTemplates, keyValuePairsContract, client, signerOrProvider] + [proposalTemplates, keyValuePairsContract, client, signerOrProvider], ); return { prepareProposalTemplateProposal }; diff --git a/src/hooks/DAO/proposal/useExecuteProposal.ts b/src/hooks/DAO/proposal/useExecuteProposal.ts index f1bb4548a4..a14a0209e5 100644 --- a/src/hooks/DAO/proposal/useExecuteProposal.ts +++ b/src/hooks/DAO/proposal/useExecuteProposal.ts @@ -43,7 +43,7 @@ export default function useExecuteProposal() { targets, values, data, - operations + operations, ), pendingMessage: t('pendingExecute'), failedMessage: t('failedExecute'), @@ -54,7 +54,7 @@ export default function useExecuteProposal() { }, }); }, - [contractCallExecuteProposal, t, azoriusContract, updateProposalState] + [contractCallExecuteProposal, t, azoriusContract, updateProposalState], ); return { diff --git a/src/hooks/DAO/proposal/useGetMetadata.ts b/src/hooks/DAO/proposal/useGetMetadata.ts index 434af88a0e..edcc6975c9 100644 --- a/src/hooks/DAO/proposal/useGetMetadata.ts +++ b/src/hooks/DAO/proposal/useGetMetadata.ts @@ -21,7 +21,7 @@ interface Transaction { const useGetMultisigMetadata = (proposal: FractalProposal | null | undefined) => { const ipfsClient = useIPFSClient(); const [multisigMetadata, setMultisigMetadata] = useState( - undefined + undefined, ); const [setValue, getValue] = useIndexedDB(DBObjectKeys.DECODED_TRANSACTIONS); @@ -29,7 +29,7 @@ const useGetMultisigMetadata = (proposal: FractalProposal | null | undefined) => if (!proposal) return; const cached: ProposalMetadata = await getValue( - CacheKeys.MULTISIG_METADATA_PREFIX + proposal.proposalId + CacheKeys.MULTISIG_METADATA_PREFIX + proposal.proposalId, ); if (cached) { setMultisigMetadata(cached); diff --git a/src/hooks/DAO/proposal/usePrepareProposal.ts b/src/hooks/DAO/proposal/usePrepareProposal.ts index 245d81062c..48d004d297 100644 --- a/src/hooks/DAO/proposal/usePrepareProposal.ts +++ b/src/hooks/DAO/proposal/usePrepareProposal.ts @@ -21,13 +21,13 @@ export function usePrepareProposal() { return signer!.resolveName(tx.targetAddress); } return tx.targetAddress; - }) + }), ); return { targets, values: transactionsWithEncoding.map(transaction => transaction.ethValue.bigNumberValue!), calldatas: transactionsWithEncoding.map( - transaction => transaction.encodedFunctionData || '' + transaction => transaction.encodedFunctionData || '', ), metaData: { title: proposalMetadata.title, @@ -36,7 +36,7 @@ export function usePrepareProposal() { }, }; }, - [signer] + [signer], ); return { prepareProposal }; } diff --git a/src/hooks/DAO/proposal/useProposals.ts b/src/hooks/DAO/proposal/useProposals.ts index 328b614ce2..7b49703aba 100644 --- a/src/hooks/DAO/proposal/useProposals.ts +++ b/src/hooks/DAO/proposal/useProposals.ts @@ -19,7 +19,7 @@ export default function useProposals({ return proposals.filter(proposal => proposal.state === state).length; } }, - [proposals] + [proposals], ); const sortedAndFilteredProposals = useMemo(() => { diff --git a/src/hooks/DAO/proposal/useRemoveProposalTemplate.ts b/src/hooks/DAO/proposal/useRemoveProposalTemplate.ts index c234b6e271..c1a4207534 100644 --- a/src/hooks/DAO/proposal/useRemoveProposalTemplate.ts +++ b/src/hooks/DAO/proposal/useRemoveProposalTemplate.ts @@ -23,7 +23,7 @@ export default function useRemoveProposalTemplate() { }; const updatedTemplatesList = proposalTemplates.filter( - (_, index: number) => index !== templateIndex + (_, index: number) => index !== templateIndex, ); const { Hash } = await client.add(JSON.stringify(updatedTemplatesList)); @@ -43,7 +43,7 @@ export default function useRemoveProposalTemplate() { return proposal; } }, - [proposalTemplates, keyValuePairsContract, client] + [proposalTemplates, keyValuePairsContract, client], ); return { prepareRemoveProposalTemplateProposal }; diff --git a/src/hooks/DAO/proposal/useSubmitProposal.ts b/src/hooks/DAO/proposal/useSubmitProposal.ts index cdc9c067b3..81c7ce71e2 100644 --- a/src/hooks/DAO/proposal/useSubmitProposal.ts +++ b/src/hooks/DAO/proposal/useSubmitProposal.ts @@ -118,7 +118,7 @@ export default function useSubmitProposal() { )[1]; const votingContract = BaseStrategy__factory.connect( votingContractAddress, - signerOrProvider + signerOrProvider, ); const isProposer = await votingContract.isProposer(user.address); return isProposer; @@ -152,7 +152,7 @@ export default function useSubmitProposal() { lookupModules, safeAPI, signerOrProvider, - ] + ], ); useEffect(() => { const loadCanUserCreateProposal = async () => { @@ -250,8 +250,8 @@ export default function useSubmitProposal() { data, operation, nonce, - } - ) + }, + ), ); await new Promise(resolve => setTimeout(resolve, 1000)); await loadDAOProposals(); @@ -268,7 +268,7 @@ export default function useSubmitProposal() { return; } }, - [signerOrProvider, safeBaseURL, chainId, loadDAOProposals, ipfsClient, multiSendContract] + [signerOrProvider, safeBaseURL, chainId, loadDAOProposals, ipfsClient, multiSendContract], ); const submitAzoriusProposal = useCallback( @@ -313,7 +313,7 @@ export default function useSubmitProposal() { title: proposalData.metaData.title, description: proposalData.metaData.description, documentationUrl: proposalData.metaData.documentationUrl, - }) + }), ) ).wait(); success = true; @@ -339,7 +339,7 @@ export default function useSubmitProposal() { setTimeout(loadDAOProposals, averageBlockTime * 1.5 * 1000); } }, - [loadDAOProposals, provider] + [loadDAOProposals, provider], ); const submitProposal = useCallback( @@ -377,7 +377,7 @@ export default function useSubmitProposal() { const votingStrategyAddress = ( await azoriusModuleContract.getStrategies( '0x0000000000000000000000000000000000000001', - 0 + 0, ) )[1]; submitAzoriusProposal({ @@ -433,7 +433,7 @@ export default function useSubmitProposal() { erc721LinearVotingContract, submitAzoriusProposal, safeAPI, - ] + ], ); return { submitProposal, pendingCreateTx, canUserCreateProposal, getCanUserCreateProposal }; diff --git a/src/hooks/DAO/proposal/useUpdateProposalState.ts b/src/hooks/DAO/proposal/useUpdateProposalState.ts index be4d199208..f7e9090fa4 100644 --- a/src/hooks/DAO/proposal/useUpdateProposalState.ts +++ b/src/hooks/DAO/proposal/useUpdateProposalState.ts @@ -27,7 +27,7 @@ export default function useUpdateProposalState({ payload: { proposalId: proposalId.toString(), state: newState }, }); }, - [azoriusContract, governanceDispatch] + [azoriusContract, governanceDispatch], ); return updateProposalState; diff --git a/src/hooks/DAO/proposal/useUserERC721VotingTokens.ts b/src/hooks/DAO/proposal/useUserERC721VotingTokens.ts index a69365162e..5cfb57df60 100644 --- a/src/hooks/DAO/proposal/useUserERC721VotingTokens.ts +++ b/src/hooks/DAO/proposal/useUserERC721VotingTokens.ts @@ -26,7 +26,7 @@ import { useFractalModules } from '../loaders/useFractalModules'; export default function useUserERC721VotingTokens( proposalId?: string, safeAddress?: string | null, - loadOnMount: boolean = true + loadOnMount: boolean = true, ) { const [totalVotingTokenIds, setTotalVotingTokenIds] = useState([]); const [totalVotingTokenAddresses, setTotalVotingTokenAddresses] = useState([]); @@ -64,7 +64,7 @@ export default function useUserERC721VotingTokens( )[1]; votingContract = LinearERC721Voting__factory.connect( votingContractAddress, - signerOrProvider + signerOrProvider, ); const addresses = await votingContract.getAllTokenAddresses(); govTokens = await Promise.all( @@ -76,14 +76,14 @@ export default function useUserERC721VotingTokens( let totalSupply = undefined; try { const tokenSentEvents = await tokenContract.queryFilter( - tokenContract.filters.Transfer(null, null) + tokenContract.filters.Transfer(null, null), ); totalSupply = BigNumber.from(tokenSentEvents.length); } catch (e) { logError('Error while getting ERC721 total supply'); } return { name, symbol, address: tokenAddress, votingWeight, totalSupply }; - }) + }), ); } } @@ -109,15 +109,15 @@ export default function useUserERC721VotingTokens( const tokenContract = ERC721__factory.connect(token.address, signerOrProvider); if ((await tokenContract.balanceOf(user.address!)).gt(0)) { const tokenSentEvents = await tokenContract.queryFilter( - tokenContract.filters.Transfer(user.address!, null) + tokenContract.filters.Transfer(user.address!, null), ); const tokenReceivedEvents = await tokenContract.queryFilter( - tokenContract.filters.Transfer(null, user.address) + tokenContract.filters.Transfer(null, user.address), ); const allEvents = tokenSentEvents .concat(tokenReceivedEvents) .sort( - (a, b) => a.blockNumber - b.blockNumber || a.transactionIndex - b.transactionIndex + (a, b) => a.blockNumber - b.blockNumber || a.transactionIndex - b.transactionIndex, ); const ownedTokenIds = new Set(); @@ -132,7 +132,7 @@ export default function useUserERC721VotingTokens( userERC721Tokens.set(token.address, ownedTokenIds); } } - }) + }), ); const tokenIdsSets = [...userERC721Tokens.values()]; @@ -154,16 +154,16 @@ export default function useUserERC721VotingTokens( const tokenVoted = await votingContract!.hasVoted( _proposalId, tokenAddress, - tokenId + tokenId, ); if (!tokenVoted) { tokenAddresses.push(tokenAddress); tokenIds.push(tokenId); } } - }) + }), ); - }) + }), ); return { @@ -181,7 +181,7 @@ export default function useUserERC721VotingTokens( safeAPI, daoAddress, user.address, - ] + ], ); const loadUserERC721VotingTokens = useCallback(async () => { diff --git a/src/hooks/DAO/useBuildDAOTx.ts b/src/hooks/DAO/useBuildDAOTx.ts index 41c74bb4af..99ad2d0183 100644 --- a/src/hooks/DAO/useBuildDAOTx.ts +++ b/src/hooks/DAO/useBuildDAOTx.ts @@ -51,7 +51,7 @@ const useBuildDAOTx = () => { async ( daoData: AzoriusERC20DAO | AzoriusERC721DAO | SafeMultisigDAO, parentAddress?: string, - parentTokenAddress?: string + parentTokenAddress?: string, ) => { let azoriusContracts; @@ -126,7 +126,7 @@ const useBuildDAOTx = () => { daoData, fallbackHandler, parentAddress, - parentTokenAddress + parentTokenAddress, ); await txBuilderFactory.setupSafeData(); @@ -146,7 +146,7 @@ const useBuildDAOTx = () => { const daoTxBuilder = txBuilderFactory.createDaoTxBuilder( parentVotingStrategyType, - parentVotingStrategyAddress + parentVotingStrategyAddress, ); // Build Tx bundle based on governance type (Azorius or Multisig) @@ -188,7 +188,7 @@ const useBuildDAOTx = () => { governance, createOptions, fallbackHandler, - ] + ], ); return [buildDao] as const; diff --git a/src/hooks/DAO/useCastFreezeVote.ts b/src/hooks/DAO/useCastFreezeVote.ts index c49d23a03b..ae5513949a 100644 --- a/src/hooks/DAO/useCastFreezeVote.ts +++ b/src/hooks/DAO/useCastFreezeVote.ts @@ -37,7 +37,7 @@ const useCastFreezeVote = ({ return getUserERC721VotingTokens(undefined, parentAddress).then(tokensInfo => (freezeVotingContract?.asSigner as ERC721FreezeVoting)[ 'castFreezeVote(address[],uint256[])' - ](tokensInfo.totalVotingTokenAddresses, tokensInfo.totalVotingTokenIds) + ](tokensInfo.totalVotingTokenAddresses, tokensInfo.totalVotingTokenIds), ); } else { return ( diff --git a/src/hooks/DAO/useClawBack.ts b/src/hooks/DAO/useClawBack.ts index 312dc8f388..bcb711c246 100644 --- a/src/hooks/DAO/useClawBack.ts +++ b/src/hooks/DAO/useClawBack.ts @@ -21,13 +21,13 @@ export default function useClawBack({ childSafeInfo, parentAddress }: IUseClawBa const handleClawBack = useCallback(async () => { if (childSafeInfo && childSafeInfo.daoAddress && parentAddress) { const childSafeBalance = await safeAPI.getBalances( - utils.getAddress(childSafeInfo.daoAddress) + utils.getAddress(childSafeInfo.daoAddress), ); const parentSafeInfo = await safeAPI.getSafeInfo(utils.getAddress(parentAddress)); if (canUserCreateProposal && parentAddress && childSafeInfo && parentSafeInfo) { const abiCoder = new ethers.utils.AbiCoder(); const fractalModule = childSafeInfo.fractalModules!.find( - module => module.moduleType === FractalModuleType.FRACTAL + module => module.moduleType === FractalModuleType.FRACTAL, ); const fractalModuleContract = fractalModule?.moduleContract as FractalModule; if (fractalModule) { @@ -36,11 +36,11 @@ export default function useClawBack({ childSafeInfo, parentAddress }: IUseClawBa // Seems like we're operating with native coin i.e ETH const txData = abiCoder.encode( ['address', 'uint256', 'bytes', 'uint8'], - [parentAddress, asset.balance, '0x', 0] + [parentAddress, asset.balance, '0x', 0], ); const fractalModuleCalldata = fractalModuleContract.interface.encodeFunctionData( 'execTx', - [txData] + [txData], ); return { target: fractalModuleContract.address, @@ -55,11 +55,11 @@ export default function useClawBack({ childSafeInfo, parentAddress }: IUseClawBa ]); const txData = abiCoder.encode( ['address', 'uint256', 'bytes', 'uint8'], - [asset.tokenAddress, 0, clawBackCalldata, 0] + [asset.tokenAddress, 0, clawBackCalldata, 0], ); const fractalModuleCalldata = fractalModuleContract.interface.encodeFunctionData( 'execTx', - [txData] + [txData], ); return { @@ -78,7 +78,7 @@ export default function useClawBack({ childSafeInfo, parentAddress }: IUseClawBa 'Transfer all funds from the targeted sub-Safe to the parent-Safe treasury.', { ns: 'proposalMetadata', - } + }, ), documentationUrl: '', }, diff --git a/src/hooks/DAO/useCreateSubDAOProposal.ts b/src/hooks/DAO/useCreateSubDAOProposal.ts index e0ea6c89ad..49c97c9386 100644 --- a/src/hooks/DAO/useCreateSubDAOProposal.ts +++ b/src/hooks/DAO/useCreateSubDAOProposal.ts @@ -24,7 +24,7 @@ export const useCreateSubDAOProposal = () => { ( daoData: AzoriusERC20DAO | AzoriusERC721DAO | SafeMultisigDAO, nonce: number | undefined, - successCallback: (daoAddress: string) => void + successCallback: (daoAddress: string) => void, ) => { const propose = async () => { if (!multiSendContract || !fractalRegistryContract || !daoAddress) { @@ -75,7 +75,7 @@ export const useCreateSubDAOProposal = () => { submitProposal, azoriusGovernance, t, - ] + ], ); return { proposeDao, pendingCreateTx, canUserCreateProposal } as const; diff --git a/src/hooks/DAO/useDAOData.ts b/src/hooks/DAO/useDAOData.ts index fe0c683160..7facd9f788 100644 --- a/src/hooks/DAO/useDAOData.ts +++ b/src/hooks/DAO/useDAOData.ts @@ -33,7 +33,7 @@ export function useLoadDAOData(fractalNode?: FractalNode, parentAddress?: string let freezeGuardContracts: FractalGuardContracts | undefined = await loadFractalGuardContracts( daoAddress, safe, - fractalModules + fractalModules, ); if (!freezeGuardContracts) { diff --git a/src/hooks/DAO/useDAOName.ts b/src/hooks/DAO/useDAOName.ts index 8b22092a6e..e990f6ea73 100644 --- a/src/hooks/DAO/useDAOName.ts +++ b/src/hooks/DAO/useDAOName.ts @@ -116,7 +116,7 @@ export function useLazyDAOName() { return createAccountSubstring(_address); }, - [getValue, setValue, provider] + [getValue, setValue, provider], ); return { getDaoName }; diff --git a/src/hooks/DAO/useDelegateVote.ts b/src/hooks/DAO/useDelegateVote.ts index 1a6140e935..eb80711927 100644 --- a/src/hooks/DAO/useDelegateVote.ts +++ b/src/hooks/DAO/useDelegateVote.ts @@ -27,7 +27,7 @@ const useDelegateVote = () => { successCallback, }); }, - [contractCallDelegateVote, t] + [contractCallDelegateVote, t], ); return { delegateVote, contractCallPending }; diff --git a/src/hooks/DAO/useDeployAzorius.ts b/src/hooks/DAO/useDeployAzorius.ts index a992afe8c8..2f6f1dca4e 100644 --- a/src/hooks/DAO/useDeployAzorius.ts +++ b/src/hooks/DAO/useDeployAzorius.ts @@ -51,7 +51,7 @@ const useDeployAzorius = () => { async ( daoData: AzoriusERC20DAO | AzoriusERC721DAO, shouldSetName?: boolean, - shouldSetSnapshot?: boolean + shouldSetSnapshot?: boolean, ) => { if (!daoAddress || !canUserCreateProposal || !safe) { return; @@ -87,7 +87,7 @@ const useDeployAzorius = () => { daoData, fallbackHandler, undefined, - undefined + undefined, ); txBuilderFactory.setSafeContract(daoAddress); @@ -148,7 +148,7 @@ const useDeployAzorius = () => { push, safe, fallbackHandler, - ] + ], ); return deployAzorius; diff --git a/src/hooks/DAO/useDeployDAO.ts b/src/hooks/DAO/useDeployDAO.ts index 063fbc29f1..6fd30af266 100644 --- a/src/hooks/DAO/useDeployDAO.ts +++ b/src/hooks/DAO/useDeployDAO.ts @@ -18,7 +18,7 @@ const useDeployDAO = () => { const deployDao = useCallback( ( daoData: SafeMultisigDAO | AzoriusERC20DAO | AzoriusERC721DAO, - successCallback: (daoAddress: string) => void + successCallback: (daoAddress: string) => void, ) => { const deploy = async () => { if (!multiSendContract) { @@ -43,7 +43,7 @@ const useDeployDAO = () => { deploy(); }, - [build, contractCallDeploy, multiSendContract, t] + [build, contractCallDeploy, multiSendContract, t], ); return [deployDao, contractCallPending] as const; diff --git a/src/hooks/DAO/useSearchDao.ts b/src/hooks/DAO/useSearchDao.ts index 5c0081ff06..085f71a671 100644 --- a/src/hooks/DAO/useSearchDao.ts +++ b/src/hooks/DAO/useSearchDao.ts @@ -30,7 +30,7 @@ export const useSearchDao = () => { } else { if (isValidAddress) { setErrorMessage( - t('errorFailedSearch', { chain: chain ? chain.name : disconnectedChain.name }) + t('errorFailedSearch', { chain: chain ? chain.name : disconnectedChain.name }), ); } else { setErrorMessage(t('errorInvalidSearch')); diff --git a/src/hooks/safe/useSafeContracts.ts b/src/hooks/safe/useSafeContracts.ts index affd66fa51..cb70ca6c8c 100644 --- a/src/hooks/safe/useSafeContracts.ts +++ b/src/hooks/safe/useSafeContracts.ts @@ -98,7 +98,7 @@ export default function useSafeContracts() { const multisigFreezeGuardMasterCopyContract = { asSigner: MultisigFreezeGuard__factory.connect( multisigFreezeGuardMasterCopy, - signerOrProvider + signerOrProvider, ), asProvider: MultisigFreezeGuard__factory.connect(multisigFreezeGuardMasterCopy, provider), }; @@ -111,7 +111,7 @@ export default function useSafeContracts() { const freezeMultisigVotingMasterCopyContract = { asSigner: MultisigFreezeVoting__factory.connect( multisigFreezeVotingMasterCopy, - signerOrProvider + signerOrProvider, ), asProvider: MultisigFreezeVoting__factory.connect(multisigFreezeVotingMasterCopy, provider), }; diff --git a/src/hooks/schemas/DAOCreate/useDAOCreateSchema.ts b/src/hooks/schemas/DAOCreate/useDAOCreateSchema.ts index f6c285114d..5977821944 100644 --- a/src/hooks/schemas/DAOCreate/useDAOCreateSchema.ts +++ b/src/hooks/schemas/DAOCreate/useDAOCreateSchema.ts @@ -51,7 +51,7 @@ export const useDAOCreateSchema = ({ isSubDAO }: { isSubDAO?: boolean }) => { is: (array: string[]) => array && array.length > 1, then: schema => schema.of( - Yup.string().test(addressValidationTest).test(uniqueAddressValidationTest) + Yup.string().test(addressValidationTest).test(uniqueAddressValidationTest), ), otherwise: schema => schema.of(Yup.string().test(addressValidationTest)), }), @@ -108,7 +108,7 @@ export const useDAOCreateSchema = ({ isSubDAO }: { isSubDAO?: boolean }) => { .shape({ value: Yup.string().test(maxAllocationValidation), }), - }) + }), ), }), }), @@ -130,7 +130,7 @@ export const useDAOCreateSchema = ({ isSubDAO }: { isSubDAO?: boolean }) => { .shape({ value: Yup.string().test(isBigNumberValidation).test(minValueValidation(1)), // Otherwise "0" treated as proper value }), - }) + }), ), }), }), @@ -171,7 +171,7 @@ export const useDAOCreateSchema = ({ isSubDAO }: { isSubDAO?: boolean }) => { uniqueNFTAddressValidationTest, minValueValidation, isBigNumberValidation, - ] + ], ); return { createDAOValidation }; }; diff --git a/src/hooks/schemas/DAOCreate/useDAOCreateTests.ts b/src/hooks/schemas/DAOCreate/useDAOCreateTests.ts index e5e3debbd4..f26c8d1f4b 100644 --- a/src/hooks/schemas/DAOCreate/useDAOCreateTests.ts +++ b/src/hooks/schemas/DAOCreate/useDAOCreateTests.ts @@ -38,7 +38,7 @@ export function useDAOCreateTests() { }, }; }, - [t] + [t], ); const allocationValidationTest = useMemo(() => { @@ -88,11 +88,11 @@ export function useDAOCreateTests() { return validation.address; } return address; - }) + }), ); const uniqueFilter = resolvedAddresses.filter( - address => address === value || address === inputValidation?.address + address => address === value || address === inputValidation?.address, ); return uniqueFilter.length === 1; }, @@ -113,12 +113,12 @@ export function useDAOCreateTests() { const filteredAllocations = tokenAllocations.filter( allocation => - allocation.amount.bigNumberValue && !allocation.amount.bigNumberValue.isZero() + allocation.amount.bigNumberValue && !allocation.amount.bigNumberValue.isZero(), ); const allocationSum = filteredAllocations.reduce( (prev, cur) => prev.add(cur.amount.bigNumberValue!), - BigNumber.from(0) + BigNumber.from(0), ); const totalAllocation = allocationSum.add(parentAllocationAmount); diff --git a/src/hooks/schemas/common/useValidationAddress.tsx b/src/hooks/schemas/common/useValidationAddress.tsx index 66438fcaba..e03d24230c 100644 --- a/src/hooks/schemas/common/useValidationAddress.tsx +++ b/src/hooks/schemas/common/useValidationAddress.tsx @@ -162,16 +162,16 @@ export const useValidationAddress = () => { return validation.address; } return address; - }) + }), ); const uniqueFilter = resolvedAddresses.filter( - address => address === value || address === inputValidation?.address + address => address === value || address === inputValidation?.address, ); return uniqueFilter.length === 1; }, - [signerOrProvider] + [signerOrProvider], ); const uniqueAddressValidationTest = useMemo(() => { @@ -196,7 +196,7 @@ export const useValidationAddress = () => { if (!value) return false; // retreive parent array const parentAddressArray = context.from[1].value.nfts.map( - ({ tokenAddress }: ERC721TokenConfig) => tokenAddress + ({ tokenAddress }: ERC721TokenConfig) => tokenAddress, ); const isUnique = await testUniqueAddressArray(value, parentAddressArray); return isUnique; diff --git a/src/hooks/schemas/createProposalTemplate/useCreateProposalTemplateSchema.ts b/src/hooks/schemas/createProposalTemplate/useCreateProposalTemplateSchema.ts index feb2183eff..9da83d62af 100644 --- a/src/hooks/schemas/createProposalTemplate/useCreateProposalTemplateSchema.ts +++ b/src/hooks/schemas/createProposalTemplate/useCreateProposalTemplateSchema.ts @@ -17,7 +17,7 @@ const useCreateProposalTemplateSchema = () => { const labelOrValueValidationTest: Yup.TestFunction = ( _, - context + context, ) => { if (!context.parent.signature) { return true; @@ -55,9 +55,9 @@ const useCreateProposalTemplateSchema = () => { message: t('labelOrValueRequired'), test: labelOrValueValidationTest, }), - }) + }), ), - }) + }), ), proposalTemplateMetadata: Yup.object().shape({ title: Yup.string().trim().required().max(50), @@ -67,7 +67,7 @@ const useCreateProposalTemplateSchema = () => { .required() .moreThan((!!safe && safe.nonce - 1) || 0), }), - [addressValidationTest, t, safe] + [addressValidationTest, t, safe], ); return { createProposalTemplateValidation }; }; diff --git a/src/hooks/schemas/proposalCreate/useCreateProposalSchema.ts b/src/hooks/schemas/proposalCreate/useCreateProposalSchema.ts index cddd7d8350..7389278cd8 100644 --- a/src/hooks/schemas/proposalCreate/useCreateProposalSchema.ts +++ b/src/hooks/schemas/proposalCreate/useCreateProposalSchema.ts @@ -39,13 +39,13 @@ export const useCreateProposalSchema = () => { const encodedFunction = encodeFunction( functionName, functionSignature, - parameters + parameters, ); return !!encodedFunction; }, }), - }) + }), ), proposalMetadata: Yup.object().shape({ title: Yup.string().notRequired(), @@ -69,7 +69,7 @@ export const useCreateProposalSchema = () => { }), nonce: Yup.number(), }), - [addressValidationTest, t] + [addressValidationTest, t], ); return { createProposalValidation }; }; diff --git a/src/hooks/stake/lido/useLidoStaking.ts b/src/hooks/stake/lido/useLidoStaking.ts index 5b853f4c82..2948f246b4 100644 --- a/src/hooks/stake/lido/useLidoStaking.ts +++ b/src/hooks/stake/lido/useLidoStaking.ts @@ -46,7 +46,7 @@ export default function useLidoStaking() { failedToastMessage: t('proposalCreateFailureToastMessage'), }); }, - [lido, signerOrProvider, daoAddress, safe, submitProposal, t] + [lido, signerOrProvider, daoAddress, safe, submitProposal, t], ); const handleUnstake = useCallback( @@ -58,14 +58,14 @@ export default function useLidoStaking() { const stETHContract = getSTETHContract(lido.stETHContractAddress, signerOrProvider); const withdrawalQueueContract = getWithdrawalQueueContract( lido.withdrawalQueueContractAddress, - signerOrProvider + signerOrProvider, ); const proposalData: ProposalExecuteData = { metaData: { title: t('Unstake stETH'), description: t( - 'This proposal will unstake your stETH from Lido and mint a Lido Withdrawal NFT which can be used to claim your ETH.' + 'This proposal will unstake your stETH from Lido and mint a Lido Withdrawal NFT which can be used to claim your ETH.', ), documentationUrl: 'https://docs.lido.fi/guides/steth-integration-guide#request-withdrawal-and-mint-nft', @@ -91,7 +91,7 @@ export default function useLidoStaking() { failedToastMessage: t('proposalCreateFailureToastMessage'), }); }, - [lido, daoAddress, safe, submitProposal, t, signerOrProvider] + [lido, daoAddress, safe, submitProposal, t, signerOrProvider], ); const handleClaimUnstakedETH = useCallback( @@ -103,14 +103,14 @@ export default function useLidoStaking() { const withdrawalQueueContract = getWithdrawalQueueContract( lido.withdrawalQueueContractAddress, - signerOrProvider + signerOrProvider, ); const proposalData: ProposalExecuteData = { metaData: { title: t('Lido Withdrawal'), description: t( - 'This proposal will burn your Lido Withdrawal NFT and return the ETH to your Safe.' + 'This proposal will burn your Lido Withdrawal NFT and return the ETH to your Safe.', ), documentationUrl: 'https://docs.lido.fi/guides/steth-integration-guide#claiming', }, @@ -128,7 +128,7 @@ export default function useLidoStaking() { failedToastMessage: t('proposalCreateFailureToastMessage'), }); }, - [lido, daoAddress, safe, submitProposal, t, signerOrProvider] + [lido, daoAddress, safe, submitProposal, t, signerOrProvider], ); return { handleStake, handleUnstake, handleClaimUnstakedETH }; diff --git a/src/hooks/toasts/useActionToast.tsx b/src/hooks/toasts/useActionToast.tsx index e9f130cf1b..049f518b15 100644 --- a/src/hooks/toasts/useActionToast.tsx +++ b/src/hooks/toasts/useActionToast.tsx @@ -57,7 +57,7 @@ export function useActionToast({ draggable: false, progress: 1, toastId, - } + }, ); }, [buttonOnClick, buttonTranslationKey, isVisible, t, testId, titleTranslationKey, toastId]); } diff --git a/src/hooks/utils/cache/useLocalDB.ts b/src/hooks/utils/cache/useLocalDB.ts index 5be2d838f6..5369e25ecd 100644 --- a/src/hooks/utils/cache/useLocalDB.ts +++ b/src/hooks/utils/cache/useLocalDB.ts @@ -60,7 +60,7 @@ export const setIndexedDBValue = async ( key: string, value: any, chainId: number, - expirationMinutes: number = CacheExpiry.ONE_WEEK + expirationMinutes: number = CacheExpiry.ONE_WEEK, ): Promise => { const val: IStorageValue = { v: value, @@ -86,7 +86,7 @@ export const setIndexedDBValue = async ( export const getIndexedDBValue = async ( storeKey: string, key: string, - chainId: number + chainId: number, ): Promise => { return withIndexedDB(async db => { let transaction: IDBTransaction, store: IDBObjectStore, request: IDBRequest; @@ -137,14 +137,14 @@ export const useIndexedDB = (objectStoreName: string) => { async (key: string, value: any, expirationMinutes: number = CacheExpiry.ONE_WEEK) => { await setIndexedDBValue(objectStoreName, key, value, chainId, expirationMinutes); }, - [chainId, objectStoreName] + [chainId, objectStoreName], ); const get = useCallback( async (key: string) => { return getIndexedDBValue(objectStoreName, key, chainId); }, - [chainId, objectStoreName] + [chainId, objectStoreName], ); return [set, get] as const; diff --git a/src/hooks/utils/cache/useLocalStorage.ts b/src/hooks/utils/cache/useLocalStorage.ts index 991fa803a8..4d3400ad7d 100644 --- a/src/hooks/utils/cache/useLocalStorage.ts +++ b/src/hooks/utils/cache/useLocalStorage.ts @@ -6,7 +6,7 @@ export const setValue = ( key: string, value: any, chainId: number, - expirationMinutes: number = CacheExpiry.ONE_WEEK + expirationMinutes: number = CacheExpiry.ONE_WEEK, ): void => { if (typeof window !== 'undefined') { const val: IStorageValue = { @@ -62,14 +62,14 @@ export const useLocalStorage = () => { (key: string, value: any, expirationMinutes: number = CacheExpiry.ONE_WEEK) => { setValue(key, value, chainId, expirationMinutes); }, - [chainId] + [chainId], ); const get = useCallback( (key: string) => { return getValue(key, chainId); }, - [chainId] + [chainId], ); return { setValue: set, getValue: get }; diff --git a/src/hooks/utils/useAddress.ts b/src/hooks/utils/useAddress.ts index 90b95fa9fa..b6267553a4 100644 --- a/src/hooks/utils/useAddress.ts +++ b/src/hooks/utils/useAddress.ts @@ -90,7 +90,7 @@ const useAddress = (addressInput: string | undefined) => { setValue( CacheKeys.ENS_RESOLVE_PREFIX + addressInput, resolvedAddress, - CacheExpiry.ONE_WEEK + CacheExpiry.ONE_WEEK, ); setAddress(resolvedAddress); setIsValidAddress(true); diff --git a/src/hooks/utils/useApproval.tsx b/src/hooks/utils/useApproval.tsx index 95033b765a..681a41a0c6 100644 --- a/src/hooks/utils/useApproval.tsx +++ b/src/hooks/utils/useApproval.tsx @@ -8,7 +8,7 @@ import { useTransaction } from './useTransaction'; const useApproval = ( tokenContract?: VotesERC20 | VotesERC20Wrapper, spenderAddress?: string, - userBalance?: BigNumber + userBalance?: BigNumber, ) => { const { address: account } = useAccount(); const [allowance, setAllowance] = useState(BigNumber.from(0)); diff --git a/src/hooks/utils/useAsyncRequest.ts b/src/hooks/utils/useAsyncRequest.ts index 291982fa58..1264a845dc 100644 --- a/src/hooks/utils/useAsyncRequest.ts +++ b/src/hooks/utils/useAsyncRequest.ts @@ -51,7 +51,7 @@ export const useAsyncRequest = () => { setPending(false); } }, - [] + [], ); return [asyncRequestFunc, pending] as const; diff --git a/src/hooks/utils/useAsyncRetry.ts b/src/hooks/utils/useAsyncRetry.ts index 2781cbffdd..9ed3f83345 100644 --- a/src/hooks/utils/useAsyncRetry.ts +++ b/src/hooks/utils/useAsyncRetry.ts @@ -8,7 +8,7 @@ function sleep(ms: number) { export type RequestWithRetries = ( func: () => Promise, retries: number, - secondsToWait?: number + secondsToWait?: number, ) => Promise; export function useAsyncRetry() { @@ -35,7 +35,7 @@ export function useAsyncRetry() { return result; }, - [] + [], ); return { requestWithRetries }; diff --git a/src/hooks/utils/useCurrentBlockNumber.ts b/src/hooks/utils/useCurrentBlockNumber.ts index f2c9cee9f1..652ec3811f 100644 --- a/src/hooks/utils/useCurrentBlockNumber.ts +++ b/src/hooks/utils/useCurrentBlockNumber.ts @@ -13,7 +13,7 @@ const useCurrentBlockNumber = () => { setIsLoaded(true); } }, - [isLoaded] + [isLoaded], ); useEffect(() => { diff --git a/src/hooks/utils/useEthersProvider.ts b/src/hooks/utils/useEthersProvider.ts index c041144bbf..50cba5eaee 100644 --- a/src/hooks/utils/useEthersProvider.ts +++ b/src/hooks/utils/useEthersProvider.ts @@ -16,8 +16,8 @@ export function useEthersProvider() { if (transport.type === 'fallback') { return new providers.FallbackProvider( (transport.transports as ReturnType[]).map( - ({ value }) => new providers.JsonRpcProvider(value?.url, network) - ) + ({ value }) => new providers.JsonRpcProvider(value?.url, network), + ), ); } return new providers.JsonRpcProvider(transport.url, network); diff --git a/src/hooks/utils/useMasterCopy.ts b/src/hooks/utils/useMasterCopy.ts index 35d0a27ab9..6183f19cfb 100644 --- a/src/hooks/utils/useMasterCopy.ts +++ b/src/hooks/utils/useMasterCopy.ts @@ -24,43 +24,43 @@ export function useMasterCopy() { const isOzLinearVoting = useCallback( (masterCopyAddress: string | `0x${string}`) => masterCopyAddress === linearVotingMasterCopyContract.asProvider.address, - [linearVotingMasterCopyContract] + [linearVotingMasterCopyContract], ); const isOzLinearVotingERC721 = useCallback( (masterCopyAddress: string | `0x${string}`) => masterCopyAddress === linearVotingERC721MasterCopyContract.asProvider.address, - [linearVotingERC721MasterCopyContract] + [linearVotingERC721MasterCopyContract], ); const isMultisigFreezeGuard = useCallback( (masterCopyAddress: string | `0x${string}`) => masterCopyAddress === multisigFreezeGuardMasterCopyContract.asProvider.address, - [multisigFreezeGuardMasterCopyContract] + [multisigFreezeGuardMasterCopyContract], ); const isMultisigFreezeVoting = useCallback( (masterCopyAddress: string | `0x${string}`) => masterCopyAddress === freezeMultisigVotingMasterCopyContract.asProvider.address, - [freezeMultisigVotingMasterCopyContract] + [freezeMultisigVotingMasterCopyContract], ); const isERC721FreezeVoting = useCallback( (masterCopyAddress: string | `0x${string}`) => masterCopyAddress === freezeERC721VotingMasterCopyContract.asProvider.address, - [freezeERC721VotingMasterCopyContract] + [freezeERC721VotingMasterCopyContract], ); const isAzorius = useCallback( (masterCopyAddress: string | `0x${string}`) => masterCopyAddress === fractalAzoriusMasterCopyContract.asProvider.address, - [fractalAzoriusMasterCopyContract] + [fractalAzoriusMasterCopyContract], ); const isFractalModule = useCallback( (masterCopyAddress: string | `0x${string}`) => masterCopyAddress === fractalModuleMasterCopyContract.asProvider.address, - [fractalModuleMasterCopyContract] + [fractalModuleMasterCopyContract], ); const getMasterCopyAddress = useCallback( async function ( contract: Contract, - proxyAddress: string | `0x${string}` + proxyAddress: string | `0x${string}`, ): Promise<[string, string | null]> { const cachedValue = getValue(CacheKeys.MASTER_COPY_PREFIX + proxyAddress); if (cachedValue) return [cachedValue, null] as const; @@ -75,7 +75,7 @@ export function useMasterCopy() { return [masterCopyAddress, null] as const; }); }, - [getValue, setValue] + [getValue, setValue], ); const getZodiacModuleProxyMasterCopyData = useCallback( @@ -106,7 +106,7 @@ export function useMasterCopy() { isMultisigFreezeVoting, isOzLinearVoting, isOzLinearVotingERC721, - ] + ], ); return { getZodiacModuleProxyMasterCopyData }; diff --git a/src/hooks/utils/useSafeDecoder.tsx b/src/hooks/utils/useSafeDecoder.tsx index 163c8077b6..70c98b6a99 100644 --- a/src/hooks/utils/useSafeDecoder.tsx +++ b/src/hooks/utils/useSafeDecoder.tsx @@ -31,7 +31,7 @@ export const useSafeDecoder = () => { } const cachedTransactions = await getValue( - CacheKeys.DECODED_TRANSACTION_PREFIX + solidityKeccak256(['string'], [to + data]) + CacheKeys.DECODED_TRANSACTION_PREFIX + solidityKeccak256(['string'], [to + data]), ); if (cachedTransactions) return cachedTransactions; @@ -75,12 +75,12 @@ export const useSafeDecoder = () => { await setValue( CacheKeys.DECODED_TRANSACTION_PREFIX + solidityKeccak256(['string'], [to + data]), - decoded + decoded, ); return decoded; }, - [getValue, safeBaseURL, setValue] + [getValue, safeBaseURL, setValue], ); return decode; diff --git a/src/hooks/utils/useSafeTransactions.ts b/src/hooks/utils/useSafeTransactions.ts index 657f9b663e..8495b979a9 100644 --- a/src/hooks/utils/useSafeTransactions.ts +++ b/src/hooks/utils/useSafeTransactions.ts @@ -39,7 +39,7 @@ export const useSafeTransactions = () => { async ( activities: Activity[], freezeGuard?: MultisigFreezeGuard, - freezeGuardData?: FreezeGuardData + freezeGuardData?: FreezeGuardData, ) => { if (freezeGuard && freezeGuardData) { return Promise.all( @@ -103,7 +103,7 @@ export const useSafeTransactions = () => { } } return { ...activity, state }; - }) + }), ); } else { return activities.map((activity, _, activityArr) => { @@ -134,7 +134,7 @@ export const useSafeTransactions = () => { }); } }, - [provider] + [provider], ); const getTransferTotal = useCallback( @@ -181,10 +181,10 @@ export const useSafeTransactions = () => { return prev; }, - new Map() + new Map(), ); }, - [nativeTokenSymbol] + [nativeTokenSymbol], ); const parseTransactions = useCallback( @@ -203,7 +203,7 @@ export const useSafeTransactions = () => { // @note for ethereum transactions event these are the execution date const eventDate = new Date( - multiSigTransaction.submissionDate || ethereumTransaction.executionDate + multiSigTransaction.submissionDate || ethereumTransaction.executionDate, ); // @note it can be assumed that if there is no transfers it a receiving event @@ -220,12 +220,12 @@ export const useSafeTransactions = () => { const totalAmount = formatWeiToValue(token.bn, token.decimals); const symbol = token.symbol; return `${totalAmount} ${symbol}`; - } + }, ); // maps address for each transfer const transferAddresses = transaction.transfers.map(transfer => - transfer.to.toLowerCase() === daoAddress.toLowerCase() ? transfer.from : transfer.to + transfer.to.toLowerCase() === daoAddress.toLowerCase() ? transfer.from : transfer.to, ); // @note this indentifies transaction a simple ETH transfer @@ -237,7 +237,7 @@ export const useSafeTransactions = () => { if (isEthSend) { transferAmountTotals.push( - `${formatWeiToValue(multiSigTransaction.value, 18)} ${nativeTokenSymbol}` + `${formatWeiToValue(multiSigTransaction.value, 18)} ${nativeTokenSymbol}`, ); transferAddresses.push(multiSigTransaction.to); } @@ -247,8 +247,8 @@ export const useSafeTransactions = () => { const eventType: any = isMultiSigTransaction ? ActivityEventType.Governance : isModuleTransaction - ? ActivityEventType.Module - : ActivityEventType.Treasury; + ? ActivityEventType.Module + : ActivityEventType.Treasury; const eventNonce = multiSigTransaction.nonce; @@ -277,14 +277,14 @@ export const useSafeTransactions = () => { ? { decodedTransactions: parseDecodedData( multiSigTransaction, - isMultiSigTransaction || isModuleTransaction + isMultiSigTransaction || isModuleTransaction, ), } : { decodedTransactions: await decode( multiSigTransaction.value, multiSigTransaction.to, - multiSigTransaction.data + multiSigTransaction.data, ), }; @@ -313,7 +313,7 @@ export const useSafeTransactions = () => { nonce: eventNonce, }; return activity; - }) + }), ); let freezeGuard: MultisigFreezeGuard | undefined; let freezeGuardData: FreezeGuardData | undefined; @@ -324,10 +324,10 @@ export const useSafeTransactions = () => { freezeGuard = guardContracts.freezeGuardContract.asProvider as MultisigFreezeGuard; freezeGuardData = { guardTimelockPeriodMs: BigNumber.from( - (await freezeGuard.timelockPeriod()) * averageBlockTime * 1000 + (await freezeGuard.timelockPeriod()) * averageBlockTime * 1000, ), guardExecutionPeriodMs: BigNumber.from( - (await freezeGuard.executionPeriod()) * averageBlockTime * 1000 + (await freezeGuard.executionPeriod()) * averageBlockTime * 1000, ), lastBlock: await provider.getBlock(blockNumber), }; @@ -343,7 +343,7 @@ export const useSafeTransactions = () => { decode, nativeTokenSymbol, provider, - ] + ], ); return { parseTransactions }; }; diff --git a/src/hooks/utils/useTimeHelpers.tsx b/src/hooks/utils/useTimeHelpers.tsx index f4f55cde67..735955ff7b 100644 --- a/src/hooks/utils/useTimeHelpers.tsx +++ b/src/hooks/utils/useTimeHelpers.tsx @@ -11,7 +11,7 @@ export const useTimeHelpers = () => { if (Number(_seconds) === 0) return t('noTimeDifference'); return formatDistance(0, Number(_seconds) * 1000, { includeSeconds: true, locale: locale }); }, - [locale, t] + [locale, t], ); return { getTimeDuration }; }; diff --git a/src/hooks/utils/useTransaction.ts b/src/hooks/utils/useTransaction.ts index 00da4cad8f..1e0cb1c4ed 100644 --- a/src/hooks/utils/useTransaction.ts +++ b/src/hooks/utils/useTransaction.ts @@ -73,7 +73,7 @@ const useTransaction = () => { toast.error(t('errorGeneral', { ns: 'common' })); }); }, - [t] + [t], ); return [contractCall, pending] as const; diff --git a/src/hooks/utils/useUpdateTimer.tsx b/src/hooks/utils/useUpdateTimer.tsx index cb9230b9a0..a75b70877b 100644 --- a/src/hooks/utils/useUpdateTimer.tsx +++ b/src/hooks/utils/useUpdateTimer.tsx @@ -36,7 +36,7 @@ export const useUpdateTimer = (safeAddress?: string | null) => { timers.current.set(methodKey, intervalId); return returnValue; }, - [isActive] + [isActive], ); // clear intervals diff --git a/src/i18n/locales/en/breadcrumbs.json b/src/i18n/locales/en/breadcrumbs.json index b4d1f92eb0..9993f4fa21 100644 --- a/src/i18n/locales/en/breadcrumbs.json +++ b/src/i18n/locales/en/breadcrumbs.json @@ -1,14 +1,14 @@ { - "headerTitle": "{{daoName}} {{subject}}", - "proposals": "Proposals", - "proposalNew": "New Proposal", - "nodes": "Hierarchy", - "treasury": "Treasury", - "proposal": "#{{proposalId}} {{proposalTitle}}", - "proposalTemplates": "Proposal Templates", - "proposalTemplate": "{{proposalTemplateTitle}}", - "proposalTemplateNew": "New Proposal Template", - "modifyGovernance": "Modify Governance", - "parentLink": "Parent-Safe", - "settings": "Settings" -} \ No newline at end of file + "headerTitle": "{{daoName}} {{subject}}", + "proposals": "Proposals", + "proposalNew": "New Proposal", + "nodes": "Hierarchy", + "treasury": "Treasury", + "proposal": "#{{proposalId}} {{proposalTitle}}", + "proposalTemplates": "Proposal Templates", + "proposalTemplate": "{{proposalTemplateTitle}}", + "proposalTemplateNew": "New Proposal Template", + "modifyGovernance": "Modify Governance", + "parentLink": "Parent-Safe", + "settings": "Settings" +} diff --git a/src/i18n/locales/en/common.json b/src/i18n/locales/en/common.json index f8c3645e61..270abb50c5 100644 --- a/src/i18n/locales/en/common.json +++ b/src/i18n/locales/en/common.json @@ -1,107 +1,107 @@ { - "advanced": "Advanced", - "no": "No", - "yes": "Yes", - "skip": "Skip", - "next": "Next", - "back": "< Back", - "accept": "Accept", - "add": "Add", - "approve": "Approve", - "created": "Created", - "create": "Create", - "remove": "Remove", - "reject": "Reject", - "view": "View", - "details": "Details", - "vote": "Vote", - "sign": "Sign", - "snapshot": "Snapshot", - "shutter": "shutter", - "send": "Send", - "transfer": "Transfer", - "received": "Received", - "to": "To", - "from": "From", - "execute": "Execute", - "timelock": "Timelock", - "execution": "Execution", - "move": "Move", - "minutes": "Min", - "quorum": "Quorum", - "deploy": "Deploy", - "abstain": "Abstain", - "loading": "Loading...", - "reload": "Reload", - "delegate": "Delegate", - "home": "Home", - "labelViewDAO": "View Safe", - "labelDAOFound": "Safe found", - "labelCurrentDAO": "This is Safe you are currently viewing", - "labelEtherscan": "View on Etherscan", - "errorInvalidENSAddress": "Invalid ENS name or address", - "errorInvalidENSName": "Invalid ENS name", - "errorInvalidERC20Address": "Invalid ERC-20 token address", - "errorInvalidERC721Address": "Invalid ERC-721 token address", - "errorInvalidBigNumber": "Invalid number value", - "errorMinimumValue": "This value can't be less then {{minValue}}", - "errorInvalidAddress": "Invalid ETH address", - "errorNotProposer": "You don't have permissions to create proposal for this Safe.", - "errorSentryFallbackTitle": "Oops!", - "errorSentryFallbackMessage": "An unexpected error occurred. Try reloading or <1>ask the devs to do something.", - "errorGeneral": "An unexpected error occurred. Please try again later.", - "labelNowishAgo": "just now", - "labelMinutesAgo": "{{count}} minutes ago", - "labelHoursAgo_one": "{{count}} hour ago", - "labelHoursAgo_other": "{{count}} hours ago", - "labelDaysAgo_one": "{{count}} day ago", - "labelDaysAgo_other": "{{count}} days ago", - "labelMonthsAgo_one": "{{count}} month ago", - "labelMonthsAgo_other": "{{count}} months ago", - "labelYearsAgo_one": "{{count}} year ago", - "labelYearsAgo_other": "{{count}} years ago", - "labelNowishLeft": "a few minutes left", - "labelMinutesLeft": "{{count}} minutes left", - "labelHoursLeft_one": "{{count}} hour left", - "labelHoursLeft_other": "{{count}} hours left", - "labelDaysLeft_one": "{{count}} day left", - "labelDaysLeft_other": "{{count}} days left", - "labelMonthsLeft_one": "{{count}} month left", - "labelMonthsLeft_other": "{{count}} months left", - "labelYearsLeft_one": "{{count}} year left", - "labelYearsLeft_other": "{{count}} years left", - "toastClipboardCopy": "Copied to clipboard", - "newest": "Newest", - "oldest": "Oldest", - "filter": "Filter", - "selectAll": "Select All", - "select": "Select", - "clear": "Clear", - "cancel": "Cancel", - "example": "Example", - "or": "or", - "abi": "ABI", - "abiSelectorHelper": "Select from the contract's predefined functions", - "abiSelectorDescription": "Autopopulate with verified functions", - "titleNodes": "Hierarchy", - "404Title": "Page not found", - "404Button": "Fractal App Home", - "signers": "Signers", - "daoDetails": "Safe details", - "signersRequired": "Signers Required", - "signersDescription": "The number of signers required to execute a proposal. Adding or removing a signer will require an proposal.", - "etherscanTip": "Open in Etherscan", - "wrapToken": "Wrap Token", - "unwrapToken": "Unwrap Token", - "max": "max", - "favoriteTooltip": "Toggle favorite", - "isMeSuffix": " (you)", - "refresh": "Refresh", - "invalidSafe1": "We couldn't find a Safe at this address on {{chain}}.", - "invalidSafe2": "Try refreshing the page or changing networks in your wallet.", - "invalidChain": "Your currently connected chain is not supported.", - "showMore": "Show More", - "showLess": "Show Less", - "poweredBy": "Powered by", - "goTo": "Go to" -} \ No newline at end of file + "advanced": "Advanced", + "no": "No", + "yes": "Yes", + "skip": "Skip", + "next": "Next", + "back": "< Back", + "accept": "Accept", + "add": "Add", + "approve": "Approve", + "created": "Created", + "create": "Create", + "remove": "Remove", + "reject": "Reject", + "view": "View", + "details": "Details", + "vote": "Vote", + "sign": "Sign", + "snapshot": "Snapshot", + "shutter": "shutter", + "send": "Send", + "transfer": "Transfer", + "received": "Received", + "to": "To", + "from": "From", + "execute": "Execute", + "timelock": "Timelock", + "execution": "Execution", + "move": "Move", + "minutes": "Min", + "quorum": "Quorum", + "deploy": "Deploy", + "abstain": "Abstain", + "loading": "Loading...", + "reload": "Reload", + "delegate": "Delegate", + "home": "Home", + "labelViewDAO": "View Safe", + "labelDAOFound": "Safe found", + "labelCurrentDAO": "This is Safe you are currently viewing", + "labelEtherscan": "View on Etherscan", + "errorInvalidENSAddress": "Invalid ENS name or address", + "errorInvalidENSName": "Invalid ENS name", + "errorInvalidERC20Address": "Invalid ERC-20 token address", + "errorInvalidERC721Address": "Invalid ERC-721 token address", + "errorInvalidBigNumber": "Invalid number value", + "errorMinimumValue": "This value can't be less then {{minValue}}", + "errorInvalidAddress": "Invalid ETH address", + "errorNotProposer": "You don't have permissions to create proposal for this Safe.", + "errorSentryFallbackTitle": "Oops!", + "errorSentryFallbackMessage": "An unexpected error occurred. Try reloading or <1>ask the devs to do something.", + "errorGeneral": "An unexpected error occurred. Please try again later.", + "labelNowishAgo": "just now", + "labelMinutesAgo": "{{count}} minutes ago", + "labelHoursAgo_one": "{{count}} hour ago", + "labelHoursAgo_other": "{{count}} hours ago", + "labelDaysAgo_one": "{{count}} day ago", + "labelDaysAgo_other": "{{count}} days ago", + "labelMonthsAgo_one": "{{count}} month ago", + "labelMonthsAgo_other": "{{count}} months ago", + "labelYearsAgo_one": "{{count}} year ago", + "labelYearsAgo_other": "{{count}} years ago", + "labelNowishLeft": "a few minutes left", + "labelMinutesLeft": "{{count}} minutes left", + "labelHoursLeft_one": "{{count}} hour left", + "labelHoursLeft_other": "{{count}} hours left", + "labelDaysLeft_one": "{{count}} day left", + "labelDaysLeft_other": "{{count}} days left", + "labelMonthsLeft_one": "{{count}} month left", + "labelMonthsLeft_other": "{{count}} months left", + "labelYearsLeft_one": "{{count}} year left", + "labelYearsLeft_other": "{{count}} years left", + "toastClipboardCopy": "Copied to clipboard", + "newest": "Newest", + "oldest": "Oldest", + "filter": "Filter", + "selectAll": "Select All", + "select": "Select", + "clear": "Clear", + "cancel": "Cancel", + "example": "Example", + "or": "or", + "abi": "ABI", + "abiSelectorHelper": "Select from the contract's predefined functions", + "abiSelectorDescription": "Autopopulate with verified functions", + "titleNodes": "Hierarchy", + "404Title": "Page not found", + "404Button": "Fractal App Home", + "signers": "Signers", + "daoDetails": "Safe details", + "signersRequired": "Signers Required", + "signersDescription": "The number of signers required to execute a proposal. Adding or removing a signer will require an proposal.", + "etherscanTip": "Open in Etherscan", + "wrapToken": "Wrap Token", + "unwrapToken": "Unwrap Token", + "max": "max", + "favoriteTooltip": "Toggle favorite", + "isMeSuffix": " (you)", + "refresh": "Refresh", + "invalidSafe1": "We couldn't find a Safe at this address on {{chain}}.", + "invalidSafe2": "Try refreshing the page or changing networks in your wallet.", + "invalidChain": "Your currently connected chain is not supported.", + "showMore": "Show More", + "showLess": "Show Less", + "poweredBy": "Powered by", + "goTo": "Go to" +} diff --git a/src/i18n/locales/en/daoCreate.json b/src/i18n/locales/en/daoCreate.json index 162e6109a4..2259317215 100644 --- a/src/i18n/locales/en/daoCreate.json +++ b/src/i18n/locales/en/daoCreate.json @@ -1,103 +1,103 @@ { - "buttonCreate": "Create a Fractal", - "addNFTButton": "Import another token", - "labelCreateSubDAOProposal": "Create a sub-Safe", - "errorLowSignerThreshold": "Threshold must be greater than 0", - "errorHighSignerThreshold": "Threshold is too high", - "labelSigners": "Total Signers", - "helperSigners": "The total number of possible signers (max 99)", - "labelSigThreshold": "Signature Threshold", - "helperSigThreshold": "The number of signers needed to approve a proposal", - "titleSignerAddresses": "Signer Addresses", - "subTitleSignerAddresses": "These users are able to submit and approve proposals", - "titleEssentials": "Establish Essentials", - "titleAzoriusConfig": "Configure Voting Token", - "titleNFTConfig": "Configure NFT Voting", - "titleSafeConfig": "Configure Multisig", - "titleSignerConfig": "Signer Parameters", - "titleGovConfig": "Compose Governance", - "titleFunding": "Add Funding", - "titleNFTsParams": "Import NFT", - "titleNFTDetails": "Details", - "nftDetailsToken": "Token", - "nftDetailsWeight": "Weight", - "n/a": "n/a", - "labelConnectWallet": "To deploy a new Fractal", - "titleFundingOptions": "Go to parent-Safe", - "labelSelectNFT": "Select NFT", - "labelNFTAddress": "NFT Address", - "helperNFTAddress": "Import an existing ERC-721 token", - "labelNFTWeight": "Weight", - "helperNFTWeight": "How many votes is this token worth?", - "labelVotingPeriod": "Voting Period", - "labelChooseGovernance": "Choose Governance", - "helperChooseGovernance": "What type of governance would you like?", - "exampleVotingPeriod": "10,080 minutes = 1 week", - "helperVotingPeriod": "The length of time (in minutes) voting is allowed after proposal creation", - "helperQuorum": "The percentage of total possible token votes required to vote on a proposal to make the result valid", - "helperQuorumThreshold": "The required votes out of the available voting weight needed to make the result valid", - "governanceDescription": "These values can be changed later via a proposal", - "titleGuardConfig": "Configure Parent Controls", - "titleFreezeParams": "Freeze Parameters", - "labelExecutionPeriod": "Execution Period", - "helperExecutionPeriod": "The length of time (in minutes) a successful proposal must be executed within", - "exampleExecutionPeriod": "2,880 minutes = 2 days", - "labelTimelockPeriod": "Timelock Period", - "helperTimelockPeriod": "The length of time (in minutes) a passed proposal must wait to be executed on the blockchain", - "exampleTimelockPeriod": "1,440 minutes = 1 day", - "labelFreezeVotesThreshold": "Freeze Votes Threshold", - "helperFreezeVotesThreshold": "Total votes required by the parent (out of {{totalVotes}}) to freeze this sub-Safe entirely", - "labelFreezeProposalPeriod": "Freeze Proposal Period", - "helperFreezeProposalPeriod": "The length of time (in minutes) for a Freeze Vote's starting and ending point", - "exampleFreezeProposalPeriod": "10,080 minutes = 1 week", - "labelFreezePeriod": "Freeze Period", - "helperFreezePeriod": "The length of time (in minutes) a successful Freeze Vote will freeze this sub-Safe", - "exampleFreezePeriod": "10,080 minutes = 1 week", - "freezeGuardDescription": "These configuration values may be changed later on by passing a proposal to do so on this sub-Safe", - "errorDuplicateAddress": "Duplicate Address", - "errorAllocation": "Invalid token allocation", - "labelParentAllocation": "Parent token holder claiming", - "helperParentAllocation": "The total number of tokens claimable by all parent token holders", - "labelAddAllocation": "+ Add Allocation", - "labelTokenName": "Token Name", - "helperTokenName": "What is your governance token called?", - "labelTokenSymbol": "Token Symbol", - "helperTokenSymbol": "The symbol for your token (2-6 chars)", - "labelTokenSupply": "Token Supply", - "helperTokenSupply": "Total available tokens", - "labelMultisigGov": "Safe Multisig", - "descMultisigGov": "Multisignature governance on a Safe{Wallet}", - "labelAzoriusErc20Gov": "Token Voting Safe", - "descAzoriusErc20Gov": "ERC-20 token voting governance on a Safe{Wallet}", - "labelAzoriusErc721Gov": "NFT Voting Safe", - "descAzoriusErc721Gov": "Assign voting rights and ownership privileges to ERC-721 token holders", - "labelFractalName": "Fractal Name", - "daoNamePlaceholder": "Name of my Safe", - "helperFractalName": "What should we call your Safe{Wallet}?", - "titleProposalSettings": "Proposal Settings", - "labelDeploySubDAO": "Create a sub-Safe Proposal", - "labelDeployAzorius": "Create Azorius deployment Proposal", - "titleAllocations": "Initial Token Allocations", - "titleTokenParams": "Token Metadata", - "titleSelectToken": "Select Token", - "titleAddress": "Address", - "titleAmount": "Amount", - "createSubDAOPendingToastMessage": "Creating your sub-Safe Proposal", - "createSubDAOSuccessToastMessage": "Deployment successful", - "createSubDAOFailureToastMessage": "Deployment failed", - "helperAllocations": "Unallocated tokens will be placed in your DAO's treasury", - "labelSelectToken": "Select Token", - "helperSelectToken": "Create a new token or use an existing ERC-20 token for your DAO's governance", - "radioLabelNewToken": "New Token", - "radioLabelExistingToken": "Existing Token", - "helperNewToken": "Create a new ERC-20 token to vote with", - "helperExistingToken": "Use an existing ERC-20 for voting", - "warningExistingToken": "This token must be wrapped in order to be used as a voting token", - "warningExistingTokenTooltip": "Fractal will deploy a new ERC-20 governance token to wrap this one. Holders can deposit their tokens to vote with or withdraw them at any time.", - "snapshot": "Snapshot", - "snapshotHelper": "Does your Safe have a Snapshot space?", - "tooltipMultisig": "Multisig is useful when you only have a small group of decision makers that all need to sign a transaction for it pass. <1>Learn more", - "tooltipTokenVoting": "Token Voting is a method to give a larger group of ERC-20 token holders governance power to propose and vote on transactions. <1>Learn more", - "toastDisconnected": "Connect an account to proceed!", - "tooltipNftVoting": "NFT Voting is good practice to give a group ERC-721 (aka NFT) holders to propose and vote on transactions. Multiple NFT addresses may be used. Learn more with <1>Fractal Docs" -} \ No newline at end of file + "buttonCreate": "Create a Fractal", + "addNFTButton": "Import another token", + "labelCreateSubDAOProposal": "Create a sub-Safe", + "errorLowSignerThreshold": "Threshold must be greater than 0", + "errorHighSignerThreshold": "Threshold is too high", + "labelSigners": "Total Signers", + "helperSigners": "The total number of possible signers (max 99)", + "labelSigThreshold": "Signature Threshold", + "helperSigThreshold": "The number of signers needed to approve a proposal", + "titleSignerAddresses": "Signer Addresses", + "subTitleSignerAddresses": "These users are able to submit and approve proposals", + "titleEssentials": "Establish Essentials", + "titleAzoriusConfig": "Configure Voting Token", + "titleNFTConfig": "Configure NFT Voting", + "titleSafeConfig": "Configure Multisig", + "titleSignerConfig": "Signer Parameters", + "titleGovConfig": "Compose Governance", + "titleFunding": "Add Funding", + "titleNFTsParams": "Import NFT", + "titleNFTDetails": "Details", + "nftDetailsToken": "Token", + "nftDetailsWeight": "Weight", + "n/a": "n/a", + "labelConnectWallet": "To deploy a new Fractal", + "titleFundingOptions": "Go to parent-Safe", + "labelSelectNFT": "Select NFT", + "labelNFTAddress": "NFT Address", + "helperNFTAddress": "Import an existing ERC-721 token", + "labelNFTWeight": "Weight", + "helperNFTWeight": "How many votes is this token worth?", + "labelVotingPeriod": "Voting Period", + "labelChooseGovernance": "Choose Governance", + "helperChooseGovernance": "What type of governance would you like?", + "exampleVotingPeriod": "10,080 minutes = 1 week", + "helperVotingPeriod": "The length of time (in minutes) voting is allowed after proposal creation", + "helperQuorum": "The percentage of total possible token votes required to vote on a proposal to make the result valid", + "helperQuorumThreshold": "The required votes out of the available voting weight needed to make the result valid", + "governanceDescription": "These values can be changed later via a proposal", + "titleGuardConfig": "Configure Parent Controls", + "titleFreezeParams": "Freeze Parameters", + "labelExecutionPeriod": "Execution Period", + "helperExecutionPeriod": "The length of time (in minutes) a successful proposal must be executed within", + "exampleExecutionPeriod": "2,880 minutes = 2 days", + "labelTimelockPeriod": "Timelock Period", + "helperTimelockPeriod": "The length of time (in minutes) a passed proposal must wait to be executed on the blockchain", + "exampleTimelockPeriod": "1,440 minutes = 1 day", + "labelFreezeVotesThreshold": "Freeze Votes Threshold", + "helperFreezeVotesThreshold": "Total votes required by the parent (out of {{totalVotes}}) to freeze this sub-Safe entirely", + "labelFreezeProposalPeriod": "Freeze Proposal Period", + "helperFreezeProposalPeriod": "The length of time (in minutes) for a Freeze Vote's starting and ending point", + "exampleFreezeProposalPeriod": "10,080 minutes = 1 week", + "labelFreezePeriod": "Freeze Period", + "helperFreezePeriod": "The length of time (in minutes) a successful Freeze Vote will freeze this sub-Safe", + "exampleFreezePeriod": "10,080 minutes = 1 week", + "freezeGuardDescription": "These configuration values may be changed later on by passing a proposal to do so on this sub-Safe", + "errorDuplicateAddress": "Duplicate Address", + "errorAllocation": "Invalid token allocation", + "labelParentAllocation": "Parent token holder claiming", + "helperParentAllocation": "The total number of tokens claimable by all parent token holders", + "labelAddAllocation": "+ Add Allocation", + "labelTokenName": "Token Name", + "helperTokenName": "What is your governance token called?", + "labelTokenSymbol": "Token Symbol", + "helperTokenSymbol": "The symbol for your token (2-6 chars)", + "labelTokenSupply": "Token Supply", + "helperTokenSupply": "Total available tokens", + "labelMultisigGov": "Safe Multisig", + "descMultisigGov": "Multisignature governance on a Safe{Wallet}", + "labelAzoriusErc20Gov": "Token Voting Safe", + "descAzoriusErc20Gov": "ERC-20 token voting governance on a Safe{Wallet}", + "labelAzoriusErc721Gov": "NFT Voting Safe", + "descAzoriusErc721Gov": "Assign voting rights and ownership privileges to ERC-721 token holders", + "labelFractalName": "Fractal Name", + "daoNamePlaceholder": "Name of my Safe", + "helperFractalName": "What should we call your Safe{Wallet}?", + "titleProposalSettings": "Proposal Settings", + "labelDeploySubDAO": "Create a sub-Safe Proposal", + "labelDeployAzorius": "Create Azorius deployment Proposal", + "titleAllocations": "Initial Token Allocations", + "titleTokenParams": "Token Metadata", + "titleSelectToken": "Select Token", + "titleAddress": "Address", + "titleAmount": "Amount", + "createSubDAOPendingToastMessage": "Creating your sub-Safe Proposal", + "createSubDAOSuccessToastMessage": "Deployment successful", + "createSubDAOFailureToastMessage": "Deployment failed", + "helperAllocations": "Unallocated tokens will be placed in your DAO's treasury", + "labelSelectToken": "Select Token", + "helperSelectToken": "Create a new token or use an existing ERC-20 token for your DAO's governance", + "radioLabelNewToken": "New Token", + "radioLabelExistingToken": "Existing Token", + "helperNewToken": "Create a new ERC-20 token to vote with", + "helperExistingToken": "Use an existing ERC-20 for voting", + "warningExistingToken": "This token must be wrapped in order to be used as a voting token", + "warningExistingTokenTooltip": "Fractal will deploy a new ERC-20 governance token to wrap this one. Holders can deposit their tokens to vote with or withdraw them at any time.", + "snapshot": "Snapshot", + "snapshotHelper": "Does your Safe have a Snapshot space?", + "tooltipMultisig": "Multisig is useful when you only have a small group of decision makers that all need to sign a transaction for it pass. <1>Learn more", + "tooltipTokenVoting": "Token Voting is a method to give a larger group of ERC-20 token holders governance power to propose and vote on transactions. <1>Learn more", + "toastDisconnected": "Connect an account to proceed!", + "tooltipNftVoting": "NFT Voting is good practice to give a group ERC-721 (aka NFT) holders to propose and vote on transactions. Multiple NFT addresses may be used. Learn more with <1>Fractal Docs" +} diff --git a/src/i18n/locales/en/daoEdit.json b/src/i18n/locales/en/daoEdit.json index 5274594c09..2f06b8028a 100644 --- a/src/i18n/locales/en/daoEdit.json +++ b/src/i18n/locales/en/daoEdit.json @@ -1,3 +1,3 @@ { - "cannotModifyGovernance": "You can't modify the governance of this Safe." -} \ No newline at end of file + "cannotModifyGovernance": "You can't modify the governance of this Safe." +} diff --git a/src/i18n/locales/en/dashboard.json b/src/i18n/locales/en/dashboard.json index 6e27351b86..91b5e919ec 100644 --- a/src/i18n/locales/en/dashboard.json +++ b/src/i18n/locales/en/dashboard.json @@ -1,30 +1,30 @@ { - "titleFavorites": "Favorites", - "emptyFavorites": "No favorites", - "errorInvalidSearch": "This is not a valid Ethereum address", - "errorFailedSearch": "Sorry, this address is not a Fractal or Safe{WALLET} on {{chain}}", - "searchDAOPlaceholder": "Enter any existing Safe address", - "titleGovernance": "Governance", - "titleType": "Type", - "titleVotingPeriod": "Voting Period", - "titleQuorum": "Quorum", - "titleProposals": "Proposals", - "titlePending": "Pending", - "titlePassed": "Passed", - "titleTreasury": "Treasury", - "noActivity": "No Activity", - "proposalDescription_one": "Proposal to execute {{count}} transaction", - "proposalDescription_other": "Proposal to execute {{count}} transactions", - "freezeDescription": "Freeze vote pending on sub-Safe", - "frozenDescription": "Safe is Frozen", - "freezeButton": "Freeze", - "freezeVotedButton": "Vote Cast", - "noVotesButton": "User Has No Votes", - "tipFreeze": "{{amount}} required freeze votes", - "proposalOnChainRejection": "that rejects transaction {{proposalId}}", - "proposalDescriptionCont": "that will", - "moduleDescription_one": "Module executed {{count}} transaction on", - "moduleDescription_other": "Module executed {{count}} transactions on", - "alertTokenClaim": "You have {{amount}} tokens to claim!", - "claim": "Claim" -} \ No newline at end of file + "titleFavorites": "Favorites", + "emptyFavorites": "No favorites", + "errorInvalidSearch": "This is not a valid Ethereum address", + "errorFailedSearch": "Sorry, this address is not a Fractal or Safe{WALLET} on {{chain}}", + "searchDAOPlaceholder": "Enter any existing Safe address", + "titleGovernance": "Governance", + "titleType": "Type", + "titleVotingPeriod": "Voting Period", + "titleQuorum": "Quorum", + "titleProposals": "Proposals", + "titlePending": "Pending", + "titlePassed": "Passed", + "titleTreasury": "Treasury", + "noActivity": "No Activity", + "proposalDescription_one": "Proposal to execute {{count}} transaction", + "proposalDescription_other": "Proposal to execute {{count}} transactions", + "freezeDescription": "Freeze vote pending on sub-Safe", + "frozenDescription": "Safe is Frozen", + "freezeButton": "Freeze", + "freezeVotedButton": "Vote Cast", + "noVotesButton": "User Has No Votes", + "tipFreeze": "{{amount}} required freeze votes", + "proposalOnChainRejection": "that rejects transaction {{proposalId}}", + "proposalDescriptionCont": "that will", + "moduleDescription_one": "Module executed {{count}} transaction on", + "moduleDescription_other": "Module executed {{count}} transactions on", + "alertTokenClaim": "You have {{amount}} tokens to claim!", + "claim": "Claim" +} diff --git a/src/i18n/locales/en/languages.json b/src/i18n/locales/en/languages.json index 3d06631ffb..680fb9d993 100644 --- a/src/i18n/locales/en/languages.json +++ b/src/i18n/locales/en/languages.json @@ -1,5 +1,5 @@ { - "tooltipTitle": "Language", - "en": "English (EN)", - "uk": "Ukrainian (UK)" -} \ No newline at end of file + "tooltipTitle": "Language", + "en": "English (EN)", + "uk": "Ukrainian (UK)" +} diff --git a/src/i18n/locales/en/menu.json b/src/i18n/locales/en/menu.json index ae47bb8cfe..ac0da311a8 100644 --- a/src/i18n/locales/en/menu.json +++ b/src/i18n/locales/en/menu.json @@ -1,21 +1,21 @@ { - "connect": "Connect", - "connectWallet": "Connect Wallet", - "disconnect": "Disconnect", - "faq": "FAQ", - "discord": "Discord", - "docs": "Docs", - "toastSwitchChain": "Switch your wallet to a supported chain: {{chainNames}}", - "network": "Network", - "wallet": "Wallet", - "titleManageDAO": "Manage Safe", - "titleViewDAODetails": "View Safe Details", - "titleManageProposalTemplate": "Manage Template", - "optionCreateSubDAO": "Add a sub-Safe", - "optionInitiateFreeze": "Initiate a Freeze", - "optionInitiateClawback": "Initiate Clawback", - "optionRemoveTemplate": "Remove Template", - "optionForkTemplate": "Fork Template", - "optionModifyGovernance": "Modify Governance", - "optionSettings": "Settings" -} \ No newline at end of file + "connect": "Connect", + "connectWallet": "Connect Wallet", + "disconnect": "Disconnect", + "faq": "FAQ", + "discord": "Discord", + "docs": "Docs", + "toastSwitchChain": "Switch your wallet to a supported chain: {{chainNames}}", + "network": "Network", + "wallet": "Wallet", + "titleManageDAO": "Manage Safe", + "titleViewDAODetails": "View Safe Details", + "titleManageProposalTemplate": "Manage Template", + "optionCreateSubDAO": "Add a sub-Safe", + "optionInitiateFreeze": "Initiate a Freeze", + "optionInitiateClawback": "Initiate Clawback", + "optionRemoveTemplate": "Remove Template", + "optionForkTemplate": "Fork Template", + "optionModifyGovernance": "Modify Governance", + "optionSettings": "Settings" +} diff --git a/src/i18n/locales/en/modals.json b/src/i18n/locales/en/modals.json index e22b96a501..24d4f4f2ac 100644 --- a/src/i18n/locales/en/modals.json +++ b/src/i18n/locales/en/modals.json @@ -1,67 +1,67 @@ { - "delegateTitle": "Delegate Voting Tokens", - "titleBalance": "Balance", - "titleLockedBalance": "Locked Balance", - "titleDelegatedTo": "Delegated To", - "linkSelfDelegate": "Delegate to Self", - "labelDelegateInput": "Delegate Address", - "sublabelDelegateInput": "Delegate your tokens to yourself or to a representative who can vote on your behalf. This does not transfer ownership of the tokens.", - "buttonDelegate": "Delegate Voting Tokens", - "buttonLockedDelegate": "Delegate Locked Voting Tokens", - "sendAssetsTitle": "Send Assets", - "selectLabel": "Select Asset", - "selectSublabel": "Available balance {{balance}}", - "amountLabel": "How much?", - "destinationLabel": "Where would you like to send this?", - "destinationSublabel": "Ethereum wallet address only", - "sendAssetsSubmit": "Submit Proposal", - "sendAssetsPendingToastMessage": "Creating proposal to send assets", - "sendAssetsSuccessToastMessage": "Send assets proposal successfully created", - "sendAssetsFailureToastMessage": "Send assets proposal failed", - "confirmUrlTitle": "This link will take you to", - "confirmAction": "Do you want to continue?", - "modalContinue": "Continue", - "modalCancel": "Cancel", - "addSignerTitle": "Add Signer", - "forkProposalTemplate": "Fork Template", - "addSignerLabel": "Add a new signer to your Safe Multisig", - "addSignerSublabel": "Ethereum wallet address or ENS only", - "updateThreshold": "Update required signers", - "select": "Select", - "updateSignerWarning": "Access to your organization will be affected", - "submit": "Submit", - "createProposal": "Create Proposal", - "removeSignerTitle": "Remove Signer", - "removeSignerLabel": "Review the signer to remove", - "addSignerPendingToastMessage": "Creating proposal to add signer", - "addSignerSuccessToastMessage": "Add signer proposal successfully created", - "addSignerFailureToastMessage": "Add signer proposal failed", - "removeSignerPendingToastMessage": "Creating proposal to remove signer", - "removeSignerSuccessToastMessage": "Remove signer proposal successfully created", - "removeSignerFailureToastMessage": "Remove signer proposal failed", - "signersRequired1": "out of", - "signersRequired2": "signers required", - "alreadySigner": "Address is already a signer", - "wrapTokenTitle": "Wrap your token", - "assetWrapTitle": "Asset to wrap", - "assetWrapAmountLabel": "How much would you like to wrap?", - "assetWrapSubLabel": "Wrap your ERC-20 token as an ERC-20 Voting token to use as governance", - "wrapTokenPendingMessage": "Wrapping tokens...", - "wrapTokenFailedMessage": "Failed to wrap tokens", - "wrapTokenSuccessMessage": "Tokens wrapped", - "wrapTokenError": "Invalid amount", - "wrapTokenButton": "Wrap", - "unwrapTokenTitle": "Unwrap your token", - "assetUnwrapTitle": "Asset to unwrap", - "assetUnwrapAmountLabel": "How much would you like to unwrap?", - "assetUnwrapSubLabel": "Unwrap your ERC-20 token as an ERC-20 Voting token to use as governance", - "unwrapTokenPendingMessage": "Unwrapping tokens...", - "unwrapTokenFailedMessage": "Failed to unwrap tokens", - "unwrapTokenSuccessMessage": "Tokens unwrapped", - "unwrapTokenError": "Invalid amount", - "unwrapTokenButton": "Unwrap", - "updateSignersTooltip": "The number of signers required to pass future proposals", - "confirmModifyGovernanceTitle": "Heads up this is a big deal!", - "confirmModifyGovernanceDescription": "Modifying your Safe's governance will change a lot of things that you should be aware of before you proceed", - "stakeTitle" : "Stake" -} \ No newline at end of file + "delegateTitle": "Delegate Voting Tokens", + "titleBalance": "Balance", + "titleLockedBalance": "Locked Balance", + "titleDelegatedTo": "Delegated To", + "linkSelfDelegate": "Delegate to Self", + "labelDelegateInput": "Delegate Address", + "sublabelDelegateInput": "Delegate your tokens to yourself or to a representative who can vote on your behalf. This does not transfer ownership of the tokens.", + "buttonDelegate": "Delegate Voting Tokens", + "buttonLockedDelegate": "Delegate Locked Voting Tokens", + "sendAssetsTitle": "Send Assets", + "selectLabel": "Select Asset", + "selectSublabel": "Available balance {{balance}}", + "amountLabel": "How much?", + "destinationLabel": "Where would you like to send this?", + "destinationSublabel": "Ethereum wallet address only", + "sendAssetsSubmit": "Submit Proposal", + "sendAssetsPendingToastMessage": "Creating proposal to send assets", + "sendAssetsSuccessToastMessage": "Send assets proposal successfully created", + "sendAssetsFailureToastMessage": "Send assets proposal failed", + "confirmUrlTitle": "This link will take you to", + "confirmAction": "Do you want to continue?", + "modalContinue": "Continue", + "modalCancel": "Cancel", + "addSignerTitle": "Add Signer", + "forkProposalTemplate": "Fork Template", + "addSignerLabel": "Add a new signer to your Safe Multisig", + "addSignerSublabel": "Ethereum wallet address or ENS only", + "updateThreshold": "Update required signers", + "select": "Select", + "updateSignerWarning": "Access to your organization will be affected", + "submit": "Submit", + "createProposal": "Create Proposal", + "removeSignerTitle": "Remove Signer", + "removeSignerLabel": "Review the signer to remove", + "addSignerPendingToastMessage": "Creating proposal to add signer", + "addSignerSuccessToastMessage": "Add signer proposal successfully created", + "addSignerFailureToastMessage": "Add signer proposal failed", + "removeSignerPendingToastMessage": "Creating proposal to remove signer", + "removeSignerSuccessToastMessage": "Remove signer proposal successfully created", + "removeSignerFailureToastMessage": "Remove signer proposal failed", + "signersRequired1": "out of", + "signersRequired2": "signers required", + "alreadySigner": "Address is already a signer", + "wrapTokenTitle": "Wrap your token", + "assetWrapTitle": "Asset to wrap", + "assetWrapAmountLabel": "How much would you like to wrap?", + "assetWrapSubLabel": "Wrap your ERC-20 token as an ERC-20 Voting token to use as governance", + "wrapTokenPendingMessage": "Wrapping tokens...", + "wrapTokenFailedMessage": "Failed to wrap tokens", + "wrapTokenSuccessMessage": "Tokens wrapped", + "wrapTokenError": "Invalid amount", + "wrapTokenButton": "Wrap", + "unwrapTokenTitle": "Unwrap your token", + "assetUnwrapTitle": "Asset to unwrap", + "assetUnwrapAmountLabel": "How much would you like to unwrap?", + "assetUnwrapSubLabel": "Unwrap your ERC-20 token as an ERC-20 Voting token to use as governance", + "unwrapTokenPendingMessage": "Unwrapping tokens...", + "unwrapTokenFailedMessage": "Failed to unwrap tokens", + "unwrapTokenSuccessMessage": "Tokens unwrapped", + "unwrapTokenError": "Invalid amount", + "unwrapTokenButton": "Unwrap", + "updateSignersTooltip": "The number of signers required to pass future proposals", + "confirmModifyGovernanceTitle": "Heads up this is a big deal!", + "confirmModifyGovernanceDescription": "Modifying your Safe's governance will change a lot of things that you should be aware of before you proceed", + "stakeTitle": "Stake" +} diff --git a/src/i18n/locales/en/navigation.json b/src/i18n/locales/en/navigation.json index 25e02c0ab9..9a8f2c0b73 100644 --- a/src/i18n/locales/en/navigation.json +++ b/src/i18n/locales/en/navigation.json @@ -1,18 +1,18 @@ { - "home": "Safe Home", - "proposals": "Proposals", - "nodes": "Hierarchy", - "treasury": "Treasury", - "proposalTemplates": "Templates", - "faq": "FAQ", - "discord": "Discord", - "documentation": "Documentation", - "ariaLabelFractalBrand": "Navigate Fractal Home", - "ariaLabelHome": "Navigate Safe Home", - "ariaLabelProposals": "Navigate Safe Proposals", - "ariaLabelActivities": "Navigate Safe Activities", - "ariaLabelTreasury": "Navigate Safe Treasury", - "ariaLabelFAQ": "Link to FAQ", - "ariaLabelDiscord": "Link to Discord", - "ariaLabelDocumentation": "Link to documentation" -} \ No newline at end of file + "home": "Safe Home", + "proposals": "Proposals", + "nodes": "Hierarchy", + "treasury": "Treasury", + "proposalTemplates": "Templates", + "faq": "FAQ", + "discord": "Discord", + "documentation": "Documentation", + "ariaLabelFractalBrand": "Navigate Fractal Home", + "ariaLabelHome": "Navigate Safe Home", + "ariaLabelProposals": "Navigate Safe Proposals", + "ariaLabelActivities": "Navigate Safe Activities", + "ariaLabelTreasury": "Navigate Safe Treasury", + "ariaLabelFAQ": "Link to FAQ", + "ariaLabelDiscord": "Link to Discord", + "ariaLabelDocumentation": "Link to documentation" +} diff --git a/src/i18n/locales/en/proposal.json b/src/i18n/locales/en/proposal.json index fd64dee58d..8a46320af7 100644 --- a/src/i18n/locales/en/proposal.json +++ b/src/i18n/locales/en/proposal.json @@ -1,134 +1,134 @@ { - "proposalOverview": "Proposal Overview", - "breakdownTitle": "Breakdown", - "labelTargetAddress": "Target Address", - "helperTargetAddress": "The smart contract address this proposal will modify", - "labelFunctionName": "Function Name", - "helperFunctionName": "The name of the function to be called if this proposal passes", - "labelFunctionSignature": "Function Signature", - "helperFunctionSignature": "The parameter types of the function (above) to be called", - "labelParameters": "Parameters", - "helperParameters": "Parameters used to call the function (comma separated)", - "labelEthValue": "ETH Value", - "helperEthValue": "Amount of ETH to send with the transaction (from your treasury)", - "ownerApproved": "Approved", - "stateModule": "Module", - "stateModuleTip": "This transaction was executed by a Safe{Wallet} module.", - "stateActive": "Active", - "stateActiveTip": "The proposal can be voted on. This is the initial state of all newly created proposals.", - "stateRejected": "Rejected", - "stateRejectedTip": "The proposal failed due to another proposal being executed with the same nonce.", - "stateExecutable": "Executable", - "stateExecutableTip": "The proposal is now able to be executed on chain.", - "stateExecuted": "Executed", - "stateExecutedTip": "The proposal has been executed.", - "stateFailed": "Failed", - "stateFailedTip": "The proposal failed to achieve quorum or did not get enough votes to pass.", - "stateClosed": "Closed", - "stateClosedTip": "The Snapshot proposal voting has been closed", - "statePending": "Pending", - "statePendingTip": "The Snapshot proposal voting has not begun yet", - "stateTimeLocked": "Timelocked", - "stateTimelockedTip": "The proposal has passed, but cannot yet be executed until its timelock period has ended.", - "stateFreezeInit": "Freeze Pending", - "stateFreezeInitTip": "The parent has initiated a vote to potentially freeze this sub-Safe.", - "stateFrozen": "Frozen", - "stateFrozenTip": "The parent has voted to freeze this Safe.", - "stateTimelockable": "Timelockable", - "stateTimelockableTip": "The proposal can now be timelocked for execution.", - "stateExpired": "Expired", - "stateExpiredTip": "The proposal passed but was not executed before the execution period ended.", - "showExecutableCode": "Show Executable Code", - "hideExecutableCode": "Hide Executable Code", - "noProposal": "No proposal found", - "emptyProposals": "No proposals found", - "createProposal": "Create Proposal", - "labelAddTransaction": "+ Add Transaction", - "errorInvalidFragments": "Invalid combination of function signature and parameters", - "proposalSummaryTitle": "Proposal Details", - "proposalSummaryStartDate": "Start Date", - "proposalSummaryEndDate": "End Date", - "snapshotTaken": "Snapshot taken", - "votingPower": "Voting power", - "votingPowerTooltip": "This is your voting power at a time of taking snapshot, which happens in the moment of proposal creation", - "show": "Show", - "proposalSupportERC20SummaryHelper": "Minimum {{quorum}}% required out of {{total}}", - "proposalSupportERC721SummaryHelper": "Minimum {{quorum}} token votes required out {{total}}", - "proposedBy": "Proposed by", - "proposalTitle_one": "Proposal to execute {{count}} transaction on {{target}}", - "proposalTitle_other": "Proposal to execute {{count}} transactions on {{target}}", - "proposalTitle": "Proposal Title", - "proposalTitleHelper": "A short title for this proposal", - "proposalTitlePlaceholder": "A new proposal", - "proposalDescription": "Proposal Description", - "proposalDescriptionHelper": "Add a brief description", - "proposalDescriptionPlaceholder": "A brief description", - "proposalAdditionalResources": "Additional Resources", - "proposalAdditionalResourcesHelper": "A link to any discussion or formal documentation", - "proposalAdditionalResourcesPlaceholder": "https://", - "votesTitle": "Votes", - "nftVotes_one": "{{count}} vote", - "nftVotes_other": "{{count}} votes", - "signTitle": "Sign Transaction", - "timelockTitle": "Timelock Transaction", - "executeTitle": "Execute Transaction", - "filterProposalsAllSelected": "All Proposals", - "filterProposalsNoneSelected": "No Proposals", - "filterProposalsNSelected": "Filter {{count}}", - "filter": "Filter", - "transaction": "Transaction", - "proposal": "Proposal", - "transactionExecutionAlertMessage": "Transactions execute in the order they are added", - "labelProposalVotingPeriod": "Voting Period", - "labelProposalQuorum": "Quorum", - "labelProposalTimelock": "Timelock", - "labelProposalSigners": "Signers", - "support": "Support", - "signers": "Signers", - "txDetailsSignersCurrent": "Current Signatures", - "txDetailsSignersRequired": "Signers Required", - "created": "Created", - "transactionHash": "Transaction Hash", - "proposalId": "Proposal Id", - "pendingSign": "Use wallet to sign transaction", - "failedSign": "Signing failed", - "successSign": "Signing successful", - "proposalCreatePendingToastMessage": "Creating new proposal", - "proposalCreateSuccessToastMessage": "Proposal successfully created", - "proposalCreateFailureToastMessage": "Proposal creation failed", - "paramTarget": "target", - "paramFunction": "function", - "paramTypes": "parameter types", - "paramInputs": "parameter inputs", - "paramValue": "transaction value", - "multisigMetadataMessage": "This transaction contains the proposal's encoded metadata.", - "multisigMetadataWarning": "Adding metadata (title, description, url) to a multisig proposal will add execution cost.", - "decodingFailedMessage": "Unable to decode transaction's data.", - "customNonce": "Custom Nonce", - "customNonceTooltip": "Set a custom proposal nonce if necessary to prevent nonce collisions", - "nonce": "Nonce", - "votingTooltip": "Time remaining to cast your vote.", - "timeLockedTooltip": "This proposal can be executed once its timelock period has passed.", - "executableTooltip": "Time remaining to execute this proposal", - "learnMore": "Learn More", - "clawBackPendingToastMessage": "Creating clawback proposal", - "clawBackSuccessToastMessage": "Clawback proposal successfully created", - "clawBackFailedToastMessage": "Error creating clawback proposal", - "proposalStartBlockNotFinalized": "You can't vote right now as proposal's start block not yet finalized.", - "currentUserAlreadyVoted": "Seems like you already casted your vote. Check the votes summary", - "customNonceError": "The nonce you entered is lower than the Safe{Wallet}'s current nonce.", - "noTimeDifference": "0 seconds", - "functionNameError": "The function name can contain only numbers and letters.", - "labelOrValueRequired": "Parameter Label or Parameter Value is required", - "votingSystem": "Voting system", - "basicSnapshotVotingSystem": "Basic voting", - "singleSnapshotVotingSystem": "Single choice voting", - "weightedSnapshotVotingSystem": "Weighted voting", - "unknownSnapshotVotingSystem": "Unknown voting system", - "ipfs": "IPFS", - "privacy": "Privacy", - "shielded": "Shielded", - "shutterPrivacy": "Shielded", - "shutterVotesHidden": "Results hidden during voting", - "totalVotes": "Total votes" -} \ No newline at end of file + "proposalOverview": "Proposal Overview", + "breakdownTitle": "Breakdown", + "labelTargetAddress": "Target Address", + "helperTargetAddress": "The smart contract address this proposal will modify", + "labelFunctionName": "Function Name", + "helperFunctionName": "The name of the function to be called if this proposal passes", + "labelFunctionSignature": "Function Signature", + "helperFunctionSignature": "The parameter types of the function (above) to be called", + "labelParameters": "Parameters", + "helperParameters": "Parameters used to call the function (comma separated)", + "labelEthValue": "ETH Value", + "helperEthValue": "Amount of ETH to send with the transaction (from your treasury)", + "ownerApproved": "Approved", + "stateModule": "Module", + "stateModuleTip": "This transaction was executed by a Safe{Wallet} module.", + "stateActive": "Active", + "stateActiveTip": "The proposal can be voted on. This is the initial state of all newly created proposals.", + "stateRejected": "Rejected", + "stateRejectedTip": "The proposal failed due to another proposal being executed with the same nonce.", + "stateExecutable": "Executable", + "stateExecutableTip": "The proposal is now able to be executed on chain.", + "stateExecuted": "Executed", + "stateExecutedTip": "The proposal has been executed.", + "stateFailed": "Failed", + "stateFailedTip": "The proposal failed to achieve quorum or did not get enough votes to pass.", + "stateClosed": "Closed", + "stateClosedTip": "The Snapshot proposal voting has been closed", + "statePending": "Pending", + "statePendingTip": "The Snapshot proposal voting has not begun yet", + "stateTimeLocked": "Timelocked", + "stateTimelockedTip": "The proposal has passed, but cannot yet be executed until its timelock period has ended.", + "stateFreezeInit": "Freeze Pending", + "stateFreezeInitTip": "The parent has initiated a vote to potentially freeze this sub-Safe.", + "stateFrozen": "Frozen", + "stateFrozenTip": "The parent has voted to freeze this Safe.", + "stateTimelockable": "Timelockable", + "stateTimelockableTip": "The proposal can now be timelocked for execution.", + "stateExpired": "Expired", + "stateExpiredTip": "The proposal passed but was not executed before the execution period ended.", + "showExecutableCode": "Show Executable Code", + "hideExecutableCode": "Hide Executable Code", + "noProposal": "No proposal found", + "emptyProposals": "No proposals found", + "createProposal": "Create Proposal", + "labelAddTransaction": "+ Add Transaction", + "errorInvalidFragments": "Invalid combination of function signature and parameters", + "proposalSummaryTitle": "Proposal Details", + "proposalSummaryStartDate": "Start Date", + "proposalSummaryEndDate": "End Date", + "snapshotTaken": "Snapshot taken", + "votingPower": "Voting power", + "votingPowerTooltip": "This is your voting power at a time of taking snapshot, which happens in the moment of proposal creation", + "show": "Show", + "proposalSupportERC20SummaryHelper": "Minimum {{quorum}}% required out of {{total}}", + "proposalSupportERC721SummaryHelper": "Minimum {{quorum}} token votes required out {{total}}", + "proposedBy": "Proposed by", + "proposalTitle_one": "Proposal to execute {{count}} transaction on {{target}}", + "proposalTitle_other": "Proposal to execute {{count}} transactions on {{target}}", + "proposalTitle": "Proposal Title", + "proposalTitleHelper": "A short title for this proposal", + "proposalTitlePlaceholder": "A new proposal", + "proposalDescription": "Proposal Description", + "proposalDescriptionHelper": "Add a brief description", + "proposalDescriptionPlaceholder": "A brief description", + "proposalAdditionalResources": "Additional Resources", + "proposalAdditionalResourcesHelper": "A link to any discussion or formal documentation", + "proposalAdditionalResourcesPlaceholder": "https://", + "votesTitle": "Votes", + "nftVotes_one": "{{count}} vote", + "nftVotes_other": "{{count}} votes", + "signTitle": "Sign Transaction", + "timelockTitle": "Timelock Transaction", + "executeTitle": "Execute Transaction", + "filterProposalsAllSelected": "All Proposals", + "filterProposalsNoneSelected": "No Proposals", + "filterProposalsNSelected": "Filter {{count}}", + "filter": "Filter", + "transaction": "Transaction", + "proposal": "Proposal", + "transactionExecutionAlertMessage": "Transactions execute in the order they are added", + "labelProposalVotingPeriod": "Voting Period", + "labelProposalQuorum": "Quorum", + "labelProposalTimelock": "Timelock", + "labelProposalSigners": "Signers", + "support": "Support", + "signers": "Signers", + "txDetailsSignersCurrent": "Current Signatures", + "txDetailsSignersRequired": "Signers Required", + "created": "Created", + "transactionHash": "Transaction Hash", + "proposalId": "Proposal Id", + "pendingSign": "Use wallet to sign transaction", + "failedSign": "Signing failed", + "successSign": "Signing successful", + "proposalCreatePendingToastMessage": "Creating new proposal", + "proposalCreateSuccessToastMessage": "Proposal successfully created", + "proposalCreateFailureToastMessage": "Proposal creation failed", + "paramTarget": "target", + "paramFunction": "function", + "paramTypes": "parameter types", + "paramInputs": "parameter inputs", + "paramValue": "transaction value", + "multisigMetadataMessage": "This transaction contains the proposal's encoded metadata.", + "multisigMetadataWarning": "Adding metadata (title, description, url) to a multisig proposal will add execution cost.", + "decodingFailedMessage": "Unable to decode transaction's data.", + "customNonce": "Custom Nonce", + "customNonceTooltip": "Set a custom proposal nonce if necessary to prevent nonce collisions", + "nonce": "Nonce", + "votingTooltip": "Time remaining to cast your vote.", + "timeLockedTooltip": "This proposal can be executed once its timelock period has passed.", + "executableTooltip": "Time remaining to execute this proposal", + "learnMore": "Learn More", + "clawBackPendingToastMessage": "Creating clawback proposal", + "clawBackSuccessToastMessage": "Clawback proposal successfully created", + "clawBackFailedToastMessage": "Error creating clawback proposal", + "proposalStartBlockNotFinalized": "You can't vote right now as proposal's start block not yet finalized.", + "currentUserAlreadyVoted": "Seems like you already casted your vote. Check the votes summary", + "customNonceError": "The nonce you entered is lower than the Safe{Wallet}'s current nonce.", + "noTimeDifference": "0 seconds", + "functionNameError": "The function name can contain only numbers and letters.", + "labelOrValueRequired": "Parameter Label or Parameter Value is required", + "votingSystem": "Voting system", + "basicSnapshotVotingSystem": "Basic voting", + "singleSnapshotVotingSystem": "Single choice voting", + "weightedSnapshotVotingSystem": "Weighted voting", + "unknownSnapshotVotingSystem": "Unknown voting system", + "ipfs": "IPFS", + "privacy": "Privacy", + "shielded": "Shielded", + "shutterPrivacy": "Shielded", + "shutterVotesHidden": "Results hidden during voting", + "totalVotes": "Total votes" +} diff --git a/src/i18n/locales/en/proposalMetadata.json b/src/i18n/locales/en/proposalMetadata.json index 9f5d75ab4d..a5df35a78c 100644 --- a/src/i18n/locales/en/proposalMetadata.json +++ b/src/i18n/locales/en/proposalMetadata.json @@ -1,20 +1,20 @@ { - "Create a sub-Safe": "Create a sub-Safe", - "Send Token": "Send Token", - "Send Eth": "Send Eth", - "Clawback Proposal": "Clawback Proposal", - "Transfer all funds from the targeted sub-Safe to the parent-Safe treasury.": "Transfer all funds from the targeted sub-Safe to the parent-Safe treasury.", - "Create Proposal Template": "Create Proposal Template", - "Execution of this proposal will create a new proposal template, attached to this Safe.": "Execution of this proposal will create a new proposal template, attached to this Safe.", - "Remove Proposal Template": "Remove Proposal Template", - "Execution of this proposal will remove proposal template, attached to this Safe.": "Execution of this proposal will remove proposal template, attached to this Safe.", - "Change governance module to Azorius": "Change governance module to Azorius", - "Update Safe Name": "Update Safe Name", - "Update Snapshot Space": "Update Snapshot Space", - "Lido Withdrawal": "Lido Withdrawal", - "This proposal will burn your Lido Withdrawal NFT and return the ETH to your Safe.": "This proposal will burn your Lido Withdrawal NFT and return the ETH to your Safe.", - "Unstake stETH": "Unstake stETH", - "This proposal will unstake stETH from Lido and mint a Lido Withdrawal NFT which can be used to claim your ETH.": "This proposal will unstake stETH from Lido and mint a Lido Withdrawal NFT which can be used to claim your ETH.", - "Stake ETH with Lido": "Stake ETH with Lido", - "This proposal will stake ETH in Lido, returning stETH to your treasury.": "This proposal will stake ETH in Lido, returning stETH to your treasury." -} \ No newline at end of file + "Create a sub-Safe": "Create a sub-Safe", + "Send Token": "Send Token", + "Send Eth": "Send Eth", + "Clawback Proposal": "Clawback Proposal", + "Transfer all funds from the targeted sub-Safe to the parent-Safe treasury.": "Transfer all funds from the targeted sub-Safe to the parent-Safe treasury.", + "Create Proposal Template": "Create Proposal Template", + "Execution of this proposal will create a new proposal template, attached to this Safe.": "Execution of this proposal will create a new proposal template, attached to this Safe.", + "Remove Proposal Template": "Remove Proposal Template", + "Execution of this proposal will remove proposal template, attached to this Safe.": "Execution of this proposal will remove proposal template, attached to this Safe.", + "Change governance module to Azorius": "Change governance module to Azorius", + "Update Safe Name": "Update Safe Name", + "Update Snapshot Space": "Update Snapshot Space", + "Lido Withdrawal": "Lido Withdrawal", + "This proposal will burn your Lido Withdrawal NFT and return the ETH to your Safe.": "This proposal will burn your Lido Withdrawal NFT and return the ETH to your Safe.", + "Unstake stETH": "Unstake stETH", + "This proposal will unstake stETH from Lido and mint a Lido Withdrawal NFT which can be used to claim your ETH.": "This proposal will unstake stETH from Lido and mint a Lido Withdrawal NFT which can be used to claim your ETH.", + "Stake ETH with Lido": "Stake ETH with Lido", + "This proposal will stake ETH in Lido, returning stETH to your treasury.": "This proposal will stake ETH in Lido, returning stETH to your treasury." +} diff --git a/src/i18n/locales/en/proposalTemplate.json b/src/i18n/locales/en/proposalTemplate.json index 79eedbb7ab..a6b9d7111c 100644 --- a/src/i18n/locales/en/proposalTemplate.json +++ b/src/i18n/locales/en/proposalTemplate.json @@ -1,30 +1,30 @@ { - "createProposalTemplate": "Add a new Proposal Template", - "proposalTemplateTitle": "Proposal Template Title", - "proposalTemplateTitleHelperText": "A short title for this proposal template", - "proposalTemplateDescription": "Description", - "proposalTemplateDescriptionHelperText": "Add a brief description", - "preview": "Preview", - "previewTitle": "Title", - "previewThumnbail": "Thumbnail", - "labelFunctionParameter": "Function Parameter", - "helperFunctionParameter": "The parameter of the function of the above smart contract to be called", - "labelParameterLabel": "Parameter Label", - "helperParameterLabel": "Add a label for a blank input", - "labelParameterValue": "Parameter Value", - "proposalTemplateLeaveBlank": "or leave blank to allow for user input", - "parameter": "Parameter", - "value": "Value", - "userInput": "user input", - "eth": "ETH", - "emptyProposalTemplates": "No templates yet.", - "emptyProposalTemplatesCreateMessage": "Add your first template.", - "removeTemplatePendingToastMessage": "Creating proposal to remove proposal template", - "removeTemplateSuccessToastMessage": "Proposal to remove template created. Vote and execute to apply changes.", - "removeTemplateFailureToastMessage": "Creating proposal for removing proposal template failed.", - "submitProposalFromTemplate": "Submit and create proposal", - "targetDAOAddressLabel": "Safe address", - "targetDAOAddressHelper": "Set the location that will receive this duplicated template", - "forkTemplateSubmitButton": "Review Forked Template", - "showParameters": "Show All" -} \ No newline at end of file + "createProposalTemplate": "Add a new Proposal Template", + "proposalTemplateTitle": "Proposal Template Title", + "proposalTemplateTitleHelperText": "A short title for this proposal template", + "proposalTemplateDescription": "Description", + "proposalTemplateDescriptionHelperText": "Add a brief description", + "preview": "Preview", + "previewTitle": "Title", + "previewThumnbail": "Thumbnail", + "labelFunctionParameter": "Function Parameter", + "helperFunctionParameter": "The parameter of the function of the above smart contract to be called", + "labelParameterLabel": "Parameter Label", + "helperParameterLabel": "Add a label for a blank input", + "labelParameterValue": "Parameter Value", + "proposalTemplateLeaveBlank": "or leave blank to allow for user input", + "parameter": "Parameter", + "value": "Value", + "userInput": "user input", + "eth": "ETH", + "emptyProposalTemplates": "No templates yet.", + "emptyProposalTemplatesCreateMessage": "Add your first template.", + "removeTemplatePendingToastMessage": "Creating proposal to remove proposal template", + "removeTemplateSuccessToastMessage": "Proposal to remove template created. Vote and execute to apply changes.", + "removeTemplateFailureToastMessage": "Creating proposal for removing proposal template failed.", + "submitProposalFromTemplate": "Submit and create proposal", + "targetDAOAddressLabel": "Safe address", + "targetDAOAddressHelper": "Set the location that will receive this duplicated template", + "forkTemplateSubmitButton": "Review Forked Template", + "showParameters": "Show All" +} diff --git a/src/i18n/locales/en/settings.json b/src/i18n/locales/en/settings.json index a5bc7d2087..b6f67ee934 100644 --- a/src/i18n/locales/en/settings.json +++ b/src/i18n/locales/en/settings.json @@ -1,23 +1,23 @@ { - "governanceTokenTitle": "Governance Token", - "governanceERC721TokenTitle": "Governance NFT Token(s)", - "governanceTokenDescription": "A governance token is a digital token (erc-20) that grants holders the right to vote and participate in the decision-making process of the DAO.", - "governanceERC721TokenDescription": "A governance NFT is a digital token based on the ERC-721 standard that grants holders the right to vote and participate in the decision-making process of the DAO.", - "governanceTokenNameLabel": "Name", - "governanceTokenSymbolLabel": "Symbol", - "governanceTokenWeightLabel": "Token Weight", - "governanceTokenTotalWeightLabel": "Total Weight", - "governanceTokenSupplyLabel": "Total Supply", - "noModulesAttached": "No Modules Attached", - "noGuardAttached": "No Guard Attached", - "modulesTitle": "Modules", - "guardTitle": "Guard", - "modulesAndGuardsTitle": "Modules and Guards", - "modulesAndGuardsDescription1": "Modules are contracts added to extends a Safe's capabilities.", - "modulesAndGuardsDescription2": "A Freeze Guard is added to all sub-Safes created on Fractal. A Freeze Guard allow the parent Safe's members to block the sub-Safe's access to funds and freeze active proposals.", - "daoMetadataName": "Safe Name", - "daoMetadataSnapshot": "Snapshot Space", - "daoMetadataDescriptionTitle": "Safe Metadata & Off-chain", - "daoMetadataDescriptionText": "Activities or transactions that occur outside of the blockchain network or aren't stored on-chain.", - "proposeChanges": "Propose Changes" -} \ No newline at end of file + "governanceTokenTitle": "Governance Token", + "governanceERC721TokenTitle": "Governance NFT Token(s)", + "governanceTokenDescription": "A governance token is a digital token (erc-20) that grants holders the right to vote and participate in the decision-making process of the DAO.", + "governanceERC721TokenDescription": "A governance NFT is a digital token based on the ERC-721 standard that grants holders the right to vote and participate in the decision-making process of the DAO.", + "governanceTokenNameLabel": "Name", + "governanceTokenSymbolLabel": "Symbol", + "governanceTokenWeightLabel": "Token Weight", + "governanceTokenTotalWeightLabel": "Total Weight", + "governanceTokenSupplyLabel": "Total Supply", + "noModulesAttached": "No Modules Attached", + "noGuardAttached": "No Guard Attached", + "modulesTitle": "Modules", + "guardTitle": "Guard", + "modulesAndGuardsTitle": "Modules and Guards", + "modulesAndGuardsDescription1": "Modules are contracts added to extends a Safe's capabilities.", + "modulesAndGuardsDescription2": "A Freeze Guard is added to all sub-Safes created on Fractal. A Freeze Guard allow the parent Safe's members to block the sub-Safe's access to funds and freeze active proposals.", + "daoMetadataName": "Safe Name", + "daoMetadataSnapshot": "Snapshot Space", + "daoMetadataDescriptionTitle": "Safe Metadata & Off-chain", + "daoMetadataDescriptionText": "Activities or transactions that occur outside of the blockchain network or aren't stored on-chain.", + "proposeChanges": "Propose Changes" +} diff --git a/src/i18n/locales/en/stake.json b/src/i18n/locales/en/stake.json index fd48964dab..7156362899 100644 --- a/src/i18n/locales/en/stake.json +++ b/src/i18n/locales/en/stake.json @@ -1,4 +1,4 @@ { - "stakeAmount": "ETH Amount", - "submitStakingProposal": "Submit staking proposal" -} \ No newline at end of file + "stakeAmount": "ETH Amount", + "submitStakingProposal": "Submit staking proposal" +} diff --git a/src/i18n/locales/en/transaction.json b/src/i18n/locales/en/transaction.json index c8a590d0bf..84d391a2e0 100644 --- a/src/i18n/locales/en/transaction.json +++ b/src/i18n/locales/en/transaction.json @@ -1,25 +1,25 @@ { - "pendingCastVote": "Casting vote", - "failedCastVote": "Vote failed", - "successCastVote": "Vote successful", - "pendingCastFreezeVote": "Casting freeze vote", - "failedCastFreezeVote": "Freeze vote failed", - "successCastFreezeVote": "Freeze vote successful", - "pendingDelegateVote": "Delegating vote", - "failedDelegateVote": "Delegation failed", - "successDelegateVote": "Delegation successful", - "pendingExecute": "Executing transaction", - "failedExecute": "Execution failed", - "successExecute": "Execution successful", - "errorTransactionUnknown": "An unknown error occurred and the transaction did not succeed. Please try again later.", - "pendingDeploySafe": "Deploying your Safe", - "failedDeploySafe": "Deployment failed", - "successDeploySafe": "Deployment successful", - "failedIndexSafe": "Your Safe was deployed but is not yet available to view. It has been added to your favorites list. ", - "errorUserDeniedTransaction": "User rejected the transaction", - "pendingTokenClaim": "Claiming {{ symbol }}", - "failedTokenClaim": "Failed to claim {{ symbol }}", - "successTokenClaim": "You have successfully claimed {{ amount }} {{ symbol }}", - "snapshotRecastVoteHelper": "You can still change your vote until the vote end date", - "modifyGovernanceSetAzoriusProposalPendingMessage": "Submitting proposal to modify governance..." -} \ No newline at end of file + "pendingCastVote": "Casting vote", + "failedCastVote": "Vote failed", + "successCastVote": "Vote successful", + "pendingCastFreezeVote": "Casting freeze vote", + "failedCastFreezeVote": "Freeze vote failed", + "successCastFreezeVote": "Freeze vote successful", + "pendingDelegateVote": "Delegating vote", + "failedDelegateVote": "Delegation failed", + "successDelegateVote": "Delegation successful", + "pendingExecute": "Executing transaction", + "failedExecute": "Execution failed", + "successExecute": "Execution successful", + "errorTransactionUnknown": "An unknown error occurred and the transaction did not succeed. Please try again later.", + "pendingDeploySafe": "Deploying your Safe", + "failedDeploySafe": "Deployment failed", + "successDeploySafe": "Deployment successful", + "failedIndexSafe": "Your Safe was deployed but is not yet available to view. It has been added to your favorites list. ", + "errorUserDeniedTransaction": "User rejected the transaction", + "pendingTokenClaim": "Claiming {{ symbol }}", + "failedTokenClaim": "Failed to claim {{ symbol }}", + "successTokenClaim": "You have successfully claimed {{ amount }} {{ symbol }}", + "snapshotRecastVoteHelper": "You can still change your vote until the vote end date", + "modifyGovernanceSetAzoriusProposalPendingMessage": "Submitting proposal to modify governance..." +} diff --git a/src/i18n/locales/en/treasury.json b/src/i18n/locales/en/treasury.json index f32ae46a59..d59e42e02d 100644 --- a/src/i18n/locales/en/treasury.json +++ b/src/i18n/locales/en/treasury.json @@ -1,25 +1,25 @@ { - "titleTreasury": "{{daoName}} Treasury", - "buttonSendAssets": "Send Assets", - "titleTransactions": "Transactions", - "titleAssets": "Assets", - "subtitleCoinBalance": "Total Coin Balance", - "subtitleStaking": "Lido ETH Staking", - "columnCoins": "Coins", - "columnValue": "Value", - "columnAllocation": "Allocation", - "columnNFTs": "NFTs", - "labelSent": "Sent", - "labelReceived": "Received", - "textEmptyTransactions": "No transfers yet", - "textMoreTransactions": "View more", - "addresses": "{{numOfAddresses}} addresses", - "approveToken": "Approve Token", - "approvalPendingMessage": "Approving tokens...", - "approvalFailedMessage": "Token approval failed!", - "approvalSuccessMessage": "Tokens approved", - "stake": "Stake", - "unstake": "Unstake", - "claimUnstakedETH": "Claim ETH", - "nonClaimableYet": "Your ETH cannot yet be claimed from Lido" -} \ No newline at end of file + "titleTreasury": "{{daoName}} Treasury", + "buttonSendAssets": "Send Assets", + "titleTransactions": "Transactions", + "titleAssets": "Assets", + "subtitleCoinBalance": "Total Coin Balance", + "subtitleStaking": "Lido ETH Staking", + "columnCoins": "Coins", + "columnValue": "Value", + "columnAllocation": "Allocation", + "columnNFTs": "NFTs", + "labelSent": "Sent", + "labelReceived": "Received", + "textEmptyTransactions": "No transfers yet", + "textMoreTransactions": "View more", + "addresses": "{{numOfAddresses}} addresses", + "approveToken": "Approve Token", + "approvalPendingMessage": "Approving tokens...", + "approvalFailedMessage": "Token approval failed!", + "approvalSuccessMessage": "Tokens approved", + "stake": "Stake", + "unstake": "Unstake", + "claimUnstakedETH": "Claim ETH", + "nonClaimableYet": "Your ETH cannot yet be claimed from Lido" +} diff --git a/src/i18n/locales/uk/breadcrumbs.json b/src/i18n/locales/uk/breadcrumbs.json index 1f338679c3..e3d32b8fdf 100644 --- a/src/i18n/locales/uk/breadcrumbs.json +++ b/src/i18n/locales/uk/breadcrumbs.json @@ -1,7 +1,7 @@ { - "proposals": "Пропозиції", - "proposalNew": "Нова Пропозиція", - "nodes": "Ієрархія", - "treasury": "Скарбниця", - "proposal": "#{{proposalId}} {{proposalTitle}}" -} \ No newline at end of file + "proposals": "Пропозиції", + "proposalNew": "Нова Пропозиція", + "nodes": "Ієрархія", + "treasury": "Скарбниця", + "proposal": "#{{proposalId}} {{proposalTitle}}" +} diff --git a/src/i18n/locales/uk/common.json b/src/i18n/locales/uk/common.json index 22ea9a4c16..78bfab3b26 100644 --- a/src/i18n/locales/uk/common.json +++ b/src/i18n/locales/uk/common.json @@ -1,64 +1,64 @@ { - "no": "Ні", - "yes": "Так", - "skip": "Пропустити", - "next": "Наступний", - "accept": "Прийняти", - "add": "Додати", - "approve": "Затвердити", - "created": "Створено", - "remove": "Прибрати", - "reject": "Відхилити", - "view": "Переглянути", - "details": "Деталі", - "vote": "Проголосувати", - "send": "Відправити", - "transfer": "перевести", - "received": "Отримано", - "to": "До", - "from": "Від", - "execute": "Виконати", - "move": "Посунути", - "minutes": "Хв.", - "quorum": "Кворум", - "deploy": "Створити", - "abstain": "Утриматись", - "loading": "Завантаження...", - "reload": "Перезавантажити", - "delegate": "Делегувати", - "home": "Головна", - "labelEtherscan": "Переглянути на Etherscan", - "errorInvalidENSAddress": "Некорректне ім'я ENS або адреса", - "errorInvalidAddress": "Некорректна Ethereum адреса", - "errorSentryFallbackTitle": "Йой!", - "errorSentryFallbackMessage": "Невідома халепка трапилась. Спробуйте перезавантажити сторінку, або <1> скажіть розробникам полагодити цю фігню!", - "errorGeneral": "Трапилась несподівана бавовна мейнфрему. Будь ласка, спробуйте пізніше, коли воно охолоне.", - "labelNowishAgo": "щойно", - "labelMinutesAgo": "{{count}} хвилин тому", - "labelHoursAgo_one": "{{count}} годину тому", - "labelHoursAgo_other": "{{count}} годин тому", - "labelDaysAgo_one": "{{count}} день тому", - "labelDaysAgo_other": "{{count}} днів тому", - "labelMonthsAgo_one": "{{count}} місяць тому", - "labelMonthsAgo_other": "{{count}} місяців тому", - "labelYearsAgo_one": "{{count}} рік тому", - "labelYearsAgo_other": "{{count}} років тому", - "labelNowishLeft": "залишилось кілька хвилин", - "labelMinutesLeft": "{{count}} хвилин лишилось", - "labelHoursLeft_one": "{{count}} година лишилась", - "labelHoursLeft_other": "{{count}} годин лишилось", - "labelDaysLeft_one": "{{count}} день лишився", - "labelDaysLeft_other": "{{count}} днів лишилось", - "labelMonthsLeft_one": "{{count}} місяць лишився", - "labelMonthsLeft_other": "{{count}} місяців лишилось", - "labelYearsLeft_one": "{{count}} рік лишився", - "labelYearsLeft_other": "{{count}} років лишилось", - "toastClipboardCopy": "Скопійовано до буферу", - "newest": "Новіші", - "oldest": "Старіші", - "filter": "Фільтрувати", - "selectAll": "Обрати усі", - "clear": "Очистити", - "cancel": "Відмінити", - "example": "Приклад" -} \ No newline at end of file + "no": "Ні", + "yes": "Так", + "skip": "Пропустити", + "next": "Наступний", + "accept": "Прийняти", + "add": "Додати", + "approve": "Затвердити", + "created": "Створено", + "remove": "Прибрати", + "reject": "Відхилити", + "view": "Переглянути", + "details": "Деталі", + "vote": "Проголосувати", + "send": "Відправити", + "transfer": "перевести", + "received": "Отримано", + "to": "До", + "from": "Від", + "execute": "Виконати", + "move": "Посунути", + "minutes": "Хв.", + "quorum": "Кворум", + "deploy": "Створити", + "abstain": "Утриматись", + "loading": "Завантаження...", + "reload": "Перезавантажити", + "delegate": "Делегувати", + "home": "Головна", + "labelEtherscan": "Переглянути на Etherscan", + "errorInvalidENSAddress": "Некорректне ім'я ENS або адреса", + "errorInvalidAddress": "Некорректна Ethereum адреса", + "errorSentryFallbackTitle": "Йой!", + "errorSentryFallbackMessage": "Невідома халепка трапилась. Спробуйте перезавантажити сторінку, або <1> скажіть розробникам полагодити цю фігню!", + "errorGeneral": "Трапилась несподівана бавовна мейнфрему. Будь ласка, спробуйте пізніше, коли воно охолоне.", + "labelNowishAgo": "щойно", + "labelMinutesAgo": "{{count}} хвилин тому", + "labelHoursAgo_one": "{{count}} годину тому", + "labelHoursAgo_other": "{{count}} годин тому", + "labelDaysAgo_one": "{{count}} день тому", + "labelDaysAgo_other": "{{count}} днів тому", + "labelMonthsAgo_one": "{{count}} місяць тому", + "labelMonthsAgo_other": "{{count}} місяців тому", + "labelYearsAgo_one": "{{count}} рік тому", + "labelYearsAgo_other": "{{count}} років тому", + "labelNowishLeft": "залишилось кілька хвилин", + "labelMinutesLeft": "{{count}} хвилин лишилось", + "labelHoursLeft_one": "{{count}} година лишилась", + "labelHoursLeft_other": "{{count}} годин лишилось", + "labelDaysLeft_one": "{{count}} день лишився", + "labelDaysLeft_other": "{{count}} днів лишилось", + "labelMonthsLeft_one": "{{count}} місяць лишився", + "labelMonthsLeft_other": "{{count}} місяців лишилось", + "labelYearsLeft_one": "{{count}} рік лишився", + "labelYearsLeft_other": "{{count}} років лишилось", + "toastClipboardCopy": "Скопійовано до буферу", + "newest": "Новіші", + "oldest": "Старіші", + "filter": "Фільтрувати", + "selectAll": "Обрати усі", + "clear": "Очистити", + "cancel": "Відмінити", + "example": "Приклад" +} diff --git a/src/i18n/locales/uk/daoCreate.json b/src/i18n/locales/uk/daoCreate.json index ff1cb88b04..9f559eda3d 100644 --- a/src/i18n/locales/uk/daoCreate.json +++ b/src/i18n/locales/uk/daoCreate.json @@ -1,53 +1,53 @@ { - "buttonCreate": "Створити Fractal", - "errorLowSignerThreshold": "Поріг голосів має бути більше ніж 0", - "errorHighSignerThreshold": "Поріг голосів зависокий", - "labelSigners": "Загалом підписантів", - "labelSigThreshold": "Поріг підписів", - "titleSignerAddresses": "Адреса підписанта", - "subTitleSignerAddresses": "Наступні користувачі матимуть змогу створювати і затверджувати пропозиції", - "titleEssentials": "Створити Основне", - "titleAzoriusConfig": "Конфігурація голосувальних токенів", - "titleSafeConfig": "Конфігурація Multisig", - "titleSignerConfig": "Параметри підписантів", - "titleGovConfig": "Скласти систему управління", - "labelSelectToken": "Обрати Токен", - "labelVotingPeriod": "Період голосування", - "labelChooseGovernance": "Обрати систему управління", - "helperChooseGovernance": "Яку схему управління ви б хотіли?", - "exampleVotingPeriod": "10080 хвилин = 1 тиждень", - "helperVotingPeriod": "Тривалість часу (у хвилинах), коли голосування дозволено після створення пропозиції", - "helperQuorum": "Відсоток від усіх можливих голосів, необхідних для проходження порогу голосування.", - "governanceDescription": "Ці значення можуть бути змінені пізніше через пропозицію", - "titleFreezeParams": "Параметри Заморозки", - "labelExecutionPeriod": "Період виконання", - "helperExecutionPeriod": "Тривалість часу (у хвилинах) коли успішна пропозиція має бути виконана", - "exampleExecutionPeriod": "2880 хвилин = 2 дні", - "helperTimelockPeriod": "Тривалість часу (у хвилинах) скільки успішна пропозиція має знаходитись у очікуванні перед тим як виконати иї у блокчейни", - "exampleTimelockPeriod": "1440 хвилин = 1 доба", - "labelFreezeVotesThreshold": "Поріг голосів замороження", - "labelFreezeProposalPeriod": "Період пропозиції замороження", - "helperFreezeProposalPeriod": "Тривалість часу (у хвилинах) від початку до кінця голосування щодо Замороження", - "exampleFreezeProposalPeriod": "10080 хвилин = 1 тиждень", - "labelFreezePeriod": "Період заморозки", - "exampleFreezePeriod": "10080 хвилин = 1 тиждень", - "errorDuplicateAddress": "Така адреса вже додана", - "labelAddAllocation": "+ Додати Розподілення", - "labelTokenName": "Ім'я токену", - "helperTokenName": "Як ваш токен голосування буде називатись?", - "labelTokenSymbol": "Символ токену", - "helperTokenSymbol": "Короткий символ вашого токену (3-5 символів)", - "labelTokenSupply": "Загальний випуск", - "helperTokenSupply": "Загальна кількість доступних токенів", - "descAzoriusGov": "Схема управління голосуванням ERC-20 токенами на контракті Safe{Wallet} гаманцю", - "labelFractalName": "Ім'я Fractal-у", - "titleProposalSettings": "Налаштування пропозицій", - "titleAllocations": "Первинне розподілення токенів", - "titleTokenParams": "Метадані токену", - "titleAddress": "Адреса", - "titleAmount": "Кількість", - "createSubDAOPendingToastMessage": "Створюємо пропозицію subDAO деплойменту", - "createSubDAOSuccessToastMessage": "Деплоймент закінчено успішно", - "createSubDAOFailureToastMessage": "Несподівана бавовна. Деплоймент провалено", - "helperAllocations": "Неросподілені токени будуть надіслани до скарбниці вашого DAO" -} \ No newline at end of file + "buttonCreate": "Створити Fractal", + "errorLowSignerThreshold": "Поріг голосів має бути більше ніж 0", + "errorHighSignerThreshold": "Поріг голосів зависокий", + "labelSigners": "Загалом підписантів", + "labelSigThreshold": "Поріг підписів", + "titleSignerAddresses": "Адреса підписанта", + "subTitleSignerAddresses": "Наступні користувачі матимуть змогу створювати і затверджувати пропозиції", + "titleEssentials": "Створити Основне", + "titleAzoriusConfig": "Конфігурація голосувальних токенів", + "titleSafeConfig": "Конфігурація Multisig", + "titleSignerConfig": "Параметри підписантів", + "titleGovConfig": "Скласти систему управління", + "labelSelectToken": "Обрати Токен", + "labelVotingPeriod": "Період голосування", + "labelChooseGovernance": "Обрати систему управління", + "helperChooseGovernance": "Яку схему управління ви б хотіли?", + "exampleVotingPeriod": "10080 хвилин = 1 тиждень", + "helperVotingPeriod": "Тривалість часу (у хвилинах), коли голосування дозволено після створення пропозиції", + "helperQuorum": "Відсоток від усіх можливих голосів, необхідних для проходження порогу голосування.", + "governanceDescription": "Ці значення можуть бути змінені пізніше через пропозицію", + "titleFreezeParams": "Параметри Заморозки", + "labelExecutionPeriod": "Період виконання", + "helperExecutionPeriod": "Тривалість часу (у хвилинах) коли успішна пропозиція має бути виконана", + "exampleExecutionPeriod": "2880 хвилин = 2 дні", + "helperTimelockPeriod": "Тривалість часу (у хвилинах) скільки успішна пропозиція має знаходитись у очікуванні перед тим як виконати иї у блокчейни", + "exampleTimelockPeriod": "1440 хвилин = 1 доба", + "labelFreezeVotesThreshold": "Поріг голосів замороження", + "labelFreezeProposalPeriod": "Період пропозиції замороження", + "helperFreezeProposalPeriod": "Тривалість часу (у хвилинах) від початку до кінця голосування щодо Замороження", + "exampleFreezeProposalPeriod": "10080 хвилин = 1 тиждень", + "labelFreezePeriod": "Період заморозки", + "exampleFreezePeriod": "10080 хвилин = 1 тиждень", + "errorDuplicateAddress": "Така адреса вже додана", + "labelAddAllocation": "+ Додати Розподілення", + "labelTokenName": "Ім'я токену", + "helperTokenName": "Як ваш токен голосування буде називатись?", + "labelTokenSymbol": "Символ токену", + "helperTokenSymbol": "Короткий символ вашого токену (3-5 символів)", + "labelTokenSupply": "Загальний випуск", + "helperTokenSupply": "Загальна кількість доступних токенів", + "descAzoriusGov": "Схема управління голосуванням ERC-20 токенами на контракті Safe{Wallet} гаманцю", + "labelFractalName": "Ім'я Fractal-у", + "titleProposalSettings": "Налаштування пропозицій", + "titleAllocations": "Первинне розподілення токенів", + "titleTokenParams": "Метадані токену", + "titleAddress": "Адреса", + "titleAmount": "Кількість", + "createSubDAOPendingToastMessage": "Створюємо пропозицію subDAO деплойменту", + "createSubDAOSuccessToastMessage": "Деплоймент закінчено успішно", + "createSubDAOFailureToastMessage": "Несподівана бавовна. Деплоймент провалено", + "helperAllocations": "Неросподілені токени будуть надіслани до скарбниці вашого DAO" +} diff --git a/src/i18n/locales/uk/daoEdit.json b/src/i18n/locales/uk/daoEdit.json index 544b7b4ddd..0967ef424b 100644 --- a/src/i18n/locales/uk/daoEdit.json +++ b/src/i18n/locales/uk/daoEdit.json @@ -1,3 +1 @@ -{ - -} \ No newline at end of file +{} diff --git a/src/i18n/locales/uk/dashboard.json b/src/i18n/locales/uk/dashboard.json index fea6bf5519..57b3b5ef87 100644 --- a/src/i18n/locales/uk/dashboard.json +++ b/src/i18n/locales/uk/dashboard.json @@ -1,23 +1,23 @@ { - "titleFavorites": "Вподобані", - "emptyFavorites": "Нема вподобайок", - "errorFailedSearch": "Вибачте, ця адреса - ані Safe{Wallet} ані Fractal", - "titleGovernance": "Система Управління", - "titleType": "Тип", - "titleVotingPeriod": "Період голосування", - "titleQuorum": "Кворум", - "titleProposals": "Пропозиції", - "titlePending": "В очікуванні", - "titlePassed": "Успішні", - "titleTreasury": "Скарбниця", - "noActivity": "Нема активності", - "proposalDescription_one": "Пропозиція виконати {{count}} транзакцію", - "proposalDescription_other": "Пропозиція виконати {{count}} транзакцій", - "freezeButton": "Заморозити", - "noVotesButton": "Користувач на має голосів", - "tipFreeze": "{{amount}} необхідні голоси замороження", - "proposalOnChainRejection": "що відхиляє транзакцію # {{proposalId}}", - "proposalDescriptionCont": "що буде", - "moduleDescription_one": "Виконано {{count}} транзакцію через модуль за адресою", - "moduleDescription_other": "Виконано {{count}} транзакцій через модуль за адресою(ми)" -} \ No newline at end of file + "titleFavorites": "Вподобані", + "emptyFavorites": "Нема вподобайок", + "errorFailedSearch": "Вибачте, ця адреса - ані Safe{Wallet} ані Fractal", + "titleGovernance": "Система Управління", + "titleType": "Тип", + "titleVotingPeriod": "Період голосування", + "titleQuorum": "Кворум", + "titleProposals": "Пропозиції", + "titlePending": "В очікуванні", + "titlePassed": "Успішні", + "titleTreasury": "Скарбниця", + "noActivity": "Нема активності", + "proposalDescription_one": "Пропозиція виконати {{count}} транзакцію", + "proposalDescription_other": "Пропозиція виконати {{count}} транзакцій", + "freezeButton": "Заморозити", + "noVotesButton": "Користувач на має голосів", + "tipFreeze": "{{amount}} необхідні голоси замороження", + "proposalOnChainRejection": "що відхиляє транзакцію # {{proposalId}}", + "proposalDescriptionCont": "що буде", + "moduleDescription_one": "Виконано {{count}} транзакцію через модуль за адресою", + "moduleDescription_other": "Виконано {{count}} транзакцій через модуль за адресою(ми)" +} diff --git a/src/i18n/locales/uk/home.json b/src/i18n/locales/uk/home.json index 5ac90b5048..e09e3a3026 100644 --- a/src/i18n/locales/uk/home.json +++ b/src/i18n/locales/uk/home.json @@ -1,3 +1,3 @@ { - "homeAttribution": "Зроблено за допомогою 💜 компанією <1>Decent DAO" -} \ No newline at end of file + "homeAttribution": "Зроблено за допомогою 💜 компанією <1>Decent DAO" +} diff --git a/src/i18n/locales/uk/languages.json b/src/i18n/locales/uk/languages.json index 5ade148438..82b0dc9e6f 100644 --- a/src/i18n/locales/uk/languages.json +++ b/src/i18n/locales/uk/languages.json @@ -1,5 +1,5 @@ { - "tooltipTitle": "Мова", - "en": "Англійська (EN)", - "uk": "Українська/Солов'їна (UK)" -} \ No newline at end of file + "tooltipTitle": "Мова", + "en": "Англійська (EN)", + "uk": "Українська/Солов'їна (UK)" +} diff --git a/src/i18n/locales/uk/menu.json b/src/i18n/locales/uk/menu.json index 69da5f1e1b..7e71558fcd 100644 --- a/src/i18n/locales/uk/menu.json +++ b/src/i18n/locales/uk/menu.json @@ -1,14 +1,14 @@ { - "connect": "Законнектити", - "connectWallet": "Законнектити гаманець", - "disconnect": "Розконектити", - "faq": "FAQ", - "discord": "Discord", - "docs": "Доки", - "toastSwitchChain": "Перемкніть ваш гаманець на одну з підтримуваних ланок: {{chainNames}}", - "network": "Мережа", - "wallet": "Крипто-гаманець", - "titleManageDAO": "Керувати DAO", - "optionInitiateFreeze": "Ініціювати Замороження", - "optionInitiateClawback": "Ініціювати Повернення Коштів" -} \ No newline at end of file + "connect": "Законнектити", + "connectWallet": "Законнектити гаманець", + "disconnect": "Розконектити", + "faq": "FAQ", + "discord": "Discord", + "docs": "Доки", + "toastSwitchChain": "Перемкніть ваш гаманець на одну з підтримуваних ланок: {{chainNames}}", + "network": "Мережа", + "wallet": "Крипто-гаманець", + "titleManageDAO": "Керувати DAO", + "optionInitiateFreeze": "Ініціювати Замороження", + "optionInitiateClawback": "Ініціювати Повернення Коштів" +} diff --git a/src/i18n/locales/uk/modals.json b/src/i18n/locales/uk/modals.json index edd6c7f91c..9f25260401 100644 --- a/src/i18n/locales/uk/modals.json +++ b/src/i18n/locales/uk/modals.json @@ -1,22 +1,22 @@ { - "delegateTitle": "Делегувати голосувальні токени", - "titleBalance": "Баланс", - "titleDelegatedTo": "Делеговано до", - "linkSelfDelegate": "Делегувати собі", - "labelDelegateInput": "Адреса, кому делеговано", - "buttonDelegate": "Делегувати токени голосування", - "sendAssetsTitle": "Відправити Активи", - "selectLabel": "Оберіть актив", - "selectSublabel": "Поточний баланс - {{balance}}", - "amountLabel": "Скільки?", - "destinationLabel": "Куди б ви хотіли це надіслати?", - "destinationSublabel": "Тільки адрема гаманця у мережі Ethereum", - "sendAssetsSubmit": "Підтвердження", - "sendAssetsPendingToastMessage": "Створення пропозиції для відправлення активів", - "sendAssetsSuccessToastMessage": "Пропозиція щодо надсилання активів створена успішно", - "sendAssetsFailureToastMessage": "Помилка при створенні пропозиціі щодо надсилання активів", - "confirmUrlTitle": "Це посилання переведе вас до", - "confirmUrlSubtitle": "Хочете продовжити?", - "modalContinue": "Продовжити", - "modalCancel": "Відмінити" -} \ No newline at end of file + "delegateTitle": "Делегувати голосувальні токени", + "titleBalance": "Баланс", + "titleDelegatedTo": "Делеговано до", + "linkSelfDelegate": "Делегувати собі", + "labelDelegateInput": "Адреса, кому делеговано", + "buttonDelegate": "Делегувати токени голосування", + "sendAssetsTitle": "Відправити Активи", + "selectLabel": "Оберіть актив", + "selectSublabel": "Поточний баланс - {{balance}}", + "amountLabel": "Скільки?", + "destinationLabel": "Куди б ви хотіли це надіслати?", + "destinationSublabel": "Тільки адрема гаманця у мережі Ethereum", + "sendAssetsSubmit": "Підтвердження", + "sendAssetsPendingToastMessage": "Створення пропозиції для відправлення активів", + "sendAssetsSuccessToastMessage": "Пропозиція щодо надсилання активів створена успішно", + "sendAssetsFailureToastMessage": "Помилка при створенні пропозиціі щодо надсилання активів", + "confirmUrlTitle": "Це посилання переведе вас до", + "confirmUrlSubtitle": "Хочете продовжити?", + "modalContinue": "Продовжити", + "modalCancel": "Відмінити" +} diff --git a/src/i18n/locales/uk/navigation.json b/src/i18n/locales/uk/navigation.json index 64ec0f0f26..8d5d9cc54b 100644 --- a/src/i18n/locales/uk/navigation.json +++ b/src/i18n/locales/uk/navigation.json @@ -1,11 +1,11 @@ { - "proposals": "Пропозиції", - "nodes": "Ієрархія DAO", - "treasury": "Скарбниця", - "faq": "FAQ", - "documentation": "Документація", - "ariaLabelFractalBrand": "Перейти до Головної", - "ariaLabelFAQ": "Посилання на FAQ", - "ariaLabelDiscord": "Посилання на Discord", - "ariaLabelDocumentation": "Посилання на документацію" -} \ No newline at end of file + "proposals": "Пропозиції", + "nodes": "Ієрархія DAO", + "treasury": "Скарбниця", + "faq": "FAQ", + "documentation": "Документація", + "ariaLabelFractalBrand": "Перейти до Головної", + "ariaLabelFAQ": "Посилання на FAQ", + "ariaLabelDiscord": "Посилання на Discord", + "ariaLabelDocumentation": "Посилання на документацію" +} diff --git a/src/i18n/locales/uk/proposal.json b/src/i18n/locales/uk/proposal.json index 3f214dede2..7c0b3655cb 100644 --- a/src/i18n/locales/uk/proposal.json +++ b/src/i18n/locales/uk/proposal.json @@ -1,94 +1,94 @@ { - "breakdownTitle": "Розбивка голосів", - "labelTargetAddress": "Цільова адреса", - "helperTargetAddress": "Адреса смарт-контракту, на якій буде виконано функцію, у разі проходження пропозиціх.", - "labelFunctionName": "Ім'я функціі", - "helperFunctionName": "Ім'я функціі зі смарт-контракту, що буде виконана у разі затвердження пропозиції", - "labelFunctionSignature": "Сигнатура функціі", - "labelParameters": "Параметри", - "helperParameters": "Параметри, використані для виконання функціі (через кому)", - "labelEthValue": "Значення ETH", - "helperEthValue": "Кількість ETH, що буде надіслана з цією транзакцією (з вашої скарбниці)", - "ownerApproved": "Затвердженно", - "stateModule": "Модуль", - "stateModuleTip": "Ця транзакція була виконана через модуль Safe{Wallet}.", - "stateActive": "Активні", - "stateActiveTip": "Можна голосувати за цією пропозицією. Це - початкова стадія для усіх створенних пропозицій.", - "stateRejected": "Відхилені", - "stateRejectedTip": "Пропозиція відхилена через те, що інша пропозиція з таким самим nonce вже була виконана.", - "stateExecutable": "Готове до виконання", - "stateExecuted": "Виконано", - "stateExecutedTip": "Цю пропозицією було виконано", - "stateFailed": "Провалено", - "stateFailedTip": "Ця пропозиція провалилась з набором кворуму або не набрала достатньо голосів", - "stateTimeLocked": "У черзі", - "stateFrozen": "Заморожено", - "stateTimelockable": "Готове до додавання у чергу", - "stateExpired": "Прострочене", - "stateExpiredTip": "Ця пропозиція загалом була прийнята, але не була виконана впродовж можливого періоду виконання", - "showExecutableCode": "Показати виконуваний код", - "hideExecutableCode": "Сховати виконуваний код", - "noProposal": "Пропозицію не знайдено", - "emptyProposals": "Пропозицій не знайдено", - "createProposal": "Створити пропозицію", - "labelAddTransaction": "+ Додати транзакцію", - "proposalSummaryTitle": "Деталі пропозиціі", - "proposalSummaryStartDate": "Початкова Дата", - "proposalSummaryEndDate": "Кінцева Дата", - "proposalSupportSummaryHelper": "Мінімальна обов'язкова підтримка - {{count}}% ", - "proposedBy": "Запропоновано від", - "proposalTitle_one": "Пропозиція виконати {{count}} транзакцію за адресою гаманцю: {{target}}.", - "proposalTitle_other": "Пропозиція виконати {{count}} транзакцій за адресою гаманцю(-ів): {{target}}.", - "proposalTitle": "Заголовок Пропозиції", - "proposalTitleHelper": "Короткий заголовок для пропозиції", - "proposalTitlePlaceholder": "Нова пропозиція", - "proposalDescription": "Описання Пропозиції", - "proposalDescriptionHelper": "Додайте коротке описання", - "proposalDescriptionPlaceholder": "Коротке описання", - "proposalAdditionalResources": "Додаткові ресурси", - "proposalAdditionalResourcesHelper": "Посилання на будь яке обговорення або формальна документація", - "proposalAdditionalResourcesPlaceholder": "https://", - "votesTitle": "Голоси", - "signTitle": "Підписати транзакцію", - "executeTitle": "Виконати транзакцію", - "filterProposalsAllSelected": "Всі пропозиції", - "filterProposalsNoneSelected": "Нема пропозицій", - "filterProposalsNSelected": "Відфільтровано {{count}}", - "filter": "Фільтр", - "transaction": "Транзакція", - "proposal": "Пропозиція", - "transactionExecutionAlertMessage": "Транзакції виконуються у порядку, у якому вони додаються", - "labelProposalVotingPeriod": "Період голосування", - "labelProposalQuorum": "Кворум", - "labelProposalSigners": "Підписанти", - "support": "Підтримка", - "signers": "Підписанти", - "txDetailsSignersCurrent": "Поточні підписи", - "txDetailsSignersRequired": "Обов'язкова кількість підписів", - "created": "Створено", - "transactionHash": "Хеш транзакції", - "proposalId": "Id пропозиції", - "pendingSign": "Використайте крипто-гаманець для підписання транзакції", - "failedSign": "Помилка при підписанні.", - "successSign": "Підписання успішно виконане", - "proposalCreatePendingToastMessage": "Створення нової пропозиції", - "proposalCreateSuccessToastMessage": "Нова пропозиція успішно створена", - "proposalCreateFailureToastMessage": "Трясця, створення пропозиції провалилось", - "paramTarget": "адреса", - "paramFunction": "функція", - "paramTypes": "тип параметрів", - "paramInputs": "значення параметрів", - "paramValue": "значення транзакції", - "decodingFailedMessage": "Неможливо декодувати дані транзакції", - "customNonce": "Власний Nonce", - "customNonceTooltip": "Зазначте власний nonce пропозиції, якщо необхідно, щоб попередити колізію", - "nonce": "Nonce", - "votingTooltip": "Часу лишилось для зарахування голосу", - "executableTooltip": "Часу лишилось для виконання пропозиції", - "learnMore": "Дізнатись більше", - "clawBackPendingToastMessage": "Створюємо пропозицію повернення коштів", - "clawBackSuccessToastMessage": "Створення пропозиції повернення коштів успішне", - "clawBackFailedToastMessage": "Халепка, помилка при створенні пропозиції повернення коштів", - "proposalStartBlockNotFinalized": "Ви не можете голосувати прямо зараз оскільки початковий блок з ланки ще не фіналізовано", - "currentUserAlreadyVoted": "Схоже, ви вже голосували. Перевірте список голосів" -} \ No newline at end of file + "breakdownTitle": "Розбивка голосів", + "labelTargetAddress": "Цільова адреса", + "helperTargetAddress": "Адреса смарт-контракту, на якій буде виконано функцію, у разі проходження пропозиціх.", + "labelFunctionName": "Ім'я функціі", + "helperFunctionName": "Ім'я функціі зі смарт-контракту, що буде виконана у разі затвердження пропозиції", + "labelFunctionSignature": "Сигнатура функціі", + "labelParameters": "Параметри", + "helperParameters": "Параметри, використані для виконання функціі (через кому)", + "labelEthValue": "Значення ETH", + "helperEthValue": "Кількість ETH, що буде надіслана з цією транзакцією (з вашої скарбниці)", + "ownerApproved": "Затвердженно", + "stateModule": "Модуль", + "stateModuleTip": "Ця транзакція була виконана через модуль Safe{Wallet}.", + "stateActive": "Активні", + "stateActiveTip": "Можна голосувати за цією пропозицією. Це - початкова стадія для усіх створенних пропозицій.", + "stateRejected": "Відхилені", + "stateRejectedTip": "Пропозиція відхилена через те, що інша пропозиція з таким самим nonce вже була виконана.", + "stateExecutable": "Готове до виконання", + "stateExecuted": "Виконано", + "stateExecutedTip": "Цю пропозицією було виконано", + "stateFailed": "Провалено", + "stateFailedTip": "Ця пропозиція провалилась з набором кворуму або не набрала достатньо голосів", + "stateTimeLocked": "У черзі", + "stateFrozen": "Заморожено", + "stateTimelockable": "Готове до додавання у чергу", + "stateExpired": "Прострочене", + "stateExpiredTip": "Ця пропозиція загалом була прийнята, але не була виконана впродовж можливого періоду виконання", + "showExecutableCode": "Показати виконуваний код", + "hideExecutableCode": "Сховати виконуваний код", + "noProposal": "Пропозицію не знайдено", + "emptyProposals": "Пропозицій не знайдено", + "createProposal": "Створити пропозицію", + "labelAddTransaction": "+ Додати транзакцію", + "proposalSummaryTitle": "Деталі пропозиціі", + "proposalSummaryStartDate": "Початкова Дата", + "proposalSummaryEndDate": "Кінцева Дата", + "proposalSupportSummaryHelper": "Мінімальна обов'язкова підтримка - {{count}}% ", + "proposedBy": "Запропоновано від", + "proposalTitle_one": "Пропозиція виконати {{count}} транзакцію за адресою гаманцю: {{target}}.", + "proposalTitle_other": "Пропозиція виконати {{count}} транзакцій за адресою гаманцю(-ів): {{target}}.", + "proposalTitle": "Заголовок Пропозиції", + "proposalTitleHelper": "Короткий заголовок для пропозиції", + "proposalTitlePlaceholder": "Нова пропозиція", + "proposalDescription": "Описання Пропозиції", + "proposalDescriptionHelper": "Додайте коротке описання", + "proposalDescriptionPlaceholder": "Коротке описання", + "proposalAdditionalResources": "Додаткові ресурси", + "proposalAdditionalResourcesHelper": "Посилання на будь яке обговорення або формальна документація", + "proposalAdditionalResourcesPlaceholder": "https://", + "votesTitle": "Голоси", + "signTitle": "Підписати транзакцію", + "executeTitle": "Виконати транзакцію", + "filterProposalsAllSelected": "Всі пропозиції", + "filterProposalsNoneSelected": "Нема пропозицій", + "filterProposalsNSelected": "Відфільтровано {{count}}", + "filter": "Фільтр", + "transaction": "Транзакція", + "proposal": "Пропозиція", + "transactionExecutionAlertMessage": "Транзакції виконуються у порядку, у якому вони додаються", + "labelProposalVotingPeriod": "Період голосування", + "labelProposalQuorum": "Кворум", + "labelProposalSigners": "Підписанти", + "support": "Підтримка", + "signers": "Підписанти", + "txDetailsSignersCurrent": "Поточні підписи", + "txDetailsSignersRequired": "Обов'язкова кількість підписів", + "created": "Створено", + "transactionHash": "Хеш транзакції", + "proposalId": "Id пропозиції", + "pendingSign": "Використайте крипто-гаманець для підписання транзакції", + "failedSign": "Помилка при підписанні.", + "successSign": "Підписання успішно виконане", + "proposalCreatePendingToastMessage": "Створення нової пропозиції", + "proposalCreateSuccessToastMessage": "Нова пропозиція успішно створена", + "proposalCreateFailureToastMessage": "Трясця, створення пропозиції провалилось", + "paramTarget": "адреса", + "paramFunction": "функція", + "paramTypes": "тип параметрів", + "paramInputs": "значення параметрів", + "paramValue": "значення транзакції", + "decodingFailedMessage": "Неможливо декодувати дані транзакції", + "customNonce": "Власний Nonce", + "customNonceTooltip": "Зазначте власний nonce пропозиції, якщо необхідно, щоб попередити колізію", + "nonce": "Nonce", + "votingTooltip": "Часу лишилось для зарахування голосу", + "executableTooltip": "Часу лишилось для виконання пропозиції", + "learnMore": "Дізнатись більше", + "clawBackPendingToastMessage": "Створюємо пропозицію повернення коштів", + "clawBackSuccessToastMessage": "Створення пропозиції повернення коштів успішне", + "clawBackFailedToastMessage": "Халепка, помилка при створенні пропозиції повернення коштів", + "proposalStartBlockNotFinalized": "Ви не можете голосувати прямо зараз оскільки початковий блок з ланки ще не фіналізовано", + "currentUserAlreadyVoted": "Схоже, ви вже голосували. Перевірте список голосів" +} diff --git a/src/i18n/locales/uk/proposalMetadata.json b/src/i18n/locales/uk/proposalMetadata.json index 6c89881878..e8a9153206 100644 --- a/src/i18n/locales/uk/proposalMetadata.json +++ b/src/i18n/locales/uk/proposalMetadata.json @@ -1,5 +1,5 @@ { - "Send Token": "Надіслати Токен", - "Send Eth": "Надіслати ETH", - "Clawback Proposal": "Пропозиція повернення коштів" -} \ No newline at end of file + "Send Token": "Надіслати Токен", + "Send Eth": "Надіслати ETH", + "Clawback Proposal": "Пропозиція повернення коштів" +} diff --git a/src/i18n/locales/uk/settings.json b/src/i18n/locales/uk/settings.json index e0712b9848..25c7ecb7bc 100644 --- a/src/i18n/locales/uk/settings.json +++ b/src/i18n/locales/uk/settings.json @@ -3,4 +3,4 @@ "guards": "Охоронці", "noModulesEnabled": "Немає ввімкнених модулів", "noGuardsEnabled": "Охоронці не ввімкнено" -} \ No newline at end of file +} diff --git a/src/i18n/locales/uk/transaction.json b/src/i18n/locales/uk/transaction.json index c5484919d6..a87893a22a 100644 --- a/src/i18n/locales/uk/transaction.json +++ b/src/i18n/locales/uk/transaction.json @@ -1,17 +1,17 @@ { - "pendingCastVote": "Зарахування голосу.", - "failedCastVote": "Помилка при зарахуванні голосу!", - "successCastVote": "Ваш голос зараховано!", - "pendingCastFreezeVote": "Виконуємо замороження голосування.", - "failedCastFreezeVote": "Помилка при замороженні голосування.", - "successCastFreezeVote": "Замороження голосування успішне!", - "pendingDelegateVote": "Делегування голосу.", - "failedDelegateVote": "Помилка при делегуванні голосу.", - "successDelegateVote": "Голос успішно делеговано!", - "pendingExecute": "Виконання транзакції.", - "failedExecute": "Помилка при виконанні транзакції!", - "successExecute": "Транзакція успішно виконана!", - "errorTransactionUnknown": "Трясця! Невідома халепка спричинила бавовнятко. Будь ласка, спробуйте пізніше.", - "successDeploySafe": "\"DAO\" успішно створено.", - "errorUserDeniedTransaction": "Здається, ви відхилили транзакцію." -} \ No newline at end of file + "pendingCastVote": "Зарахування голосу.", + "failedCastVote": "Помилка при зарахуванні голосу!", + "successCastVote": "Ваш голос зараховано!", + "pendingCastFreezeVote": "Виконуємо замороження голосування.", + "failedCastFreezeVote": "Помилка при замороженні голосування.", + "successCastFreezeVote": "Замороження голосування успішне!", + "pendingDelegateVote": "Делегування голосу.", + "failedDelegateVote": "Помилка при делегуванні голосу.", + "successDelegateVote": "Голос успішно делеговано!", + "pendingExecute": "Виконання транзакції.", + "failedExecute": "Помилка при виконанні транзакції!", + "successExecute": "Транзакція успішно виконана!", + "errorTransactionUnknown": "Трясця! Невідома халепка спричинила бавовнятко. Будь ласка, спробуйте пізніше.", + "successDeploySafe": "\"DAO\" успішно створено.", + "errorUserDeniedTransaction": "Здається, ви відхилили транзакцію." +} diff --git a/src/i18n/locales/uk/treasury.json b/src/i18n/locales/uk/treasury.json index 1e5ad70a2c..7da2099aae 100644 --- a/src/i18n/locales/uk/treasury.json +++ b/src/i18n/locales/uk/treasury.json @@ -1,16 +1,16 @@ { - "titleTreasury": "{{daoName}} Скарбниця", - "buttonSendAssets": "Надіслати активи", - "titleTransactions": "Транзакції", - "titleAssets": "Активи", - "subtitleCoinBalance": "Загальний баланс монети", - "columnCoins": "Монети", - "columnValue": "Значення", - "columnAllocation": "Розподілення", - "columnNFTs": "NFT", - "labelSent": "Відправлено", - "labelReceived": "Отримано", - "textEmptyTransactions": "Наразі нема виконаних трансферів", - "textMoreTransactions": "Більше...", - "addresses": "{{numOfAddresses}} адресами" -} \ No newline at end of file + "titleTreasury": "{{daoName}} Скарбниця", + "buttonSendAssets": "Надіслати активи", + "titleTransactions": "Транзакції", + "titleAssets": "Активи", + "subtitleCoinBalance": "Загальний баланс монети", + "columnCoins": "Монети", + "columnValue": "Значення", + "columnAllocation": "Розподілення", + "columnNFTs": "NFT", + "labelSent": "Відправлено", + "labelReceived": "Отримано", + "textEmptyTransactions": "Наразі нема виконаних трансферів", + "textMoreTransactions": "Більше...", + "addresses": "{{numOfAddresses}} адресами" +} diff --git a/src/models/AzoriusTxBuilder.ts b/src/models/AzoriusTxBuilder.ts index 52655294fe..068635e96b 100644 --- a/src/models/AzoriusTxBuilder.ts +++ b/src/models/AzoriusTxBuilder.ts @@ -57,7 +57,7 @@ export class AzoriusTxBuilder extends BaseTxBuilder { safeContract: GnosisSafeL2, predictedSafeAddress: string, parentAddress?: string, - parentTokenAddress?: string + parentTokenAddress?: string, ) { super( signerOrProvider, @@ -65,7 +65,7 @@ export class AzoriusTxBuilder extends BaseTxBuilder { azoriusContracts, daoData, parentAddress, - parentTokenAddress + parentTokenAddress, ); this.safeContract = safeContract; @@ -120,8 +120,8 @@ export class AzoriusTxBuilder extends BaseTxBuilder { 'removeOwner', [this.baseContracts.multiSendContract.address, owner, 1], 0, - false - ) + false, + ), ); return removeOwnerTxs; } @@ -135,7 +135,7 @@ export class AzoriusTxBuilder extends BaseTxBuilder { 'setAzorius', // contract function name [this.azoriusContract!.address], 0, - false + false, ); } @@ -145,7 +145,7 @@ export class AzoriusTxBuilder extends BaseTxBuilder { 'enableModule', [this.azoriusContract!.address], 0, - false + false, ); } @@ -155,7 +155,7 @@ export class AzoriusTxBuilder extends BaseTxBuilder { 'addOwnerWithThreshold', [this.azoriusContract!.address, 1], 0, - false + false, ); } @@ -165,7 +165,7 @@ export class AzoriusTxBuilder extends BaseTxBuilder { 'removeOwner', [this.azoriusContract!.address, this.baseContracts.multiSendContract.address, 1], 0, - false + false, ); } @@ -179,7 +179,7 @@ export class AzoriusTxBuilder extends BaseTxBuilder { this.tokenNonce, ], 0, - false + false, ); } @@ -197,7 +197,7 @@ export class AzoriusTxBuilder extends BaseTxBuilder { this.strategyNonce, ], 0, - false + false, ); } @@ -211,7 +211,7 @@ export class AzoriusTxBuilder extends BaseTxBuilder { this.azoriusNonce, ], 0, - false + false, ); } @@ -225,7 +225,7 @@ export class AzoriusTxBuilder extends BaseTxBuilder { this.claimNonce, ], 0, - false + false, ); } @@ -236,7 +236,7 @@ export class AzoriusTxBuilder extends BaseTxBuilder { 'approve', [this.predictedTokenClaimAddress, azoriusGovernanceDaoData.parentAllocationAmount], 0, - false + false, ); } @@ -250,7 +250,7 @@ export class AzoriusTxBuilder extends BaseTxBuilder { this.tokenNonce, ], 0, - false + false, ); } @@ -262,13 +262,13 @@ export class AzoriusTxBuilder extends BaseTxBuilder { this.encodedSetupERC20WrapperData = this.azoriusContracts!.votesERC20WrapperMasterCopyContract.interface.encodeFunctionData( 'setUp', - [encodedInitTokenData] + [encodedInitTokenData], ); } public setPredictedERC20WrapperAddress() { const tokenByteCodeLinear = generateContractByteCodeLinear( - this.azoriusContracts!.votesERC20WrapperMasterCopyContract.address.slice(2) + this.azoriusContracts!.votesERC20WrapperMasterCopyContract.address.slice(2), ); const tokenSalt = generateSalt(this.encodedSetupERC20WrapperData!, this.tokenNonce); @@ -276,7 +276,7 @@ export class AzoriusTxBuilder extends BaseTxBuilder { this.predictedTokenAddress = getCreate2Address( this.baseContracts.zodiacModuleProxyFactoryContract.address, tokenSalt, - solidityKeccak256(['bytes'], [tokenByteCodeLinear]) + solidityKeccak256(['bytes'], [tokenByteCodeLinear]), ); } @@ -290,13 +290,13 @@ export class AzoriusTxBuilder extends BaseTxBuilder { }; private calculateTokenAllocations( - azoriusGovernanceDaoData: AzoriusERC20DAO + azoriusGovernanceDaoData: AzoriusERC20DAO, ): [string[], BigNumber[]] { const tokenAllocationsOwners = azoriusGovernanceDaoData.tokenAllocations.map( - tokenAllocation => tokenAllocation.address + tokenAllocation => tokenAllocation.address, ); const tokenAllocationsValues = azoriusGovernanceDaoData.tokenAllocations.map( - tokenAllocation => tokenAllocation.amount || BigNumber.from(0) + tokenAllocation => tokenAllocation.amount || BigNumber.from(0), ); const tokenAllocationSum = tokenAllocationsValues.reduce((accumulator, tokenAllocation) => { return tokenAllocation!.add(accumulator); @@ -324,7 +324,7 @@ export class AzoriusTxBuilder extends BaseTxBuilder { azoriusGovernanceDaoData.tokenSymbol, tokenAllocationsOwners, tokenAllocationsValues, - ] + ], ); this.encodedSetupTokenData = @@ -335,7 +335,7 @@ export class AzoriusTxBuilder extends BaseTxBuilder { private setPredictedTokenAddress() { const tokenByteCodeLinear = generateContractByteCodeLinear( - this.azoriusContracts!.votesTokenMasterCopyContract.address.slice(2) + this.azoriusContracts!.votesTokenMasterCopyContract.address.slice(2), ); const tokenSalt = generateSalt(this.encodedSetupTokenData!, this.tokenNonce); @@ -343,7 +343,7 @@ export class AzoriusTxBuilder extends BaseTxBuilder { this.predictedTokenAddress = getCreate2Address( this.baseContracts.zodiacModuleProxyFactoryContract.address, tokenSalt, - solidityKeccak256(['bytes'], [tokenByteCodeLinear]) + solidityKeccak256(['bytes'], [tokenByteCodeLinear]), ); } @@ -356,7 +356,7 @@ export class AzoriusTxBuilder extends BaseTxBuilder { this.parentTokenAddress, this.predictedTokenAddress, azoriusGovernanceDaoData.parentAllocationAmount, - ] + ], ); this.encodedSetupTokenClaimData = this.azoriusContracts!.claimingMasterCopyContract.interface.encodeFunctionData('setUp', [ @@ -366,7 +366,7 @@ export class AzoriusTxBuilder extends BaseTxBuilder { private setPredictedTokenClaimAddress() { const tokenByteCodeLinear = generateContractByteCodeLinear( - this.azoriusContracts!.claimingMasterCopyContract.address.slice(2) + this.azoriusContracts!.claimingMasterCopyContract.address.slice(2), ); const tokenSalt = generateSalt(this.encodedSetupTokenClaimData!, this.claimNonce); @@ -374,7 +374,7 @@ export class AzoriusTxBuilder extends BaseTxBuilder { this.predictedTokenClaimAddress = getCreate2Address( this.baseContracts.zodiacModuleProxyFactoryContract.address, tokenSalt, - solidityKeccak256(['bytes'], [tokenByteCodeLinear]) + solidityKeccak256(['bytes'], [tokenByteCodeLinear]), ); } @@ -393,22 +393,22 @@ export class AzoriusTxBuilder extends BaseTxBuilder { BigNumber.from(1), // proposer weight, how much is needed to create a proposal. azoriusGovernanceDaoData.quorumPercentage.mul(quorumDenominator.div(100)), // quorom numerator, denominator is 1,000,000, so quorum percentage is quorumNumerator * 100 / quorumDenominator BigNumber.from(500000), // basis numerator, denominator is 1,000,000, so basis percentage is 50% (simple majority) - ] + ], ); const encodedStrategySetupData = this.azoriusContracts!.linearVotingMasterCopyContract.interface.encodeFunctionData( 'setUp', - [encodedStrategyInitParams] + [encodedStrategyInitParams], ); const strategyByteCodeLinear = generateContractByteCodeLinear( - this.azoriusContracts!.linearVotingMasterCopyContract.address.slice(2) + this.azoriusContracts!.linearVotingMasterCopyContract.address.slice(2), ); const strategySalt = solidityKeccak256( ['bytes32', 'uint256'], - [solidityKeccak256(['bytes'], [encodedStrategySetupData]), this.strategyNonce] + [solidityKeccak256(['bytes'], [encodedStrategySetupData]), this.strategyNonce], ); this.encodedStrategySetupData = encodedStrategySetupData; @@ -416,7 +416,7 @@ export class AzoriusTxBuilder extends BaseTxBuilder { this.predictedStrategyAddress = getCreate2Address( this.baseContracts.zodiacModuleProxyFactoryContract.address, strategySalt, - solidityKeccak256(['bytes'], [strategyByteCodeLinear]) + solidityKeccak256(['bytes'], [strategyByteCodeLinear]), ); } else if (azoriusGovernanceDaoData.votingStrategyType === VotingStrategyType.LINEAR_ERC721) { const daoData = azoriusGovernanceDaoData as AzoriusERC721DAO; @@ -432,22 +432,22 @@ export class AzoriusTxBuilder extends BaseTxBuilder { daoData.quorumThreshold, // quorom threshold. Since smart contract can't know total of NFTs minted - we need to provide it manually BigNumber.from(1), // proposer weight, how much is needed to create a proposal. BigNumber.from(500000), // basis numerator, denominator is 1,000,000, so basis percentage is 50% (simple majority) - ] + ], ); const encodedStrategySetupData = this.azoriusContracts!.linearVotingERC721MasterCopyContract.interface.encodeFunctionData( 'setUp', - [encodedStrategyInitParams] + [encodedStrategyInitParams], ); const strategyByteCodeLinear = generateContractByteCodeLinear( - this.azoriusContracts!.linearVotingERC721MasterCopyContract.address.slice(2) + this.azoriusContracts!.linearVotingERC721MasterCopyContract.address.slice(2), ); const strategySalt = solidityKeccak256( ['bytes32', 'uint256'], - [solidityKeccak256(['bytes'], [encodedStrategySetupData]), this.strategyNonce] + [solidityKeccak256(['bytes'], [encodedStrategySetupData]), this.strategyNonce], ); this.encodedStrategySetupData = encodedStrategySetupData; @@ -455,7 +455,7 @@ export class AzoriusTxBuilder extends BaseTxBuilder { this.predictedStrategyAddress = getCreate2Address( this.baseContracts.zodiacModuleProxyFactoryContract.address, strategySalt, - solidityKeccak256(['bytes'], [strategyByteCodeLinear]) + solidityKeccak256(['bytes'], [strategyByteCodeLinear]), ); } } @@ -472,17 +472,17 @@ export class AzoriusTxBuilder extends BaseTxBuilder { [this.predictedStrategyAddress], azoriusGovernanceDaoData.timelock, // timelock period in blocks azoriusGovernanceDaoData.executionPeriod, // execution period in blocks - ] + ], ); const encodedSetupAzoriusData = this.azoriusContracts!.fractalAzoriusMasterCopyContract.interface.encodeFunctionData( 'setUp', - [encodedInitAzoriusData] + [encodedInitAzoriusData], ); const azoriusByteCodeLinear = generateContractByteCodeLinear( - this.azoriusContracts!.fractalAzoriusMasterCopyContract.address.slice(2) + this.azoriusContracts!.fractalAzoriusMasterCopyContract.address.slice(2), ); const azoriusSalt = generateSalt(encodedSetupAzoriusData, this.azoriusNonce); @@ -490,7 +490,7 @@ export class AzoriusTxBuilder extends BaseTxBuilder { this.predictedAzoriusAddress = getCreate2Address( this.baseContracts.zodiacModuleProxyFactoryContract.address, azoriusSalt, - solidityKeccak256(['bytes'], [azoriusByteCodeLinear]) + solidityKeccak256(['bytes'], [azoriusByteCodeLinear]), ); } @@ -498,21 +498,21 @@ export class AzoriusTxBuilder extends BaseTxBuilder { const daoData = this.daoData as AzoriusGovernanceDAO; this.azoriusContract = Azorius__factory.connect( this.predictedAzoriusAddress!, - this.signerOrProvider + this.signerOrProvider, ); if (daoData.votingStrategyType === VotingStrategyType.LINEAR_ERC20) { this.linearVotingContract = LinearERC20Voting__factory.connect( this.predictedStrategyAddress!, - this.signerOrProvider + this.signerOrProvider, ); this.votesTokenContract = VotesERC20__factory.connect( this.predictedTokenAddress!, - this.signerOrProvider + this.signerOrProvider, ); } else if (daoData.votingStrategyType === VotingStrategyType.LINEAR_ERC721) { this.linearERC721VotingContract = LinearERC721Voting__factory.connect( this.predictedStrategyAddress!, - this.signerOrProvider + this.signerOrProvider, ); } } diff --git a/src/models/BaseTxBuilder.ts b/src/models/BaseTxBuilder.ts index 5d9ee3fe2c..d9c5ee903d 100644 --- a/src/models/BaseTxBuilder.ts +++ b/src/models/BaseTxBuilder.ts @@ -22,7 +22,7 @@ export class BaseTxBuilder { azoriusContracts: AzoriusContracts | undefined, daoData: SafeMultisigDAO | AzoriusERC20DAO | AzoriusERC721DAO | SubDAO, parentAddress?: string, - parentTokenAddress?: string + parentTokenAddress?: string, ) { this.signerOrProvider = signerOrProvider; this.baseContracts = baseContracts; diff --git a/src/models/DaoTxBuilder.ts b/src/models/DaoTxBuilder.ts index 970248d0a0..1290235ed0 100644 --- a/src/models/DaoTxBuilder.ts +++ b/src/models/DaoTxBuilder.ts @@ -47,7 +47,7 @@ export class DaoTxBuilder extends BaseTxBuilder { parentAddress?: string, parentTokenAddress?: string, parentStrategyType?: VotingStrategyType, - parentStrategyAddress?: string + parentStrategyAddress?: string, ) { super( signerOrProvider, @@ -55,7 +55,7 @@ export class DaoTxBuilder extends BaseTxBuilder { azoriusContracts, daoData, parentAddress, - parentTokenAddress + parentTokenAddress, ); this.predictedSafeAddress = predictedSafeAddress; @@ -73,7 +73,7 @@ export class DaoTxBuilder extends BaseTxBuilder { public async buildAzoriusTx( shouldSetName: boolean = true, shouldSetSnapshot: boolean = true, - existingSafe?: { owners: string[] } + existingSafe?: { owners: string[] }, ): Promise { const azoriusTxBuilder = await this.txBuilderFactory.createAzoriusTxBuilder(); @@ -91,7 +91,7 @@ export class DaoTxBuilder extends BaseTxBuilder { this.internalTxs = this.internalTxs.concat( azoriusTxBuilder.buildVotingContractSetupTx(), - azoriusTxBuilder.buildEnableAzoriusModuleTx() + azoriusTxBuilder.buildEnableAzoriusModuleTx(), ); if (this.parentAddress) { @@ -100,7 +100,7 @@ export class DaoTxBuilder extends BaseTxBuilder { azoriusTxBuilder.linearVotingContract?.address ?? azoriusTxBuilder.linearERC721VotingContract?.address, this.parentStrategyType, - this.parentStrategyAddress + this.parentStrategyAddress, ); this.internalTxs = this.internalTxs.concat([ @@ -168,7 +168,7 @@ export class DaoTxBuilder extends BaseTxBuilder { undefined, undefined, this.parentStrategyType, - this.parentStrategyAddress + this.parentStrategyAddress, ); this.internalTxs = this.internalTxs.concat([ @@ -207,7 +207,7 @@ export class DaoTxBuilder extends BaseTxBuilder { this.baseContracts.zodiacModuleProxyFactoryContract, this.safeContract!, this.saltNum, - this.parentAddress + this.parentAddress, ); this.enableFractalModuleTx = enableFractalModuleTx; @@ -224,7 +224,7 @@ export class DaoTxBuilder extends BaseTxBuilder { 'updateDAOName', [this.daoData.daoName], 0, - false + false, ); } @@ -234,7 +234,7 @@ export class DaoTxBuilder extends BaseTxBuilder { 'updateValues', [['snapshotURL'], [this.daoData.snapshotURL]], 0, - false + false, ); } @@ -258,7 +258,7 @@ export class DaoTxBuilder extends BaseTxBuilder { signatures, // sigs ], 0, - false + false, ); } } diff --git a/src/models/FreezeGuardTxBuilder.ts b/src/models/FreezeGuardTxBuilder.ts index b276168f89..666c531c86 100644 --- a/src/models/FreezeGuardTxBuilder.ts +++ b/src/models/FreezeGuardTxBuilder.ts @@ -63,7 +63,7 @@ export class FreezeGuardTxBuilder extends BaseTxBuilder { azoriusAddress?: string, strategyAddress?: string, parentStrategyType?: VotingStrategyType, - parentStrategyAddress?: string + parentStrategyAddress?: string, ) { super( signerOrProvider, @@ -71,7 +71,7 @@ export class FreezeGuardTxBuilder extends BaseTxBuilder { azoriusContracts, daoData, parentAddress, - parentTokenAddress + parentTokenAddress, ); this.safeContract = safeContract; @@ -99,13 +99,13 @@ export class FreezeGuardTxBuilder extends BaseTxBuilder { this.freezeVotingType === ERC20FreezeVoting__factory ? this.baseContracts.freezeERC20VotingMasterCopyContract.address : this.freezeVotingType === ERC721FreezeVoting__factory - ? this.baseContracts.freezeERC721VotingMasterCopyContract.address - : this.baseContracts.freezeMultisigVotingMasterCopyContract.address, + ? this.baseContracts.freezeERC721VotingMasterCopyContract.address + : this.baseContracts.freezeMultisigVotingMasterCopyContract.address, this.freezeVotingCallData, this.saltNum, ], 0, - false + false, ); } @@ -126,11 +126,11 @@ export class FreezeGuardTxBuilder extends BaseTxBuilder { this.parentStrategyType === VotingStrategyType.LINEAR_ERC721 ? this.parentStrategyAddress : this.parentTokenAddress ?? this.parentAddress, // Parent Votes Token or Parent Safe Address - ] + ], ), ], 0, - false + false, ); } @@ -179,26 +179,26 @@ export class FreezeGuardTxBuilder extends BaseTxBuilder { } const freezeVotingByteCodeLinear = generateContractByteCodeLinear( - freezeVotesMasterCopyContract.address.slice(2) + freezeVotesMasterCopyContract.address.slice(2), ); this.freezeVotingAddress = getCreate2Address( this.baseContracts.zodiacModuleProxyFactoryContract.address, generateSalt(this.freezeVotingCallData!, this.saltNum), - solidityKeccak256(['bytes'], [freezeVotingByteCodeLinear]) + solidityKeccak256(['bytes'], [freezeVotingByteCodeLinear]), ); } private setFreezeGuardAddress() { const freezeGuardByteCodeLinear = generateContractByteCodeLinear( - this.getGuardMasterCopyAddress().slice(2) + this.getGuardMasterCopyAddress().slice(2), ); const freezeGuardSalt = generateSalt(this.freezeGuardCallData!, this.saltNum); this.freezeGuardAddress = generatePredictedModuleAddress( this.baseContracts.zodiacModuleProxyFactoryContract.address, freezeGuardSalt, - freezeGuardByteCodeLinear + freezeGuardByteCodeLinear, ); } @@ -224,9 +224,9 @@ export class FreezeGuardTxBuilder extends BaseTxBuilder { this.parentAddress, // Owner -- Parent DAO this.freezeVotingAddress, // Freeze Voting this.safeContract.address, // Safe - ] + ], ), - ] + ], ); } @@ -244,9 +244,9 @@ export class FreezeGuardTxBuilder extends BaseTxBuilder { this.strategyAddress, // Base Strategy this.azoriusAddress, // Azorius subDaoData.executionPeriod, // Execution Period - ] + ], ), - ] + ], ); } diff --git a/src/models/MultisigTxBuilder.ts b/src/models/MultisigTxBuilder.ts index c3eec410e2..89cac1fd6c 100644 --- a/src/models/MultisigTxBuilder.ts +++ b/src/models/MultisigTxBuilder.ts @@ -32,7 +32,7 @@ export class MultisigTxBuilder { this.daoData.signatureThreshold, ], 0, - false + false, ); } } diff --git a/src/models/TxBuilderFactory.ts b/src/models/TxBuilderFactory.ts index cce5385120..392452bb2d 100644 --- a/src/models/TxBuilderFactory.ts +++ b/src/models/TxBuilderFactory.ts @@ -35,7 +35,7 @@ export class TxBuilderFactory extends BaseTxBuilder { daoData: SafeMultisigDAO | AzoriusERC20DAO | AzoriusERC721DAO | SubDAO, fallbackHandler: string, parentAddress?: string, - parentTokenAddress?: string + parentTokenAddress?: string, ) { super( signerOrProvider, @@ -43,7 +43,7 @@ export class TxBuilderFactory extends BaseTxBuilder { azoriusContracts, daoData, parentAddress, - parentTokenAddress + parentTokenAddress, ); this.fallbackHandler = fallbackHandler; @@ -63,7 +63,7 @@ export class TxBuilderFactory extends BaseTxBuilder { this.daoData as SafeMultisigDAO, this.saltNum, this.fallbackHandler, - !!this.azoriusContracts + !!this.azoriusContracts, ); this.predictedSafeAddress = predictedSafeAddress; @@ -74,7 +74,7 @@ export class TxBuilderFactory extends BaseTxBuilder { public createDaoTxBuilder( parentStrategyType?: VotingStrategyType, - parentStrategyAddress?: string + parentStrategyAddress?: string, ): DaoTxBuilder { return new DaoTxBuilder( this.signerOrProvider, @@ -89,7 +89,7 @@ export class TxBuilderFactory extends BaseTxBuilder { this.parentAddress, this.parentTokenAddress, parentStrategyType, - parentStrategyAddress + parentStrategyAddress, ); } @@ -97,7 +97,7 @@ export class TxBuilderFactory extends BaseTxBuilder { azoriusAddress?: string, strategyAddress?: string, parentStrategyType?: VotingStrategyType, - parentStrategyAddress?: string // User only with ERC-721 parent + parentStrategyAddress?: string, // User only with ERC-721 parent ): FreezeGuardTxBuilder { return new FreezeGuardTxBuilder( this.signerOrProvider, @@ -111,7 +111,7 @@ export class TxBuilderFactory extends BaseTxBuilder { azoriusAddress, strategyAddress, parentStrategyType, - parentStrategyAddress + parentStrategyAddress, ); } @@ -119,7 +119,7 @@ export class TxBuilderFactory extends BaseTxBuilder { return new MultisigTxBuilder( this.baseContracts, this.daoData as SafeMultisigDAO, - this.safeContract! + this.safeContract!, ); } @@ -132,7 +132,7 @@ export class TxBuilderFactory extends BaseTxBuilder { this.safeContract!, this.predictedSafeAddress!, this.parentAddress, - this.parentTokenAddress + this.parentTokenAddress, ); await azoriusTxBuilder.init(); diff --git a/src/models/helpers/fractalModuleData.ts b/src/models/helpers/fractalModuleData.ts index efc35c9102..9d4ede6e69 100644 --- a/src/models/helpers/fractalModuleData.ts +++ b/src/models/helpers/fractalModuleData.ts @@ -25,7 +25,7 @@ export const fractalModuleData = ( zodiacModuleProxyFactoryContract: ModuleProxyFactory, safeContract: GnosisSafeL2, saltNum: string, - parentAddress?: string + parentAddress?: string, ): FractalModuleData => { const fractalModuleCalldata = FractalModule__factory.createInterface().encodeFunctionData( 'setUp', @@ -37,13 +37,13 @@ export const fractalModuleData = ( safeContract.address, // Avatar safeContract.address, // Target [], // Authorized Controllers - ] + ], ), - ] + ], ); const fractalByteCodeLinear = generateContractByteCodeLinear( - fractalModuleMasterCopyContract.address.slice(2) + fractalModuleMasterCopyContract.address.slice(2), ); const fractalSalt = generateSalt(fractalModuleCalldata, saltNum); @@ -55,7 +55,7 @@ export const fractalModuleData = ( const predictedFractalModuleAddress = generatePredictedModuleAddress( zodiacModuleProxyFactoryContract.address, fractalSalt, - fractalByteCodeLinear + fractalByteCodeLinear, ); const enableFractalModuleTx = buildContractCall( @@ -63,7 +63,7 @@ export const fractalModuleData = ( 'enableModule', [predictedFractalModuleAddress], 0, - false + false, ); return { diff --git a/src/models/helpers/safeData.ts b/src/models/helpers/safeData.ts index bf2de3060b..bfbcbf432c 100644 --- a/src/models/helpers/safeData.ts +++ b/src/models/helpers/safeData.ts @@ -14,7 +14,7 @@ export const safeData = async ( daoData: SafeMultisigDAO, saltNum: string, fallbackHandler: string, - hasAzorius?: boolean + hasAzorius?: boolean, ) => { const signers = hasAzorius ? [multiSendContract.address] @@ -38,12 +38,12 @@ export const safeData = async ( safeFactoryContract.address, solidityKeccak256( ['bytes', 'uint256'], - [solidityKeccak256(['bytes'], [createSafeCalldata]), saltNum] + [solidityKeccak256(['bytes'], [createSafeCalldata]), saltNum], ), solidityKeccak256( ['bytes', 'uint256'], - [await safeFactoryContract.proxyCreationCode(), safeSingletonContract.address] - ) + [await safeFactoryContract.proxyCreationCode(), safeSingletonContract.address], + ), ); const createSafeTx = buildContractCall( @@ -51,7 +51,7 @@ export const safeData = async ( 'createProxyWithNonce', [safeSingletonContract.address, createSafeCalldata, saltNum], 0, - false + false, ); return { diff --git a/src/models/helpers/utils.ts b/src/models/helpers/utils.ts index 8b32f827f4..24cc09b693 100644 --- a/src/models/helpers/utils.ts +++ b/src/models/helpers/utils.ts @@ -17,21 +17,21 @@ export const generateContractByteCodeLinear = (contractAddress: string): string export const generateSalt = (calldata: string, saltNum: string): string => { return solidityKeccak256( ['bytes32', 'uint256'], - [solidityKeccak256(['bytes'], [calldata]), saltNum] + [solidityKeccak256(['bytes'], [calldata]), saltNum], ); }; export const generatePredictedModuleAddress = ( zodiacProxyAddress: string, salt: string, - byteCode: string + byteCode: string, ): string => { return getCreate2Address(zodiacProxyAddress, salt, solidityKeccak256(['bytes'], [byteCode])); }; export const buildDeployZodiacModuleTx = ( zodiacProxyFactoryContract: ModuleProxyFactory, - params: string[] + params: string[], ): SafeTransaction => { return buildContractCall(zodiacProxyFactoryContract, 'deployModule', params, 0, false); }; diff --git a/src/providers/App/combinedReducer.ts b/src/providers/App/combinedReducer.ts index bf121c1e9d..3e4aa5afc2 100644 --- a/src/providers/App/combinedReducer.ts +++ b/src/providers/App/combinedReducer.ts @@ -43,7 +43,7 @@ export const combinedReducer = (state: Fractal, action: FractalActions) => { treasury: treasuryReducer(state.treasury, action as TreasuryActions), governanceContracts: governanceContractsReducer( state.governanceContracts, - action as GovernanceContractActions + action as GovernanceContractActions, ), guardContracts: guardContractReducer(state.guardContracts, action as GuardContractActions), guard: guardReducer(state.guard, action as FractalGuardActions), diff --git a/src/providers/App/governance/reducer.ts b/src/providers/App/governance/reducer.ts index 47a8d9db05..987772409f 100644 --- a/src/providers/App/governance/reducer.ts +++ b/src/providers/App/governance/reducer.ts @@ -39,7 +39,7 @@ export const governanceReducer = (state: FractalGovernance, action: FractalGover proposals: [ ...action.payload, ...(proposals || []).filter( - proposal => !!(proposal as SnapshotProposal).snapshotProposalId + proposal => !!(proposal as SnapshotProposal).snapshotProposalId, ), ], }; @@ -63,7 +63,7 @@ export const governanceReducer = (state: FractalGovernance, action: FractalGover // Yet, I'm not sure of the cause :( proposals: [...(proposals || []), action.payload].filter( (proposal, index, array) => - index === array.findIndex(p => p.proposalId === proposal.proposalId) + index === array.findIndex(p => p.proposalId === proposal.proposalId), ), }; case FractalGovernanceAction.UPDATE_NEW_AZORIUS_ERC20_VOTE: { diff --git a/src/providers/App/governanceContracts/reducer.ts b/src/providers/App/governanceContracts/reducer.ts index 6ccccc09f7..11ae0f7d5b 100644 --- a/src/providers/App/governanceContracts/reducer.ts +++ b/src/providers/App/governanceContracts/reducer.ts @@ -12,7 +12,7 @@ export const initialGovernanceContractsState: FractalGovernanceContracts = { export const governanceContractsReducer = ( state: FractalGovernanceContracts, - action: GovernanceContractActions + action: GovernanceContractActions, ) => { switch (action.type) { case GovernanceContractAction.SET_GOVERNANCE_CONTRACT: diff --git a/src/providers/App/guardContracts/reducer.ts b/src/providers/App/guardContracts/reducer.ts index 38fc4329f6..daf4ab768b 100644 --- a/src/providers/App/guardContracts/reducer.ts +++ b/src/providers/App/guardContracts/reducer.ts @@ -10,7 +10,7 @@ export const initialGuardContractsState: FractalGuardContracts = { export const guardContractReducer = ( state: FractalGuardContracts, - action: GuardContractActions + action: GuardContractActions, ) => { switch (action.type) { case GuardContractAction.SET_GUARD_CONTRACT: diff --git a/src/providers/App/hooks/useIPFSClient.ts b/src/providers/App/hooks/useIPFSClient.ts index 10f16247f8..622b6165e5 100644 --- a/src/providers/App/hooks/useIPFSClient.ts +++ b/src/providers/App/hooks/useIPFSClient.ts @@ -4,7 +4,7 @@ import { useMemo, useCallback } from 'react'; const INFURA_AUTH = 'Basic ' + Buffer.from( - `${process.env.NEXT_PUBLIC_INFURA_IPFS_API_KEY}:${process.env.NEXT_PUBLIC_INFURA_IPFS_API_SECRET}` + `${process.env.NEXT_PUBLIC_INFURA_IPFS_API_KEY}:${process.env.NEXT_PUBLIC_INFURA_IPFS_API_SECRET}`, ).toString('base64'); const BASE_URL = 'https://ipfs.infura.io:5001/api/v0'; @@ -28,7 +28,7 @@ export default function useIPFSClient() { cat, add, }), - [cat, add] + [cat, add], ); return client; diff --git a/src/providers/App/hooks/useSafeAPI.ts b/src/providers/App/hooks/useSafeAPI.ts index cdcdf47f8d..84b9bb176f 100644 --- a/src/providers/App/hooks/useSafeAPI.ts +++ b/src/providers/App/hooks/useSafeAPI.ts @@ -58,7 +58,7 @@ class CachingSafeServiceClient extends SafeServiceClient { private async request( cacheKey: string, cacheMinutes: number, - endpoint: () => Promise + endpoint: () => Promise, ): Promise { let value: T = await this.getCache(cacheKey); if (!value) { @@ -82,7 +82,7 @@ class CachingSafeServiceClient extends SafeServiceClient { const value = await this.request( 'getServiceMasterCopiesInfo', CacheExpiry.ONE_DAY, - super.getServiceMasterCopiesInfo + super.getServiceMasterCopiesInfo, ); return value; } @@ -129,7 +129,7 @@ class CachingSafeServiceClient extends SafeServiceClient { return value; } override async getModuleTransactions( - safeAddress: string + safeAddress: string, ): Promise { const value = await this.request('getModuleTransactions' + safeAddress, 1, () => { return super.getModuleTransactions(safeAddress); @@ -137,7 +137,7 @@ class CachingSafeServiceClient extends SafeServiceClient { return value; } override async getMultisigTransactions( - safeAddress: string + safeAddress: string, ): Promise { const value = await this.request('getMultisigTransactions' + safeAddress, 1, () => { return super.getMultisigTransactions(safeAddress); @@ -146,20 +146,20 @@ class CachingSafeServiceClient extends SafeServiceClient { } override async getPendingTransactions( safeAddress: string, - currentNonce?: number | undefined + currentNonce?: number | undefined, ): Promise { const value = await this.request( 'getPendingTransactions' + safeAddress + currentNonce?.toString(), 1, () => { return super.getPendingTransactions(safeAddress, currentNonce); - } + }, ); return value; } override async getBalances( safeAddress: string, - options?: SafeBalancesOptions | undefined + options?: SafeBalancesOptions | undefined, ): Promise { const value = await this.request('getBalances' + safeAddress + options?.toString(), 1, () => { return super.getBalances(safeAddress, options); @@ -168,27 +168,27 @@ class CachingSafeServiceClient extends SafeServiceClient { } override async getUsdBalances( safeAddress: string, - options?: SafeBalancesUsdOptions | undefined + options?: SafeBalancesUsdOptions | undefined, ): Promise { const value = await this.request( 'getUsdBalances' + safeAddress + options?.toString(), 1, () => { return super.getUsdBalances(safeAddress, options); - } + }, ); return value; } override async getCollectibles( safeAddress: string, - options?: SafeCollectiblesOptions | undefined + options?: SafeCollectiblesOptions | undefined, ): Promise { const value = await this.request( 'getCollectibles' + safeAddress + options?.toString(), 1, () => { return super.getCollectibles(safeAddress, options); - } + }, ); return value; } @@ -206,14 +206,14 @@ class CachingSafeServiceClient extends SafeServiceClient { } override async getAllTransactions( safeAddress: string, - options?: AllTransactionsOptions + options?: AllTransactionsOptions, ): Promise { const value = await this.request( 'getAllTransactions' + safeAddress + options?.toString(), 1, () => { return super.getAllTransactions(safeAddress, options); - } + }, ); return value; } diff --git a/src/providers/App/treasury/reducer.ts b/src/providers/App/treasury/reducer.ts index 2017dd98c7..e4dec33816 100644 --- a/src/providers/App/treasury/reducer.ts +++ b/src/providers/App/treasury/reducer.ts @@ -9,7 +9,7 @@ export const initialTreasuryState: FractalTreasury = { export const treasuryReducer = ( state: FractalTreasury, - action: TreasuryActions + action: TreasuryActions, ): FractalTreasury => { switch (action.type) { case TreasuryAction.UPDATE_TREASURY: diff --git a/src/providers/App/useReadOnlyValues.ts b/src/providers/App/useReadOnlyValues.ts index f965d07189..e332be9766 100644 --- a/src/providers/App/useReadOnlyValues.ts +++ b/src/providers/App/useReadOnlyValues.ts @@ -48,7 +48,7 @@ export const useReadOnlyValues = ({ node, governance }: Fractal, _account?: stri const tokenContract = ERC721__factory.connect(address, signerOrProvider); const userBalance = await tokenContract.balanceOf(_account); return userBalance.mul(votingWeight); - }) + }), ) ).reduce((prev, curr) => prev.add(curr), BigNumber.from(0)); return userVotingWeight; diff --git a/src/providers/NetworkConfig/NetworkConfigProvider.tsx b/src/providers/NetworkConfig/NetworkConfigProvider.tsx index 7d97760fe5..5b091e318a 100644 --- a/src/providers/NetworkConfig/NetworkConfigProvider.tsx +++ b/src/providers/NetworkConfig/NetworkConfigProvider.tsx @@ -33,7 +33,7 @@ const getNetworkConfig = (chainId: number) => { export function NetworkConfigProvider({ children }: { children: ReactNode }) { const { chain } = useNetwork(); const [config, setConfig] = useState( - getNetworkConfig(chain?.id || disconnectedChain.id) + getNetworkConfig(chain?.id || disconnectedChain.id), ); useEffect(() => { diff --git a/src/providers/NetworkConfig/rainbow-kit.config.ts b/src/providers/NetworkConfig/rainbow-kit.config.ts index 06e1ef22ef..bbf061522e 100644 --- a/src/providers/NetworkConfig/rainbow-kit.config.ts +++ b/src/providers/NetworkConfig/rainbow-kit.config.ts @@ -48,10 +48,10 @@ const defaultWallets = [injectedWallet({ chains }), coinbaseWallet({ appName: AP if (process.env.NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID) { defaultWallets.push( - walletConnectWallet({ chains, projectId: process.env.NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID }) + walletConnectWallet({ chains, projectId: process.env.NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID }), ); defaultWallets.push( - metaMaskWallet({ chains, projectId: process.env.NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID }) + metaMaskWallet({ chains, projectId: process.env.NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID }), ); } // allows connection to localhost only in development mode. diff --git a/src/types/createDAO.ts b/src/types/createDAO.ts index f50b14bcaf..275cdfdc11 100644 --- a/src/types/createDAO.ts +++ b/src/types/createDAO.ts @@ -108,7 +108,7 @@ export interface AzoriusERC721DAO export interface SafeMultisigDAO extends DAOEssentials, SafeConfiguration {} export type DAOTrigger = ( - daoData: SafeMultisigDAO | AzoriusERC20DAO | AzoriusERC721DAO | SubDAO + daoData: SafeMultisigDAO | AzoriusERC20DAO | AzoriusERC721DAO | SubDAO, ) => void; export type AddressValidationMap = Map; @@ -133,7 +133,7 @@ export type TokenAllocation = { export type CreateDAOFunc = ( daoData: SafeMultisigDAO, - successCallback: DeployDAOSuccessCallback + successCallback: DeployDAOSuccessCallback, ) => void; export type DeployDAOSuccessCallback = (daoAddress: string) => void; export type DAODetails = { diff --git a/src/types/daoProposal.ts b/src/types/daoProposal.ts index fb6fd7b9b1..37535cb50d 100644 --- a/src/types/daoProposal.ts +++ b/src/types/daoProposal.ts @@ -121,7 +121,7 @@ export type ProposalVotesSummary = { export type ProposalVote = { voter: string; - choice: typeof VOTE_CHOICES[number]; + choice: (typeof VOTE_CHOICES)[number]; weight: BigNumber; }; diff --git a/src/types/votingFungibleToken.ts b/src/types/votingFungibleToken.ts index e6f1833f8a..72e414fd6a 100644 --- a/src/types/votingFungibleToken.ts +++ b/src/types/votingFungibleToken.ts @@ -6,20 +6,20 @@ export type DelegateChangedListener = ( delegator: string, fromDelegate: string, toDelegate: string, - _: any + _: any, ) => void; export type DelegateVotesChangedListener = ( delegate: string, previousBalance: BigNumber, currentBalance: BigNumber, - _: any + _: any, ) => void; export type ClaimListener = ( parentToken: string, childToken: string, claimer: string, amount: BigNumber, - _: any + _: any, ) => void; export interface ITokenData { diff --git a/src/utils/api.spec.ts b/src/utils/api.spec.ts index d3b698e7b3..67537c7929 100644 --- a/src/utils/api.spec.ts +++ b/src/utils/api.spec.ts @@ -7,7 +7,7 @@ describe('Safe URL builder tests', () => { 'https://safe-transaction-sepolia.safe.global', '/about', {}, - 'v1' + 'v1', ); const EXPECTED_URL = 'https://safe-transaction-sepolia.safe.global/api/v1/about'; expect(safeTransactionURL).toEqual(EXPECTED_URL); @@ -18,7 +18,7 @@ describe('Safe URL builder tests', () => { 'https://safe-transaction-sepolia.safe.global', `/safes/${constants.AddressZero}/multisig-transactions`, { target: constants.AddressZero }, - 'v1' + 'v1', ); const EXPECTED_URL = `https://safe-transaction-sepolia.safe.global/api/v1/safes/${constants.AddressZero}/multisig-transactions?target=${constants.AddressZero}`; expect(safeTransactionURL).toEqual(EXPECTED_URL); diff --git a/src/utils/api.ts b/src/utils/api.ts index bbfa0c2fd5..64e48c26a6 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -11,7 +11,7 @@ export const buildSafeApiUrl = ( baseUrl: string, pathname: string, queryParams: { [key: string]: string } = {}, - version: 'v1' | 'v2' = 'v1' + version: 'v1' | 'v2' = 'v1', ) => { const SAFE_URL = `${baseUrl}/api/${version}`; if (!Object.keys(queryParams).length) { diff --git a/src/utils/azorius.ts b/src/utils/azorius.ts index 13c2a43416..3d79f1739c 100644 --- a/src/utils/azorius.ts +++ b/src/utils/azorius.ts @@ -31,7 +31,7 @@ import { getTimeStamp } from './contract'; export const getAzoriusProposalState = async ( azoriusContract: Azorius, - proposalId: BigNumber + proposalId: BigNumber, ): Promise => { const state = await azoriusContract.proposalState(proposalId); return strategyFractalProposalStates[state]; @@ -40,7 +40,7 @@ export const getAzoriusProposalState = async ( const getQuorum = async ( strategyContract: LinearERC20Voting | LinearERC721Voting, strategyType: VotingStrategyType, - proposalId: BigNumber + proposalId: BigNumber, ) => { let quorum; @@ -65,7 +65,7 @@ const getQuorum = async ( export const getProposalVotesSummary = async ( strategy: LinearERC20Voting | LinearERC721Voting, strategyType: VotingStrategyType, - proposalId: BigNumber + proposalId: BigNumber, ): Promise => { try { const { yesVotes, noVotes, abstainVotes } = await strategy.getProposalVotes(proposalId); @@ -93,7 +93,7 @@ export const getProposalVotesSummary = async ( export const getProposalVotes = async ( strategyContract: LinearERC20Voting | LinearERC721Voting, - proposalId: BigNumber + proposalId: BigNumber, ): Promise => { const voteEventFilter = strategyContract.filters.Voted(); const votes = await strategyContract.queryFilter(voteEventFilter); @@ -115,7 +115,7 @@ export const mapProposalCreatedEventToProposal = async ( proposer: string, azoriusContract: ContractConnection, provider: Providers, - data?: ProposalData + data?: ProposalData, ) => { const { endBlock, startBlock, abstainVotes, yesVotes, noVotes } = await strategyContract.getProposalVotes(proposalId); @@ -137,11 +137,10 @@ export const mapProposalCreatedEventToProposal = async ( let transactionHash: string | undefined; if (state === FractalProposalState.EXECUTED) { const proposalExecutedFilter = azoriusContract.asProvider.filters.ProposalExecuted(); - const proposalExecutedEvents = await azoriusContract.asProvider.queryFilter( - proposalExecutedFilter - ); + const proposalExecutedEvents = + await azoriusContract.asProvider.queryFilter(proposalExecutedFilter); const executedEvent = proposalExecutedEvents.find(event => - BigNumber.from(event.args[0]).eq(proposalId) + BigNumber.from(event.args[0]).eq(proposalId), ); transactionHash = executedEvent?.transactionHash; } @@ -166,7 +165,7 @@ export const mapProposalCreatedEventToProposal = async ( export const parseMultiSendTransactions = ( eventTransactionMap: Map, - parameters?: Parameter[] + parameters?: Parameter[], ) => { if (!parameters || !parameters.length) { return; @@ -203,7 +202,7 @@ export const parseDecodedData = ( multiSigTransaction: | SafeMultisigTransactionWithTransfersResponse | SafeMultisigTransactionResponse, - isMultiSigTransaction: boolean + isMultiSigTransaction: boolean, ): DecodedTransaction[] => { const eventTransactionMap = new Map(); const dataDecoded = multiSigTransaction.dataDecoded as any as DataDecoded; diff --git a/src/utils/contract.ts b/src/utils/contract.ts index 40f81ce922..4ca304b3d9 100644 --- a/src/utils/contract.ts +++ b/src/utils/contract.ts @@ -45,7 +45,7 @@ export const getTimeStamp = async (blockNumber: number | 'latest', provider: Pro export const blocksToSeconds = async ( numOfBlocks: number, - provider: Providers + provider: Providers, ): Promise => { if (!provider || !numOfBlocks) { return 0; @@ -61,7 +61,7 @@ export const blocksToSeconds = async ( export const getEstimatedNumberOfBlocks = async ( timeInMinutes: BigNumber, - provider: Providers + provider: Providers, ): Promise => { const seconds = timeInMinutes.toNumber() * 60; const averageBlockTime = await getAverageBlockTime(provider); diff --git a/src/utils/crypto.ts b/src/utils/crypto.ts index 0edc9a1e66..3ca73a64ab 100644 --- a/src/utils/crypto.ts +++ b/src/utils/crypto.ts @@ -23,7 +23,7 @@ function splitIgnoreBrackets(str: string): string[] { export const encodeFunction = ( _functionName: string, _functionSignature?: string, - _parameters?: string + _parameters?: string, ) => { let functionSignature = `function ${_functionName}`; if (_functionSignature) { @@ -44,7 +44,7 @@ export const encodeFunction = ( param .substring(1, param.length - 1) .split(',') - .map(p => (p = p.trim())) + .map(p => (p = p.trim())), ); } else if (param.startsWith('(')) { // This is part of tuple param, we need to re-assemble it. There should be better solution to this within splitIgnoreBrackets with regex. @@ -86,7 +86,7 @@ export const encodeFunction = ( try { return new utils.Interface([functionSignature]).encodeFunctionData( _functionName, - parametersFixedWithBool + parametersFixedWithBool, ); } catch (e) { logError(e); diff --git a/src/utils/guard.ts b/src/utils/guard.ts index eb6600aa79..de899b5fbc 100644 --- a/src/utils/guard.ts +++ b/src/utils/guard.ts @@ -9,7 +9,7 @@ import { getTimeStamp } from './contract'; export async function getTxTimelockedTimestamp( activity: Activity, freezeGuard: MultisigFreezeGuard, - provider: Providers + provider: Providers, ) { const multiSigTransaction = activity.transaction as SafeMultisigTransactionWithTransfersResponse; @@ -17,13 +17,13 @@ export async function getTxTimelockedTimestamp( multiSigTransaction.confirmations!.map(confirmation => ({ signer: confirmation.owner, data: confirmation.signature, - })) + })), ); const signaturesHash = ethers.utils.solidityKeccak256(['bytes'], [signatures]); const timelockedTimestamp = await getTimeStamp( await freezeGuard.getTransactionTimelockedBlock(signaturesHash), - provider + provider, ); return timelockedTimestamp; } diff --git a/src/utils/numberFormats.ts b/src/utils/numberFormats.ts index f45f23f4cb..07194f0ba0 100644 --- a/src/utils/numberFormats.ts +++ b/src/utils/numberFormats.ts @@ -7,7 +7,7 @@ export const DEFAULT_DATE_FORMAT = 'yyyy-MM-dd'; export const formatPercentage = ( numerator: BigNumber | number | string, - denominator: BigNumber | number | string + denominator: BigNumber | number | string, ) => { const fraction = bigDecimal.divide(numerator.toString(), denominator.toString(), 18); const percent = parseFloat(bigDecimal.multiply(fraction, 100)); @@ -32,7 +32,7 @@ export const formatUSD = (rawUSD: number | string) => { export const formatCoinUnits = ( rawBalance: BigNumber | string, decimals?: number, - symbol?: string + symbol?: string, ): number => { if (!rawBalance) rawBalance = '0'; return symbol @@ -49,7 +49,7 @@ export const formatCoin = ( truncate: boolean, decimals?: number, symbol?: string, - showSymbol: boolean = true + showSymbol: boolean = true, ): string => { const amount = formatCoinUnits(rawBalance, decimals, symbol); diff --git a/src/utils/percentFormat.spec.ts b/src/utils/percentFormat.spec.ts index f1241bd54f..1d4a57db60 100644 --- a/src/utils/percentFormat.spec.ts +++ b/src/utils/percentFormat.spec.ts @@ -82,17 +82,17 @@ describe('Percent formatting, BigNumber', () => { }); test('correct 1.1%, number', () => { expect(formatPercentage(BigNumber.from('10000000000'), BigNumber.from('909090909091'))).toBe( - '1.1%' + '1.1%', ); }); test('correct 1.01%, number', () => { expect(formatPercentage(BigNumber.from('10000000000'), BigNumber.from('990099009901'))).toBe( - '1.01%' + '1.01%', ); }); test('correct 1.001%, number', () => { expect(formatPercentage(BigNumber.from('10000000000'), BigNumber.from('999000999001'))).toBe( - '1%' + '1%', ); }); }); diff --git a/src/utils/safeData.ts b/src/utils/safeData.ts index 1d10b6d5f8..266e174677 100644 --- a/src/utils/safeData.ts +++ b/src/utils/safeData.ts @@ -10,7 +10,7 @@ import { AssetTotals, SafeTransferType } from '../types'; */ export const totalsReducer = ( prev: Map, - cur: TransferWithTokenInfoResponse + cur: TransferWithTokenInfoResponse, ) => { if (cur.type === SafeTransferType.ETHER && cur.value) { const prevValue = prev.get(constants.AddressZero)!; diff --git a/src/utils/shutter.ts b/src/utils/shutter.ts index 49c30b1de9..d97db8611a 100644 --- a/src/utils/shutter.ts +++ b/src/utils/shutter.ts @@ -5,7 +5,7 @@ import { BigNumber, utils } from 'ethers'; export default async function encryptWithShutter( choice: string, - id: string + id: string, ): Promise { const shutterPath = '/assets/scripts/shutter-crypto.wasm'; await init(shutterPath); diff --git a/src/utils/signatures.ts b/src/utils/signatures.ts index e8bb4e2a2a..92368fe3f2 100644 --- a/src/utils/signatures.ts +++ b/src/utils/signatures.ts @@ -38,12 +38,12 @@ export interface SafeSignature { export const calculateSafeTransactionHash = ( safeAddress: string, safeTx: SafeAPITransaction, - chainId: BigNumberish + chainId: BigNumberish, ): string => { return utils._TypedDataEncoder.hash( { verifyingContract: safeAddress, chainId }, EIP712_SAFE_TX_TYPE, - safeTx + safeTx, ); }; @@ -69,7 +69,7 @@ export const safeSignMessage = async ( signer: Signer, safeAddress: string, safeTx: SafeAPITransaction, - chainId?: BigNumberish + chainId?: BigNumberish, ): Promise => { const cid = chainId || (await signer.provider!.getNetwork()).chainId; return signHash(signer, calculateSafeTransactionHash(safeAddress, safeTx, cid)); diff --git a/test/encodeFunction.test.ts b/test/encodeFunction.test.ts index 2af09600a9..5b71c4763b 100644 --- a/test/encodeFunction.test.ts +++ b/test/encodeFunction.test.ts @@ -39,39 +39,38 @@ test('Function encoding with [uint8=100]', () => { test('Function encoding with tuple', () => { const encoded = new utils.Interface([ - 'function someFooWithTupleAndLargeNumbers((address,address,address,uint88,uint88,uint88,uint88,uint88,uint64,uint64,uint40,uint40,uint40,uint40,bool,bytes32),uint256,uint256,bytes32)' - ]).encodeFunctionData( - 'someFooWithTupleAndLargeNumbers', + 'function someFooWithTupleAndLargeNumbers((address,address,address,uint88,uint88,uint88,uint88,uint88,uint64,uint64,uint40,uint40,uint40,uint40,bool,bytes32),uint256,uint256,bytes32)', + ]).encodeFunctionData('someFooWithTupleAndLargeNumbers', [ [ - [ - '0x7b79995e5f793A07Bc00c21412e50Ecae098E7f9', - '0x7f63C82b83B9375c21efbEAd2010F003d7FAD746', - '0xE19f640d1FC22FeAf12DbD86b52bEa8Ac7d43E41', - 0, - 0, - '309485009821345068724781055', - '309485009821345068724781055', - '309485009821345068724781055', - '990000000000000000', - '10000000000000000', - 1708516800, - 1708905540, - 0, - 0, - true, - '0x0000000000000000000000000000000000000000000000000000000000000000' - ], - '40000000000000000000000000', - '1000000000000000000', - '0x1111111111111111111111111111111111111111111111111111111111111111' - ]); + '0x7b79995e5f793A07Bc00c21412e50Ecae098E7f9', + '0x7f63C82b83B9375c21efbEAd2010F003d7FAD746', + '0xE19f640d1FC22FeAf12DbD86b52bEa8Ac7d43E41', + 0, + 0, + '309485009821345068724781055', + '309485009821345068724781055', + '309485009821345068724781055', + '990000000000000000', + '10000000000000000', + 1708516800, + 1708905540, + 0, + 0, + true, + '0x0000000000000000000000000000000000000000000000000000000000000000', + ], + '40000000000000000000000000', + '1000000000000000000', + '0x1111111111111111111111111111111111111111111111111111111111111111', + ]); expect( encodeFunction( - 'someFooWithTupleAndLargeNumbers', - '(address,address,address,uint88,uint88,uint88,uint88,uint88,uint64,uint64,uint40,uint40,uint40,uint40,bool,bytes32),uint256,uint256,bytes32', - '(0x7b79995e5f793A07Bc00c21412e50Ecae098E7f9,0x7f63C82b83B9375c21efbEAd2010F003d7FAD746,0xE19f640d1FC22FeAf12DbD86b52bEa8Ac7d43E41,0,0,309485009821345068724781055,309485009821345068724781055,309485009821345068724781055,990000000000000000,10000000000000000,1708516800,1708905540,0,0,true,0x0000000000000000000000000000000000000000000000000000000000000000),40000000000000000000000000,1000000000000000000,0x1111111111111111111111111111111111111111111111111111111111111111' - )).toEqual(encoded) -}) + 'someFooWithTupleAndLargeNumbers', + '(address,address,address,uint88,uint88,uint88,uint88,uint88,uint64,uint64,uint40,uint40,uint40,uint40,bool,bytes32),uint256,uint256,bytes32', + '(0x7b79995e5f793A07Bc00c21412e50Ecae098E7f9,0x7f63C82b83B9375c21efbEAd2010F003d7FAD746,0xE19f640d1FC22FeAf12DbD86b52bEa8Ac7d43E41,0,0,309485009821345068724781055,309485009821345068724781055,309485009821345068724781055,990000000000000000,10000000000000000,1708516800,1708905540,0,0,true,0x0000000000000000000000000000000000000000000000000000000000000000),40000000000000000000000000,1000000000000000000,0x1111111111111111111111111111111111111111111111111111111111111111', + ), + ).toEqual(encoded); +}); // TODO: This test cases would fail, which is known issue. We'll need to improve our implementation test.skip('Function encoding with [string="true"]', () => { diff --git a/tests/QA_TESTING.md b/tests/QA_TESTING.md index 999d3ac4e3..14b6cd5912 100644 --- a/tests/QA_TESTING.md +++ b/tests/QA_TESTING.md @@ -1,20 +1,23 @@ ## App Homepage (disconnected) + https://app.staging.fractalframework.xyz/ ### General + - [ ] **Version number is the expected version** - [ ] App icon links to App Homepage - [ ] FAQ, Discord, Docs links in sidebar are correct - [ ] Sidebar language toggle works - [ ] Favorites menu appears and displays favorites - [ ] Favorites link to their respective DAO homepage -- [ ] *Connect Wallet dropdown* -> *Connect* opens Connect a Wallet modal -- [ ] *Connect Wallet dropdown* shows default network (Sepolia) -- [ ] *Connect your wallet* in page content opens Connect a Wallet modal +- [ ] _Connect Wallet dropdown_ -> _Connect_ opens Connect a Wallet modal +- [ ] _Connect Wallet dropdown_ shows default network (Sepolia) +- [ ] _Connect your wallet_ in page content opens Connect a Wallet modal - [ ] FAQ, Discord, Docs links in page content are correct - [ ] no DAO related sidebar icons appear ### Search + - [ ] arbitrary text search returns invalid Eth address message - [ ] nonexistant ENS search returns invalid Eth address message - [ ] personal wallet address (EOA) search (0x123..) gives invalid Fractal / Safe message @@ -23,11 +26,13 @@ https://app.staging.fractalframework.xyz/ - [ ] Safe ENS search gives link to DAO message ## App Homepage (connected) + https://app.staging.fractalframework.xyz/ -click *Connect your wallet* -> *MetaMask* and connect your wallet +click _Connect your wallet_ -> _MetaMask_ and connect your wallet ### With Personal Wallet Address (0x123..) + - [ ] top right wallet menu is labeled with truncated address - [ ] opening wallet menu displays truncated address - [ ] wallet menu address click copies address to clipboard @@ -37,21 +42,25 @@ click *Connect your wallet* -> *MetaMask* and connect your wallet - [ ] Create a Fractal button in page content links to DAO creation flow ### With Personal ENS Address + - [ ] top right wallet menu is labeled with ENS image / name - [ ] opening wallet menu displays ENS name / image ## Create a DAO + https://app.staging.fractalframework.xyz/create ### Step 1: Establish Essentials + - [ ] trash icon returns user to the App Homepage - [ ] Fractal name accepts any text - [ ] Name is required to enable Next - [ ] user can select between Safe / Token Voting options -- [ ] Next links to *Configure Multisig* OR *Configure Voting Token* +- [ ] Next links to _Configure Multisig_ OR _Configure Voting Token_ ### Step 2: Configure Multisig -From Step 1: *enter a name* -> *select Multisig* -> *Next* + +From Step 1: _enter a name_ -> _select Multisig_ -> _Next_ - [ ] Prev button returns user to Establish Essentials - [ ] Total Signers must be greater than 0 and less than 100 @@ -63,7 +72,8 @@ From Step 1: *enter a name* -> *select Multisig* -> *Next* - [ ] Deploy button initiates a transaction to deploy the DAO ### Step 2: Configure Voting Token -From Step 1: *enter a name* -> *select Voting Token* -> *Next* + +From Step 1: _enter a name_ -> _select Voting Token_ -> _Next_ - [ ] Prev button returns user to Establish Essentials - [ ] Token Name accepts any text @@ -75,10 +85,11 @@ From Step 1: *enter a name* -> *select Voting Token* -> *Next* - [ ] Delete button deletes the allocation row - [ ] totals from Amount fields must be less than or equal to Token Supply - [ ] All Allocation rows are required to enable Next -- [ ] Next button links to *Compose Governance* +- [ ] Next button links to _Compose Governance_ ### Step 3: Compose Governance -From Step 2: *enter Token Name / Symbol / Supply* -> *enter allocation Address / Amount* -> *Next* + +From Step 2: _enter Token Name / Symbol / Supply_ -> _enter allocation Address / Amount_ -> _Next_ - [ ] Prev button returns user to Configure Voting Token - [ ] Voting Period accepts any whole number @@ -88,15 +99,18 @@ From Step 2: *enter Token Name / Symbol / Supply* -> *enter allocation Address / - [ ] Deploy button initiates a transaction to deploy the DAO ## DAO Home (Token Voting) + https://app.staging.fractalframework.xyz/daos/{address} ### Sidebar + - [ ] DAO Home is highlighted - [ ] DAO Hierarchy links to the DAO hierarchy - [ ] Proposals links to the DAO proposals - [ ] Treasury links to the DAO treasury ### DAO Details + - [ ] DAO name is displayed - [ ] clicking DAO address copies it to clipboard - [ ] Favorite icon toggles favorite status of the DAO @@ -108,12 +122,14 @@ https://app.staging.fractalframework.xyz/daos/{address} - [ ] Treasury card links to Treasury ### Proposals List + - [ ] Proposals are displayed - [ ] Newest / Oldest toggle list by creation date - [ ] Details button links to the Proposal Details - [ ] State filter filters by Proposal states (Active, Timelocked, Executable, Executed, Failed) ## DAO Hierarchy (Token Voting) + https://app.staging.fractalframework.xyz/daos/{address}/hierarchy - [ ] DAO Hierarchy is highlighted in sidebar @@ -125,17 +141,19 @@ https://app.staging.fractalframework.xyz/daos/{address}/hierarchy - [ ] subDAO name links to subDAO's homepage ## Proposals (Token Voting) + https://app.staging.fractalframework.xyz/daos/{address}/proposals ### General + - [ ] Proposals is highlighted in sidebar - [ ] DAO name links to DAO homepage - [ ] Delegate button appears IF connected address is a token holder - [ ] Create button links to Proposal creation flow -- [ ] -TODO more here +- [ ] TODO more here ### Delegate Modal + - [ ] token balance is shown - [ ] Delegated To is shown (who connected address delegates their tokens to) - [ ] Delegate to Self copies connected address into input field @@ -144,9 +162,11 @@ TODO more here - [ ] Delegate Voting Tokens button initiates transaction to delegate and closes the modal ## Treasury (Token Voting) + https://app.staging.fractalframework.xyz/daos/{address}/treasury ### General + - [ ] Treasury is highlighted in sidebar - [ ] DAO name links to DAO homepage - [ ] Transactions list displays incoming / outgoing assets @@ -160,6 +180,7 @@ https://app.staging.fractalframework.xyz/daos/{address}/treasury - [ ] Send Assets button appears ONLY if connected address is a delegated token holder ### Send Assets + - [ ] Clicking X or outside modal dismisses the modal - [ ] Select Asset dropdown lists ERC-20 tokens - [ ] How Much? accepts any number greater than 0 or less than available selected token balance @@ -168,19 +189,24 @@ https://app.staging.fractalframework.xyz/daos/{address}/treasury - [ ] Submit Proposal button initiates transaction to create a Proposal ## Templates (Token Voting) + https://app.staging.fractalframework.xyz/daos/{address}/ ???? TODO ### General + - [ ] Templates is highlighted in sidebar - [ ] TODO ## Proposal Details (Token Voting) + - [ ] TODO ## Proposal Creation (Token Voting) + https://app.staging.fractalframework.xyz/daos/{address}/proposals/new ### Step 1: Proposal Metadata + - [ ] Voting Period, Quorum, Timelock appear under Proposal Details - [ ] Proposal Title accepts any text - [ ] Proposal Description accepts any text @@ -189,6 +215,7 @@ https://app.staging.fractalframework.xyz/daos/{address}/proposals/new - [ ] Next links to transaction builder ### Step 2: Transaction Builder + - [ ] Prev links to Proposal Metadata - [ ] Target Address accepts any Eth address or valid ENS - [ ] Function Name accepts any alpha numeric text (no spaces) @@ -201,15 +228,18 @@ https://app.staging.fractalframework.xyz/daos/{address}/proposals/new - [ ] Create Proposal button initiates transaction to create the proposal ## SubDAO Creation (Token Voting) + https://app.staging.fractalframework.xyz/daos/{parentAddress}/new ### General + - [ ] All aspects of [Create a DAO](#create-a-dao) - [ ] Advanced appears in Configure Voting Token - [ ] Parent token holder claiming accepts any number up to 18 decimal places - [ ] Parent token holder amount included in total allocation calculation to enable Next ### Step 4: Configure Parent Controls + - [ ] Prev button returns user to Compose Governance - [ ] Execution Period accepts any whole number - [ ] Freeze Votes Threshold accepts any whole number @@ -219,19 +249,22 @@ https://app.staging.fractalframework.xyz/daos/{parentAddress}/new - [ ] Create subDAO Proposal initiates transaction to submit proposal ## Freeze Voting + - [ ] TODO ## Token Claim -- *Create a subDAO Proposal* -- *During subDAO creation, add token claim amount in Advanced* -- *Pass the Proposal* + +- _Create a subDAO Proposal_ +- _During subDAO creation, add token claim amount in Advanced_ +- _Pass the Proposal_ - [ ] Blue Claim component does NOT appear on the DAO homepage for a connected non token holder - [ ] Blue Claim component DOES appear on the DAO homepage for a connected token holder - [ ] Claim button initiates a transaction which immediately claims tokens for the caller ## Clawback -- *Initiate a Freeze on a subDAO* + +- _Initiate a Freeze on a subDAO_ - [ ] Initiate Clawback option does NOT appear in the Manage DAO menu of the subDAO's homepage for NON rootDAO delegated token holders - [ ] Initiate Clawback option DOES appear in the Manage DAO menu of the subDAO's homepage for rootDAO delegated token holders @@ -239,12 +272,14 @@ https://app.staging.fractalframework.xyz/daos/{parentAddress}/new - [ ] Passing the Clawback Proposal on the rootDAO transfers all ERC20 tokens from the subDAO to the rootDAO ## DAO Home (Multisig) + https://app.staging.fractalframework.xyz/daos/{address} - [ ] Governace card displays Multisig governance details - [ ] Proposals card displays Pending / Passed counts ## Manage Signers (Multisig) + https://app.staging.fractalframework.xyz/daos/{address}/settings - [ ] List of current signers appears @@ -254,4 +289,4 @@ https://app.staging.fractalframework.xyz/daos/{address}/settings - [ ] Add modal creates add signer proposal - [ ] Add modal does not allow you to add an existing signer again -## TODO more multisig specific stuff \ No newline at end of file +## TODO more multisig specific stuff diff --git a/tests/home/connect-wallet.spec.ts b/tests/home/connect-wallet.spec.ts index 3f658a55fd..2ca06737cb 100644 --- a/tests/home/connect-wallet.spec.ts +++ b/tests/home/connect-wallet.spec.ts @@ -14,7 +14,7 @@ test('wallet should auto connect', async ({ page }) => { /* Assert defined wallet address is present ("0xf39F...2266") */ await home.clickAccountMenu(); await expect(page.locator('[data-testid="walletMenu-accountDisplay"]')).toContainText( - '0xf39F...2266' + '0xf39F...2266', ); }); diff --git a/tests/models/mock/SafeMocker.ts b/tests/models/mock/SafeMocker.ts index f09ea17c5c..2641d88ee5 100644 --- a/tests/models/mock/SafeMocker.ts +++ b/tests/models/mock/SafeMocker.ts @@ -37,7 +37,7 @@ export abstract class SafeMocker { await route.fulfill({ body: JSON.stringify(handler(request)), }); - } + }, ); } } diff --git a/tests/models/mock/data/creation.ts b/tests/models/mock/data/creation.ts index c01935a4b0..37e6b10a5b 100644 --- a/tests/models/mock/data/creation.ts +++ b/tests/models/mock/data/creation.ts @@ -72,7 +72,7 @@ export const createSubgraphDAO = ( address: string, name: string, hierarchy: DAO[], - parentAddress?: string + parentAddress?: string, ) => { return { data: { diff --git a/tsconfig.json b/tsconfig.json index ca09d9c784..088de09f39 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,11 +1,7 @@ { "compilerOptions": { "target": "ESNext", - "lib": [ - "dom", - "dom.iterable", - "esnext" - ], + "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, "esModuleInterop": true, @@ -26,9 +22,7 @@ } ] }, - "exclude": [ - "docker" - ], + "exclude": ["docker"], "include": [ "src", "tests", @@ -36,6 +30,6 @@ "app", ".next/types/**/*.ts", "next-env.d.ts", - "next.config.js", + "next.config.js" ] } diff --git a/vitest.config.ts b/vitest.config.ts index 8732721244..dc041f02c0 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,9 +1,9 @@ -import { defineConfig } from 'vitest/config' -import react from '@vitejs/plugin-react' - +import { defineConfig } from 'vitest/config'; +import react from '@vitejs/plugin-react'; + export default defineConfig({ plugins: [react()], test: { environment: 'jsdom', }, -}) +}); From a02fa685d6761f7526bfb21c5cdb8e8359b40bb1 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Thu, 7 Mar 2024 13:01:33 -0500 Subject: [PATCH 41/56] Add github action to lint and pretty --- .github/workflows/code-syntax.yaml | 33 ++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 .github/workflows/code-syntax.yaml diff --git a/.github/workflows/code-syntax.yaml b/.github/workflows/code-syntax.yaml new file mode 100644 index 0000000000..a4b5230ef2 --- /dev/null +++ b/.github/workflows/code-syntax.yaml @@ -0,0 +1,33 @@ +name: Code Syntax +on: + push: + branches: + - develop + pull_request: +jobs: + lint: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Set up node + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + - name: Install dependencies + run: npm ci + - name: Run tests + run: npm run lint + pretty: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Set up node + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + - name: Install dependencies + run: npm ci + - name: Run tests + run: npm run pretty:check From ad98cf43f268bd0d3a0fde474df94c1e6cda2aaa Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Thu, 7 Mar 2024 13:14:42 -0500 Subject: [PATCH 42/56] Build grapql client before running jobs --- .github/workflows/code-syntax.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/code-syntax.yaml b/.github/workflows/code-syntax.yaml index a4b5230ef2..56bc3cb10d 100644 --- a/.github/workflows/code-syntax.yaml +++ b/.github/workflows/code-syntax.yaml @@ -16,6 +16,8 @@ jobs: node-version-file: '.nvmrc' - name: Install dependencies run: npm ci + - name: Build GraphQL client + run: npm run graphql:build - name: Run tests run: npm run lint pretty: @@ -29,5 +31,7 @@ jobs: node-version-file: '.nvmrc' - name: Install dependencies run: npm ci + - name: Build GraphQL client + run: npm run graphql:build - name: Run tests run: npm run pretty:check From d17a103ae1a5a5adaa91c1a9780f9536b2932b16 Mon Sep 17 00:00:00 2001 From: Kirill Klimenko Date: Thu, 7 Mar 2024 19:22:14 +0100 Subject: [PATCH 43/56] Fix another batch of TS errors --- app/create/page.tsx | 2 +- src/components/DaoCreator/index.tsx | 12 ++++++-- .../MultisigProposalDetails/TxActions.tsx | 2 +- .../pages/DaoHierarchy/useFetchNodes.tsx | 24 ++++++++-------- .../DaoSettings/components/Metadata/index.tsx | 10 ++++++- .../ui/menus/ManageDAO/ManageDAOMenu.tsx | 22 +++------------ .../DAO/loaders/governance/useERC721Tokens.ts | 2 +- .../governance/useSafeMultisigProposals.ts | 2 +- src/hooks/DAO/loaders/useFractalFreeze.ts | 22 ++++++++------- .../DAO/loaders/useFractalGuardContracts.ts | 27 ++++++++---------- src/hooks/DAO/loaders/useFractalTreasury.ts | 14 ++++------ .../DAO/loaders/useGovernanceContracts.ts | 28 ++++++++----------- src/hooks/DAO/loaders/useLoadDAONode.ts | 2 +- .../DAO/proposal/useCreateProposalTemplate.ts | 4 +-- .../DAO/proposal/useRemoveProposalTemplate.ts | 4 +-- src/hooks/DAO/proposal/useSubmitProposal.ts | 7 +++-- .../DAO/proposal/useUserERC721VotingTokens.ts | 8 +++--- src/hooks/DAO/useDAOName.ts | 9 +++--- src/hooks/safe/useIsSafe.ts | 2 +- src/types/fractal.ts | 2 +- 20 files changed, 98 insertions(+), 107 deletions(-) diff --git a/app/create/page.tsx b/app/create/page.tsx index 2fe1bd9925..8aaf16713a 100644 --- a/app/create/page.tsx +++ b/app/create/page.tsx @@ -28,7 +28,7 @@ export default function DaoCreatePage() { setRedirectPending(true); const { getAddress } = ethers.utils; const daoFound = await requestWithRetries( - () => safeAPI.getSafeCreationInfo(getAddress(daoAddress)), + async () => (safeAPI ? safeAPI.getSafeCreationInfo(getAddress(daoAddress)) : undefined), 8 ); toggleFavorite(daoAddress); diff --git a/src/components/DaoCreator/index.tsx b/src/components/DaoCreator/index.tsx index 65889e5434..83309ed149 100644 --- a/src/components/DaoCreator/index.tsx +++ b/src/components/DaoCreator/index.tsx @@ -38,7 +38,9 @@ function DaoCreator({ ...values.multisig, freezeGuard, }); - deployDAO(data); + if (data) { + deployDAO(data); + } return; } case GovernanceType.AZORIUS_ERC20: { @@ -48,7 +50,9 @@ function DaoCreator({ ...values.erc20Token, freezeGuard, }); - deployDAO(data); + if (data) { + deployDAO(data); + } return; } case GovernanceType.AZORIUS_ERC721: { @@ -58,7 +62,9 @@ function DaoCreator({ ...values.erc721Token, freezeGuard, }); - deployDAO(data); + if (data) { + deployDAO(data); + } return; } } diff --git a/src/components/Proposals/MultisigProposalDetails/TxActions.tsx b/src/components/Proposals/MultisigProposalDetails/TxActions.tsx index 52df951948..e21780c6b5 100644 --- a/src/components/Proposals/MultisigProposalDetails/TxActions.tsx +++ b/src/components/Proposals/MultisigProposalDetails/TxActions.tsx @@ -67,7 +67,7 @@ export function TxActions({ pendingMessage: t('pendingSign'), successMessage: t('successSign'), successCallback: async (signature: string) => { - await safeAPI.confirmTransaction(proposal.proposalId, signature); + await safeAPI!.confirmTransaction(proposal.proposalId, signature); await loadSafeMultisigProposals(); }, }); diff --git a/src/components/pages/DaoHierarchy/useFetchNodes.tsx b/src/components/pages/DaoHierarchy/useFetchNodes.tsx index 2e93232e74..d2a9ee1113 100644 --- a/src/components/pages/DaoHierarchy/useFetchNodes.tsx +++ b/src/components/pages/DaoHierarchy/useFetchNodes.tsx @@ -16,11 +16,7 @@ export function useFetchNodes(address?: string) { const { node: { safe, nodeHierarchy }, - baseContracts: { - multisigFreezeGuardMasterCopyContract, - azoriusFreezeGuardMasterCopyContract, - fractalAzoriusMasterCopyContract, - }, + baseContracts, } = useFractal(); const safeAPI = useSafeAPI(); @@ -37,7 +33,12 @@ export function useFetchNodes(address?: string) { const getDAOOwner = useCallback( async (safeInfo?: Partial) => { - if (safeInfo && safeInfo.guard && multisigFreezeGuardMasterCopyContract) { + if (safeInfo && safeInfo.guard && baseContracts) { + const { + multisigFreezeGuardMasterCopyContract, + azoriusFreezeGuardMasterCopyContract, + fractalAzoriusMasterCopyContract, + } = baseContracts; if (safeInfo.guard !== ethers.constants.AddressZero) { const guard = multisigFreezeGuardMasterCopyContract.asProvider.attach(safeInfo.guard); const guardOwner = await guard.owner(); @@ -70,18 +71,15 @@ export function useFetchNodes(address?: string) { } return undefined; }, - [ - multisigFreezeGuardMasterCopyContract, - azoriusFreezeGuardMasterCopyContract, - fractalAzoriusMasterCopyContract, - lookupModules, - ] + [baseContracts, lookupModules] ); const fetchDAOInfo = useCallback( async (safeAddress: string) => { const { getAddress } = ethers.utils; - return (await safeAPI.getSafeInfo(getAddress(safeAddress))) as SafeInfoResponseWithGuard; + if (safeAPI) { + return (await safeAPI.getSafeInfo(getAddress(safeAddress))) as SafeInfoResponseWithGuard; + } }, [safeAPI] ); diff --git a/src/components/pages/DaoSettings/components/Metadata/index.tsx b/src/components/pages/DaoSettings/components/Metadata/index.tsx index 940549576b..3e17271262 100644 --- a/src/components/pages/DaoSettings/components/Metadata/index.tsx +++ b/src/components/pages/DaoSettings/components/Metadata/index.tsx @@ -21,7 +21,7 @@ export default function MetadataContainer() { const { canUserCreateProposal, submitProposal } = useSubmitProposal(); const { - baseContracts: { fractalRegistryContract, keyValuePairsContract }, + baseContracts, node: { daoName, daoSnapshotURL, daoAddress, safe }, readOnly: { user: { votingWeight }, @@ -53,6 +53,10 @@ export default function MetadataContainer() { const submitProposalSuccessCallback = () => push(DAO_ROUTES.proposals.relative(daoAddress)); const handleEditDAOName = () => { + if (!baseContracts) { + return; + } + const { fractalRegistryContract } = baseContracts; const proposalData: ProposalExecuteData = { metaData: { title: t('Update Safe Name', { ns: 'proposalMetadata' }), @@ -77,6 +81,10 @@ export default function MetadataContainer() { }; const handleEditDAOSnapshotURL = () => { + if (!baseContracts) { + return; + } + const { keyValuePairsContract } = baseContracts; const proposalData: ProposalExecuteData = { metaData: { title: t('Update Snapshot Space', { ns: 'proposalMetadata' }), diff --git a/src/components/ui/menus/ManageDAO/ManageDAOMenu.tsx b/src/components/ui/menus/ManageDAO/ManageDAOMenu.tsx index 154e3bca26..c5864f4365 100644 --- a/src/components/ui/menus/ManageDAO/ManageDAOMenu.tsx +++ b/src/components/ui/menus/ManageDAO/ManageDAOMenu.tsx @@ -58,12 +58,7 @@ export function ManageDAOMenu({ const { node: { safe }, governance: { type }, - baseContracts: { - fractalAzoriusMasterCopyContract, - zodiacModuleProxyFactoryContract, - linearVotingERC721MasterCopyContract, - linearVotingMasterCopyContract, - }, + baseContracts, } = useFractal(); const currentTime = BigNumber.from(useBlockTimestamp()); const { push } = useRouter(); @@ -93,9 +88,10 @@ export function ManageDAOMenu({ // are the same - we can simply grab governance type from global scope and avoid double-fetching setGovernanceType(type); } else { - if (fractalNode?.fractalModules) { + if (fractalNode?.fractalModules && baseContracts) { let result = GovernanceType.MULTISIG; const azoriusModule = getAzoriusModuleFromModules(fractalNode?.fractalModules); + const { fractalAzoriusMasterCopyContract } = baseContracts; if (!!azoriusModule) { const azoriusContract = { asProvider: fractalAzoriusMasterCopyContract.asProvider.attach( @@ -128,17 +124,7 @@ export function ManageDAOMenu({ }; loadGovernanceType(); - }, [ - fractalAzoriusMasterCopyContract, - linearVotingERC721MasterCopyContract, - linearVotingMasterCopyContract, - fractalNode, - safe, - safeAddress, - type, - zodiacModuleProxyFactoryContract, - getZodiacModuleProxyMasterCopyData, - ]); + }, [fractalNode, safe, safeAddress, type, getZodiacModuleProxyMasterCopyData, baseContracts]); const handleNavigateToSettings = useCallback( () => push(DAO_ROUTES.settings.relative(safeAddress)), diff --git a/src/hooks/DAO/loaders/governance/useERC721Tokens.ts b/src/hooks/DAO/loaders/governance/useERC721Tokens.ts index ec2c48857b..ddea6bc438 100644 --- a/src/hooks/DAO/loaders/governance/useERC721Tokens.ts +++ b/src/hooks/DAO/loaders/governance/useERC721Tokens.ts @@ -14,7 +14,7 @@ export default function useERC721Tokens() { action, } = useFractal(); const loadERC721Tokens = useCallback(async () => { - if (!erc721LinearVotingContract) { + if (!erc721LinearVotingContract || !signerOrProvider) { return; } diff --git a/src/hooks/DAO/loaders/governance/useSafeMultisigProposals.ts b/src/hooks/DAO/loaders/governance/useSafeMultisigProposals.ts index 50498c3299..9868404a5c 100644 --- a/src/hooks/DAO/loaders/governance/useSafeMultisigProposals.ts +++ b/src/hooks/DAO/loaders/governance/useSafeMultisigProposals.ts @@ -16,7 +16,7 @@ export const useSafeMultisigProposals = () => { const { parseTransactions } = useSafeTransactions(); const loadSafeMultisigProposals = useCallback(async () => { - if (!daoAddress) { + if (!daoAddress || !safeAPI) { return; } try { diff --git a/src/hooks/DAO/loaders/useFractalFreeze.ts b/src/hooks/DAO/loaders/useFractalFreeze.ts index 997626f904..daed9297d8 100644 --- a/src/hooks/DAO/loaders/useFractalFreeze.ts +++ b/src/hooks/DAO/loaders/useFractalFreeze.ts @@ -34,7 +34,7 @@ export const useFractalFreeze = ({ const { node: { daoAddress }, - baseContracts: { votesTokenMasterCopyContract, safeSingletonContract }, + baseContracts, guardContracts, action, } = useFractal(); @@ -49,7 +49,14 @@ export const useFractalFreeze = ({ const loadFractalFreezeGuard = useCallback( async ({ freezeVotingContract, freezeVotingType: freezeVotingType }: FractalGuardContracts) => { - if (freezeVotingType == null || !freezeVotingContract || !account || !provider) return; + if ( + freezeVotingType == null || + !freezeVotingContract || + !account || + !provider || + !baseContracts + ) + return; let userHasVotes: boolean = false; const freezeCreatedBlock = await ( freezeVotingContract!.asProvider as @@ -87,6 +94,8 @@ export const useFractalFreeze = ({ isFrozen, }; + const { votesTokenMasterCopyContract, safeSingletonContract } = baseContracts; + if (freezeVotingType === FreezeVotingType.MULTISIG) { const safeContract = safeSingletonContract!.asProvider.attach( await (freezeVotingContract!.asProvider as MultisigFreezeVoting).parentGnosisSafe() @@ -131,14 +140,7 @@ export const useFractalFreeze = ({ isFreezeSet.current = true; return freeze; }, - [ - account, - provider, - safeSingletonContract, - votesTokenMasterCopyContract, - getUserERC721VotingTokens, - parentSafeAddress, - ] + [account, provider, baseContracts, getUserERC721VotingTokens, parentSafeAddress] ); const setFractalFreezeGuard = useCallback( diff --git a/src/hooks/DAO/loaders/useFractalGuardContracts.ts b/src/hooks/DAO/loaders/useFractalGuardContracts.ts index 777f68ada6..f2af8b8c91 100644 --- a/src/hooks/DAO/loaders/useFractalGuardContracts.ts +++ b/src/hooks/DAO/loaders/useFractalGuardContracts.ts @@ -18,13 +18,7 @@ export const useFractalGuardContracts = ({ loadOnMount = true }: { loadOnMount?: const loadKey = useRef(); const { node: { daoAddress, safe, fractalModules, isModulesLoaded }, - baseContracts: { - freezeERC20VotingMasterCopyContract, - freezeERC721VotingMasterCopyContract, - freezeMultisigVotingMasterCopyContract, - azoriusFreezeGuardMasterCopyContract, - multisigFreezeGuardMasterCopyContract, - }, + baseContracts, action, } = useFractal(); @@ -38,6 +32,16 @@ export const useFractalGuardContracts = ({ loadOnMount = true }: { loadOnMount?: _safe: SafeInfoResponseWithGuard, _fractalModules: FractalModuleData[] ) => { + if (!baseContracts) { + return; + } + const { + freezeERC20VotingMasterCopyContract, + freezeERC721VotingMasterCopyContract, + freezeMultisigVotingMasterCopyContract, + azoriusFreezeGuardMasterCopyContract, + multisigFreezeGuardMasterCopyContract, + } = baseContracts; const { guard } = _safe; let freezeGuardContract: @@ -103,14 +107,7 @@ export const useFractalGuardContracts = ({ loadOnMount = true }: { loadOnMount?: return contracts; } }, - [ - freezeERC20VotingMasterCopyContract, - freezeERC721VotingMasterCopyContract, - freezeMultisigVotingMasterCopyContract, - azoriusFreezeGuardMasterCopyContract, - multisigFreezeGuardMasterCopyContract, - getZodiacModuleProxyMasterCopyData, - ] + [baseContracts, getZodiacModuleProxyMasterCopyData] ); const setGuardContracts = useCallback(async () => { diff --git a/src/hooks/DAO/loaders/useFractalTreasury.ts b/src/hooks/DAO/loaders/useFractalTreasury.ts index 2ddc1e3f99..847e96c794 100644 --- a/src/hooks/DAO/loaders/useFractalTreasury.ts +++ b/src/hooks/DAO/loaders/useFractalTreasury.ts @@ -1,10 +1,10 @@ import axios from 'axios'; import { useEffect, useCallback, useRef } from 'react'; +import { usePublicClient } from 'wagmi'; import { logError } from '../../../helpers/errorLogging'; import { useFractal } from '../../../providers/App/AppProvider'; import { useSafeAPI } from '../../../providers/App/hooks/useSafeAPI'; import { TreasuryAction } from '../../../providers/App/treasury/action'; -import { useEthersProvider } from '../../../providers/Ethers/hooks/useEthersProvider'; import { useNetworkConfig } from '../../../providers/NetworkConfig/NetworkConfigProvider'; import { buildSafeApiUrl } from '../../../utils'; import { useUpdateTimer } from './../../utils/useUpdateTimer'; @@ -18,16 +18,14 @@ export const useFractalTreasury = () => { } = useFractal(); const safeAPI = useSafeAPI(); - const { - network: { chainId }, - } = useEthersProvider(); + const { chain } = usePublicClient(); const { safeBaseURL } = useNetworkConfig(); const { setMethodOnInterval } = useUpdateTimer(daoAddress); const loadTreasury = useCallback(async () => { - if (!daoAddress) { + if (!daoAddress || !safeAPI) { return; } const [assetsFungible, assetsNonFungible, transfers] = await Promise.all([ @@ -56,11 +54,11 @@ export const useFractalTreasury = () => { }, [daoAddress, safeAPI, safeBaseURL, action]); useEffect(() => { - if (daoAddress && chainId + daoAddress !== loadKey.current) { - loadKey.current = chainId + daoAddress; + if (daoAddress && chain.id + daoAddress !== loadKey.current) { + loadKey.current = chain.id + daoAddress; setMethodOnInterval(loadTreasury); } - }, [chainId, daoAddress, loadTreasury, setMethodOnInterval]); + }, [chain, daoAddress, loadTreasury, setMethodOnInterval]); return; }; diff --git a/src/hooks/DAO/loaders/useGovernanceContracts.ts b/src/hooks/DAO/loaders/useGovernanceContracts.ts index 509f86a96a..7a9bac9e2e 100644 --- a/src/hooks/DAO/loaders/useGovernanceContracts.ts +++ b/src/hooks/DAO/loaders/useGovernanceContracts.ts @@ -19,17 +19,7 @@ import useSignerOrProvider from '../../utils/useSignerOrProvider'; export const useGovernanceContracts = () => { // tracks the current valid DAO address; helps prevent unnecessary calls const currentValidAddress = useRef(); - const { - node, - baseContracts: { - votesTokenMasterCopyContract, - fractalAzoriusMasterCopyContract, - votesERC20WrapperMasterCopyContract, - linearVotingMasterCopyContract, - linearVotingERC721MasterCopyContract, - }, - action, - } = useFractal(); + const { node, baseContracts, action } = useFractal(); const { getZodiacModuleProxyMasterCopyData } = useMasterCopy(); const provider = useEthersProvider(); const signerOrProvider = useSignerOrProvider(); @@ -37,6 +27,16 @@ export const useGovernanceContracts = () => { const { fractalModules, isModulesLoaded, daoAddress } = node; const loadGovernanceContracts = useCallback(async () => { + if (!baseContracts) { + return; + } + const { + votesTokenMasterCopyContract, + fractalAzoriusMasterCopyContract, + votesERC20WrapperMasterCopyContract, + linearVotingMasterCopyContract, + linearVotingERC721MasterCopyContract, + } = baseContracts; const azoriusModule = getAzoriusModuleFromModules(fractalModules); const azoriusModuleContract = azoriusModule?.moduleContract as Azorius; @@ -195,12 +195,8 @@ export const useGovernanceContracts = () => { action, provider, signerOrProvider, - votesTokenMasterCopyContract, - fractalAzoriusMasterCopyContract, - votesERC20WrapperMasterCopyContract, getZodiacModuleProxyMasterCopyData, - linearVotingMasterCopyContract, - linearVotingERC721MasterCopyContract, + baseContracts, fractalModules, ]); diff --git a/src/hooks/DAO/loaders/useLoadDAONode.ts b/src/hooks/DAO/loaders/useLoadDAONode.ts index 3dd8cba499..32bc28d1a3 100644 --- a/src/hooks/DAO/loaders/useLoadDAONode.ts +++ b/src/hooks/DAO/loaders/useLoadDAONode.ts @@ -42,7 +42,7 @@ export const useLoadDAONode = () => { const loadDao = useCallback( async (_daoAddress: string): Promise => { - if (utils.isAddress(_daoAddress)) { + if (utils.isAddress(_daoAddress) && safeAPI) { try { const graphNodeInfo = formatDAOQuery( await getDAOInfo({ variables: { daoAddress: _daoAddress } }), diff --git a/src/hooks/DAO/proposal/useCreateProposalTemplate.ts b/src/hooks/DAO/proposal/useCreateProposalTemplate.ts index 6ab5965f88..d90edfea9c 100644 --- a/src/hooks/DAO/proposal/useCreateProposalTemplate.ts +++ b/src/hooks/DAO/proposal/useCreateProposalTemplate.ts @@ -11,7 +11,7 @@ import useSignerOrProvider from '../../utils/useSignerOrProvider'; export default function useCreateProposalTemplate() { const signerOrProvider = useSignerOrProvider(); - const { keyValuePairsContract } = useSafeContracts(); + const keyValuePairsContract = useSafeContracts()?.keyValuePairsContract; const client = useIPFSClient(); const { governance: { proposalTemplates }, @@ -19,7 +19,7 @@ export default function useCreateProposalTemplate() { const prepareProposalTemplateProposal = useCallback( async (values: CreateProposalTemplateForm) => { - if (proposalTemplates) { + if (proposalTemplates && signerOrProvider && keyValuePairsContract) { const proposalMetadata = { title: 'Create Proposal Template', description: diff --git a/src/hooks/DAO/proposal/useRemoveProposalTemplate.ts b/src/hooks/DAO/proposal/useRemoveProposalTemplate.ts index c234b6e271..785b6a8aef 100644 --- a/src/hooks/DAO/proposal/useRemoveProposalTemplate.ts +++ b/src/hooks/DAO/proposal/useRemoveProposalTemplate.ts @@ -6,7 +6,7 @@ import { ProposalExecuteData } from '../../../types'; import useSafeContracts from '../../safe/useSafeContracts'; export default function useRemoveProposalTemplate() { - const { keyValuePairsContract } = useSafeContracts(); + const keyValuePairsContract = useSafeContracts()?.keyValuePairsContract; const client = useIPFSClient(); const { governance: { proposalTemplates }, @@ -14,7 +14,7 @@ export default function useRemoveProposalTemplate() { const prepareRemoveProposalTemplateProposal = useCallback( async (templateIndex: number) => { - if (proposalTemplates) { + if (proposalTemplates && keyValuePairsContract) { const proposalMetadata = { title: 'Remove Proposal Template', description: diff --git a/src/hooks/DAO/proposal/useSubmitProposal.ts b/src/hooks/DAO/proposal/useSubmitProposal.ts index 8e705965a9..00c4494637 100644 --- a/src/hooks/DAO/proposal/useSubmitProposal.ts +++ b/src/hooks/DAO/proposal/useSubmitProposal.ts @@ -64,7 +64,7 @@ export default function useSubmitProposal() { const { node: { safe, fractalModules }, - baseContracts: { multiSendContract }, + baseContracts, guardContracts: { freezeVotingContract }, governanceContracts: { ozLinearVotingContract, erc721LinearVotingContract }, governance: { type }, @@ -171,9 +171,10 @@ export default function useSubmitProposal() { successCallback, safeAddress, }: ISubmitProposal) => { - if (!proposalData) { + if (!proposalData || !baseContracts) { return; } + const { multiSendContract } = baseContracts; const toastId = toast(pendingToastMessage, { autoClose: false, @@ -268,7 +269,7 @@ export default function useSubmitProposal() { return; } }, - [signerOrProvider, safeBaseURL, chainId, loadDAOProposals, ipfsClient, multiSendContract] + [signerOrProvider, safeBaseURL, chainId, loadDAOProposals, ipfsClient, baseContracts] ); const submitAzoriusProposal = useCallback( diff --git a/src/hooks/DAO/proposal/useUserERC721VotingTokens.ts b/src/hooks/DAO/proposal/useUserERC721VotingTokens.ts index a69365162e..5ab79cced6 100644 --- a/src/hooks/DAO/proposal/useUserERC721VotingTokens.ts +++ b/src/hooks/DAO/proposal/useUserERC721VotingTokens.ts @@ -53,7 +53,7 @@ export default function useUserERC721VotingTokens( if (_safeAddress && daoAddress !== _safeAddress) { // Means getting these for any safe, primary use case - calculating user voting weight for freeze voting - const safeInfo = await safeAPI.getSafeInfo(utils.getAddress(_safeAddress)); + const safeInfo = await safeAPI!.getSafeInfo(utils.getAddress(_safeAddress)); const safeModules = await lookupModules(safeInfo.modules); const azoriusModule = getAzoriusModuleFromModules(safeModules); if (azoriusModule && azoriusModule.moduleContract) { @@ -64,12 +64,12 @@ export default function useUserERC721VotingTokens( )[1]; votingContract = LinearERC721Voting__factory.connect( votingContractAddress, - signerOrProvider + signerOrProvider! ); const addresses = await votingContract.getAllTokenAddresses(); govTokens = await Promise.all( addresses.map(async tokenAddress => { - const tokenContract = ERC721__factory.connect(tokenAddress, signerOrProvider); + const tokenContract = ERC721__factory.connect(tokenAddress, signerOrProvider!); const votingWeight = await votingContract!.getTokenWeight(tokenAddress); const name = await tokenContract.name(); const symbol = await tokenContract.symbol(); @@ -106,7 +106,7 @@ export default function useUserERC721VotingTokens( // Using `map` instead of `forEach` to simplify usage of `Promise.all` // and guarantee syncronous contractFn assignment govTokens.map(async token => { - const tokenContract = ERC721__factory.connect(token.address, signerOrProvider); + const tokenContract = ERC721__factory.connect(token.address, signerOrProvider!); if ((await tokenContract.balanceOf(user.address!)).gt(0)) { const tokenSentEvents = await tokenContract.queryFilter( tokenContract.filters.Transfer(user.address!, null) diff --git a/src/hooks/DAO/useDAOName.ts b/src/hooks/DAO/useDAOName.ts index b0a7921f93..72dfbe797c 100644 --- a/src/hooks/DAO/useDAOName.ts +++ b/src/hooks/DAO/useDAOName.ts @@ -22,9 +22,7 @@ export default function useDAOName({ address?: string; registryName?: string | null; }) { - const { - baseContracts: { fractalRegistryContract }, - } = useFractal(); + const { baseContracts } = useFractal(); const [daoRegistryName, setDAORegistryName] = useState(''); const { chain } = usePublicClient(); @@ -36,7 +34,7 @@ export default function useDAOName({ const { setValue, getValue } = useLocalStorage(); const getDaoName = useCallback(async () => { - if (!address) { + if (!address || !baseContracts) { setDAORegistryName(''); return; } @@ -51,6 +49,7 @@ export default function useDAOName({ setDAORegistryName(cachedName); return; } + const { fractalRegistryContract } = baseContracts; if (!fractalRegistryContract) { setDAORegistryName(createAccountSubstring(address)); return; @@ -72,7 +71,7 @@ export default function useDAOName({ setValue(CacheKeys.DAO_NAME_PREFIX + address, daoName, 60); setDAORegistryName(daoName); } - }, [address, ensName, fractalRegistryContract, getValue, setValue, registryName]); + }, [address, ensName, baseContracts, getValue, setValue, registryName]); useEffect(() => { (async () => { diff --git a/src/hooks/safe/useIsSafe.ts b/src/hooks/safe/useIsSafe.ts index f1ce2dc935..5d0aa096cf 100644 --- a/src/hooks/safe/useIsSafe.ts +++ b/src/hooks/safe/useIsSafe.ts @@ -22,7 +22,7 @@ export const useIsSafe = (address: string | undefined) => { setSafeLoading(true); setIsSafe(undefined); - if (!address || !isAddress(address)) { + if (!address || !isAddress(address) || !safeAPI) { setIsSafe(false); setSafeLoading(false); return; diff --git a/src/types/fractal.ts b/src/types/fractal.ts index 64234e9b92..ce6c670f08 100644 --- a/src/types/fractal.ts +++ b/src/types/fractal.ts @@ -194,7 +194,7 @@ export interface ITokenAccount { * @param dispatch - This object contains the dispatch functions for the Fractal DAO. */ export interface FractalStore extends Fractal { - baseContracts: FractalContracts; + baseContracts?: FractalContracts; action: { dispatch: Dispatch; loadReadOnlyValues: () => Promise; From d969571f9974c1299b3992d724a01d267e21656c Mon Sep 17 00:00:00 2001 From: Kirill Klimenko Date: Thu, 7 Mar 2024 19:37:41 +0100 Subject: [PATCH 44/56] Moar linting fixes --- .../DaoSettings/components/Metadata/index.tsx | 2 +- .../components/Signers/hooks/useAddSigner.ts | 10 ++- .../Signers/hooks/useRemoveSigner.ts | 19 ++--- .../DAO/loaders/governance/useERC20Claim.ts | 8 +- src/hooks/DAO/loaders/useFractalModules.ts | 20 ++--- src/hooks/DAO/useBuildDAOTx.ts | 81 ++++++------------- src/hooks/DAO/useCreateSubDAOProposal.ts | 17 +--- src/hooks/DAO/useDeployAzorius.ts | 60 ++++++-------- src/hooks/DAO/useDeployDAO.ts | 10 +-- src/hooks/utils/useMasterCopy.ts | 58 +++++++------ 10 files changed, 109 insertions(+), 176 deletions(-) diff --git a/src/components/pages/DaoSettings/components/Metadata/index.tsx b/src/components/pages/DaoSettings/components/Metadata/index.tsx index 3e17271262..6d373b6e9d 100644 --- a/src/components/pages/DaoSettings/components/Metadata/index.tsx +++ b/src/components/pages/DaoSettings/components/Metadata/index.tsx @@ -188,7 +188,7 @@ export default function MetadataContainer() { onChange={handleSnapshotURLChange} value={snapshotURL} disabled={!userHasVotingWeight} - placeholder="example.eth" + placeholder="httpsexample.eth" testId="daoSettings.snapshotUrl" gridContainerProps={{ display: 'inline-flex', diff --git a/src/components/pages/DaoSettings/components/Signers/hooks/useAddSigner.ts b/src/components/pages/DaoSettings/components/Signers/hooks/useAddSigner.ts index 5c22f89455..824eb92b0e 100644 --- a/src/components/pages/DaoSettings/components/Signers/hooks/useAddSigner.ts +++ b/src/components/pages/DaoSettings/components/Signers/hooks/useAddSigner.ts @@ -8,9 +8,7 @@ import { ProposalExecuteData } from '../../../../../../types'; const useAddSigner = () => { const { submitProposal } = useSubmitProposal(); const { t } = useTranslation(['modals']); - const { - baseContracts: { safeSingletonContract }, - } = useFractal(); + const { baseContracts } = useFractal(); const addSigner = useCallback( async ({ newSigner, @@ -25,6 +23,10 @@ const useAddSigner = () => { daoAddress: string | null; close: () => void; }) => { + if (!baseContracts) { + return; + } + const { safeSingletonContract } = baseContracts; const description = 'Add Signer'; const calldatas = [ @@ -54,7 +56,7 @@ const useAddSigner = () => { failedToastMessage: t('addSignerFailureToastMessage'), }); }, - [safeSingletonContract.asSigner.interface, submitProposal, t] + [baseContracts, submitProposal, t] ); return addSigner; diff --git a/src/components/pages/DaoSettings/components/Signers/hooks/useRemoveSigner.ts b/src/components/pages/DaoSettings/components/Signers/hooks/useRemoveSigner.ts index da247c3edd..16ee429dbd 100644 --- a/src/components/pages/DaoSettings/components/Signers/hooks/useRemoveSigner.ts +++ b/src/components/pages/DaoSettings/components/Signers/hooks/useRemoveSigner.ts @@ -20,11 +20,13 @@ const useRemoveSigner = ({ }) => { const { submitProposal } = useSubmitProposal(); const { t } = useTranslation(['modals']); - const { - baseContracts: { safeSingletonContract }, - } = useFractal(); + const { baseContracts } = useFractal(); const removeSigner = useCallback(async () => { + if (!baseContracts) { + return; + } + const { safeSingletonContract } = baseContracts; const description = 'Remove Signers'; const calldatas = [ @@ -53,16 +55,7 @@ const useRemoveSigner = ({ successToastMessage: t('removeSignerSuccessToastMessage'), failedToastMessage: t('removeSignerFailureToastMessage'), }); - }, [ - safeSingletonContract.asProvider.interface, - prevSigner, - signerToRemove, - threshold, - daoAddress, - submitProposal, - nonce, - t, - ]); + }, [baseContracts, prevSigner, signerToRemove, threshold, daoAddress, submitProposal, nonce, t]); return removeSigner; }; diff --git a/src/hooks/DAO/loaders/governance/useERC20Claim.ts b/src/hooks/DAO/loaders/governance/useERC20Claim.ts index a9fc4e0424..193bdd9b3f 100644 --- a/src/hooks/DAO/loaders/governance/useERC20Claim.ts +++ b/src/hooks/DAO/loaders/governance/useERC20Claim.ts @@ -9,13 +9,13 @@ import { FractalGovernanceAction } from '../../../../providers/App/governance/ac export function useERC20Claim() { const { governanceContracts: { tokenContract }, - baseContracts: { claimingMasterCopyContract }, + baseContracts, action, } = useFractal(); const loadTokenClaimContract = useCallback(async () => { - if (!claimingMasterCopyContract || !tokenContract) return; - + if (!baseContracts || !tokenContract) return; + const { claimingMasterCopyContract } = baseContracts; const approvalFilter = tokenContract.asProvider.filters.Approval(); const approvals = await tokenContract.asProvider.queryFilter(approvalFilter); if (!approvals.length) return; @@ -38,7 +38,7 @@ export function useERC20Claim() { type: FractalGovernanceAction.SET_CLAIMING_CONTRACT, payload: possibleTokenClaimContract, }); - }, [claimingMasterCopyContract, tokenContract, action]); + }, [baseContracts, tokenContract, action]); useEffect(() => { loadTokenClaimContract(); diff --git a/src/hooks/DAO/loaders/useFractalModules.ts b/src/hooks/DAO/loaders/useFractalModules.ts index a09d54c46b..96df63e86a 100644 --- a/src/hooks/DAO/loaders/useFractalModules.ts +++ b/src/hooks/DAO/loaders/useFractalModules.ts @@ -4,9 +4,7 @@ import { FractalModuleData, FractalModuleType } from '../../../types'; import { useMasterCopy } from '../../utils/useMasterCopy'; export const useFractalModules = () => { - const { - baseContracts: { fractalAzoriusMasterCopyContract, fractalModuleMasterCopyContract }, - } = useFractal(); + const { baseContracts } = useFractal(); const { getZodiacModuleProxyMasterCopyData } = useMasterCopy(); const lookupModules = useCallback( async (_moduleAddresses: string[]) => { @@ -16,15 +14,17 @@ export const useFractalModules = () => { let safeModule: FractalModuleData; - if (masterCopyData.isAzorius) { + if (masterCopyData.isAzorius && baseContracts) { safeModule = { - moduleContract: fractalAzoriusMasterCopyContract.asSigner.attach(moduleAddress), + moduleContract: + baseContracts.fractalAzoriusMasterCopyContract.asSigner.attach(moduleAddress), moduleAddress: moduleAddress, moduleType: FractalModuleType.AZORIUS, }; - } else if (masterCopyData.isFractalModule) { + } else if (masterCopyData.isFractalModule && baseContracts) { safeModule = { - moduleContract: fractalModuleMasterCopyContract.asSigner.attach(moduleAddress), + moduleContract: + baseContracts.fractalModuleMasterCopyContract.asSigner.attach(moduleAddress), moduleAddress: moduleAddress, moduleType: FractalModuleType.FRACTAL, }; @@ -41,11 +41,7 @@ export const useFractalModules = () => { ); return modules; }, - [ - fractalAzoriusMasterCopyContract, - fractalModuleMasterCopyContract, - getZodiacModuleProxyMasterCopyData, - ] + [baseContracts, getZodiacModuleProxyMasterCopyData] ); return lookupModules; }; diff --git a/src/hooks/DAO/useBuildDAOTx.ts b/src/hooks/DAO/useBuildDAOTx.ts index 41c74bb4af..e2ce2e9ce3 100644 --- a/src/hooks/DAO/useBuildDAOTx.ts +++ b/src/hooks/DAO/useBuildDAOTx.ts @@ -22,26 +22,7 @@ const useBuildDAOTx = () => { } = useNetworkConfig(); const { - baseContracts: { - multiSendContract, - safeFactoryContract, - safeSingletonContract, - linearVotingMasterCopyContract, - linearVotingERC721MasterCopyContract, - fractalAzoriusMasterCopyContract, - zodiacModuleProxyFactoryContract, - fractalRegistryContract, - fractalModuleMasterCopyContract, - multisigFreezeGuardMasterCopyContract, - azoriusFreezeGuardMasterCopyContract, - freezeMultisigVotingMasterCopyContract, - freezeERC20VotingMasterCopyContract, - freezeERC721VotingMasterCopyContract, - votesTokenMasterCopyContract, - claimingMasterCopyContract, - votesERC20WrapperMasterCopyContract, - keyValuePairsContract, - }, + baseContracts, readOnly: { user, dao }, governance, governanceContracts: { erc721LinearVotingContract }, @@ -55,24 +36,29 @@ const useBuildDAOTx = () => { ) => { let azoriusContracts; - if ( - !user.address || - !signerOrProvider || - !multiSendContract || - !fractalRegistryContract || - !zodiacModuleProxyFactoryContract || - !fractalModuleMasterCopyContract || - !multisigFreezeGuardMasterCopyContract || - !freezeMultisigVotingMasterCopyContract || - !freezeERC20VotingMasterCopyContract || - !safeFactoryContract || - !safeSingletonContract || - !claimingMasterCopyContract || - !votesERC20WrapperMasterCopyContract || - !keyValuePairsContract - ) { + if (!user.address || !signerOrProvider || !baseContracts) { return; } + const { + multiSendContract, + safeFactoryContract, + safeSingletonContract, + linearVotingMasterCopyContract, + linearVotingERC721MasterCopyContract, + fractalAzoriusMasterCopyContract, + zodiacModuleProxyFactoryContract, + fractalRegistryContract, + fractalModuleMasterCopyContract, + multisigFreezeGuardMasterCopyContract, + azoriusFreezeGuardMasterCopyContract, + freezeMultisigVotingMasterCopyContract, + freezeERC20VotingMasterCopyContract, + freezeERC721VotingMasterCopyContract, + votesTokenMasterCopyContract, + claimingMasterCopyContract, + votesERC20WrapperMasterCopyContract, + keyValuePairsContract, + } = baseContracts; if ( createOptions.includes(GovernanceType.AZORIUS_ERC721) && @@ -105,7 +91,7 @@ const useBuildDAOTx = () => { } as AzoriusContracts; } - const baseContracts = { + const buildrerBaseContracts = { fractalModuleMasterCopyContract: fractalModuleMasterCopyContract.asSigner, fractalRegistryContract: fractalRegistryContract.asSigner, safeFactoryContract: safeFactoryContract.asSigner, @@ -121,7 +107,7 @@ const useBuildDAOTx = () => { const txBuilderFactory = new TxBuilderFactory( signerOrProvider, - baseContracts, + buildrerBaseContracts, azoriusContracts, daoData, fallbackHandler, @@ -165,24 +151,7 @@ const useBuildDAOTx = () => { [ user.address, signerOrProvider, - multiSendContract, - fractalRegistryContract, - zodiacModuleProxyFactoryContract, - fractalModuleMasterCopyContract, - multisigFreezeGuardMasterCopyContract, - freezeMultisigVotingMasterCopyContract, - freezeERC20VotingMasterCopyContract, - freezeERC721VotingMasterCopyContract, - safeFactoryContract, - safeSingletonContract, - claimingMasterCopyContract, - votesERC20WrapperMasterCopyContract, - keyValuePairsContract, - fractalAzoriusMasterCopyContract, - linearVotingMasterCopyContract, - linearVotingERC721MasterCopyContract, - votesTokenMasterCopyContract, - azoriusFreezeGuardMasterCopyContract, + baseContracts, erc721LinearVotingContract, dao, governance, diff --git a/src/hooks/DAO/useCreateSubDAOProposal.ts b/src/hooks/DAO/useCreateSubDAOProposal.ts index e0ea6c89ad..36c2d14d5a 100644 --- a/src/hooks/DAO/useCreateSubDAOProposal.ts +++ b/src/hooks/DAO/useCreateSubDAOProposal.ts @@ -8,9 +8,7 @@ import useSubmitProposal from './proposal/useSubmitProposal'; import useBuildDAOTx from './useBuildDAOTx'; export const useCreateSubDAOProposal = () => { - const { - baseContracts: { multiSendContract, fractalRegistryContract }, - } = useFractal(); + const { baseContracts } = useFractal(); const { t } = useTranslation(['daoCreate', 'proposal', 'proposalMetadata']); const { submitProposal, pendingCreateTx, canUserCreateProposal } = useSubmitProposal(); @@ -27,9 +25,10 @@ export const useCreateSubDAOProposal = () => { successCallback: (daoAddress: string) => void ) => { const propose = async () => { - if (!multiSendContract || !fractalRegistryContract || !daoAddress) { + if (!baseContracts || !daoAddress) { return; } + const { multiSendContract, fractalRegistryContract } = baseContracts; const builtSafeTx = await build(daoData, daoAddress, azoriusGovernance.votesToken?.address); if (!builtSafeTx) { @@ -67,15 +66,7 @@ export const useCreateSubDAOProposal = () => { }; propose(); }, - [ - multiSendContract, - fractalRegistryContract, - build, - daoAddress, - submitProposal, - azoriusGovernance, - t, - ] + [baseContracts, build, daoAddress, submitProposal, azoriusGovernance, t] ); return { proposeDao, pendingCreateTx, canUserCreateProposal } as const; diff --git a/src/hooks/DAO/useDeployAzorius.ts b/src/hooks/DAO/useDeployAzorius.ts index a992afe8c8..9ed222c273 100644 --- a/src/hooks/DAO/useDeployAzorius.ts +++ b/src/hooks/DAO/useDeployAzorius.ts @@ -24,24 +24,7 @@ const useDeployAzorius = () => { } = useNetworkConfig(); const { node: { daoAddress, safe }, - baseContracts: { - multiSendContract, - safeFactoryContract, - safeSingletonContract, - linearVotingMasterCopyContract, - fractalAzoriusMasterCopyContract, - zodiacModuleProxyFactoryContract, - fractalRegistryContract, - fractalModuleMasterCopyContract, - multisigFreezeGuardMasterCopyContract, - azoriusFreezeGuardMasterCopyContract, - freezeMultisigVotingMasterCopyContract, - freezeERC20VotingMasterCopyContract, - votesTokenMasterCopyContract, - claimingMasterCopyContract, - votesERC20WrapperMasterCopyContract, - keyValuePairsContract, - }, + baseContracts, } = useFractal(); const { t } = useTranslation(['transaction', 'proposalMetadata']); @@ -53,9 +36,27 @@ const useDeployAzorius = () => { shouldSetName?: boolean, shouldSetSnapshot?: boolean ) => { - if (!daoAddress || !canUserCreateProposal || !safe) { + if (!daoAddress || !canUserCreateProposal || !safe || !baseContracts) { return; } + const { + multiSendContract, + safeFactoryContract, + safeSingletonContract, + linearVotingMasterCopyContract, + fractalAzoriusMasterCopyContract, + zodiacModuleProxyFactoryContract, + fractalRegistryContract, + fractalModuleMasterCopyContract, + multisigFreezeGuardMasterCopyContract, + azoriusFreezeGuardMasterCopyContract, + freezeMultisigVotingMasterCopyContract, + freezeERC20VotingMasterCopyContract, + votesTokenMasterCopyContract, + claimingMasterCopyContract, + votesERC20WrapperMasterCopyContract, + keyValuePairsContract, + } = baseContracts; let azoriusContracts; azoriusContracts = { @@ -67,7 +68,7 @@ const useDeployAzorius = () => { votesERC20WrapperMasterCopyContract: votesERC20WrapperMasterCopyContract.asProvider, } as AzoriusContracts; - const baseContracts = { + const builderBaseContracts = { fractalModuleMasterCopyContract: fractalModuleMasterCopyContract.asProvider, fractalRegistryContract: fractalRegistryContract.asProvider, safeFactoryContract: safeFactoryContract.asProvider, @@ -82,7 +83,7 @@ const useDeployAzorius = () => { const txBuilderFactory = new TxBuilderFactory( signerOrProvider, - baseContracts, + builderBaseContracts, azoriusContracts, daoData, fallbackHandler, @@ -125,22 +126,7 @@ const useDeployAzorius = () => { }, [ signerOrProvider, - multiSendContract, - fractalRegistryContract, - zodiacModuleProxyFactoryContract, - fractalModuleMasterCopyContract, - multisigFreezeGuardMasterCopyContract, - freezeMultisigVotingMasterCopyContract, - freezeERC20VotingMasterCopyContract, - safeFactoryContract, - safeSingletonContract, - claimingMasterCopyContract, - votesERC20WrapperMasterCopyContract, - keyValuePairsContract, - fractalAzoriusMasterCopyContract, - linearVotingMasterCopyContract, - votesTokenMasterCopyContract, - azoriusFreezeGuardMasterCopyContract, + baseContracts, t, canUserCreateProposal, daoAddress, diff --git a/src/hooks/DAO/useDeployDAO.ts b/src/hooks/DAO/useDeployDAO.ts index 063fbc29f1..5bcff69018 100644 --- a/src/hooks/DAO/useDeployDAO.ts +++ b/src/hooks/DAO/useDeployDAO.ts @@ -6,9 +6,7 @@ import { useTransaction } from '../utils/useTransaction'; import useBuildDAOTx from './useBuildDAOTx'; const useDeployDAO = () => { - const { - baseContracts: { multiSendContract }, - } = useFractal(); + const { baseContracts } = useFractal(); const [contractCallDeploy, contractCallPending] = useTransaction(); const [build] = useBuildDAOTx(); @@ -21,10 +19,12 @@ const useDeployDAO = () => { successCallback: (daoAddress: string) => void ) => { const deploy = async () => { - if (!multiSendContract) { + if (!baseContracts) { return; } + const { multiSendContract } = baseContracts; + const builtSafeTx = await build(daoData); if (!builtSafeTx) { return; @@ -43,7 +43,7 @@ const useDeployDAO = () => { deploy(); }, - [build, contractCallDeploy, multiSendContract, t] + [build, contractCallDeploy, baseContracts, t] ); return [deployDao, contractCallPending] as const; diff --git a/src/hooks/utils/useMasterCopy.ts b/src/hooks/utils/useMasterCopy.ts index 35d0a27ab9..dfcb26f7c3 100644 --- a/src/hooks/utils/useMasterCopy.ts +++ b/src/hooks/utils/useMasterCopy.ts @@ -8,53 +8,43 @@ import { useLocalStorage } from './cache/useLocalStorage'; export function useMasterCopy() { const { getValue, setValue } = useLocalStorage(); - const { - baseContracts: { - zodiacModuleProxyFactoryContract, - linearVotingMasterCopyContract, - linearVotingERC721MasterCopyContract, - multisigFreezeGuardMasterCopyContract, - freezeMultisigVotingMasterCopyContract, - freezeERC721VotingMasterCopyContract, - fractalAzoriusMasterCopyContract, - fractalModuleMasterCopyContract, - }, - } = useFractal(); + const { baseContracts } = useFractal(); const isOzLinearVoting = useCallback( (masterCopyAddress: string | `0x${string}`) => - masterCopyAddress === linearVotingMasterCopyContract.asProvider.address, - [linearVotingMasterCopyContract] + masterCopyAddress === baseContracts?.linearVotingMasterCopyContract.asProvider.address, + [baseContracts] ); const isOzLinearVotingERC721 = useCallback( (masterCopyAddress: string | `0x${string}`) => - masterCopyAddress === linearVotingERC721MasterCopyContract.asProvider.address, - [linearVotingERC721MasterCopyContract] + masterCopyAddress === baseContracts?.linearVotingERC721MasterCopyContract.asProvider.address, + [baseContracts] ); const isMultisigFreezeGuard = useCallback( (masterCopyAddress: string | `0x${string}`) => - masterCopyAddress === multisigFreezeGuardMasterCopyContract.asProvider.address, - [multisigFreezeGuardMasterCopyContract] + masterCopyAddress === baseContracts?.multisigFreezeGuardMasterCopyContract.asProvider.address, + [baseContracts] ); const isMultisigFreezeVoting = useCallback( (masterCopyAddress: string | `0x${string}`) => - masterCopyAddress === freezeMultisigVotingMasterCopyContract.asProvider.address, - [freezeMultisigVotingMasterCopyContract] + masterCopyAddress === + baseContracts?.freezeMultisigVotingMasterCopyContract.asProvider.address, + [baseContracts] ); const isERC721FreezeVoting = useCallback( (masterCopyAddress: string | `0x${string}`) => - masterCopyAddress === freezeERC721VotingMasterCopyContract.asProvider.address, - [freezeERC721VotingMasterCopyContract] + masterCopyAddress === baseContracts?.freezeERC721VotingMasterCopyContract.asProvider.address, + [baseContracts] ); const isAzorius = useCallback( (masterCopyAddress: string | `0x${string}`) => - masterCopyAddress === fractalAzoriusMasterCopyContract.asProvider.address, - [fractalAzoriusMasterCopyContract] + masterCopyAddress === baseContracts?.fractalAzoriusMasterCopyContract.asProvider.address, + [baseContracts] ); const isFractalModule = useCallback( (masterCopyAddress: string | `0x${string}`) => - masterCopyAddress === fractalModuleMasterCopyContract.asProvider.address, - [fractalModuleMasterCopyContract] + masterCopyAddress === baseContracts?.fractalModuleMasterCopyContract.asProvider.address, + [baseContracts] ); const getMasterCopyAddress = useCallback( @@ -80,10 +70,16 @@ export function useMasterCopy() { const getZodiacModuleProxyMasterCopyData = useCallback( async function (proxyAddress: string | `0x${string}`) { - const contract = getEventRPC(zodiacModuleProxyFactoryContract); - const [masterCopyAddress, error] = await getMasterCopyAddress(contract, proxyAddress); - if (error) { - console.error(error); + let masterCopyAddress = ''; + let error; + if (baseContracts) { + const contract = getEventRPC( + baseContracts?.zodiacModuleProxyFactoryContract + ); + [masterCopyAddress, error] = await getMasterCopyAddress(contract, proxyAddress); + if (error) { + console.error(error); + } } return { address: masterCopyAddress, @@ -97,7 +93,6 @@ export function useMasterCopy() { }; }, [ - zodiacModuleProxyFactoryContract, getMasterCopyAddress, isAzorius, isFractalModule, @@ -106,6 +101,7 @@ export function useMasterCopy() { isMultisigFreezeVoting, isOzLinearVoting, isOzLinearVotingERC721, + baseContracts, ] ); From 224828047bd2a45f925a33e60587f6d39bbb931d Mon Sep 17 00:00:00 2001 From: Kirill Klimenko Date: Thu, 7 Mar 2024 20:09:57 +0100 Subject: [PATCH 45/56] Fix token address chain namespacing --- netlify/functions/tokenPrices.mts | 41 ++++++++++++++++---------- src/providers/App/hooks/usePriceAPI.ts | 16 +++++----- 2 files changed, 33 insertions(+), 24 deletions(-) diff --git a/netlify/functions/tokenPrices.mts b/netlify/functions/tokenPrices.mts index bb9ae05894..8182350d08 100644 --- a/netlify/functions/tokenPrices.mts +++ b/netlify/functions/tokenPrices.mts @@ -38,7 +38,8 @@ function sanitizeUserInput(tokensString: string, network: SupportedNetworks) { async function splitData( config: Config, tokens: string[], - responseBodyCallback: (address: string, price: number) => void + responseBodyCallback: (address: string, price: number) => void, + network: SupportedNetworks ) { // Try to get all of the tokens from our store. // Any token address that we don't have a record for will @@ -46,7 +47,7 @@ async function splitData( const possibleCachedTokenPrices = await Promise.all( tokens.map( tokenAddress => - config.store.getWithMetadata(tokenAddress, { + config.store.getWithMetadata(`${network}/${tokenAddress}`, { type: 'json', }) as Promise | null ) @@ -96,10 +97,11 @@ async function storeTokenPrice( config: Config, tokenAddress: string, price: number, - expiration: number + expiration: number, + network: SupportedNetworks ) { await config.store.setJSON( - tokenAddress, + `${network}/${tokenAddress}`, { tokenAddress, price }, { metadata: { expiration: config.now + expiration } } ); @@ -108,7 +110,8 @@ async function storeTokenPrice( async function processTokenPricesResponse( config: Config, tokenPricesResponseJson: Record, - responseBodyCallback: (address: string, price: number) => void + responseBodyCallback: (address: string, price: number) => void, + network: SupportedNetworks ) { const coinGeckoResponseAddresses = Object.keys(tokenPricesResponseJson); for await (const tokenAddress of coinGeckoResponseAddresses) { @@ -119,12 +122,12 @@ async function processTokenPricesResponse( // we should consider it as though CoinGecko doesn't support // this address and not query it again for a while. if (price === undefined) { - await storeTokenPrice(config, sanitizedAddress, 0, config.invalidAddressCacheTime); + await storeTokenPrice(config, sanitizedAddress, 0, config.invalidAddressCacheTime, network); } else { // Otherwise, update the cache with the new price and update // the response object. responseBodyCallback(sanitizedAddress, price); - await storeTokenPrice(config, sanitizedAddress, price, config.validAddressCacheTime); + await storeTokenPrice(config, sanitizedAddress, price, config.validAddressCacheTime, network); } } @@ -134,20 +137,22 @@ async function processTokenPricesResponse( async function processUnknownAddresses( config: Config, needPricesTokenAddresses: string[], - responseAddresses: string[] + responseAddresses: string[], + network: SupportedNetworks ) { const unknownAddresses = needPricesTokenAddresses .filter(x => !responseAddresses.includes(x)) .map(address => address.toLowerCase()); for await (const tokenAddress of unknownAddresses) { - await storeTokenPrice(config, tokenAddress, 0, config.invalidAddressCacheTime); + await storeTokenPrice(config, tokenAddress, 0, config.invalidAddressCacheTime, network); } } async function coinGeckoRequestAndResponse( config: Config, url: string, - responseBodyCallback: (address: string, price: number) => void + responseBodyCallback: (address: string, price: number) => void, + network: SupportedNetworks ) { // Make the request to CoinGecko. // Response is of shape: @@ -167,7 +172,8 @@ async function coinGeckoRequestAndResponse( const responseAddresses = processTokenPricesResponse( config, ethPriceResponseJson, - responseBodyCallback + responseBodyCallback, + network ); return responseAddresses; @@ -200,7 +206,7 @@ export default async function getTokenPrices(request: Request) { return Response.json({ error: 'Requested network is not supported' }, { status: 400 }); } - const store = getStore(`${networkParam}-token-prices`); + const store = getStore('token-prices'); const now = Math.floor(Date.now() / 1000); const invalidAddressCacheTime = parseInt(process.env.TOKEN_PRICE_INVALID_CACHE_MINUTES); const validAddressCacheTime = parseInt(process.env.TOKEN_PRICE_VALID_CACHE_MINUTES); @@ -218,7 +224,8 @@ export default async function getTokenPrices(request: Request) { tokens, (address, price) => { responseBody[address] = price; - } + }, + networkParam ); // If there are no expired token prices, and no token addresses that we @@ -245,7 +252,8 @@ export default async function getTokenPrices(request: Request) { getTokenPricesUrl(needPricesTokenAddresses, networkParam), (address, price) => { responseBody[address] = price; - } + }, + networkParam ); } catch (e) { console.error('Error while querying CoinGecko', e); @@ -257,7 +265,7 @@ export default async function getTokenPrices(request: Request) { // in our store with a long expiration for all addresses that CoinGecko // isn't tracking (likely spam tokens), so as to not continually query // CoinGecko with these addresses - await processUnknownAddresses(config, needPricesTokenAddresses, responseAddresses); + await processUnknownAddresses(config, needPricesTokenAddresses, responseAddresses, networkParam); // Do we need to get the price of our chain's gas token? if (needNativeAsset) { @@ -267,7 +275,8 @@ export default async function getTokenPrices(request: Request) { getNativeAssetPriceUrl(networkParam), (address, price) => { responseBody[address] = price; - } + }, + networkParam ); } catch (e) { console.error('Error while querying CoinGecko', e); diff --git a/src/providers/App/hooks/usePriceAPI.ts b/src/providers/App/hooks/usePriceAPI.ts index 59fb5c4091..be807ed6f2 100644 --- a/src/providers/App/hooks/usePriceAPI.ts +++ b/src/providers/App/hooks/usePriceAPI.ts @@ -30,19 +30,19 @@ export default function usePriceAPI() { ); const pricesResponseBody = await pricesResponse.json(); - if (pricesResponseBody.error) { - // We don't need to log error here as it is supposed to be logged through Netlify function anyway - toast.warning(t('tokenPriceFetchingError')); - if (pricesResponseBody.data) { - // Netlify function might fail due to rate limit of CoinGecko error, but it still will return cached prices. - return pricesResponseBody.data; - } - } else { + if (pricesResponseBody.data) { return pricesResponseBody.data; + } else { + // In theory - we shouldn't get into such situation, when request data is missing, but also we haven't fallen into catch case + // Yet, better safe than sorry + logError('Error fetching prices, response data is missing!', pricesResponseBody); + toast.warning(t('tokenPriceFetchingError')); + return; } } } catch (e) { logError('Error while getting tokens prices', e); + toast.warning(t('tokenPriceFetchingError')); return; } }, From d17ae4e4692a1e829f74686a0c1fb875e9926f29 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Thu, 7 Mar 2024 14:34:57 -0500 Subject: [PATCH 46/56] Move location of a variable in .env --- .env | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.env b/.env index df3aabc62a..a7ce680e70 100644 --- a/.env +++ b/.env @@ -10,9 +10,9 @@ NEXT_PUBLIC_ETHERSCAN_API_KEY="" NEXT_PUBLIC_INFURA_IPFS_API_KEY="" # IPFS pinning (*) NEXT_PUBLIC_INFURA_IPFS_API_SECRET="" +# Shutter Public Key +NEXT_PUBLIC_SHUTTER_EON_PUBKEY=0x0e6493bbb4ee8b19aa9b70367685049ff01dc9382c46aed83f8bc07d2a5ba3e6030bd83b942c1fd3dff5b79bef3b40bf6b666e51e7f0be14ed62daaffad47435265f5c9403b1a801921981f7d8659a9bd91fe92fb1cf9afdb16178a532adfaf51a237103874bb03afafe9cab2118dae1be5f08a0a28bf488c1581e9db4bc23ca # site preview links NEXT_PUBLIC_SITE_URL="https://app.dev.fractalframework.xyz/" # WalletConnect Cloud Project ID NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID="" -# Shutter Public Key -NEXT_PUBLIC_SHUTTER_EON_PUBKEY=0x0e6493bbb4ee8b19aa9b70367685049ff01dc9382c46aed83f8bc07d2a5ba3e6030bd83b942c1fd3dff5b79bef3b40bf6b666e51e7f0be14ed62daaffad47435265f5c9403b1a801921981f7d8659a9bd91fe92fb1cf9afdb16178a532adfaf51a237103874bb03afafe9cab2118dae1be5f08a0a28bf488c1581e9db4bc23ca \ No newline at end of file From 0141cce4b171b4312682775e3cea9e1265857dfa Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Thu, 7 Mar 2024 14:35:16 -0500 Subject: [PATCH 47/56] Update the Sentry imports --- package-lock.json | 568 +++++++++++++++++++++++++++++++++------------- package.json | 3 +- 2 files changed, 417 insertions(+), 154 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2668a872e6..78d64262a6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,8 +24,7 @@ "@safe-global/safe-deployments": "^1.23.0", "@safe-global/safe-ethers-lib": "^1.9.2", "@safe-global/safe-service-client": "^1.5.2", - "@sentry/react": "^7.42.0", - "@sentry/tracing": "^7.42.0", + "@sentry/react": "^7.104.0", "@shutter-network/shutter-crypto": "^1.0.1", "@snapshot-labs/snapshot.js": "^0.7.3", "@ukstv/jazzicon-react": "^1.0.0", @@ -9925,43 +9924,186 @@ ], "peer": true }, - "node_modules/@sentry/browser": { - "version": "7.42.0", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.42.0.tgz", - "integrity": "sha512-xTwfvrQPmYTkAvGivoJFadPLKLDS2N57D/18NA1gcrnF8NwR+I28x3I9ziVUiMCYX+6nJuqBNlMALAEPbb2G5A==", - "dependencies": { - "@sentry/core": "7.42.0", - "@sentry/replay": "7.42.0", - "@sentry/types": "7.42.0", - "@sentry/utils": "7.42.0", - "tslib": "^1.9.3" + "node_modules/@sentry-internal/feedback": { + "version": "7.104.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-7.104.0.tgz", + "integrity": "sha512-+OWqm+X9ZfEQQmxVoZsc9lpzd85pabAT+bEj57StRMTnfdRbD9TippS20nCD9N2Ql5v2/41NfiPONMejGbnOwg==", + "dependencies": { + "@sentry/core": "7.104.0", + "@sentry/types": "7.104.0", + "@sentry/utils": "7.104.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@sentry-internal/feedback/node_modules/@sentry/core": { + "version": "7.104.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.104.0.tgz", + "integrity": "sha512-XPndD6IGQGd07/EntvYVzOWQUo/Gd7L3DwYFeEKeBv6ByWjbBNmVZFRhU0GPPsCHKyW9yMU9OO9diLSS4ijsRg==", + "dependencies": { + "@sentry/types": "7.104.0", + "@sentry/utils": "7.104.0" }, "engines": { "node": ">=8" } }, - "node_modules/@sentry/browser/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + "node_modules/@sentry-internal/feedback/node_modules/@sentry/types": { + "version": "7.104.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.104.0.tgz", + "integrity": "sha512-5bs0xe0+GZR4QBm9Nrqw59o0sv3kBtCosrZDVxBru/dQbrfnB+/kVorvuM0rV3+coNITTKcKDegSZmK1d2uOGQ==", + "engines": { + "node": ">=8" + } }, - "node_modules/@sentry/core": { - "version": "7.42.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.42.0.tgz", - "integrity": "sha512-vNcTyoQz5kUXo5vMGDyc5BJMO0UugPvMfYMQVxqt/BuDNR30LVhY+DL2tW1DFZDvRvyn5At+H7kSTj6GFrANXQ==", + "node_modules/@sentry-internal/feedback/node_modules/@sentry/utils": { + "version": "7.104.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.104.0.tgz", + "integrity": "sha512-ZVg+xZirI9DlOi0NegNVocswdh/8p6QkzlQzDQY2LP2CC6JQdmwi64o0S4rPH4YIHNKQJTpIjduoxeKgd1EO5g==", "dependencies": { - "@sentry/types": "7.42.0", - "@sentry/utils": "7.42.0", - "tslib": "^1.9.3" + "@sentry/types": "7.104.0" }, "engines": { "node": ">=8" } }, - "node_modules/@sentry/core/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + "node_modules/@sentry-internal/replay-canvas": { + "version": "7.104.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-7.104.0.tgz", + "integrity": "sha512-gfdnkFIpxAveKNghkvRCqv+hSiBkxYVoyFZLTvUPuM9Cmvmket1/PpnuWMC2jNtCEewG3gxkPDd4EaT9oa1HZQ==", + "dependencies": { + "@sentry/core": "7.104.0", + "@sentry/replay": "7.104.0", + "@sentry/types": "7.104.0", + "@sentry/utils": "7.104.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@sentry-internal/replay-canvas/node_modules/@sentry/core": { + "version": "7.104.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.104.0.tgz", + "integrity": "sha512-XPndD6IGQGd07/EntvYVzOWQUo/Gd7L3DwYFeEKeBv6ByWjbBNmVZFRhU0GPPsCHKyW9yMU9OO9diLSS4ijsRg==", + "dependencies": { + "@sentry/types": "7.104.0", + "@sentry/utils": "7.104.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry-internal/replay-canvas/node_modules/@sentry/types": { + "version": "7.104.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.104.0.tgz", + "integrity": "sha512-5bs0xe0+GZR4QBm9Nrqw59o0sv3kBtCosrZDVxBru/dQbrfnB+/kVorvuM0rV3+coNITTKcKDegSZmK1d2uOGQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry-internal/replay-canvas/node_modules/@sentry/utils": { + "version": "7.104.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.104.0.tgz", + "integrity": "sha512-ZVg+xZirI9DlOi0NegNVocswdh/8p6QkzlQzDQY2LP2CC6JQdmwi64o0S4rPH4YIHNKQJTpIjduoxeKgd1EO5g==", + "dependencies": { + "@sentry/types": "7.104.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry-internal/tracing": { + "version": "7.104.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.104.0.tgz", + "integrity": "sha512-2z7OijM1J5ndJUiJJElC3iH9qb/Eb8eYm2v8oJhM8WVdc5uCKfrQuYHNgGOnmY2FOCfEUlTmMQGpDw7DJ67L5w==", + "dependencies": { + "@sentry/core": "7.104.0", + "@sentry/types": "7.104.0", + "@sentry/utils": "7.104.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry-internal/tracing/node_modules/@sentry/core": { + "version": "7.104.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.104.0.tgz", + "integrity": "sha512-XPndD6IGQGd07/EntvYVzOWQUo/Gd7L3DwYFeEKeBv6ByWjbBNmVZFRhU0GPPsCHKyW9yMU9OO9diLSS4ijsRg==", + "dependencies": { + "@sentry/types": "7.104.0", + "@sentry/utils": "7.104.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry-internal/tracing/node_modules/@sentry/types": { + "version": "7.104.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.104.0.tgz", + "integrity": "sha512-5bs0xe0+GZR4QBm9Nrqw59o0sv3kBtCosrZDVxBru/dQbrfnB+/kVorvuM0rV3+coNITTKcKDegSZmK1d2uOGQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry-internal/tracing/node_modules/@sentry/utils": { + "version": "7.104.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.104.0.tgz", + "integrity": "sha512-ZVg+xZirI9DlOi0NegNVocswdh/8p6QkzlQzDQY2LP2CC6JQdmwi64o0S4rPH4YIHNKQJTpIjduoxeKgd1EO5g==", + "dependencies": { + "@sentry/types": "7.104.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/browser": { + "version": "7.104.0", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.104.0.tgz", + "integrity": "sha512-HsqO+mr1SowGoP0VbuWrQ2DZT0t5PLomy7LEYa6+4lbOemnY+5YV2NSwBTKbjYysvKipSwaRtPhXrsXsMaz8Bg==", + "dependencies": { + "@sentry-internal/feedback": "7.104.0", + "@sentry-internal/replay-canvas": "7.104.0", + "@sentry-internal/tracing": "7.104.0", + "@sentry/core": "7.104.0", + "@sentry/replay": "7.104.0", + "@sentry/types": "7.104.0", + "@sentry/utils": "7.104.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/browser/node_modules/@sentry/core": { + "version": "7.104.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.104.0.tgz", + "integrity": "sha512-XPndD6IGQGd07/EntvYVzOWQUo/Gd7L3DwYFeEKeBv6ByWjbBNmVZFRhU0GPPsCHKyW9yMU9OO9diLSS4ijsRg==", + "dependencies": { + "@sentry/types": "7.104.0", + "@sentry/utils": "7.104.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/browser/node_modules/@sentry/types": { + "version": "7.104.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.104.0.tgz", + "integrity": "sha512-5bs0xe0+GZR4QBm9Nrqw59o0sv3kBtCosrZDVxBru/dQbrfnB+/kVorvuM0rV3+coNITTKcKDegSZmK1d2uOGQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/browser/node_modules/@sentry/utils": { + "version": "7.104.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.104.0.tgz", + "integrity": "sha512-ZVg+xZirI9DlOi0NegNVocswdh/8p6QkzlQzDQY2LP2CC6JQdmwi64o0S4rPH4YIHNKQJTpIjduoxeKgd1EO5g==", + "dependencies": { + "@sentry/types": "7.104.0" + }, + "engines": { + "node": ">=8" + } }, "node_modules/@sentry/hub": { "version": "5.30.0", @@ -10115,15 +10257,15 @@ "peer": true }, "node_modules/@sentry/react": { - "version": "7.42.0", - "resolved": "https://registry.npmjs.org/@sentry/react/-/react-7.42.0.tgz", - "integrity": "sha512-DOGK+vuSZq5lTiqVU6wVay0AUMjtSPZu25gzLIXntfoqw36CLUswP7ew61+Tas6tpXDdf4lR3uxJRwySiQLopw==", + "version": "7.104.0", + "resolved": "https://registry.npmjs.org/@sentry/react/-/react-7.104.0.tgz", + "integrity": "sha512-JdPzX/rJ4sSr/pVFOKwVrUhr8McCn38w5Q+/wdCabO8fdUkoBe4P05LRCH4Rng0uOk8MeEQ+EvfMVB79DmxIgQ==", "dependencies": { - "@sentry/browser": "7.42.0", - "@sentry/types": "7.42.0", - "@sentry/utils": "7.42.0", - "hoist-non-react-statics": "^3.3.2", - "tslib": "^1.9.3" + "@sentry/browser": "7.104.0", + "@sentry/core": "7.104.0", + "@sentry/types": "7.104.0", + "@sentry/utils": "7.104.0", + "hoist-non-react-statics": "^3.3.2" }, "engines": { "node": ">=8" @@ -10132,68 +10274,82 @@ "react": "15.x || 16.x || 17.x || 18.x" } }, - "node_modules/@sentry/react/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + "node_modules/@sentry/react/node_modules/@sentry/core": { + "version": "7.104.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.104.0.tgz", + "integrity": "sha512-XPndD6IGQGd07/EntvYVzOWQUo/Gd7L3DwYFeEKeBv6ByWjbBNmVZFRhU0GPPsCHKyW9yMU9OO9diLSS4ijsRg==", + "dependencies": { + "@sentry/types": "7.104.0", + "@sentry/utils": "7.104.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/react/node_modules/@sentry/types": { + "version": "7.104.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.104.0.tgz", + "integrity": "sha512-5bs0xe0+GZR4QBm9Nrqw59o0sv3kBtCosrZDVxBru/dQbrfnB+/kVorvuM0rV3+coNITTKcKDegSZmK1d2uOGQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/react/node_modules/@sentry/utils": { + "version": "7.104.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.104.0.tgz", + "integrity": "sha512-ZVg+xZirI9DlOi0NegNVocswdh/8p6QkzlQzDQY2LP2CC6JQdmwi64o0S4rPH4YIHNKQJTpIjduoxeKgd1EO5g==", + "dependencies": { + "@sentry/types": "7.104.0" + }, + "engines": { + "node": ">=8" + } }, "node_modules/@sentry/replay": { - "version": "7.42.0", - "resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.42.0.tgz", - "integrity": "sha512-81HQm20hrW0+0eZ5sZf8KsSekkAlI0/u/M+9ZmOn2bHpGihqAM/O/lrXhTzaRXdpX/9NSwSCGY9k7LIRNMKaEQ==", + "version": "7.104.0", + "resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.104.0.tgz", + "integrity": "sha512-HmWBr/u+SNeULxCxM8lJb2iqhjizeLGJtuKSShPEguEXIUT4kzdoqLh6wn7BAjiKzhmyjrnBcosR5LUqJtGYZQ==", "dependencies": { - "@sentry/core": "7.42.0", - "@sentry/types": "7.42.0", - "@sentry/utils": "7.42.0" + "@sentry-internal/tracing": "7.104.0", + "@sentry/core": "7.104.0", + "@sentry/types": "7.104.0", + "@sentry/utils": "7.104.0" }, "engines": { "node": ">=12" } }, - "node_modules/@sentry/tracing": { - "version": "7.42.0", - "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-7.42.0.tgz", - "integrity": "sha512-0veGu3Ntweuj1pwWrJIFHmVdow4yufCreGIhsNDyrclwOjaTY3uI8iA6N62+hhtxOvqv+xueB98K1DvT5liPCQ==", + "node_modules/@sentry/replay/node_modules/@sentry/core": { + "version": "7.104.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.104.0.tgz", + "integrity": "sha512-XPndD6IGQGd07/EntvYVzOWQUo/Gd7L3DwYFeEKeBv6ByWjbBNmVZFRhU0GPPsCHKyW9yMU9OO9diLSS4ijsRg==", "dependencies": { - "@sentry/core": "7.42.0", - "@sentry/types": "7.42.0", - "@sentry/utils": "7.42.0", - "tslib": "^1.9.3" + "@sentry/types": "7.104.0", + "@sentry/utils": "7.104.0" }, "engines": { "node": ">=8" } }, - "node_modules/@sentry/tracing/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/@sentry/types": { - "version": "7.42.0", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.42.0.tgz", - "integrity": "sha512-Ga0xaBIR/peuXQ88hI9a5TNY3GLNoH8jpsgPaAjAtRHkLsTx0y3AR+PrD7pUysza9QjvG+Qux01DRvLgaNKOHA==", + "node_modules/@sentry/replay/node_modules/@sentry/types": { + "version": "7.104.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.104.0.tgz", + "integrity": "sha512-5bs0xe0+GZR4QBm9Nrqw59o0sv3kBtCosrZDVxBru/dQbrfnB+/kVorvuM0rV3+coNITTKcKDegSZmK1d2uOGQ==", "engines": { "node": ">=8" } }, - "node_modules/@sentry/utils": { - "version": "7.42.0", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.42.0.tgz", - "integrity": "sha512-cBiDZVipC+is+IVgsTQLJyZWUZQxlLZ9GarNT+XZOZ5BFh0acFtz88hO6+S7vGmhcx2aCvsdC9yb2Yf+BphK6Q==", + "node_modules/@sentry/replay/node_modules/@sentry/utils": { + "version": "7.104.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.104.0.tgz", + "integrity": "sha512-ZVg+xZirI9DlOi0NegNVocswdh/8p6QkzlQzDQY2LP2CC6JQdmwi64o0S4rPH4YIHNKQJTpIjduoxeKgd1EO5g==", "dependencies": { - "@sentry/types": "7.42.0", - "tslib": "^1.9.3" + "@sentry/types": "7.104.0" }, "engines": { "node": ">=8" } }, - "node_modules/@sentry/utils/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, "node_modules/@shutter-network/shutter-crypto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@shutter-network/shutter-crypto/-/shutter-crypto-1.0.1.tgz", @@ -35477,39 +35633,144 @@ } } }, - "@sentry/browser": { - "version": "7.42.0", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.42.0.tgz", - "integrity": "sha512-xTwfvrQPmYTkAvGivoJFadPLKLDS2N57D/18NA1gcrnF8NwR+I28x3I9ziVUiMCYX+6nJuqBNlMALAEPbb2G5A==", - "requires": { - "@sentry/core": "7.42.0", - "@sentry/replay": "7.42.0", - "@sentry/types": "7.42.0", - "@sentry/utils": "7.42.0", - "tslib": "^1.9.3" + "@sentry-internal/feedback": { + "version": "7.104.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-7.104.0.tgz", + "integrity": "sha512-+OWqm+X9ZfEQQmxVoZsc9lpzd85pabAT+bEj57StRMTnfdRbD9TippS20nCD9N2Ql5v2/41NfiPONMejGbnOwg==", + "requires": { + "@sentry/core": "7.104.0", + "@sentry/types": "7.104.0", + "@sentry/utils": "7.104.0" }, "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + "@sentry/core": { + "version": "7.104.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.104.0.tgz", + "integrity": "sha512-XPndD6IGQGd07/EntvYVzOWQUo/Gd7L3DwYFeEKeBv6ByWjbBNmVZFRhU0GPPsCHKyW9yMU9OO9diLSS4ijsRg==", + "requires": { + "@sentry/types": "7.104.0", + "@sentry/utils": "7.104.0" + } + }, + "@sentry/types": { + "version": "7.104.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.104.0.tgz", + "integrity": "sha512-5bs0xe0+GZR4QBm9Nrqw59o0sv3kBtCosrZDVxBru/dQbrfnB+/kVorvuM0rV3+coNITTKcKDegSZmK1d2uOGQ==" + }, + "@sentry/utils": { + "version": "7.104.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.104.0.tgz", + "integrity": "sha512-ZVg+xZirI9DlOi0NegNVocswdh/8p6QkzlQzDQY2LP2CC6JQdmwi64o0S4rPH4YIHNKQJTpIjduoxeKgd1EO5g==", + "requires": { + "@sentry/types": "7.104.0" + } } } }, - "@sentry/core": { - "version": "7.42.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.42.0.tgz", - "integrity": "sha512-vNcTyoQz5kUXo5vMGDyc5BJMO0UugPvMfYMQVxqt/BuDNR30LVhY+DL2tW1DFZDvRvyn5At+H7kSTj6GFrANXQ==", + "@sentry-internal/replay-canvas": { + "version": "7.104.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-7.104.0.tgz", + "integrity": "sha512-gfdnkFIpxAveKNghkvRCqv+hSiBkxYVoyFZLTvUPuM9Cmvmket1/PpnuWMC2jNtCEewG3gxkPDd4EaT9oa1HZQ==", "requires": { - "@sentry/types": "7.42.0", - "@sentry/utils": "7.42.0", - "tslib": "^1.9.3" + "@sentry/core": "7.104.0", + "@sentry/replay": "7.104.0", + "@sentry/types": "7.104.0", + "@sentry/utils": "7.104.0" }, "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + "@sentry/core": { + "version": "7.104.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.104.0.tgz", + "integrity": "sha512-XPndD6IGQGd07/EntvYVzOWQUo/Gd7L3DwYFeEKeBv6ByWjbBNmVZFRhU0GPPsCHKyW9yMU9OO9diLSS4ijsRg==", + "requires": { + "@sentry/types": "7.104.0", + "@sentry/utils": "7.104.0" + } + }, + "@sentry/types": { + "version": "7.104.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.104.0.tgz", + "integrity": "sha512-5bs0xe0+GZR4QBm9Nrqw59o0sv3kBtCosrZDVxBru/dQbrfnB+/kVorvuM0rV3+coNITTKcKDegSZmK1d2uOGQ==" + }, + "@sentry/utils": { + "version": "7.104.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.104.0.tgz", + "integrity": "sha512-ZVg+xZirI9DlOi0NegNVocswdh/8p6QkzlQzDQY2LP2CC6JQdmwi64o0S4rPH4YIHNKQJTpIjduoxeKgd1EO5g==", + "requires": { + "@sentry/types": "7.104.0" + } + } + } + }, + "@sentry-internal/tracing": { + "version": "7.104.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.104.0.tgz", + "integrity": "sha512-2z7OijM1J5ndJUiJJElC3iH9qb/Eb8eYm2v8oJhM8WVdc5uCKfrQuYHNgGOnmY2FOCfEUlTmMQGpDw7DJ67L5w==", + "requires": { + "@sentry/core": "7.104.0", + "@sentry/types": "7.104.0", + "@sentry/utils": "7.104.0" + }, + "dependencies": { + "@sentry/core": { + "version": "7.104.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.104.0.tgz", + "integrity": "sha512-XPndD6IGQGd07/EntvYVzOWQUo/Gd7L3DwYFeEKeBv6ByWjbBNmVZFRhU0GPPsCHKyW9yMU9OO9diLSS4ijsRg==", + "requires": { + "@sentry/types": "7.104.0", + "@sentry/utils": "7.104.0" + } + }, + "@sentry/types": { + "version": "7.104.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.104.0.tgz", + "integrity": "sha512-5bs0xe0+GZR4QBm9Nrqw59o0sv3kBtCosrZDVxBru/dQbrfnB+/kVorvuM0rV3+coNITTKcKDegSZmK1d2uOGQ==" + }, + "@sentry/utils": { + "version": "7.104.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.104.0.tgz", + "integrity": "sha512-ZVg+xZirI9DlOi0NegNVocswdh/8p6QkzlQzDQY2LP2CC6JQdmwi64o0S4rPH4YIHNKQJTpIjduoxeKgd1EO5g==", + "requires": { + "@sentry/types": "7.104.0" + } + } + } + }, + "@sentry/browser": { + "version": "7.104.0", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.104.0.tgz", + "integrity": "sha512-HsqO+mr1SowGoP0VbuWrQ2DZT0t5PLomy7LEYa6+4lbOemnY+5YV2NSwBTKbjYysvKipSwaRtPhXrsXsMaz8Bg==", + "requires": { + "@sentry-internal/feedback": "7.104.0", + "@sentry-internal/replay-canvas": "7.104.0", + "@sentry-internal/tracing": "7.104.0", + "@sentry/core": "7.104.0", + "@sentry/replay": "7.104.0", + "@sentry/types": "7.104.0", + "@sentry/utils": "7.104.0" + }, + "dependencies": { + "@sentry/core": { + "version": "7.104.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.104.0.tgz", + "integrity": "sha512-XPndD6IGQGd07/EntvYVzOWQUo/Gd7L3DwYFeEKeBv6ByWjbBNmVZFRhU0GPPsCHKyW9yMU9OO9diLSS4ijsRg==", + "requires": { + "@sentry/types": "7.104.0", + "@sentry/utils": "7.104.0" + } + }, + "@sentry/types": { + "version": "7.104.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.104.0.tgz", + "integrity": "sha512-5bs0xe0+GZR4QBm9Nrqw59o0sv3kBtCosrZDVxBru/dQbrfnB+/kVorvuM0rV3+coNITTKcKDegSZmK1d2uOGQ==" + }, + "@sentry/utils": { + "version": "7.104.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.104.0.tgz", + "integrity": "sha512-ZVg+xZirI9DlOi0NegNVocswdh/8p6QkzlQzDQY2LP2CC6JQdmwi64o0S4rPH4YIHNKQJTpIjduoxeKgd1EO5g==", + "requires": { + "@sentry/types": "7.104.0" + } } } }, @@ -35641,70 +35902,73 @@ } }, "@sentry/react": { - "version": "7.42.0", - "resolved": "https://registry.npmjs.org/@sentry/react/-/react-7.42.0.tgz", - "integrity": "sha512-DOGK+vuSZq5lTiqVU6wVay0AUMjtSPZu25gzLIXntfoqw36CLUswP7ew61+Tas6tpXDdf4lR3uxJRwySiQLopw==", + "version": "7.104.0", + "resolved": "https://registry.npmjs.org/@sentry/react/-/react-7.104.0.tgz", + "integrity": "sha512-JdPzX/rJ4sSr/pVFOKwVrUhr8McCn38w5Q+/wdCabO8fdUkoBe4P05LRCH4Rng0uOk8MeEQ+EvfMVB79DmxIgQ==", "requires": { - "@sentry/browser": "7.42.0", - "@sentry/types": "7.42.0", - "@sentry/utils": "7.42.0", - "hoist-non-react-statics": "^3.3.2", - "tslib": "^1.9.3" + "@sentry/browser": "7.104.0", + "@sentry/core": "7.104.0", + "@sentry/types": "7.104.0", + "@sentry/utils": "7.104.0", + "hoist-non-react-statics": "^3.3.2" }, "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + "@sentry/core": { + "version": "7.104.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.104.0.tgz", + "integrity": "sha512-XPndD6IGQGd07/EntvYVzOWQUo/Gd7L3DwYFeEKeBv6ByWjbBNmVZFRhU0GPPsCHKyW9yMU9OO9diLSS4ijsRg==", + "requires": { + "@sentry/types": "7.104.0", + "@sentry/utils": "7.104.0" + } + }, + "@sentry/types": { + "version": "7.104.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.104.0.tgz", + "integrity": "sha512-5bs0xe0+GZR4QBm9Nrqw59o0sv3kBtCosrZDVxBru/dQbrfnB+/kVorvuM0rV3+coNITTKcKDegSZmK1d2uOGQ==" + }, + "@sentry/utils": { + "version": "7.104.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.104.0.tgz", + "integrity": "sha512-ZVg+xZirI9DlOi0NegNVocswdh/8p6QkzlQzDQY2LP2CC6JQdmwi64o0S4rPH4YIHNKQJTpIjduoxeKgd1EO5g==", + "requires": { + "@sentry/types": "7.104.0" + } } } }, "@sentry/replay": { - "version": "7.42.0", - "resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.42.0.tgz", - "integrity": "sha512-81HQm20hrW0+0eZ5sZf8KsSekkAlI0/u/M+9ZmOn2bHpGihqAM/O/lrXhTzaRXdpX/9NSwSCGY9k7LIRNMKaEQ==", + "version": "7.104.0", + "resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.104.0.tgz", + "integrity": "sha512-HmWBr/u+SNeULxCxM8lJb2iqhjizeLGJtuKSShPEguEXIUT4kzdoqLh6wn7BAjiKzhmyjrnBcosR5LUqJtGYZQ==", "requires": { - "@sentry/core": "7.42.0", - "@sentry/types": "7.42.0", - "@sentry/utils": "7.42.0" - } - }, - "@sentry/tracing": { - "version": "7.42.0", - "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-7.42.0.tgz", - "integrity": "sha512-0veGu3Ntweuj1pwWrJIFHmVdow4yufCreGIhsNDyrclwOjaTY3uI8iA6N62+hhtxOvqv+xueB98K1DvT5liPCQ==", - "requires": { - "@sentry/core": "7.42.0", - "@sentry/types": "7.42.0", - "@sentry/utils": "7.42.0", - "tslib": "^1.9.3" - }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - } - } - }, - "@sentry/types": { - "version": "7.42.0", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.42.0.tgz", - "integrity": "sha512-Ga0xaBIR/peuXQ88hI9a5TNY3GLNoH8jpsgPaAjAtRHkLsTx0y3AR+PrD7pUysza9QjvG+Qux01DRvLgaNKOHA==" - }, - "@sentry/utils": { - "version": "7.42.0", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.42.0.tgz", - "integrity": "sha512-cBiDZVipC+is+IVgsTQLJyZWUZQxlLZ9GarNT+XZOZ5BFh0acFtz88hO6+S7vGmhcx2aCvsdC9yb2Yf+BphK6Q==", - "requires": { - "@sentry/types": "7.42.0", - "tslib": "^1.9.3" + "@sentry-internal/tracing": "7.104.0", + "@sentry/core": "7.104.0", + "@sentry/types": "7.104.0", + "@sentry/utils": "7.104.0" }, "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + "@sentry/core": { + "version": "7.104.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.104.0.tgz", + "integrity": "sha512-XPndD6IGQGd07/EntvYVzOWQUo/Gd7L3DwYFeEKeBv6ByWjbBNmVZFRhU0GPPsCHKyW9yMU9OO9diLSS4ijsRg==", + "requires": { + "@sentry/types": "7.104.0", + "@sentry/utils": "7.104.0" + } + }, + "@sentry/types": { + "version": "7.104.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.104.0.tgz", + "integrity": "sha512-5bs0xe0+GZR4QBm9Nrqw59o0sv3kBtCosrZDVxBru/dQbrfnB+/kVorvuM0rV3+coNITTKcKDegSZmK1d2uOGQ==" + }, + "@sentry/utils": { + "version": "7.104.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.104.0.tgz", + "integrity": "sha512-ZVg+xZirI9DlOi0NegNVocswdh/8p6QkzlQzDQY2LP2CC6JQdmwi64o0S4rPH4YIHNKQJTpIjduoxeKgd1EO5g==", + "requires": { + "@sentry/types": "7.104.0" + } } } }, diff --git a/package.json b/package.json index 4b8e2006f9..bd85fe824d 100644 --- a/package.json +++ b/package.json @@ -19,8 +19,7 @@ "@safe-global/safe-deployments": "^1.23.0", "@safe-global/safe-ethers-lib": "^1.9.2", "@safe-global/safe-service-client": "^1.5.2", - "@sentry/react": "^7.42.0", - "@sentry/tracing": "^7.42.0", + "@sentry/react": "^7.104.0", "@shutter-network/shutter-crypto": "^1.0.1", "@snapshot-labs/snapshot.js": "^0.7.3", "@ukstv/jazzicon-react": "^1.0.0", From 621771c905edb34c75c358dfe8a5b40698f4ac34 Mon Sep 17 00:00:00 2001 From: Kirill Klimenko Date: Thu, 7 Mar 2024 20:47:35 +0100 Subject: [PATCH 48/56] Swap expiration with time of fetching --- .env | 6 ++--- netlify/functions/tokenPrices.mts | 38 ++++++++++--------------------- 2 files changed, 14 insertions(+), 30 deletions(-) diff --git a/.env b/.env index c36bb2b353..fe1f56c9fe 100644 --- a/.env +++ b/.env @@ -16,9 +16,7 @@ NEXT_PUBLIC_SITE_URL="https://app.dev.fractalframework.xyz/" NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID="" # CoinGecko API key COINGECKO_API_KEY="" -# Minutes to cache prices for valid token addresses -TOKEN_PRICE_VALID_CACHE_MINUTES="" -# Minutes to cache "0" for invalid token addresses -TOKEN_PRICE_INVALID_CACHE_MINUTES="" +# Minutes to cache prices for token addresses +TOKEN_PRICE_CACHE_INTERVAL_MINUTES="" # Shutter Public Key NEXT_PUBLIC_SHUTTER_EON_PUBKEY=0x0e6493bbb4ee8b19aa9b70367685049ff01dc9382c46aed83f8bc07d2a5ba3e6030bd83b942c1fd3dff5b79bef3b40bf6b666e51e7f0be14ed62daaffad47435265f5c9403b1a801921981f7d8659a9bd91fe92fb1cf9afdb16178a532adfaf51a237103874bb03afafe9cab2118dae1be5f08a0a28bf488c1581e9db4bc23ca \ No newline at end of file diff --git a/netlify/functions/tokenPrices.mts b/netlify/functions/tokenPrices.mts index 8182350d08..5b69a288f4 100644 --- a/netlify/functions/tokenPrices.mts +++ b/netlify/functions/tokenPrices.mts @@ -11,15 +11,14 @@ type TokenPriceWithMetadata = { price: number; }; metadata: { - expiration: number; + fetched: number; }; }; type Config = { store: Store; now: number; - invalidAddressCacheTime: number; - validAddressCacheTime: number; + cacheTime: number; }; const SUPPORTED_NETWORKS = ['ethereum'] as const; @@ -62,7 +61,7 @@ async function splitData( // Let's pull out all of the expired addresses from our cache. const expiredCachedTokenAddresses = cachedTokenPrices - .filter(tokenPrice => tokenPrice.metadata.expiration < config.now) + .filter(tokenPrice => tokenPrice.metadata.fetched > config.now + config.cacheTime) .map(tokenPrice => tokenPrice.data.tokenAddress); // Finally let's get a list of all of the token addresses that @@ -97,13 +96,12 @@ async function storeTokenPrice( config: Config, tokenAddress: string, price: number, - expiration: number, network: SupportedNetworks ) { await config.store.setJSON( `${network}/${tokenAddress}`, { tokenAddress, price }, - { metadata: { expiration: config.now + expiration } } + { metadata: { fetched: config.now } } ); } @@ -115,20 +113,14 @@ async function processTokenPricesResponse( ) { const coinGeckoResponseAddresses = Object.keys(tokenPricesResponseJson); for await (const tokenAddress of coinGeckoResponseAddresses) { - const price = tokenPricesResponseJson[tokenAddress].usd; + const price = tokenPricesResponseJson[tokenAddress].usd || 0; const sanitizedAddress = tokenAddress.toLowerCase(); // Sometimes no USD price is returned. If this happens, // we should consider it as though CoinGecko doesn't support // this address and not query it again for a while. - if (price === undefined) { - await storeTokenPrice(config, sanitizedAddress, 0, config.invalidAddressCacheTime, network); - } else { - // Otherwise, update the cache with the new price and update - // the response object. - responseBodyCallback(sanitizedAddress, price); - await storeTokenPrice(config, sanitizedAddress, price, config.validAddressCacheTime, network); - } + responseBodyCallback(sanitizedAddress, price); + await storeTokenPrice(config, sanitizedAddress, price, network); } return coinGeckoResponseAddresses; @@ -144,7 +136,7 @@ async function processUnknownAddresses( .filter(x => !responseAddresses.includes(x)) .map(address => address.toLowerCase()); for await (const tokenAddress of unknownAddresses) { - await storeTokenPrice(config, tokenAddress, 0, config.invalidAddressCacheTime, network); + await storeTokenPrice(config, tokenAddress, 0, network); } } @@ -185,13 +177,8 @@ export default async function getTokenPrices(request: Request) { return Response.json({ error: 'Error while fetching prices' }, { status: 503 }); } - if (!process.env.TOKEN_PRICE_INVALID_CACHE_MINUTES) { - console.error('TOKEN_PRICE_INVALID_CACHE_MINUTES is not set'); - return Response.json({ error: 'Error while fetching prices' }, { status: 503 }); - } - - if (!process.env.TOKEN_PRICE_VALID_CACHE_MINUTES) { - console.error('TOKEN_PRICE_VALID_CACHE_MINUTES is not set'); + if (!process.env.TOKEN_PRICE_CACHE_INTERVAL_MINUTES) { + console.error('TOKEN_PRICE_CACHE_INTERVAL_MINUTES is not set'); return Response.json({ error: 'Error while fetching prices' }, { status: 503 }); } @@ -208,9 +195,8 @@ export default async function getTokenPrices(request: Request) { const store = getStore('token-prices'); const now = Math.floor(Date.now() / 1000); - const invalidAddressCacheTime = parseInt(process.env.TOKEN_PRICE_INVALID_CACHE_MINUTES); - const validAddressCacheTime = parseInt(process.env.TOKEN_PRICE_VALID_CACHE_MINUTES); - const config = { store, now, invalidAddressCacheTime, validAddressCacheTime }; + const cacheTime = parseInt(process.env.TOKEN_PRICE_CACHE_INTERVAL_MINUTES); + const config = { store, now, cacheTime }; const { tokens, needNativeAsset } = sanitizeUserInput(tokensStringParam, networkParam); From 96420f866036c14e1ab06c90493073541f68a33a Mon Sep 17 00:00:00 2001 From: Kirill Klimenko Date: Thu, 7 Mar 2024 20:51:32 +0100 Subject: [PATCH 49/56] Cleanup comments --- netlify/functions/tokenPrices.mts | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/netlify/functions/tokenPrices.mts b/netlify/functions/tokenPrices.mts index 5b69a288f4..e9dfb864d1 100644 --- a/netlify/functions/tokenPrices.mts +++ b/netlify/functions/tokenPrices.mts @@ -113,12 +113,12 @@ async function processTokenPricesResponse( ) { const coinGeckoResponseAddresses = Object.keys(tokenPricesResponseJson); for await (const tokenAddress of coinGeckoResponseAddresses) { + // Sometimes no USD price is returned. If this happens, + // we should consider it as though CoinGecko doesn't support + // this address and fallback to 0. const price = tokenPricesResponseJson[tokenAddress].usd || 0; const sanitizedAddress = tokenAddress.toLowerCase(); - // Sometimes no USD price is returned. If this happens, - // we should consider it as though CoinGecko doesn't support - // this address and not query it again for a while. responseBodyCallback(sanitizedAddress, price); await storeTokenPrice(config, sanitizedAddress, price, network); } @@ -245,12 +245,6 @@ export default async function getTokenPrices(request: Request) { console.error('Error while querying CoinGecko', e); return Response.json({ error: 'Error while fetching prices', data: responseBody }); } - - // In the previous request, CoinGecko will only respond back with prices - // for token addresses that it knows about. We should store a price of 0 - // in our store with a long expiration for all addresses that CoinGecko - // isn't tracking (likely spam tokens), so as to not continually query - // CoinGecko with these addresses await processUnknownAddresses(config, needPricesTokenAddresses, responseAddresses, networkParam); // Do we need to get the price of our chain's gas token? From dfaa28531016eb23137d7545eacf53a366a27f1d Mon Sep 17 00:00:00 2001 From: Kirill Klimenko Date: Thu, 7 Mar 2024 21:00:47 +0100 Subject: [PATCH 50/56] Reverse adding cacheTime --- netlify/functions/tokenPrices.mts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netlify/functions/tokenPrices.mts b/netlify/functions/tokenPrices.mts index e9dfb864d1..629e0a436b 100644 --- a/netlify/functions/tokenPrices.mts +++ b/netlify/functions/tokenPrices.mts @@ -61,7 +61,7 @@ async function splitData( // Let's pull out all of the expired addresses from our cache. const expiredCachedTokenAddresses = cachedTokenPrices - .filter(tokenPrice => tokenPrice.metadata.fetched > config.now + config.cacheTime) + .filter(tokenPrice => tokenPrice.metadata.fetched + config.cacheTime > config.now) .map(tokenPrice => tokenPrice.data.tokenAddress); // Finally let's get a list of all of the token addresses that From e629f271a43a68d8de55010478d33a77f6c946c8 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Thu, 7 Mar 2024 15:01:45 -0500 Subject: [PATCH 51/56] Add env var for Sentry DSN url --- .env | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.env b/.env index a7ce680e70..967dec6088 100644 --- a/.env +++ b/.env @@ -10,6 +10,8 @@ NEXT_PUBLIC_ETHERSCAN_API_KEY="" NEXT_PUBLIC_INFURA_IPFS_API_KEY="" # IPFS pinning (*) NEXT_PUBLIC_INFURA_IPFS_API_SECRET="" +# Sentry DSN URL, not used locally +NEXT_PUBLIC_SENTRY_DSN_URL="" # Shutter Public Key NEXT_PUBLIC_SHUTTER_EON_PUBKEY=0x0e6493bbb4ee8b19aa9b70367685049ff01dc9382c46aed83f8bc07d2a5ba3e6030bd83b942c1fd3dff5b79bef3b40bf6b666e51e7f0be14ed62daaffad47435265f5c9403b1a801921981f7d8659a9bd91fe92fb1cf9afdb16178a532adfaf51a237103874bb03afafe9cab2118dae1be5f08a0a28bf488c1581e9db4bc23ca # site preview links From b5a93f5a4f7972ddb8158004334042027af44927 Mon Sep 17 00:00:00 2001 From: Kirill Klimenko Date: Thu, 7 Mar 2024 21:01:58 +0100 Subject: [PATCH 52/56] Fix cached token addresses filtering --- netlify/functions/tokenPrices.mts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netlify/functions/tokenPrices.mts b/netlify/functions/tokenPrices.mts index 629e0a436b..be18b08370 100644 --- a/netlify/functions/tokenPrices.mts +++ b/netlify/functions/tokenPrices.mts @@ -61,7 +61,7 @@ async function splitData( // Let's pull out all of the expired addresses from our cache. const expiredCachedTokenAddresses = cachedTokenPrices - .filter(tokenPrice => tokenPrice.metadata.fetched + config.cacheTime > config.now) + .filter(tokenPrice => tokenPrice.metadata.fetched + config.cacheTime < config.now) .map(tokenPrice => tokenPrice.data.tokenAddress); // Finally let's get a list of all of the token addresses that From d92b9328febf796c480c93c6da9da44b1aac0e3e Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Thu, 7 Mar 2024 15:02:26 -0500 Subject: [PATCH 53/56] Clean up area that inits Sentry, we want it running on all deployed sites --- src/helpers/errorLogging.ts | 80 ++++++++++--------------------------- 1 file changed, 20 insertions(+), 60 deletions(-) diff --git a/src/helpers/errorLogging.ts b/src/helpers/errorLogging.ts index 1c59058c1f..0ade8689bd 100644 --- a/src/helpers/errorLogging.ts +++ b/src/helpers/errorLogging.ts @@ -1,70 +1,30 @@ import * as Sentry from '@sentry/react'; -import { BrowserTracing } from '@sentry/tracing'; -import { isProd } from '../utils/dev'; /** - * Sentry key which allows pushing error events. Since this only allows submission of new events, - * but not reading them, this is fine to have public. - * - * https://docs.sentry.io/product/sentry-basics/dsn-explainer/ - */ -const SENTRY_DSN_DEV = - 'https://23bdd0d2ce384e51a6b9d8c478767327@o4505173268365312.ingest.sentry.io/4505195485265920'; - -/** - * Initializes error logging. We do not log Sentry data in production. + * Initializes error logging. */ export function initErrorLogging() { - if (!isProd()) { - Sentry.init({ - dsn: SENTRY_DSN_DEV, - integrations: [new BrowserTracing()], - - // Setting tracesSampleRate to 1.0 captures 100% - // of sentry transactions for performance monitoring. - tracesSampleRate: 1.0, - - debug: true, - }); + if ( + process.env.NODE_ENV === 'development' || + process.env.NEXT_PUBLIC_SENTRY_DSN_URL === undefined + ) { + return; } -} -/** - * Sets the wallet address of the currently connected wallet, - * in order to add more context to Sentry events. - * Pass `null` to unset the current wallet. - * @param walletAddress the wallet address of the currently connected wallet - */ -export function setLoggedWallet(walletAddress: string | null) { - if (isProd()) return; - if (!walletAddress) { - Sentry.setUser(null); - } else { - Sentry.setUser({ - id: walletAddress, - }); - } -} + Sentry.init({ + dsn: process.env.NEXT_PUBLIC_SENTRY_DSN_URL, + integrations: [Sentry.browserTracingIntegration(), Sentry.replayIntegration()], -/** - * Adds arbitrary context parameters, which are indexed/searchable - * on Sentry event logging. - * @param key the context key - * @param value the context value - */ -export function setErrorContext(key: string, value: string) { - if (isProd()) return; - Sentry.setTag(key, value); -} + // Set tracesSampleRate to 1.0 to capture 100% + // of transactions for performance monitoring. + // We recommend adjusting this value in production + tracesSampleRate: 1.0, -/** - * Clears any additional context that has been added to currently logging - * Sentry events. - */ -export function clearErrorContext() { - if (isProd()) return; - Sentry.setTags({}); - setLoggedWallet(null); + // Capture Replay for 10% of all sessions, + // plus for 100% of sessions with an error + replaysSessionSampleRate: 0.1, + replaysOnErrorSampleRate: 1.0, + }); } /** @@ -74,7 +34,7 @@ export function clearErrorContext() { */ export function logError(error: any, ...optionalParams: any[]) { console.error(error, optionalParams); - if (isProd()) return; + if (process.env.NODE_ENV === 'development') return; if (typeof error === 'string' || error instanceof String) { Sentry.captureMessage(error + ': ' + optionalParams); } else { @@ -94,7 +54,7 @@ export class FractalErrorBoundary extends Sentry.ErrorBoundary { errorInfo: React.ErrorInfo ) { logError(error, errorInfo); - if (isProd()) return; + if (process.env.NODE_ENV === 'development') return; super.componentDidCatch(error, errorInfo); } } From 2f85687d7c515472e1e0f19edbb096e0e6397c49 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Thu, 7 Mar 2024 15:45:11 -0500 Subject: [PATCH 54/56] Convert minutes to seconds, specify in variable names that we're dealing with seconds --- netlify/functions/tokenPrices.mts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/netlify/functions/tokenPrices.mts b/netlify/functions/tokenPrices.mts index be18b08370..b363896f75 100644 --- a/netlify/functions/tokenPrices.mts +++ b/netlify/functions/tokenPrices.mts @@ -17,8 +17,8 @@ type TokenPriceWithMetadata = { type Config = { store: Store; - now: number; - cacheTime: number; + nowSeconds: number; + cacheTimeSeconds: number; }; const SUPPORTED_NETWORKS = ['ethereum'] as const; @@ -61,7 +61,7 @@ async function splitData( // Let's pull out all of the expired addresses from our cache. const expiredCachedTokenAddresses = cachedTokenPrices - .filter(tokenPrice => tokenPrice.metadata.fetched + config.cacheTime < config.now) + .filter(tokenPrice => tokenPrice.metadata.fetched + config.cacheTimeSeconds < config.nowSeconds) .map(tokenPrice => tokenPrice.data.tokenAddress); // Finally let's get a list of all of the token addresses that @@ -101,7 +101,7 @@ async function storeTokenPrice( await config.store.setJSON( `${network}/${tokenAddress}`, { tokenAddress, price }, - { metadata: { fetched: config.now } } + { metadata: { fetched: config.nowSeconds } } ); } @@ -194,9 +194,9 @@ export default async function getTokenPrices(request: Request) { } const store = getStore('token-prices'); - const now = Math.floor(Date.now() / 1000); - const cacheTime = parseInt(process.env.TOKEN_PRICE_CACHE_INTERVAL_MINUTES); - const config = { store, now, cacheTime }; + const nowSeconds = Math.floor(Date.now() / 1000); + const cacheTimeSeconds = parseInt(process.env.TOKEN_PRICE_CACHE_INTERVAL_MINUTES) * 60; + const config = { store, nowSeconds, cacheTimeSeconds }; const { tokens, needNativeAsset } = sanitizeUserInput(tokensStringParam, networkParam); From daf34df962156507970c301862070cce72bb684f Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Thu, 7 Mar 2024 15:59:09 -0500 Subject: [PATCH 55/56] Prettier updates --- netlify/functions/tokenPrices.mts | 32 +++++++++++++------------- src/providers/App/hooks/usePriceAPI.ts | 4 ++-- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/netlify/functions/tokenPrices.mts b/netlify/functions/tokenPrices.mts index b363896f75..530399cf55 100644 --- a/netlify/functions/tokenPrices.mts +++ b/netlify/functions/tokenPrices.mts @@ -22,7 +22,7 @@ type Config = { }; const SUPPORTED_NETWORKS = ['ethereum'] as const; -type SupportedNetworks = typeof SUPPORTED_NETWORKS[number]; +type SupportedNetworks = (typeof SUPPORTED_NETWORKS)[number]; function sanitizeUserInput(tokensString: string, network: SupportedNetworks) { const rawTokenAddresses = tokensString.split(','); @@ -38,7 +38,7 @@ async function splitData( config: Config, tokens: string[], responseBodyCallback: (address: string, price: number) => void, - network: SupportedNetworks + network: SupportedNetworks, ) { // Try to get all of the tokens from our store. // Any token address that we don't have a record for will @@ -48,15 +48,15 @@ async function splitData( tokenAddress => config.store.getWithMetadata(`${network}/${tokenAddress}`, { type: 'json', - }) as Promise | null - ) + }) as Promise | null, + ), ); // Filter out the null values, leaving us with an array of // TokenPricesWithMetadata. All of these TokenPrices will be either // expired or unexpired. const cachedTokenPrices = possibleCachedTokenPrices.filter( - (possible): possible is TokenPriceWithMetadata => possible !== null + (possible): possible is TokenPriceWithMetadata => possible !== null, ); // Let's pull out all of the expired addresses from our cache. @@ -68,7 +68,7 @@ async function splitData( // we don't have any record of in our cache. const uncachedTokenAddresses = tokens.filter( address => - !cachedTokenPrices.find(cachedTokenPrice => cachedTokenPrice.data.tokenAddress === address) + !cachedTokenPrices.find(cachedTokenPrice => cachedTokenPrice.data.tokenAddress === address), ); // We'll update our response with those cached expired and @@ -82,7 +82,7 @@ async function splitData( function getTokenPricesUrl(tokens: string[], network: SupportedNetworks) { const tokenPricesUrl = `${PUBLIC_DEMO_API_BASE_URL}simple/token_price/${network}/${AUTH_QUERY_PARAM}&vs_currencies=usd&contract_addresses=${tokens.join( - ',' + ',', )}`; return tokenPricesUrl; } @@ -96,12 +96,12 @@ async function storeTokenPrice( config: Config, tokenAddress: string, price: number, - network: SupportedNetworks + network: SupportedNetworks, ) { await config.store.setJSON( `${network}/${tokenAddress}`, { tokenAddress, price }, - { metadata: { fetched: config.nowSeconds } } + { metadata: { fetched: config.nowSeconds } }, ); } @@ -109,7 +109,7 @@ async function processTokenPricesResponse( config: Config, tokenPricesResponseJson: Record, responseBodyCallback: (address: string, price: number) => void, - network: SupportedNetworks + network: SupportedNetworks, ) { const coinGeckoResponseAddresses = Object.keys(tokenPricesResponseJson); for await (const tokenAddress of coinGeckoResponseAddresses) { @@ -130,7 +130,7 @@ async function processUnknownAddresses( config: Config, needPricesTokenAddresses: string[], responseAddresses: string[], - network: SupportedNetworks + network: SupportedNetworks, ) { const unknownAddresses = needPricesTokenAddresses .filter(x => !responseAddresses.includes(x)) @@ -144,7 +144,7 @@ async function coinGeckoRequestAndResponse( config: Config, url: string, responseBodyCallback: (address: string, price: number) => void, - network: SupportedNetworks + network: SupportedNetworks, ) { // Make the request to CoinGecko. // Response is of shape: @@ -165,7 +165,7 @@ async function coinGeckoRequestAndResponse( config, ethPriceResponseJson, responseBodyCallback, - network + network, ); return responseAddresses; @@ -211,7 +211,7 @@ export default async function getTokenPrices(request: Request) { (address, price) => { responseBody[address] = price; }, - networkParam + networkParam, ); // If there are no expired token prices, and no token addresses that we @@ -239,7 +239,7 @@ export default async function getTokenPrices(request: Request) { (address, price) => { responseBody[address] = price; }, - networkParam + networkParam, ); } catch (e) { console.error('Error while querying CoinGecko', e); @@ -256,7 +256,7 @@ export default async function getTokenPrices(request: Request) { (address, price) => { responseBody[address] = price; }, - networkParam + networkParam, ); } catch (e) { console.error('Error while querying CoinGecko', e); diff --git a/src/providers/App/hooks/usePriceAPI.ts b/src/providers/App/hooks/usePriceAPI.ts index be807ed6f2..28fc0f8494 100644 --- a/src/providers/App/hooks/usePriceAPI.ts +++ b/src/providers/App/hooks/usePriceAPI.ts @@ -26,7 +26,7 @@ export default function usePriceAPI() { } if (tokensAddresses.length > 0) { const pricesResponse = await fetch( - `/.netlify/functions/tokenPrices?tokens=${tokensAddresses.join(',')}&network=ethereum` + `/.netlify/functions/tokenPrices?tokens=${tokensAddresses.join(',')}&network=ethereum`, ); const pricesResponseBody = await pricesResponse.json(); @@ -46,7 +46,7 @@ export default function usePriceAPI() { return; } }, - [chainId, t] + [chainId, t], ); return { getTokenPrices }; } From ed46ee252b72015c3555786c574b99ae4e6887a8 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Thu, 7 Mar 2024 16:13:27 -0500 Subject: [PATCH 56/56] Pretty fix --- src/providers/Ethers/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/providers/Ethers/index.tsx b/src/providers/Ethers/index.tsx index 0d4f5cf1f2..586e508bde 100644 --- a/src/providers/Ethers/index.tsx +++ b/src/providers/Ethers/index.tsx @@ -33,8 +33,8 @@ export default function EthersContextProvider({ children }: { children: ReactNod if (transport.type === 'fallback') { return new providers.FallbackProvider( (transport.transports as ReturnType[]).map( - ({ value }) => new providers.JsonRpcProvider(value?.url, network) - ) + ({ value }) => new providers.JsonRpcProvider(value?.url, network), + ), ); } return new providers.JsonRpcProvider(transport.url, network);