diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 000000000..12ac3025e --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +yarn lint-staged --allow-empty diff --git a/.prettierrc b/.prettierrc index a0d1c9a90..007de7c2a 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,5 +1,8 @@ { - "printWidth": 120, + "printWidth": 100, + "arrowParens": "avoid", "trailingComma": "all", - "singleQuote": true + "singleQuote": true, + "semi": false, + "endOfLine": "auto" } diff --git a/apps/compound/config-overrides.js b/apps/compound/config-overrides.js index cfe7814f0..f4ec1f4f0 100644 --- a/apps/compound/config-overrides.js +++ b/apps/compound/config-overrides.js @@ -1,8 +1,8 @@ -const webpack = require('webpack'); +const webpack = require('webpack') module.exports = { webpack: function (config, env) { - const fallback = config.resolve.fallback || {}; + const fallback = config.resolve.fallback || {} // https://github.com/ChainSafe/web3.js#web3-and-create-react-app Object.assign(fallback, { @@ -15,39 +15,39 @@ module.exports = { url: require.resolve('url'), // https://stackoverflow.com/questions/68707553/uncaught-referenceerror-buffer-is-not-defined buffer: require.resolve('buffer'), - }); + }) - config.resolve.fallback = fallback; + config.resolve.fallback = fallback config.plugins = (config.plugins || []).concat([ new webpack.ProvidePlugin({ process: 'process/browser', Buffer: ['buffer', 'Buffer'], }), - ]); + ]) // https://github.com/facebook/create-react-app/issues/11924 - config.ignoreWarnings = [/to parse source map/i]; + config.ignoreWarnings = [/to parse source map/i] - return config; + return config }, jest: function (config) { - return config; + return config }, devServer: function (configFunction) { return function (proxy, allowedHost) { - const config = configFunction(proxy, allowedHost); + const config = configFunction(proxy, allowedHost) config.headers = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET', 'Access-Control-Allow-Headers': 'X-Requested-With, content-type, Authorization', - }; + } - return config; - }; + return config + } }, paths: function (paths) { - return paths; + return paths }, -}; +} diff --git a/apps/compound/src/App.tsx b/apps/compound/src/App.tsx index 33348671e..897e47972 100644 --- a/apps/compound/src/App.tsx +++ b/apps/compound/src/App.tsx @@ -1,8 +1,8 @@ -import { useEffect, useMemo, useState } from 'react'; -import Big from 'big.js'; -import { Button, Select, Text, Loader, Tab, ButtonLink } from '@gnosis.pm/safe-react-components'; -import { useSafeAppsSDK } from '@gnosis.pm/safe-apps-react-sdk'; -import { getTokenList, TokenItem } from './config'; +import { useEffect, useMemo, useState } from 'react' +import Big from 'big.js' +import { Button, Select, Text, Loader, Tab, ButtonLink } from '@gnosis.pm/safe-react-components' +import { useSafeAppsSDK } from '@gnosis.pm/safe-apps-react-sdk' +import { getTokenList, TokenItem } from './config' import { SelectContainer, InfoContainer, @@ -10,110 +10,119 @@ import { StyledTitle, StyledTextField, LoaderContainer, -} from './styles'; -import { getTokenInteractions, parseEvents } from './tokensTransfers'; -import useComp from './hooks/useComp'; -import useWeb3 from './hooks/useWeb3'; -import useCToken from './hooks/useCToken'; -import CompBalance from './components/CompBalance'; -import InfoRow from './components/InfoRow'; -import WidgetWrapper from './components/WidgetWrapper'; -import { BigNumberInput } from 'big-number-input'; - -type Operation = 'lock' | 'withdraw'; - -const WITHDRAW = 'withdraw'; -const SUPPLY = 'supply'; +} from './styles' +import { getTokenInteractions, parseEvents } from './tokensTransfers' +import useComp from './hooks/useComp' +import useWeb3 from './hooks/useWeb3' +import useCToken from './hooks/useCToken' +import CompBalance from './components/CompBalance' +import InfoRow from './components/InfoRow' +import WidgetWrapper from './components/WidgetWrapper' +import { BigNumberInput } from 'big-number-input' + +type Operation = 'lock' | 'withdraw' + +const WITHDRAW = 'withdraw' +const SUPPLY = 'supply' const TABS = [ { id: SUPPLY, label: 'Supply' }, { id: WITHDRAW, label: 'Withdraw' }, -]; +] const CompoundWidget = () => { - const [ethBalance, setEthBalance] = useState('0'); - const [tokenList, setTokenList] = useState>(); - const [selectedToken, setSelectedToken] = useState(); - const { cTokenInstance, tokenInstance } = useCToken(selectedToken); - const [selectedTab, setSelectedTab] = useState(SUPPLY); - const [interestEarn, setInterestEarn] = useState('0'); - const [tokenBalance, setTokenBalance] = useState('0'); - const [underlyingBalance, setUnderlyingBalance] = useState('0'); - const [inputValue, setInputValue] = useState(''); - const [inputError, setInputError] = useState(); - const { web3 } = useWeb3(); - const { sdk: appsSdk, safe: safeInfo, connected } = useSafeAppsSDK(); - const { cTokenSupplyAPY, cDistributionTokenSupplyAPY, claimableComp, claimComp } = useComp(selectedToken); - const isMainnet = useMemo(() => safeInfo.chainId === 1, [safeInfo.chainId]); + const [ethBalance, setEthBalance] = useState('0') + const [tokenList, setTokenList] = useState>() + const [selectedToken, setSelectedToken] = useState() + const { cTokenInstance, tokenInstance } = useCToken(selectedToken) + const [selectedTab, setSelectedTab] = useState(SUPPLY) + const [interestEarn, setInterestEarn] = useState('0') + const [tokenBalance, setTokenBalance] = useState('0') + const [underlyingBalance, setUnderlyingBalance] = useState('0') + const [inputValue, setInputValue] = useState('') + const [inputError, setInputError] = useState() + const { web3 } = useWeb3() + const { sdk: appsSdk, safe: safeInfo, connected } = useSafeAppsSDK() + const { cTokenSupplyAPY, cDistributionTokenSupplyAPY, claimableComp, claimComp } = + useComp(selectedToken) + const isMainnet = useMemo(() => safeInfo.chainId === 1, [safeInfo.chainId]) // fetch eth balance useEffect(() => { const fetchEthBalance = async () => { try { if (safeInfo.safeAddress) { - const balance = await web3?.eth.getBalance(safeInfo.safeAddress); + const balance = await web3?.eth.getBalance(safeInfo.safeAddress) if (balance) { - setEthBalance(balance); + setEthBalance(balance) } } } catch (err) { - console.error(err); + console.error(err) } - }; + } - fetchEthBalance(); - }, [web3, safeInfo.safeAddress]); + fetchEthBalance() + }, [web3, safeInfo.safeAddress]) useEffect(() => { - (async () => { + ;(async () => { if (!safeInfo) { - return; + return } - const tokenListRes = await getTokenList(safeInfo.chainId); - setTokenList(tokenListRes); - setSelectedToken(tokenListRes.find((t) => t.id === 'ETH')); - })(); - }, [safeInfo]); + const tokenListRes = await getTokenList(safeInfo.chainId) + setTokenList(tokenListRes) + setSelectedToken(tokenListRes.find(t => t.id === 'ETH')) + })() + }, [safeInfo]) // on selectedToken useEffect(() => { if (!selectedToken || !web3) { - return; + return } - setInterestEarn('0'); - setTokenBalance('0'); - setUnderlyingBalance('0'); - setInputValue(''); - setInputError(undefined); - }, [selectedToken, web3]); + setInterestEarn('0') + setTokenBalance('0') + setUnderlyingBalance('0') + setInputValue('') + setInputError(undefined) + }, [selectedToken, web3]) useEffect(() => { const getData = async () => { if (!safeInfo.safeAddress || !selectedToken || !cTokenInstance || !tokenInstance) { - return; + return } // wait until cToken is correctly updated - if (selectedToken.cTokenAddr.toLocaleLowerCase() !== cTokenInstance?.['_address'].toLocaleLowerCase()) { - return; + if ( + selectedToken.cTokenAddr.toLocaleLowerCase() !== + cTokenInstance?.['_address'].toLocaleLowerCase() + ) { + return } // wait until token is correctly updated - if (selectedToken.tokenAddr.toLocaleLowerCase() !== tokenInstance?.['_address'].toLocaleLowerCase()) { - return; + if ( + selectedToken.tokenAddr.toLocaleLowerCase() !== + tokenInstance?.['_address'].toLocaleLowerCase() + ) { + return } // get token Balance - let tokenBalance; + let tokenBalance if (selectedToken.id === 'ETH') { - tokenBalance = ethBalance; + tokenBalance = ethBalance } else { - tokenBalance = await tokenInstance.methods.balanceOf(safeInfo.safeAddress).call(); + tokenBalance = await tokenInstance.methods.balanceOf(safeInfo.safeAddress).call() } // get token Locked amount - const underlyingBalance = await cTokenInstance.methods.balanceOfUnderlying(safeInfo.safeAddress).call(); + const underlyingBalance = await cTokenInstance.methods + .balanceOfUnderlying(safeInfo.safeAddress) + .call() // get interest earned const tokenEvents = await getTokenInteractions( @@ -121,53 +130,54 @@ const CompoundWidget = () => { safeInfo.safeAddress, selectedToken.tokenAddr, selectedToken.cTokenAddr, - ); - const { deposits, withdrawals } = parseEvents(safeInfo.safeAddress, tokenEvents); + ) + const { deposits, withdrawals } = parseEvents(safeInfo.safeAddress, tokenEvents) const earned = new Big(underlyingBalance) .div(10 ** selectedToken.decimals) .plus(withdrawals) - .minus(deposits); - const underlyingEarned = earned.lt('0') ? '0' : earned.toFixed(4); + .minus(deposits) + const underlyingEarned = earned.lt('0') ? '0' : earned.toFixed(4) // update all the values in a row to avoid UI flickers - selectedToken.id === 'ETH' ? setInterestEarn('-') : setInterestEarn(underlyingEarned); - setTokenBalance(tokenBalance); - setUnderlyingBalance(underlyingBalance); - }; + selectedToken.id === 'ETH' ? setInterestEarn('-') : setInterestEarn(underlyingEarned) + setTokenBalance(tokenBalance) + setUnderlyingBalance(underlyingBalance) + } - getData(); - }, [safeInfo, selectedToken, cTokenInstance, tokenInstance, ethBalance]); + getData() + }, [safeInfo, selectedToken, cTokenInstance, tokenInstance, ethBalance]) const bNumberToHumanFormat = (value: string) => { if (!selectedToken) { - return ''; + return '' } - return new Big(value).div(10 ** selectedToken.decimals).toFixed(4); - }; + return new Big(value).div(10 ** selectedToken.decimals).toFixed(4) + } const validateInputValue = (operation: Operation): boolean => { - setInputError(undefined); + setInputError(undefined) - const currentValueBN = new Big(inputValue); - const comparisonValueBN = operation === 'lock' ? new Big(tokenBalance) : new Big(underlyingBalance); + const currentValueBN = new Big(inputValue) + const comparisonValueBN = + operation === 'lock' ? new Big(tokenBalance) : new Big(underlyingBalance) if (currentValueBN.gt(comparisonValueBN)) { - const value = operation === 'lock' ? tokenBalance : underlyingBalance; - setInputError(`Max value is ${bNumberToHumanFormat(value)}`); - return false; + const value = operation === 'lock' ? tokenBalance : underlyingBalance + setInputError(`Max value is ${bNumberToHumanFormat(value)}`) + return false } - return true; - }; + return true + } const lock = async () => { if (!selectedToken || !validateInputValue('lock') || !web3) { - return; + return } - const supplyParameter = web3.eth.abi.encodeParameter('uint256', inputValue.toString()); + const supplyParameter = web3.eth.abi.encodeParameter('uint256', inputValue.toString()) - let txs; + let txs if (selectedToken.id === 'ETH') { txs = [ @@ -176,103 +186,106 @@ const CompoundWidget = () => { value: supplyParameter, data: cTokenInstance?.methods.mint().encodeABI(), }, - ]; + ] } else { txs = [ { to: selectedToken.tokenAddr, value: '0', - data: tokenInstance?.methods.approve(selectedToken.cTokenAddr, supplyParameter).encodeABI(), + data: tokenInstance?.methods + .approve(selectedToken.cTokenAddr, supplyParameter) + .encodeABI(), }, { to: selectedToken.cTokenAddr, value: '0', data: cTokenInstance?.methods.mint(supplyParameter).encodeABI(), }, - ]; + ] } try { - await appsSdk.txs.send({ txs }); + await appsSdk.txs.send({ txs }) } catch (error) { - console.error('Lock: Transaction rejected or failed: ', error); + console.error('Lock: Transaction rejected or failed: ', error) } - setInputValue(''); - }; + setInputValue('') + } const withdraw = async () => { if (!selectedToken || !validateInputValue('withdraw') || !web3) { - return; + return } - const supplyParameter = web3.eth.abi.encodeParameter('uint256', inputValue.toString()); + const supplyParameter = web3.eth.abi.encodeParameter('uint256', inputValue.toString()) const txs = [ { to: selectedToken.cTokenAddr, value: '0', data: cTokenInstance?.methods.redeemUnderlying(supplyParameter).encodeABI(), }, - ]; + ] try { - await appsSdk.txs.send({ txs }); + await appsSdk.txs.send({ txs }) } catch (error) { - console.error('Withdraw: Transaction rejected or failed: ', error); + console.error('Withdraw: Transaction rejected or failed: ', error) } - setInputValue(''); - }; + setInputValue('') + } const isWithdrawDisabled = () => { if (!!inputError || !inputValue) { - return true; + return true } - const bigInput = new Big(inputValue); + const bigInput = new Big(inputValue) - return bigInput.eq('0') || bigInput.gt(underlyingBalance); - }; + return bigInput.eq('0') || bigInput.gt(underlyingBalance) + } const isSupplyDisabled = () => { if (!!inputError || !inputValue) { - return true; + return true } - const bigInput = new Big(inputValue); + const bigInput = new Big(inputValue) - return bigInput.eq('0') || bigInput.gt(tokenBalance); - }; + return bigInput.eq('0') || bigInput.gt(tokenBalance) + } const onSelectItem = (id: string) => { if (!tokenList) { - return; + return } - const selectedToken = tokenList.find((t) => t.id === id); + const selectedToken = tokenList.find(t => t.id === id) if (!selectedToken) { - return; + return } - setSelectedToken(selectedToken); - }; + setSelectedToken(selectedToken) + } const onInputChange = (value: string) => { - setInputError(undefined); - setInputValue(value); - }; + setInputError(undefined) + setInputValue(value) + } const handleTabsChange = (selected: string) => { - setSelectedTab(selected); - setInputValue(''); - }; + setSelectedTab(selected) + setInputValue('') + } - const handleMaxInputValue = () => setInputValue(selectedTab === SUPPLY ? tokenBalance : underlyingBalance); + const handleMaxInputValue = () => + setInputValue(selectedTab === SUPPLY ? tokenBalance : underlyingBalance) if (!selectedToken || !connected) { return ( - ); + ) } return ( @@ -280,20 +293,36 @@ const CompoundWidget = () => { Compound - ~ {bNumberToHumanFormat(tokenBalance)} - + - + {isMainnet && ( - + )} @@ -330,7 +359,14 @@ const CompoundWidget = () => { )} {selectedTab === SUPPLY && ( - )} @@ -338,7 +374,7 @@ const CompoundWidget = () => { {isMainnet && } - ); -}; + ) +} -export default CompoundWidget; +export default CompoundWidget diff --git a/apps/compound/src/abis/CErc20.ts b/apps/compound/src/abis/CErc20.ts index 01451206f..a3daa4f53 100644 --- a/apps/compound/src/abis/CErc20.ts +++ b/apps/compound/src/abis/CErc20.ts @@ -614,6 +614,6 @@ const CErc20ABI = [ name: 'Approval', type: 'event', }, -]; +] -export default CErc20ABI; +export default CErc20ABI diff --git a/apps/compound/src/abis/CToken.ts b/apps/compound/src/abis/CToken.ts index 40982691e..b77a6d5b0 100644 --- a/apps/compound/src/abis/CToken.ts +++ b/apps/compound/src/abis/CToken.ts @@ -1121,6 +1121,6 @@ const CTokenABI = [ name: 'Approval', type: 'event', }, -]; +] -export default CTokenABI; +export default CTokenABI diff --git a/apps/compound/src/abis/CWEth.ts b/apps/compound/src/abis/CWEth.ts index ab609e9ba..fbefd6ffd 100644 --- a/apps/compound/src/abis/CWEth.ts +++ b/apps/compound/src/abis/CWEth.ts @@ -593,6 +593,6 @@ const CWethABI = [ name: 'Approval', type: 'event', }, -]; +] -export default CWethABI; +export default CWethABI diff --git a/apps/compound/src/abis/CompoundLens.ts b/apps/compound/src/abis/CompoundLens.ts index 2ae3f610a..f7a1cfd58 100644 --- a/apps/compound/src/abis/CompoundLens.ts +++ b/apps/compound/src/abis/CompoundLens.ts @@ -3,7 +3,11 @@ const CompoundLensABI = [ constant: false, inputs: [ { internalType: 'contract Comp', name: 'comp', type: 'address' }, - { internalType: 'contract ComptrollerLensInterface', name: 'comptroller', type: 'address' }, + { + internalType: 'contract ComptrollerLensInterface', + name: 'comptroller', + type: 'address', + }, { internalType: 'address', name: 'account', type: 'address' }, ], name: 'getCompBalanceMetadataExt', @@ -24,6 +28,6 @@ const CompoundLensABI = [ stateMutability: 'nonpayable', type: 'function', }, -]; +] -export default CompoundLensABI; +export default CompoundLensABI diff --git a/apps/compound/src/abis/Comptroller.ts b/apps/compound/src/abis/Comptroller.ts index 39271a78f..5fe4a6f90 100644 --- a/apps/compound/src/abis/Comptroller.ts +++ b/apps/compound/src/abis/Comptroller.ts @@ -1,5 +1,10 @@ const ComptrollerABI = [ - { inputs: [], payable: false, stateMutability: 'nonpayable', type: 'constructor' }, + { + inputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'constructor', + }, { constant: false, inputs: [ @@ -30,6 +35,6 @@ const ComptrollerABI = [ stateMutability: 'view', type: 'function', }, -]; +] -export default ComptrollerABI; +export default ComptrollerABI diff --git a/apps/compound/src/abis/UniSwapAnchoredViewABI.ts b/apps/compound/src/abis/UniSwapAnchoredViewABI.ts index 4ba280f61..b4ae54d63 100644 --- a/apps/compound/src/abis/UniSwapAnchoredViewABI.ts +++ b/apps/compound/src/abis/UniSwapAnchoredViewABI.ts @@ -6,6 +6,6 @@ const UniSwapAnchoredViewABI = [ stateMutability: 'view', type: 'function', }, -]; +] -export default UniSwapAnchoredViewABI; +export default UniSwapAnchoredViewABI diff --git a/apps/compound/src/components/CompBalance.tsx b/apps/compound/src/components/CompBalance.tsx index 7e8e957c8..4907eee17 100644 --- a/apps/compound/src/components/CompBalance.tsx +++ b/apps/compound/src/components/CompBalance.tsx @@ -1,24 +1,24 @@ -import { SyntheticEvent, useCallback } from 'react'; -import { Title, ButtonLink } from '@gnosis.pm/safe-react-components'; -import styled from 'styled-components'; -import { InfoContainer } from '../styles'; -import InfoRow from './InfoRow'; +import { SyntheticEvent, useCallback } from 'react' +import { Title, ButtonLink } from '@gnosis.pm/safe-react-components' +import styled from 'styled-components' +import { InfoContainer } from '../styles' +import InfoRow from './InfoRow' type Props = { - balance: number | undefined; - onCollect: () => void; -}; + balance: number | undefined + onCollect: () => void +} export default function CompBalance({ balance, onCollect }: Props): React.ReactElement { const handleOnCollect = useCallback( (event: SyntheticEvent) => { - event.preventDefault(); + event.preventDefault() if (balance && balance > 0) { - onCollect(); + onCollect() } }, [balance, onCollect], - ); + ) return ( @@ -35,9 +35,9 @@ export default function CompBalance({ balance, onCollect }: Props): React.ReactE } /> - ); + ) } const Button = styled(ButtonLink)` display: inline-block; -`; +` diff --git a/apps/compound/src/components/InfoRow.tsx b/apps/compound/src/components/InfoRow.tsx index 44db56fe9..4bb576c33 100644 --- a/apps/compound/src/components/InfoRow.tsx +++ b/apps/compound/src/components/InfoRow.tsx @@ -1,10 +1,10 @@ -import { Divider, Text } from '@gnosis.pm/safe-react-components'; -import styled from 'styled-components'; +import { Divider, Text } from '@gnosis.pm/safe-react-components' +import styled from 'styled-components' type Props = { - label: string; - data: string | React.ReactElement | undefined; -}; + label: string + data: string | React.ReactElement | undefined +} export default function InfoRow({ label, data }: Props): React.ReactElement { return ( @@ -15,11 +15,11 @@ export default function InfoRow({ label, data }: Props): React.ReactElement { - ); + ) } const InfoRowContainer = styled.div` display: flex; width: 100%; justify-content: space-between; -`; +` diff --git a/apps/compound/src/components/WidgetWrapper.tsx b/apps/compound/src/components/WidgetWrapper.tsx index 1dfd8566c..eff38a943 100644 --- a/apps/compound/src/components/WidgetWrapper.tsx +++ b/apps/compound/src/components/WidgetWrapper.tsx @@ -1,17 +1,17 @@ -import React from 'react'; -import styled from 'styled-components'; +import React from 'react' +import styled from 'styled-components' const Card = styled.div` width: 400px; padding: 24px; -`; +` const WidgetWrapper: React.FC = ({ children }) => { return (
{children}
- ); -}; + ) +} -export default WidgetWrapper; +export default WidgetWrapper diff --git a/apps/compound/src/config.ts b/apps/compound/src/config.ts index 2589c48e7..3252a0559 100644 --- a/apps/compound/src/config.ts +++ b/apps/compound/src/config.ts @@ -1,59 +1,65 @@ -import { CHAINS } from './utils/networks'; -import { cToken, getMarkets } from './http/compoundApi'; -import STATIC_CONFIG from './tokens'; +import { CHAINS } from './utils/networks' +import { cToken, getMarkets } from './http/compoundApi' +import STATIC_CONFIG from './tokens' export type TokenItem = { - id: string; - label: string; - iconUrl: string; - decimals: number; - tokenAddr: string; - cTokenAddr: string; -}; + id: string + label: string + iconUrl: string + decimals: number + tokenAddr: string + cTokenAddr: string +} const ETH_UNDERLYING_ADDRESS: { [key: number]: string } = { 1: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', 4: '0xc778417e063141139fce010982780140aa0cd5ab', -}; +} -const EXCLUDE_TOKENS = ['cWBTC', 'cSAI', 'cREP']; +const EXCLUDE_TOKENS = ['cWBTC', 'cSAI', 'cREP'] const getSymbolIconUrl = (symbol: string) => - `https://app.compound.finance/compound-components/assets/asset_${symbol === 'WBTC' ? 'BTC' : symbol}.svg`; + `https://app.compound.finance/compound-components/assets/asset_${ + symbol === 'WBTC' ? 'BTC' : symbol + }.svg` -const filterTokens = (token: cToken) => !EXCLUDE_TOKENS.includes(token.symbol); -const orderTokensBySymbol = (a: cToken, b: cToken) => ('' + a.underlying_symbol).localeCompare(b.underlying_symbol); +const filterTokens = (token: cToken) => !EXCLUDE_TOKENS.includes(token.symbol) +const orderTokensBySymbol = (a: cToken, b: cToken) => + ('' + a.underlying_symbol).localeCompare(b.underlying_symbol) const transformFromCompoundResponse = (token: cToken, chainId: number): TokenItem => { return { id: token.underlying_symbol, label: token.underlying_symbol, iconUrl: getSymbolIconUrl(token.underlying_symbol), decimals: token.cash.value.split('.')[1].length, - tokenAddr: token.underlying_symbol === 'ETH' ? ETH_UNDERLYING_ADDRESS[chainId] : token.underlying_address, + tokenAddr: + token.underlying_symbol === 'ETH' + ? ETH_UNDERLYING_ADDRESS[chainId] + : token.underlying_address, cTokenAddr: token.token_address, - }; -}; + } +} export const getTokenList = async (chainId: number): Promise => { if (chainId !== CHAINS.RINKEBY && chainId !== CHAINS.MAINNET) { - throw Error(`Not supported Chain id ${chainId}`); + throw Error(`Not supported Chain id ${chainId}`) } if (chainId === CHAINS.RINKEBY) { - return STATIC_CONFIG; + return STATIC_CONFIG } - const cToken = await getMarkets(); + const cToken = await getMarkets() return cToken .filter(filterTokens) .sort(orderTokensBySymbol) .reduce((tokenItems: TokenItem[], cToken: cToken): TokenItem[] => { if (cToken?.cash?.value?.split('.')?.[1]?.length) { - const transformedToken = transformFromCompoundResponse(cToken, chainId); - return [...tokenItems, transformedToken]; + const transformedToken = transformFromCompoundResponse(cToken, chainId) + return [...tokenItems, transformedToken] } - return tokenItems; - }, []); -}; + return tokenItems + }, []) +} diff --git a/apps/compound/src/global.ts b/apps/compound/src/global.ts index 5f88cf7ae..7d9558ea3 100644 --- a/apps/compound/src/global.ts +++ b/apps/compound/src/global.ts @@ -1,6 +1,6 @@ -import { createGlobalStyle } from "styled-components"; -import avertaFont from "@gnosis.pm/safe-react-components/dist/fonts/averta-normal.woff2"; -import avertaBoldFont from "@gnosis.pm/safe-react-components/dist/fonts/averta-bold.woff2"; +import { createGlobalStyle } from 'styled-components' +import avertaFont from '@gnosis.pm/safe-react-components/dist/fonts/averta-normal.woff2' +import avertaBoldFont from '@gnosis.pm/safe-react-components/dist/fonts/averta-bold.woff2' const GlobalStyle = createGlobalStyle` html { @@ -23,6 +23,6 @@ const GlobalStyle = createGlobalStyle` url(${avertaFont}) format('woff2'), url(${avertaBoldFont}) format('woff'); } -`; +` -export default GlobalStyle; +export default GlobalStyle diff --git a/apps/compound/src/hooks/useCToken.ts b/apps/compound/src/hooks/useCToken.ts index 695557c7e..f4445457d 100644 --- a/apps/compound/src/hooks/useCToken.ts +++ b/apps/compound/src/hooks/useCToken.ts @@ -1,32 +1,32 @@ -import { useEffect, useState } from 'react'; -import { AbiItem } from 'web3-utils'; -import { Contract } from 'web3-eth-contract'; -import CErc20ABI from '../abis/CErc20'; -import CWethABI from '../abis/CWEth'; -import { TokenItem } from '../config'; -import useWeb3 from './useWeb3'; +import { useEffect, useState } from 'react' +import { AbiItem } from 'web3-utils' +import { Contract } from 'web3-eth-contract' +import CErc20ABI from '../abis/CErc20' +import CWethABI from '../abis/CWEth' +import { TokenItem } from '../config' +import useWeb3 from './useWeb3' export default function useCToken(selectedToken: TokenItem | undefined) { - const { web3 } = useWeb3(); - const [cTokenInstance, setCTokenInstance] = useState(); - const [tokenInstance, setTokenInstance] = useState(); + const { web3 } = useWeb3() + const [cTokenInstance, setCTokenInstance] = useState() + const [tokenInstance, setTokenInstance] = useState() useEffect(() => { if (!selectedToken || !web3) { - return; + return } - setTokenInstance(new web3.eth.Contract(CErc20ABI as AbiItem[], selectedToken.tokenAddr)); + setTokenInstance(new web3.eth.Contract(CErc20ABI as AbiItem[], selectedToken.tokenAddr)) if (selectedToken.id === 'ETH') { - setCTokenInstance(new web3.eth.Contract(CWethABI as AbiItem[], selectedToken.cTokenAddr)); + setCTokenInstance(new web3.eth.Contract(CWethABI as AbiItem[], selectedToken.cTokenAddr)) } else { - setCTokenInstance(new web3.eth.Contract(CErc20ABI as AbiItem[], selectedToken.cTokenAddr)); + setCTokenInstance(new web3.eth.Contract(CErc20ABI as AbiItem[], selectedToken.cTokenAddr)) } - }, [selectedToken, web3]); + }, [selectedToken, web3]) return { cTokenInstance, tokenInstance, - }; + } } diff --git a/apps/compound/src/hooks/useComp.ts b/apps/compound/src/hooks/useComp.ts index 2bfc29efa..a18096422 100644 --- a/apps/compound/src/hooks/useComp.ts +++ b/apps/compound/src/hooks/useComp.ts @@ -1,127 +1,147 @@ -import { useSafeAppsSDK } from '@gnosis.pm/safe-apps-react-sdk'; -import { useCallback, useEffect, useState } from 'react'; -import { AbiItem } from 'web3-utils'; -import { Contract } from 'web3-eth-contract'; -import ComptrollerABI from '../abis/Comptroller'; -import { TokenItem } from '../config'; -import useCToken from './useCToken'; -import usePriceFeed from './useOpenPriceFeed'; -import useWeb3 from './useWeb3'; -import CompoundLensABI from '../abis/CompoundLens'; -import { getMarketAddressesForSafeAccount } from '../http/compoundApi'; - -const COMPOUND_LENS_ADDRESS = '0xdCbDb7306c6Ff46f77B349188dC18cEd9DF30299'; +import { useSafeAppsSDK } from '@gnosis.pm/safe-apps-react-sdk' +import { useCallback, useEffect, useState } from 'react' +import { AbiItem } from 'web3-utils' +import { Contract } from 'web3-eth-contract' +import ComptrollerABI from '../abis/Comptroller' +import { TokenItem } from '../config' +import useCToken from './useCToken' +import usePriceFeed from './useOpenPriceFeed' +import useWeb3 from './useWeb3' +import CompoundLensABI from '../abis/CompoundLens' +import { getMarketAddressesForSafeAccount } from '../http/compoundApi' + +const COMPOUND_LENS_ADDRESS = '0xdCbDb7306c6Ff46f77B349188dC18cEd9DF30299' export default function useComp(selectedToken: TokenItem | undefined) { - const { sdk, safe } = useSafeAppsSDK(); - const { web3 } = useWeb3(); - const [comptrollerInstance, setComptrollerInstance] = useState(); - const [compoundLensInstance, setCompoundLensInstance] = useState(); - const [claimableComp, setClaimableComp] = useState(); - const [comptrollerAddress, setComptrollerAddress] = useState(); - const [cTokenSupplyAPY, setCTokenSupplyAPY] = useState(); - const [cDistributionTokenSupplyAPY, setCDistributionTokenSupplyAPY] = useState(); - const { opfInstance } = usePriceFeed(); - const { cTokenInstance, tokenInstance } = useCToken(selectedToken); + const { sdk, safe } = useSafeAppsSDK() + const { web3 } = useWeb3() + const [comptrollerInstance, setComptrollerInstance] = useState() + const [compoundLensInstance, setCompoundLensInstance] = useState() + const [claimableComp, setClaimableComp] = useState() + const [comptrollerAddress, setComptrollerAddress] = useState() + const [cTokenSupplyAPY, setCTokenSupplyAPY] = useState() + const [cDistributionTokenSupplyAPY, setCDistributionTokenSupplyAPY] = useState() + const { opfInstance } = usePriceFeed() + const { cTokenInstance, tokenInstance } = useCToken(selectedToken) useEffect(() => { - (async () => { - const address = await cTokenInstance?.methods.comptroller().call(); - setComptrollerAddress(address); - })(); - }, [cTokenInstance, selectedToken, web3]); + ;(async () => { + const address = await cTokenInstance?.methods.comptroller().call() + setComptrollerAddress(address) + })() + }, [cTokenInstance, selectedToken, web3]) useEffect(() => { if (!web3 || !comptrollerAddress) { - return; + return } - setComptrollerInstance(new web3.eth.Contract(ComptrollerABI as AbiItem[], comptrollerAddress)); - setCompoundLensInstance(new web3.eth.Contract(CompoundLensABI as AbiItem[], COMPOUND_LENS_ADDRESS)); - }, [web3, comptrollerAddress]); + setComptrollerInstance(new web3.eth.Contract(ComptrollerABI as AbiItem[], comptrollerAddress)) + setCompoundLensInstance( + new web3.eth.Contract(CompoundLensABI as AbiItem[], COMPOUND_LENS_ADDRESS), + ) + }, [web3, comptrollerAddress]) useEffect(() => { // Using getCompBalanceMetadataExt for get the COMP value allowed to collect // https://gist.github.com/ajb413/f1cf80c988ed679092cff4e4b01e3d94 if (!comptrollerInstance) { - return; + return } - (async () => { + ;(async () => { try { - const compAddress = await comptrollerInstance?.methods?.getCompAddress().call(); + const compAddress = await comptrollerInstance?.methods?.getCompAddress().call() const accrued = await compoundLensInstance?.methods ?.getCompBalanceMetadataExt(compAddress, comptrollerAddress, safe?.safeAddress) - .call(); - setClaimableComp(accrued?.allocated / 10 ** 18); + .call() + setClaimableComp(accrued?.allocated / 10 ** 18) } catch (e) { - console.error(e); + console.error(e) } - })(); - }, [compoundLensInstance, comptrollerInstance, comptrollerAddress, safe]); + })() + }, [compoundLensInstance, comptrollerInstance, comptrollerAddress, safe]) useEffect(() => { // Calculate APYs // https://gist.github.com/ajb413/d32442edae9251ad395436d5b80d4480 - if (!cTokenInstance || !comptrollerInstance || !opfInstance || !selectedToken || !tokenInstance) { - return; + if ( + !cTokenInstance || + !comptrollerInstance || + !opfInstance || + !selectedToken || + !tokenInstance + ) { + return } // wait until cToken is correctly updated - if (selectedToken.cTokenAddr.toLocaleLowerCase() !== cTokenInstance?.['_address'].toLocaleLowerCase()) { - return; + if ( + selectedToken.cTokenAddr.toLocaleLowerCase() !== + cTokenInstance?.['_address'].toLocaleLowerCase() + ) { + return } // wait until token is correctly updated - if (selectedToken.tokenAddr.toLocaleLowerCase() !== tokenInstance?.['_address'].toLocaleLowerCase()) { - return; + if ( + selectedToken.tokenAddr.toLocaleLowerCase() !== + tokenInstance?.['_address'].toLocaleLowerCase() + ) { + return } - const ethMantissa = 1e18; - const blocksPerDay = 6570; // 13.15 seconds per block - const daysPerYear = 365; + const ethMantissa = 1e18 + const blocksPerDay = 6570 // 13.15 seconds per block + const daysPerYear = 365 // Calculate Supply APY - (async () => { - const supplyRatePerBlock = await cTokenInstance.methods.supplyRatePerBlock().call(); - const supplyApy = (Math.pow((supplyRatePerBlock / ethMantissa) * blocksPerDay + 1, daysPerYear) - 1) * 100; - setCTokenSupplyAPY((Math.round(supplyApy * 100) / 100).toString()); - })(); + ;(async () => { + const supplyRatePerBlock = await cTokenInstance.methods.supplyRatePerBlock().call() + const supplyApy = + (Math.pow((supplyRatePerBlock / ethMantissa) * blocksPerDay + 1, daysPerYear) - 1) * 100 + setCTokenSupplyAPY((Math.round(supplyApy * 100) / 100).toString()) + })() // Calculate Distribution APY - (async () => { - let compSpeedSupply = await comptrollerInstance?.methods?.compSupplySpeeds(selectedToken.cTokenAddr).call(); - let compPrice = await opfInstance?.methods?.price('COMP').call(); - let assetPrice = await opfInstance?.methods?.price(selectedToken.id === 'WBTC' ? 'BTC' : selectedToken.id).call(); - let totalSupply = await cTokenInstance?.methods?.totalSupply().call(); - let exchangeRate = await cTokenInstance?.methods?.exchangeRateCurrent().call(); + ;(async () => { + let compSpeedSupply = await comptrollerInstance?.methods + ?.compSupplySpeeds(selectedToken.cTokenAddr) + .call() + let compPrice = await opfInstance?.methods?.price('COMP').call() + let assetPrice = await opfInstance?.methods + ?.price(selectedToken.id === 'WBTC' ? 'BTC' : selectedToken.id) + .call() + let totalSupply = await cTokenInstance?.methods?.totalSupply().call() + let exchangeRate = await cTokenInstance?.methods?.exchangeRateCurrent().call() // Total supply needs to be converted from cTokens - const apxBlockSpeedInSeconds = 13.15; - exchangeRate = +exchangeRate.toString() / Math.pow(10, selectedToken.decimals); + const apxBlockSpeedInSeconds = 13.15 + exchangeRate = +exchangeRate.toString() / Math.pow(10, selectedToken.decimals) - compSpeedSupply = compSpeedSupply / 1e18; // COMP has 18 decimal places - compPrice = compPrice / 1e6; // price feed is USD price with 6 decimal places - assetPrice = assetPrice / 1e6; - totalSupply = (+totalSupply.toString() * exchangeRate) / Math.pow(10, 18); + compSpeedSupply = compSpeedSupply / 1e18 // COMP has 18 decimal places + compPrice = compPrice / 1e6 // price feed is USD price with 6 decimal places + assetPrice = assetPrice / 1e6 + totalSupply = (+totalSupply.toString() * exchangeRate) / Math.pow(10, 18) - const compPerDaySupply = compSpeedSupply * ((60 * 60 * 24) / apxBlockSpeedInSeconds); + const compPerDaySupply = compSpeedSupply * ((60 * 60 * 24) / apxBlockSpeedInSeconds) - const compSupplyApy = 100 * (Math.pow(1 + (compPrice * compPerDaySupply) / (totalSupply * assetPrice), 365) - 1); + const compSupplyApy = + 100 * (Math.pow(1 + (compPrice * compPerDaySupply) / (totalSupply * assetPrice), 365) - 1) - setCDistributionTokenSupplyAPY((Math.round(compSupplyApy * 100) / 100).toString()); - })(); - }, [cTokenInstance, comptrollerInstance, opfInstance, selectedToken, tokenInstance]); + setCDistributionTokenSupplyAPY((Math.round(compSupplyApy * 100) / 100).toString()) + })() + }, [cTokenInstance, comptrollerInstance, opfInstance, selectedToken, tokenInstance]) const claimComp = useCallback(async () => { if (!comptrollerAddress) { - return; + return } // Get all the cToken addresses the safe account is using - const allMarkets: string[] = await getMarketAddressesForSafeAccount(safe.safeAddress); + const allMarkets: string[] = await getMarketAddressesForSafeAccount(safe.safeAddress) if (allMarkets.length) { const txs = [ @@ -130,15 +150,15 @@ export default function useComp(selectedToken: TokenItem | undefined) { value: '0', data: comptrollerInstance?.methods.claimComp(safe?.safeAddress, allMarkets).encodeABI(), }, - ]; + ] try { - await sdk.txs.send({ txs }); + await sdk.txs.send({ txs }) } catch { - console.error('Collect COMP: Transaction rejected or failed'); + console.error('Collect COMP: Transaction rejected or failed') } } - }, [comptrollerAddress, comptrollerInstance, safe, sdk]); + }, [comptrollerAddress, comptrollerInstance, safe, sdk]) return { comptrollerInstance, @@ -146,5 +166,5 @@ export default function useComp(selectedToken: TokenItem | undefined) { claimComp, cTokenSupplyAPY, cDistributionTokenSupplyAPY, - }; + } } diff --git a/apps/compound/src/hooks/useOpenPriceFeed.ts b/apps/compound/src/hooks/useOpenPriceFeed.ts index 86e6ad539..d885f8d8d 100644 --- a/apps/compound/src/hooks/useOpenPriceFeed.ts +++ b/apps/compound/src/hooks/useOpenPriceFeed.ts @@ -1,30 +1,35 @@ // https://compound.finance/docs/prices -import { useEffect, useState } from 'react'; -import { AbiItem } from 'web3-utils'; -import { Contract } from 'web3-eth-contract'; -import useWeb3 from './useWeb3'; -import UniSwapAnchoredViewABI from '../abis/UniSwapAnchoredViewABI'; -import { useSafeAppsSDK } from '@gnosis.pm/safe-apps-react-sdk'; +import { useEffect, useState } from 'react' +import { AbiItem } from 'web3-utils' +import { Contract } from 'web3-eth-contract' +import useWeb3 from './useWeb3' +import UniSwapAnchoredViewABI from '../abis/UniSwapAnchoredViewABI' +import { useSafeAppsSDK } from '@gnosis.pm/safe-apps-react-sdk' export default function useOpenPriceFeed() { - const { safe } = useSafeAppsSDK(); - const [opfInstance, setOpfInstance] = useState(); - const { web3 } = useWeb3(); + const { safe } = useSafeAppsSDK() + const [opfInstance, setOpfInstance] = useState() + const { web3 } = useWeb3() useEffect(() => { if (!web3 || !safe) { - return; + return } - setOpfInstance(new web3.eth.Contract(UniSwapAnchoredViewABI as AbiItem[], OPEN_PRICE_FEED_CONTRACT[safe.chainId])); - }, [web3, safe]); + setOpfInstance( + new web3.eth.Contract( + UniSwapAnchoredViewABI as AbiItem[], + OPEN_PRICE_FEED_CONTRACT[safe.chainId], + ), + ) + }, [web3, safe]) return { opfInstance, - }; + } } const OPEN_PRICE_FEED_CONTRACT: { [key: number]: string } = { 1: '0x046728da7cb8272284238bd3e47909823d63a58d', 4: '0x01f590Fc7399A71BbD4cF15538FF82b2133AEbb6', -}; +} diff --git a/apps/compound/src/hooks/useWeb3.ts b/apps/compound/src/hooks/useWeb3.ts index c54ce99b4..7b655fbbb 100644 --- a/apps/compound/src/hooks/useWeb3.ts +++ b/apps/compound/src/hooks/useWeb3.ts @@ -1,42 +1,42 @@ -import { useEffect, useState } from 'react'; -import Web3 from 'web3'; -import { useSafeAppsSDK } from '@gnosis.pm/safe-apps-react-sdk'; +import { useEffect, useState } from 'react' +import Web3 from 'web3' +import { useSafeAppsSDK } from '@gnosis.pm/safe-apps-react-sdk' function useWeb3(): { web3: Web3 | undefined } { - const [web3, setWeb3] = useState(); + const [web3, setWeb3] = useState() - const { sdk } = useSafeAppsSDK(); + const { sdk } = useSafeAppsSDK() useEffect(() => { const setWeb3Instance = async () => { - const chainInfo = await sdk.safe.getChainInfo(); + const chainInfo = await sdk.safe.getChainInfo() if (!chainInfo) { - return; + return } - const rpcUrlGetter = rpcUrlGetterByNetwork[chainInfo.chainId as CHAINS]; + const rpcUrlGetter = rpcUrlGetterByNetwork[chainInfo.chainId as CHAINS] if (!rpcUrlGetter) { - throw Error(`RPC URL not defined for ${chainInfo.chainName} chain`); + throw Error(`RPC URL not defined for ${chainInfo.chainName} chain`) } - const rpcUrl = rpcUrlGetter(process.env.REACT_APP_RPC_TOKEN); + const rpcUrl = rpcUrlGetter(process.env.REACT_APP_RPC_TOKEN) - const web3Instance = new Web3(rpcUrl); + const web3Instance = new Web3(rpcUrl) - setWeb3(web3Instance); - }; + setWeb3(web3Instance) + } - setWeb3Instance(); - }, [sdk.safe]); + setWeb3Instance() + }, [sdk.safe]) return { web3, - }; + } } -export default useWeb3; +export default useWeb3 export enum CHAINS { MAINNET = '1', @@ -44,8 +44,8 @@ export enum CHAINS { } export const rpcUrlGetterByNetwork: { - [key in CHAINS]: null | ((token?: string) => string); + [key in CHAINS]: null | ((token?: string) => string) } = { - [CHAINS.MAINNET]: (token) => `https://mainnet.infura.io/v3/${token}`, - [CHAINS.RINKEBY]: (token) => `https://rinkeby.infura.io/v3/${token}`, -}; + [CHAINS.MAINNET]: token => `https://mainnet.infura.io/v3/${token}`, + [CHAINS.RINKEBY]: token => `https://rinkeby.infura.io/v3/${token}`, +} diff --git a/apps/compound/src/http/compoundApi.ts b/apps/compound/src/http/compoundApi.ts index 7ef7f47a8..61bbfa9b4 100644 --- a/apps/compound/src/http/compoundApi.ts +++ b/apps/compound/src/http/compoundApi.ts @@ -1,33 +1,35 @@ -const COMPOUND_API_BASE_URL = 'https://api.compound.finance/api/v2/'; +const COMPOUND_API_BASE_URL = 'https://api.compound.finance/api/v2/' export type cToken = { - symbol: string; - token_address: string; - underlying_symbol: string; - underlying_address: string; + symbol: string + token_address: string + underlying_symbol: string + underlying_address: string cash: { - value: string; - }; -}; + value: string + } +} export async function getMarketAddressesForSafeAccount(safeAddress: string): Promise { try { - const response = await fetch(`${COMPOUND_API_BASE_URL}/governance/comp/account?address=${safeAddress}`); - const { markets } = await response.json(); - return markets.map(({ address }: { address: string }): string => address); + const response = await fetch( + `${COMPOUND_API_BASE_URL}/governance/comp/account?address=${safeAddress}`, + ) + const { markets } = await response.json() + return markets.map(({ address }: { address: string }): string => address) } catch (error) { - console.error(error); - return []; + console.error(error) + return [] } } export async function getMarkets(): Promise { try { - const response = await fetch(`${COMPOUND_API_BASE_URL}/ctoken`); - const { cToken } = await response.json(); - return cToken; + const response = await fetch(`${COMPOUND_API_BASE_URL}/ctoken`) + const { cToken } = await response.json() + return cToken } catch (error) { - console.error(error); - return []; + console.error(error) + return [] } } diff --git a/apps/compound/src/index.tsx b/apps/compound/src/index.tsx index a93fc6298..9d19919c7 100644 --- a/apps/compound/src/index.tsx +++ b/apps/compound/src/index.tsx @@ -1,12 +1,12 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import { SafeProvider } from '@gnosis.pm/safe-apps-react-sdk'; +import React from 'react' +import ReactDOM from 'react-dom' +import { SafeProvider } from '@gnosis.pm/safe-apps-react-sdk' -import GlobalStyles from './global'; -import * as serviceWorker from './serviceWorker'; -import App from './App'; -import { ThemeProvider } from 'styled-components'; -import { Loader, theme, Title } from '@gnosis.pm/safe-react-components'; +import GlobalStyles from './global' +import * as serviceWorker from './serviceWorker' +import App from './App' +import { ThemeProvider } from 'styled-components' +import { Loader, theme, Title } from '@gnosis.pm/safe-react-components' ReactDOM.render( @@ -25,9 +25,9 @@ ReactDOM.render( , document.getElementById('root'), -); +) // If you want your app to work offline and load faster, you can change // unregister() to register() below. Note this comes with some pitfalls. // Learn more about service workers: https://bit.ly/CRA-PWA -serviceWorker.unregister(); +serviceWorker.unregister() diff --git a/apps/compound/src/serviceWorker.ts b/apps/compound/src/serviceWorker.ts index d5f0275a7..018ffc4f7 100644 --- a/apps/compound/src/serviceWorker.ts +++ b/apps/compound/src/serviceWorker.ts @@ -15,50 +15,45 @@ const isLocalhost = Boolean( // [::1] is the IPv6 localhost address. window.location.hostname === '[::1]' || // 127.0.0.0/8 are considered localhost for IPv4. - window.location.hostname.match( - /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ - ) -); + window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/), +) type Config = { - onSuccess?: (registration: ServiceWorkerRegistration) => void; - onUpdate?: (registration: ServiceWorkerRegistration) => void; -}; + onSuccess?: (registration: ServiceWorkerRegistration) => void + onUpdate?: (registration: ServiceWorkerRegistration) => void +} export function register(config?: Config) { if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { // The URL constructor is available in all browsers that support SW. - const publicUrl = new URL( - process.env.PUBLIC_URL, - window.location.href - ); + const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href) if (publicUrl.origin !== window.location.origin) { // Our service worker won't work if PUBLIC_URL is on a different origin // from what our page is served on. This might happen if a CDN is used to // serve assets; see https://github.com/facebook/create-react-app/issues/2374 - return; + return } window.addEventListener('load', () => { - const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; + const swUrl = `${process.env.PUBLIC_URL}/service-worker.js` if (isLocalhost) { // This is running on localhost. Let's check if a service worker still exists or not. - checkValidServiceWorker(swUrl, config); + checkValidServiceWorker(swUrl, config) // Add some additional logging to localhost, pointing developers to the // service worker/PWA documentation. navigator.serviceWorker.ready.then(() => { console.log( 'This web app is being served cache-first by a service ' + - 'worker. To learn more, visit https://bit.ly/CRA-PWA' - ); - }); + 'worker. To learn more, visit https://bit.ly/CRA-PWA', + ) + }) } else { // Is not localhost. Just register service worker - registerValidSW(swUrl, config); + registerValidSW(swUrl, config) } - }); + }) } } @@ -67,9 +62,9 @@ function registerValidSW(swUrl: string, config?: Config) { .register(swUrl) .then(registration => { registration.onupdatefound = () => { - const installingWorker = registration.installing; + const installingWorker = registration.installing if (installingWorker == null) { - return; + return } installingWorker.onstatechange = () => { if (installingWorker.state === 'installed') { @@ -79,41 +74,41 @@ function registerValidSW(swUrl: string, config?: Config) { // content until all client tabs are closed. console.log( 'New content is available and will be used when all ' + - 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' - ); + 'tabs for this page are closed. See https://bit.ly/CRA-PWA.', + ) // Execute callback if (config && config.onUpdate) { - config.onUpdate(registration); + config.onUpdate(registration) } } else { // At this point, everything has been precached. // It's the perfect time to display a // "Content is cached for offline use." message. - console.log('Content is cached for offline use.'); + console.log('Content is cached for offline use.') // Execute callback if (config && config.onSuccess) { - config.onSuccess(registration); + config.onSuccess(registration) } } } - }; - }; + } + } }) .catch(error => { - console.error('Error during service worker registration:', error); - }); + console.error('Error during service worker registration:', error) + }) } function checkValidServiceWorker(swUrl: string, config?: Config) { // Check if the service worker can be found. If it can't reload the page. fetch(swUrl, { - headers: { 'Service-Worker': 'script' } + headers: { 'Service-Worker': 'script' }, }) .then(response => { // Ensure service worker exists, and that we really are getting a JS file. - const contentType = response.headers.get('content-type'); + const contentType = response.headers.get('content-type') if ( response.status === 404 || (contentType != null && contentType.indexOf('javascript') === -1) @@ -121,25 +116,23 @@ function checkValidServiceWorker(swUrl: string, config?: Config) { // No service worker found. Probably a different app. Reload the page. navigator.serviceWorker.ready.then(registration => { registration.unregister().then(() => { - window.location.reload(); - }); - }); + window.location.reload() + }) + }) } else { // Service worker found. Proceed as normal. - registerValidSW(swUrl, config); + registerValidSW(swUrl, config) } }) .catch(() => { - console.log( - 'No internet connection found. App is running in offline mode.' - ); - }); + console.log('No internet connection found. App is running in offline mode.') + }) } export function unregister() { if ('serviceWorker' in navigator) { navigator.serviceWorker.ready.then(registration => { - registration.unregister(); - }); + registration.unregister() + }) } } diff --git a/apps/compound/src/setupTests.ts b/apps/compound/src/setupTests.ts index 74b1a275a..2eb59b05d 100644 --- a/apps/compound/src/setupTests.ts +++ b/apps/compound/src/setupTests.ts @@ -2,4 +2,4 @@ // allows you to do things like: // expect(element).toHaveTextContent(/react/i) // learn more: https://github.com/testing-library/jest-dom -import '@testing-library/jest-dom/extend-expect'; +import '@testing-library/jest-dom/extend-expect' diff --git a/apps/compound/src/styles.tsx b/apps/compound/src/styles.tsx index 347745e2c..0abc57b21 100644 --- a/apps/compound/src/styles.tsx +++ b/apps/compound/src/styles.tsx @@ -1,5 +1,5 @@ -import { TextField, Title } from '@gnosis.pm/safe-react-components'; -import styled from 'styled-components'; +import { TextField, Title } from '@gnosis.pm/safe-react-components' +import styled from 'styled-components' export const SelectContainer = styled.div` display: flex; @@ -11,7 +11,7 @@ export const SelectContainer = styled.div` *:first-child { margin-right: 5px; } -`; +` export const InfoContainer = styled.div` margin-top: 20px; @@ -19,7 +19,7 @@ export const InfoContainer = styled.div` flex-direction: column; align-items: left; width: 100%; -`; +` export const ButtonContainer = styled.div` display: flex; @@ -29,17 +29,17 @@ export const ButtonContainer = styled.div` button:not(:first-child) { margin-left: 10px; } -`; +` export const StyledTextField = styled(TextField)` &.MuiTextField-root { width: 100%; } -`; +` export const StyledTitle = styled(Title)` margin-top: 0; -`; +` export const LoaderContainer = styled.div` display: flex; @@ -48,4 +48,4 @@ export const LoaderContainer = styled.div` justify-content: center; width: calc(100vw - 50px); height: calc(100vh); -`; +` diff --git a/apps/compound/src/tokens.ts b/apps/compound/src/tokens.ts index d16437392..9d2e60055 100644 --- a/apps/compound/src/tokens.ts +++ b/apps/compound/src/tokens.ts @@ -9,7 +9,7 @@ const TOKENS = { cUSDC: '0x5B281A6DdA0B271e91ae35DE655Ad301C976edb1', ZRX: '0xddea378A6dDC8AfeC82C36E9b0078826bf9e68B6', cZRX: '0x52201ff1720134bBbBB2f6BC97Bf3715490EC19B', -}; +} const STATIC_CONFIG = [ { @@ -52,6 +52,6 @@ const STATIC_CONFIG = [ tokenAddr: TOKENS.ZRX, cTokenAddr: TOKENS.cZRX, }, -]; +] -export default STATIC_CONFIG; +export default STATIC_CONFIG diff --git a/apps/compound/src/tokensTransfers.ts b/apps/compound/src/tokensTransfers.ts index 39b1737bb..f634e35e7 100644 --- a/apps/compound/src/tokensTransfers.ts +++ b/apps/compound/src/tokensTransfers.ts @@ -1,19 +1,19 @@ -import { ApolloClient, InMemoryCache, gql } from '@apollo/client'; -import { CHAINS } from './utils/networks'; +import { ApolloClient, InMemoryCache, gql } from '@apollo/client' +import { CHAINS } from './utils/networks' export type TokenInteractionData = { - amount: string; - destination: string; - sender?: string; -}; + amount: string + destination: string + sender?: string +} -const RINKEBY = 'https://api.thegraph.com/subgraphs/name/protofire/token-registry-rinkeby'; -const MAINNET = 'https://api.thegraph.com/subgraphs/name/protofire/token-registry'; +const RINKEBY = 'https://api.thegraph.com/subgraphs/name/protofire/token-registry-rinkeby' +const MAINNET = 'https://api.thegraph.com/subgraphs/name/protofire/token-registry' const subgraphUri: { [key in CHAINS.MAINNET | CHAINS.RINKEBY]: string } = { [CHAINS.RINKEBY]: RINKEBY, [CHAINS.MAINNET]: MAINNET, -}; +} const TRANSFER_EVENTS = gql` query TransferEvents($first: Int!, $skip: Int!, $token: String!, $addresses: [String!]!) { @@ -27,7 +27,7 @@ const TRANSFER_EVENTS = gql` destination } } -`; +` async function getTransferEvents( client: any, @@ -35,10 +35,10 @@ async function getTransferEvents( tokenAddr: string, cTokenAddr: string, ): Promise> { - let ended = false; - let first = 100; - let skip = 0; - let transferEvents: Array = []; + let ended = false + let first = 100 + let skip = 0 + let transferEvents: Array = [] while (!ended) { try { @@ -50,20 +50,20 @@ async function getTransferEvents( token: tokenAddr.toLocaleLowerCase(), addresses: [cTokenAddr, safeAddress], }, - }); - skip += first; + }) + skip += first - transferEvents = [...transferEvents, ...res.data.transferEvents]; + transferEvents = [...transferEvents, ...res.data.transferEvents] if (res.data.transferEvents.length < first) { - ended = true; + ended = true } } catch (error) { - ended = true; - throw error; + ended = true + throw error } } - return transferEvents; + return transferEvents } const MINT_EVENTS = gql` @@ -73,17 +73,17 @@ const MINT_EVENTS = gql` destination } } -`; +` async function getMintEvents( client: any, safeAddress: string, tokenAddr: string, ): Promise> { - let ended = false; - let first = 100; - let skip = 0; - let mintEvents: Array = []; + let ended = false + let first = 100 + let skip = 0 + let mintEvents: Array = [] while (!ended) { try { @@ -95,20 +95,20 @@ async function getMintEvents( token: tokenAddr.toLocaleLowerCase(), safeAddress: safeAddress, }, - }); - skip += first; + }) + skip += first - mintEvents = [...mintEvents, ...res.data.mintEvents]; + mintEvents = [...mintEvents, ...res.data.mintEvents] if (res.data.mintEvents.length < first) { - ended = true; + ended = true } } catch (error) { - ended = true; - throw error; + ended = true + throw error } } - return mintEvents; + return mintEvents } export async function getTokenInteractions( @@ -118,33 +118,33 @@ export async function getTokenInteractions( cTokenAddr: string, ) { if (chainId !== CHAINS.RINKEBY && chainId !== CHAINS.MAINNET) { - return []; + return [] } const client = new ApolloClient({ uri: subgraphUri[chainId], - cache: new InMemoryCache() - }); + cache: new InMemoryCache(), + }) - const mintEventsRes = await getMintEvents(client, safeAddress, tokenAddr); - const transferEventsRes = await getTransferEvents(client, safeAddress, tokenAddr, cTokenAddr); - return [...mintEventsRes, ...transferEventsRes]; + const mintEventsRes = await getMintEvents(client, safeAddress, tokenAddr) + const transferEventsRes = await getTransferEvents(client, safeAddress, tokenAddr, cTokenAddr) + return [...mintEventsRes, ...transferEventsRes] } export function parseEvents(senderAddress: string, tokenEvents: Array) { - let deposits = 0; - let withdrawals = 0; + let deposits = 0 + let withdrawals = 0 - tokenEvents.forEach((event) => { - const parsedAmount = Number(event.amount); + tokenEvents.forEach(event => { + const parsedAmount = Number(event.amount) if (!Number.isNaN(parsedAmount)) { event.sender && event.sender.toLowerCase() === senderAddress.toLowerCase() ? (deposits += parsedAmount) - : (withdrawals += parsedAmount); + : (withdrawals += parsedAmount) } - }); + }) return { deposits, withdrawals, - }; + } } diff --git a/apps/compound/src/typings/custom.d.ts b/apps/compound/src/typings/custom.d.ts index 16ef5c298..2e323377f 100644 --- a/apps/compound/src/typings/custom.d.ts +++ b/apps/compound/src/typings/custom.d.ts @@ -1,4 +1,4 @@ declare module '*.svg' { - const content: React.FunctionComponent>; - export default content; - } \ No newline at end of file + const content: React.FunctionComponent> + export default content +} diff --git a/apps/compound/src/typings/ethers.d.ts b/apps/compound/src/typings/ethers.d.ts index 6aba0d9c4..5f3c567d8 100644 --- a/apps/compound/src/typings/ethers.d.ts +++ b/apps/compound/src/typings/ethers.d.ts @@ -1 +1 @@ -declare module 'ethers/utils'; \ No newline at end of file +declare module 'ethers/utils' diff --git a/apps/compound/src/typings/fonts.d.ts b/apps/compound/src/typings/fonts.d.ts index 7bbf68bbc..85a0dfc7e 100644 --- a/apps/compound/src/typings/fonts.d.ts +++ b/apps/compound/src/typings/fonts.d.ts @@ -1,2 +1,2 @@ -declare module '*.woff'; -declare module '*.woff2'; +declare module '*.woff' +declare module '*.woff2' diff --git a/apps/compound/src/utils/networks.ts b/apps/compound/src/utils/networks.ts index b519647e5..129bbcb7e 100644 --- a/apps/compound/src/utils/networks.ts +++ b/apps/compound/src/utils/networks.ts @@ -4,10 +4,10 @@ enum CHAINS { } const networkByChainId: { - [key in CHAINS]: string; + [key in CHAINS]: string } = { [CHAINS.MAINNET]: 'MAINNET', [CHAINS.RINKEBY]: 'RINKEBY', -}; +} -export { CHAINS, networkByChainId }; +export { CHAINS, networkByChainId } diff --git a/apps/drain-safe/config-overrides.js b/apps/drain-safe/config-overrides.js index cfe7814f0..f4ec1f4f0 100644 --- a/apps/drain-safe/config-overrides.js +++ b/apps/drain-safe/config-overrides.js @@ -1,8 +1,8 @@ -const webpack = require('webpack'); +const webpack = require('webpack') module.exports = { webpack: function (config, env) { - const fallback = config.resolve.fallback || {}; + const fallback = config.resolve.fallback || {} // https://github.com/ChainSafe/web3.js#web3-and-create-react-app Object.assign(fallback, { @@ -15,39 +15,39 @@ module.exports = { url: require.resolve('url'), // https://stackoverflow.com/questions/68707553/uncaught-referenceerror-buffer-is-not-defined buffer: require.resolve('buffer'), - }); + }) - config.resolve.fallback = fallback; + config.resolve.fallback = fallback config.plugins = (config.plugins || []).concat([ new webpack.ProvidePlugin({ process: 'process/browser', Buffer: ['buffer', 'Buffer'], }), - ]); + ]) // https://github.com/facebook/create-react-app/issues/11924 - config.ignoreWarnings = [/to parse source map/i]; + config.ignoreWarnings = [/to parse source map/i] - return config; + return config }, jest: function (config) { - return config; + return config }, devServer: function (configFunction) { return function (proxy, allowedHost) { - const config = configFunction(proxy, allowedHost); + const config = configFunction(proxy, allowedHost) config.headers = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET', 'Access-Control-Allow-Headers': 'X-Requested-With, content-type, Authorization', - }; + } - return config; - }; + return config + } }, paths: function (paths) { - return paths; + return paths }, -}; +} diff --git a/apps/drain-safe/src/GlobalStyle.ts b/apps/drain-safe/src/GlobalStyle.ts index 05da37427..3c709691e 100644 --- a/apps/drain-safe/src/GlobalStyle.ts +++ b/apps/drain-safe/src/GlobalStyle.ts @@ -1,6 +1,6 @@ -import { createGlobalStyle } from 'styled-components'; -import avertaFont from '@gnosis.pm/safe-react-components/dist/fonts/averta-normal.woff2'; -import avertaBoldFont from '@gnosis.pm/safe-react-components/dist/fonts/averta-bold.woff2'; +import { createGlobalStyle } from 'styled-components' +import avertaFont from '@gnosis.pm/safe-react-components/dist/fonts/averta-normal.woff2' +import avertaBoldFont from '@gnosis.pm/safe-react-components/dist/fonts/averta-bold.woff2' const GlobalStyle = createGlobalStyle` html { @@ -29,6 +29,6 @@ const GlobalStyle = createGlobalStyle` url(${avertaFont}) format('woff2'), url(${avertaBoldFont}) format('woff'); } -`; +` -export default GlobalStyle; +export default GlobalStyle diff --git a/apps/drain-safe/src/__tests__/App.test.js b/apps/drain-safe/src/__tests__/App.test.js index 05fed3a3e..34d62578b 100644 --- a/apps/drain-safe/src/__tests__/App.test.js +++ b/apps/drain-safe/src/__tests__/App.test.js @@ -1,11 +1,11 @@ -import { screen, waitFor, fireEvent } from '@testing-library/react'; -import { within } from '@testing-library/dom'; -import { useSafeAppsSDK } from '@gnosis.pm/safe-apps-react-sdk'; -import { mockTxsRequest, mockInitialBalances, renderWithProviders } from '../utils/test-helpers'; -import App from '../components/App'; +import { screen, waitFor, fireEvent } from '@testing-library/react' +import { within } from '@testing-library/dom' +import { useSafeAppsSDK } from '@gnosis.pm/safe-apps-react-sdk' +import { mockTxsRequest, mockInitialBalances, renderWithProviders } from '../utils/test-helpers' +import App from '../components/App' jest.mock('@gnosis.pm/safe-apps-react-sdk', () => { - const originalModule = jest.requireActual('@gnosis.pm/safe-apps-react-sdk'); + const originalModule = jest.requireActual('@gnosis.pm/safe-apps-react-sdk') const sdk = { sdk: { txs: { send: jest.fn().mockResolvedValue({ safeTxHash: 'safeTxHash' }) }, @@ -37,142 +37,146 @@ jest.mock('@gnosis.pm/safe-apps-react-sdk', () => { safeAddress: '0x57CB13cbef735FbDD65f5f2866638c546464E45F', chainId: 'chainId', }, - }; + } return { ...originalModule, useSafeAppsSDK: () => sdk, - }; -}); + } +}) describe('', () => { it('should render the tokens in the safe balance', async () => { - renderWithProviders(); + renderWithProviders() - expect(await screen.findByText(/ether/i)).toBeInTheDocument(); - expect(await screen.findByText(/0.949938510499549077/)).toBeInTheDocument(); - }); + expect(await screen.findByText(/ether/i)).toBeInTheDocument() + expect(await screen.findByText(/0.949938510499549077/)).toBeInTheDocument() + }) it('should drain the safe when submit button is clicked', async () => { - renderWithProviders(); - const { sdk } = useSafeAppsSDK(); + renderWithProviders() + const { sdk } = useSafeAppsSDK() - await screen.findByText(/chainlink token/i); - fireEvent.change(screen.getByRole('textbox'), { target: { value: '0x301812eb4c89766875eFe61460f7a8bBC0CadB96' } }); - fireEvent.click(screen.getByText(/transfer everything/i)); - await waitFor(() => expect(sdk.txs.send).toHaveBeenCalledWith(mockTxsRequest)); - }); + await screen.findByText(/chainlink token/i) + fireEvent.change(screen.getByRole('textbox'), { + target: { value: '0x301812eb4c89766875eFe61460f7a8bBC0CadB96' }, + }) + fireEvent.click(screen.getByText(/transfer everything/i)) + await waitFor(() => expect(sdk.txs.send).toHaveBeenCalledWith(mockTxsRequest)) + }) it('should drain the safe when submit button is clicked removing the tokens excluded by the user', async () => { - renderWithProviders(); - const { sdk } = useSafeAppsSDK(); + renderWithProviders() + const { sdk } = useSafeAppsSDK() - await screen.findByText(/chainlink token/i); - const checkboxes = await screen.findAllByRole('checkbox'); + await screen.findByText(/chainlink token/i) + const checkboxes = await screen.findAllByRole('checkbox') - fireEvent.click(checkboxes[2]); - fireEvent.click(checkboxes[4]); - fireEvent.click(checkboxes[5]); - fireEvent.change(screen.getByRole('textbox'), { target: { value: '0x301812eb4c89766875eFe61460f7a8bBC0CadB96' } }); - fireEvent.click(screen.getByText(/transfer 2 assets/i)); + fireEvent.click(checkboxes[2]) + fireEvent.click(checkboxes[4]) + fireEvent.click(checkboxes[5]) + fireEvent.change(screen.getByRole('textbox'), { + target: { value: '0x301812eb4c89766875eFe61460f7a8bBC0CadB96' }, + }) + fireEvent.click(screen.getByText(/transfer 2 assets/i)) await waitFor(() => expect(sdk.txs.send).toHaveBeenCalledWith({ txs: [mockTxsRequest.txs[0], mockTxsRequest.txs[2]], }), - ); - }); + ) + }) it('should show an error if no recipient address is entered', async () => { - renderWithProviders(); + renderWithProviders() - await screen.findByText(/chainLink token/i); - fireEvent.click(screen.getByText(/transfer everything/i)); + await screen.findByText(/chainLink token/i) + fireEvent.click(screen.getByText(/transfer everything/i)) - expect(await screen.findByText(/please enter a valid recipient address/i)).toBeInTheDocument(); - }); + expect(await screen.findByText(/please enter a valid recipient address/i)).toBeInTheDocument() + }) it('should allow to order token by string prop', async () => { - renderWithProviders(); + renderWithProviders() - await screen.findByText(/chainlink token/i); - const assetColumnHeaderElement = screen.getByText(/asset/i); - fireEvent.click(assetColumnHeaderElement); + await screen.findByText(/chainlink token/i) + const assetColumnHeaderElement = screen.getByText(/asset/i) + fireEvent.click(assetColumnHeaderElement) await waitFor(() => { - const tableRows = document.querySelectorAll('.MuiDataGrid-row'); - expect(within(tableRows[4]).getByText(/chainlink token/i)).toBeDefined(); - expect(within(tableRows[0]).getByText(/uniswap/i)).toBeDefined(); - }); + const tableRows = document.querySelectorAll('.MuiDataGrid-row') + expect(within(tableRows[4]).getByText(/chainlink token/i)).toBeDefined() + expect(within(tableRows[0]).getByText(/uniswap/i)).toBeDefined() + }) - fireEvent.click(assetColumnHeaderElement); + fireEvent.click(assetColumnHeaderElement) await waitFor(() => { - const tableRows = document.querySelectorAll('.MuiDataGrid-row'); - expect(within(tableRows[0]).getByText(/chainlink token/i)).toBeDefined(); - expect(within(tableRows[4]).getByText(/uniswap/i)).toBeDefined(); - }); - }); + const tableRows = document.querySelectorAll('.MuiDataGrid-row') + expect(within(tableRows[0]).getByText(/chainlink token/i)).toBeDefined() + expect(within(tableRows[4]).getByText(/uniswap/i)).toBeDefined() + }) + }) it('should allow to order token by numeric prop', async () => { - renderWithProviders(); + renderWithProviders() - await screen.findByText(/chainLink token/i); - const amountColumnHeaderElement = screen.getByText(/amount/i); - fireEvent.click(amountColumnHeaderElement); + await screen.findByText(/chainLink token/i) + const amountColumnHeaderElement = screen.getByText(/amount/i) + fireEvent.click(amountColumnHeaderElement) await waitFor(() => { - const tableRows = document.querySelectorAll('.MuiDataGrid-row'); - expect(within(tableRows[0]).getByText(/dai/i)).toBeDefined(); - expect(within(tableRows[4]).getByText(/maker/i)).toBeDefined(); - }); + const tableRows = document.querySelectorAll('.MuiDataGrid-row') + expect(within(tableRows[0]).getByText(/dai/i)).toBeDefined() + expect(within(tableRows[4]).getByText(/maker/i)).toBeDefined() + }) - fireEvent.click(amountColumnHeaderElement); + fireEvent.click(amountColumnHeaderElement) await waitFor(() => { - const tableRows = document.querySelectorAll('.MuiDataGrid-row'); - expect(within(tableRows[4]).getByText(/dai/i)).toBeDefined(); - expect(within(tableRows[0]).getByText(/maker/i)).toBeDefined(); - }); - }); + const tableRows = document.querySelectorAll('.MuiDataGrid-row') + expect(within(tableRows[4]).getByText(/dai/i)).toBeDefined() + expect(within(tableRows[0]).getByText(/maker/i)).toBeDefined() + }) + }) it('Shows a Warning icon when token transfer cost is higher than its current market value ', async () => { - renderWithProviders(); + renderWithProviders() - await screen.findByText(/maker/i); + await screen.findByText(/maker/i) const warningTooltip = - /Beware that the cost of this token transfer could be higher than its current market value \(Estimated transfer cost: /i; + /Beware that the cost of this token transfer could be higher than its current market value \(Estimated transfer cost: /i await waitFor(() => { - const tableRows = document.querySelectorAll('.MuiDataGrid-row'); + const tableRows = document.querySelectorAll('.MuiDataGrid-row') // warning only should be present in Maker (MKR) row - const makerRow = tableRows[3]; - expect(within(makerRow).getByText(/maker/i)).toBeDefined(); - expect(within(makerRow).queryByTitle(warningTooltip)).toBeInTheDocument(); + const makerRow = tableRows[3] + expect(within(makerRow).getByText(/maker/i)).toBeDefined() + expect(within(makerRow).queryByTitle(warningTooltip)).toBeInTheDocument() // warning should NOT be present in other rows - expect(within(tableRows[0]).queryByTitle(warningTooltip)).not.toBeInTheDocument(); - expect(within(tableRows[1]).queryByTitle(warningTooltip)).not.toBeInTheDocument(); - expect(within(tableRows[2]).queryByTitle(warningTooltip)).not.toBeInTheDocument(); - }); - }); + expect(within(tableRows[0]).queryByTitle(warningTooltip)).not.toBeInTheDocument() + expect(within(tableRows[1]).queryByTitle(warningTooltip)).not.toBeInTheDocument() + expect(within(tableRows[2]).queryByTitle(warningTooltip)).not.toBeInTheDocument() + }) + }) it('Filter native token without value', async () => { - let balances = mockInitialBalances; - balances[0].fiatBalance = '0.00000'; - const { sdk } = useSafeAppsSDK(); + let balances = mockInitialBalances + balances[0].fiatBalance = '0.00000' + const { sdk } = useSafeAppsSDK() sdk.safe.experimental_getBalances = jest.fn().mockImplementationOnce(() => Promise.resolve({ items: balances, }), - ); + ) - renderWithProviders(); + renderWithProviders() - await screen.findByText(/maker/i); + await screen.findByText(/maker/i) - expect(document.querySelectorAll('.MuiDataGrid-row').length).toEqual(4); - }); -}); + expect(document.querySelectorAll('.MuiDataGrid-row').length).toEqual(4) + }) +}) diff --git a/apps/drain-safe/src/__tests__/sdk-helpers.test.js b/apps/drain-safe/src/__tests__/sdk-helpers.test.js index e2c23b8df..07522e8da 100644 --- a/apps/drain-safe/src/__tests__/sdk-helpers.test.js +++ b/apps/drain-safe/src/__tests__/sdk-helpers.test.js @@ -1,27 +1,27 @@ -import { encodeTxData, tokenToTx } from '../utils/sdk-helpers'; -import erc20 from '../abis/erc20'; +import { encodeTxData, tokenToTx } from '../utils/sdk-helpers' +import erc20 from '../abis/erc20' describe('Safe SDK helpers', () => { describe('encodeTxData', () => { it('encodes a simple transfer call', () => { - const data = encodeTxData(erc20.transfer, '0xb3b83bf204C458B461de9B0CD2739DB152b4fa5A', 1000); + const data = encodeTxData(erc20.transfer, '0xb3b83bf204C458B461de9B0CD2739DB152b4fa5A', 1000) expect(data).toEqual( '0xa9059cbb000000000000000000000000b3b83bf204c458b461de9b0cd2739db152b4fa5a00000000000000000000000000000000000000000000000000000000000003e8', - ); + ) - const data2 = encodeTxData(erc20.transfer, '0xb3b83bf204C458B461de9B0CD2739DB152b4fa5A', 1); + const data2 = encodeTxData(erc20.transfer, '0xb3b83bf204C458B461de9B0CD2739DB152b4fa5A', 1) expect(data2).toEqual( '0xa9059cbb000000000000000000000000b3b83bf204c458b461de9b0cd2739db152b4fa5a0000000000000000000000000000000000000000000000000000000000000001', - ); - }); + ) + }) it('fails on invalid address', () => { - const badAddress = '0xb3b83bf204C458B46'; + const badAddress = '0xb3b83bf204C458B46' expect(() => encodeTxData(erc20.transfer, badAddress, 1000)).toThrow( `Given address "${badAddress}" is not a valid Ethereum address.`, - ); - }); - }); + ) + }) + }) describe('tokenToTx', () => { it('creats a tx for a ERC-20 token', () => { @@ -30,20 +30,24 @@ describe('Safe SDK helpers', () => { decimals: 18, symbol: 'DAI', name: 'Dai', - logoUri: 'https://gnosis-safe-token-logos.s3.amazonaws.com/0x5592EC0cfb4dbc12D3aB100b257153436a1f0FEa.png', + logoUri: + 'https://gnosis-safe-token-logos.s3.amazonaws.com/0x5592EC0cfb4dbc12D3aB100b257153436a1f0FEa.png', + }, + tokenInfo: { + type: 'ERC20', + address: '0x5592EC0cfb4dbc12D3aB100b257153436a1f0FEa', }, - tokenInfo: { type: 'ERC20', address: '0x5592EC0cfb4dbc12D3aB100b257153436a1f0FEa' }, balance: '20000000000000000000000', fiatBalance: '136972.3434', fiatConversion: '6.8486', - }); + }) expect(tx).toEqual({ to: '0x5592EC0cfb4dbc12D3aB100b257153436a1f0FEa', value: '0', data: '0xa9059cbb000000000000000000000000b3b83bf204c458b461de9b0cd2739db152b4fa5a00000000000000000000000000000000000000000000043c33c1937564800000', - }); - }); + }) + }) it('creats a tx for ETH', () => { const tx = tokenToTx('0xb3b83bf204C458B461de9B0CD2739DB152b4fa5A', { @@ -52,13 +56,13 @@ describe('Safe SDK helpers', () => { fiatBalance: '4574.8946', fiatConversion: '2287.4473', tokenInfo: { type: 'NATIVE_TOKEN', address: null }, - }); + }) expect(tx).toEqual({ to: '0xb3b83bf204C458B461de9B0CD2739DB152b4fa5A', value: '2000000000000000000', data: '0x', - }); - }); - }); -}); + }) + }) + }) +}) diff --git a/apps/drain-safe/src/abis/erc20.ts b/apps/drain-safe/src/abis/erc20.ts index 7d0ae473b..2ddc37b1b 100644 --- a/apps/drain-safe/src/abis/erc20.ts +++ b/apps/drain-safe/src/abis/erc20.ts @@ -1,4 +1,4 @@ -import { AbiItem } from 'web3-utils'; +import { AbiItem } from 'web3-utils' const erc20: { [key: string]: AbiItem } = { transfer: { @@ -24,6 +24,6 @@ const erc20: { [key: string]: AbiItem } = { stateMutability: 'nonpayable', type: 'function', }, -}; +} -export default erc20; +export default erc20 diff --git a/apps/drain-safe/src/components/AddressInput.tsx b/apps/drain-safe/src/components/AddressInput.tsx index c133a9532..dd7f33306 100644 --- a/apps/drain-safe/src/components/AddressInput.tsx +++ b/apps/drain-safe/src/components/AddressInput.tsx @@ -1,5 +1,5 @@ -import styled from 'styled-components'; -import { AddressInput } from '@gnosis.pm/safe-react-components'; +import styled from 'styled-components' +import { AddressInput } from '@gnosis.pm/safe-react-components' export default styled(AddressInput)` && { @@ -14,4 +14,4 @@ export default styled(AddressInput)` color: #008c73; } } -`; +` diff --git a/apps/drain-safe/src/components/App.tsx b/apps/drain-safe/src/components/App.tsx index aafc99621..73f662322 100644 --- a/apps/drain-safe/src/components/App.tsx +++ b/apps/drain-safe/src/components/App.tsx @@ -1,133 +1,133 @@ -import React, { useState, useEffect, useMemo, useCallback } from 'react'; -import { Title, Text } from '@gnosis.pm/safe-react-components'; -import { useSafeAppsSDK } from '@gnosis.pm/safe-apps-react-sdk'; -import web3Utils from 'web3-utils'; -import { BigNumber } from 'bignumber.js'; - -import useBalances, { BalancesType } from '../hooks/use-balances'; -import { tokenToTx } from '../utils/sdk-helpers'; -import FormContainer from './FormContainer'; -import Flex from './Flex'; -import Logo from './Logo'; -import Balances from './Balances'; -import SubmitButton from './SubmitButton'; -import CancelButton from './CancelButton'; -import AddressInput from './AddressInput'; -import useWeb3 from '../hooks/useWeb3'; -import TimedComponent from './TimedComponent'; +import React, { useState, useEffect, useMemo, useCallback } from 'react' +import { Title, Text } from '@gnosis.pm/safe-react-components' +import { useSafeAppsSDK } from '@gnosis.pm/safe-apps-react-sdk' +import web3Utils from 'web3-utils' +import { BigNumber } from 'bignumber.js' + +import useBalances, { BalancesType } from '../hooks/use-balances' +import { tokenToTx } from '../utils/sdk-helpers' +import FormContainer from './FormContainer' +import Flex from './Flex' +import Logo from './Logo' +import Balances from './Balances' +import SubmitButton from './SubmitButton' +import CancelButton from './CancelButton' +import AddressInput from './AddressInput' +import useWeb3 from '../hooks/useWeb3' +import TimedComponent from './TimedComponent' const App = (): React.ReactElement => { - const { sdk, safe } = useSafeAppsSDK(); - const { web3 } = useWeb3(); + const { sdk, safe } = useSafeAppsSDK() + const { web3 } = useWeb3() const { assets, selectedTokens, setSelectedTokens, error: balancesError, - }: BalancesType = useBalances(safe.safeAddress, safe.chainId); - const [submitting, setSubmitting] = useState(false); - const [toAddress, setToAddress] = useState(''); - const [isFinished, setFinished] = useState(false); - const [error, setError] = useState(''); - const [gasPrice, setGasPrice] = useState(new BigNumber(0)); - const [networkPrefix, setNetworkPrefix] = useState(''); + }: BalancesType = useBalances(safe.safeAddress, safe.chainId) + const [submitting, setSubmitting] = useState(false) + const [toAddress, setToAddress] = useState('') + const [isFinished, setFinished] = useState(false) + const [error, setError] = useState('') + const [gasPrice, setGasPrice] = useState(new BigNumber(0)) + const [networkPrefix, setNetworkPrefix] = useState('') const onError = (userMsg: string, err: Error) => { - setError(`${userMsg}: ${err.message}`); - console.error(userMsg, err); - }; + setError(`${userMsg}: ${err.message}`) + console.error(userMsg, err) + } const sendTxs = async (): Promise => { const txs = assets - .filter((item) => selectedTokens.includes(item.tokenInfo.address)) - .map((item) => tokenToTx(toAddress, item)); - const data = await sdk.txs.send({ txs }); + .filter(item => selectedTokens.includes(item.tokenInfo.address)) + .map(item => tokenToTx(toAddress, item)) + const data = await sdk.txs.send({ txs }) - return data?.safeTxHash; - }; + return data?.safeTxHash + } const submitTx = async (): Promise => { if (!web3Utils.isAddress(toAddress)) { - setError('Please enter a valid recipient address'); - return; + setError('Please enter a valid recipient address') + return } - setError(''); - setSubmitting(true); + setError('') + setSubmitting(true) try { - await sendTxs(); + await sendTxs() } catch (e) { - setSubmitting(false); - onError('Failed sending transactions', e as Error); - return; + setSubmitting(false) + onError('Failed sending transactions', e as Error) + return } - setSubmitting(false); - setFinished(true); - setToAddress(''); - setSelectedTokens(assets.map((token) => token.tokenInfo.address)); - }; + setSubmitting(false) + setFinished(true) + setToAddress('') + setSelectedTokens(assets.map(token => token.tokenInfo.address)) + } const onSubmit = (e: React.FormEvent) => { - e.preventDefault(); - submitTx(); - }; + e.preventDefault() + submitTx() + } const onCancel = () => { - setError(''); - setSubmitting(false); - }; + setError('') + setSubmitting(false) + } const onToAddressChange = useCallback((address: string): void => { - setToAddress(address); - setError(''); - }, []); + setToAddress(address) + setError('') + }, []) const transferStatusText = useMemo(() => { if (!selectedTokens.length) { - return 'No tokens selected'; + return 'No tokens selected' } if (selectedTokens.length === assets.length) { - return 'Transfer everything'; + return 'Transfer everything' } - const assetsToTransferCount = selectedTokens.length; - return `Transfer ${assetsToTransferCount} asset${assetsToTransferCount > 1 ? 's' : ''}`; - }, [assets, selectedTokens]); + const assetsToTransferCount = selectedTokens.length + return `Transfer ${assetsToTransferCount} asset${assetsToTransferCount > 1 ? 's' : ''}` + }, [assets, selectedTokens]) const getAddressFromDomain = useCallback( (address: string) => web3?.eth.ens.getAddress(address) || Promise.resolve(address), [web3], - ); + ) useEffect(() => { if (balancesError) { - onError('Failed fetching balances', balancesError); + onError('Failed fetching balances', balancesError) } - }, [balancesError]); + }, [balancesError]) useEffect(() => { sdk.eth.getGasPrice().then((gasPrice: string) => { - setGasPrice(new BigNumber(gasPrice)); - }); - }, [sdk.eth]); + setGasPrice(new BigNumber(gasPrice)) + }) + }, [sdk.eth]) - const ethFiatPrice = Number(assets[0]?.fiatConversion || 0); + const ethFiatPrice = Number(assets[0]?.fiatConversion || 0) useEffect(() => { const getChainInfo = async () => { try { - const { shortName } = await sdk.safe.getChainInfo(); - setNetworkPrefix(shortName); + const { shortName } = await sdk.safe.getChainInfo() + setNetworkPrefix(shortName) } catch (e) { - console.error('Unable to get chain info:', e); + console.error('Unable to get chain info:', e) } - }; + } - getChainInfo(); - }, [sdk]); + getChainInfo() + }, [sdk]) return ( @@ -177,7 +177,7 @@ const App = (): React.ReactElement => { You don't have any transferable assets )} - ); -}; + ) +} -export default App; +export default App diff --git a/apps/drain-safe/src/components/Balances.tsx b/apps/drain-safe/src/components/Balances.tsx index a49a8c256..3168e5d09 100644 --- a/apps/drain-safe/src/components/Balances.tsx +++ b/apps/drain-safe/src/components/Balances.tsx @@ -1,14 +1,14 @@ -import { useMemo, useState, useEffect } from 'react'; -import { DataTable } from '@gnosis.pm/safe-react-components'; -import { GridColDef, GridRowsProp, GridSelectionModel, GridDensityTypes } from '@mui/x-data-grid'; -import { TokenBalance, TokenInfo, TokenType } from '@gnosis.pm/safe-apps-sdk'; -import BigNumber from 'bignumber.js'; +import { useMemo, useState, useEffect } from 'react' +import { DataTable } from '@gnosis.pm/safe-react-components' +import { GridColDef, GridRowsProp, GridSelectionModel, GridDensityTypes } from '@mui/x-data-grid' +import { TokenBalance, TokenInfo, TokenType } from '@gnosis.pm/safe-apps-sdk' +import BigNumber from 'bignumber.js' -import { formatTokenValue } from '../utils/formatters'; -import Icon from './Icon'; -import CurrencyCell from './CurrencyCell'; +import { formatTokenValue } from '../utils/formatters' +import Icon from './Icon' +import CurrencyCell from './CurrencyCell' -const CURRENCY = 'USD'; +const CURRENCY = 'USD' const ethToken: TokenInfo = { logoUri: './eth.svg', @@ -17,7 +17,7 @@ const ethToken: TokenInfo = { decimals: 18, type: TokenType['NATIVE_TOKEN'], address: '', -}; +} function Balances({ assets, @@ -25,16 +25,16 @@ function Balances({ gasPrice, ethFiatPrice, }: { - assets: TokenBalance[]; - ethFiatPrice: number; - onSelectionChange: (addresses: string[]) => void; - gasPrice: BigNumber; + assets: TokenBalance[] + ethFiatPrice: number + onSelectionChange: (addresses: string[]) => void + gasPrice: BigNumber }): JSX.Element { - const [selectionModel, setSelectionModel] = useState([]); + const [selectionModel, setSelectionModel] = useState([]) useEffect(() => { - setSelectionModel(assets.map((item) => item.tokenInfo.address)); - }, [assets]); + setSelectionModel(assets.map(item => item.tokenInfo.address)) + }, [assets]) const dataGridColumns: GridColDef[] = [ { @@ -43,24 +43,24 @@ function Balances({ flex: 1, sortComparator: (v1, v2, param1: any, param2: any) => { if (param1.value.name < param2.value.name) { - return -1; + return -1 } if (param1.value.name > param2.value.name) { - return 1; + return 1 } - return 0; + return 0 }, renderCell: (params: any) => { - const { logoUri, symbol, name } = params.value; + const { logoUri, symbol, name } = params.value return ( <> {name} - ); + ) }, }, { @@ -68,7 +68,7 @@ function Balances({ headerName: 'Amount', flex: 1, sortComparator: (v1, v2, param1: any, param2: any) => { - return param1.value - param2.value; + return param1.value - param2.value }, }, { @@ -76,28 +76,33 @@ function Balances({ headerName: 'Value', flex: 1, sortComparator: (v1, v2, param1: any, param2: any) => { - return param1.value.fiatBalance - param2.value.fiatBalance; + return param1.value.fiatBalance - param2.value.fiatBalance }, renderCell: (params: any) => ( - + ), }, - ]; + ] const dataGridRows: GridRowsProp = useMemo( () => assets.slice().map((item: TokenBalance) => { - const token = item.tokenInfo || ethToken; + const token = item.tokenInfo || ethToken return { id: token.address, asset: token, amount: formatTokenValue(item.balance, token.decimals), value: item, - }; + } }), [assets], - ); + ) return ( { - setSelectionModel(newSelection); - onSelectionChange(newSelection as string[]); + setSelectionModel(newSelection) + onSelectionChange(newSelection as string[]) }} /> - ); + ) } -export default Balances; +export default Balances diff --git a/apps/drain-safe/src/components/CancelButton.tsx b/apps/drain-safe/src/components/CancelButton.tsx index 8442d4935..4056e33a1 100644 --- a/apps/drain-safe/src/components/CancelButton.tsx +++ b/apps/drain-safe/src/components/CancelButton.tsx @@ -1,5 +1,5 @@ -import { Button, Loader } from '@gnosis.pm/safe-react-components'; -import Flex from './Flex'; +import { Button, Loader } from '@gnosis.pm/safe-react-components' +import Flex from './Flex' function CancelButton({ children }: { children: string }): JSX.Element { return ( @@ -13,7 +13,7 @@ function CancelButton({ children }: { children: string }): JSX.Element { - ); + ) } -export default CancelButton; +export default CancelButton diff --git a/apps/drain-safe/src/components/CurrencyCell.tsx b/apps/drain-safe/src/components/CurrencyCell.tsx index 7da1c0487..ece22bb49 100644 --- a/apps/drain-safe/src/components/CurrencyCell.tsx +++ b/apps/drain-safe/src/components/CurrencyCell.tsx @@ -1,13 +1,13 @@ -import { TokenBalance } from '@gnosis.pm/safe-apps-sdk'; -import { Icon, Tooltip } from '@gnosis.pm/safe-react-components'; -import BigNumber from 'bignumber.js'; -import { useEffect, useState } from 'react'; -import styled from 'styled-components'; -import web3Utils from 'web3-utils'; -import { formatCurrencyValue } from '../utils/formatters'; -import { tokenToTx } from '../utils/sdk-helpers'; -import Flex from './Flex'; -import { useSafeAppsSDK } from '@gnosis.pm/safe-apps-react-sdk'; +import { TokenBalance } from '@gnosis.pm/safe-apps-sdk' +import { Icon, Tooltip } from '@gnosis.pm/safe-react-components' +import BigNumber from 'bignumber.js' +import { useEffect, useState } from 'react' +import styled from 'styled-components' +import web3Utils from 'web3-utils' +import { formatCurrencyValue } from '../utils/formatters' +import { tokenToTx } from '../utils/sdk-helpers' +import Flex from './Flex' +import { useSafeAppsSDK } from '@gnosis.pm/safe-apps-react-sdk' function CurrencyCell({ item, @@ -15,49 +15,54 @@ function CurrencyCell({ gasPrice, ethFiatPrice, }: { - item: TokenBalance; - currency: string; - gasPrice: BigNumber; - ethFiatPrice: number; + item: TokenBalance + currency: string + gasPrice: BigNumber + ethFiatPrice: number }) { - const label = formatCurrencyValue(item.fiatBalance, currency); - const [transferCostInFiat, setTransferCostInFiat] = useState(new BigNumber(0)); + const label = formatCurrencyValue(item.fiatBalance, currency) + const [transferCostInFiat, setTransferCostInFiat] = useState(new BigNumber(0)) - const { safe, sdk } = useSafeAppsSDK(); + const { safe, sdk } = useSafeAppsSDK() // Transfer cost estimation useEffect(() => { const estimateTransferCost = async () => { try { - const sendTokenTx = tokenToTx(safe.safeAddress, item); + const sendTokenTx = tokenToTx(safe.safeAddress, item) const estimatedTransferGas = await sdk.eth.getEstimateGas({ ...sendTokenTx, - value: item.tokenInfo.type === 'NATIVE_TOKEN' ? `0x${Number(sendTokenTx.value).toString(16)}` : undefined, + value: + item.tokenInfo.type === 'NATIVE_TOKEN' + ? `0x${Number(sendTokenTx.value).toString(16)}` + : undefined, from: safe.safeAddress, - }); + }) - const gasCostInWei = gasPrice.multipliedBy(estimatedTransferGas); - const gasCostInEther = new BigNumber(web3Utils.fromWei(gasCostInWei.toString(), 'ether')); + const gasCostInWei = gasPrice.multipliedBy(estimatedTransferGas) + const gasCostInEther = new BigNumber(web3Utils.fromWei(gasCostInWei.toString(), 'ether')) - const transferCostInFiat = gasCostInEther.multipliedBy(ethFiatPrice); + const transferCostInFiat = gasCostInEther.multipliedBy(ethFiatPrice) - setTransferCostInFiat(transferCostInFiat); + setTransferCostInFiat(transferCostInFiat) } catch (e) { - console.log('Error: ', e); + console.log('Error: ', e) } - }; - estimateTransferCost(); - }, [gasPrice, ethFiatPrice, item, sdk, safe]); + } + estimateTransferCost() + }, [gasPrice, ethFiatPrice, item, sdk, safe]) // if transfer cost is higher than token market value, we show a warning icon & tooltip in the cell const showWarningIcon = - ethFiatPrice > 0 && gasPrice.toNumber() > 0 && transferCostInFiat.toNumber() >= Number(item.fiatBalance); + ethFiatPrice > 0 && + gasPrice.toNumber() > 0 && + transferCostInFiat.toNumber() >= Number(item.fiatBalance) const warningTooltip = `Beware that the cost of this token transfer could be higher than its current market value (Estimated transfer cost: ${formatCurrencyValue( transferCostInFiat.toString(), currency, - )})`; + )})` return showWarningIcon ? ( @@ -68,11 +73,11 @@ function CurrencyCell({ ) : ( {label} - ); + ) } -export default CurrencyCell; +export default CurrencyCell const StyledIcon = styled(Icon)` margin-left: 4px; -`; +` diff --git a/apps/drain-safe/src/components/Flex.tsx b/apps/drain-safe/src/components/Flex.tsx index 8cdd40212..20a45bdd1 100644 --- a/apps/drain-safe/src/components/Flex.tsx +++ b/apps/drain-safe/src/components/Flex.tsx @@ -1,14 +1,14 @@ -import styled, { css } from 'styled-components'; +import styled, { css } from 'styled-components' const Flex = styled.div<{ centered?: boolean }>` display: flex; align-items: center; - ${(props) => + ${props => props.centered && css` justify-content: center; `} -`; +` -export default Flex; +export default Flex diff --git a/apps/drain-safe/src/components/FormContainer.tsx b/apps/drain-safe/src/components/FormContainer.tsx index c3ceb2b7f..b01ad546a 100644 --- a/apps/drain-safe/src/components/FormContainer.tsx +++ b/apps/drain-safe/src/components/FormContainer.tsx @@ -1,4 +1,4 @@ -import styled from 'styled-components'; +import styled from 'styled-components' const FormContainer = styled.form` margin-bottom: 2rem; @@ -10,6 +10,6 @@ const FormContainer = styled.form` grid-template-columns: 1fr; grid-column-gap: 1rem; grid-row-gap: 1rem; -`; +` -export default FormContainer; +export default FormContainer diff --git a/apps/drain-safe/src/components/Icon.tsx b/apps/drain-safe/src/components/Icon.tsx index d1f20c92b..5fa5887fd 100644 --- a/apps/drain-safe/src/components/Icon.tsx +++ b/apps/drain-safe/src/components/Icon.tsx @@ -1,30 +1,30 @@ -import React, { useState } from 'react'; -import styled from 'styled-components'; +import React, { useState } from 'react' +import styled from 'styled-components' interface Props { - logoUri: string | null; - symbol: string; + logoUri: string | null + symbol: string } const IconImg = styled.img` margin-right: 10px; height: 1.5em; width: auto; -`; +` -const defaultIcon = './question.svg'; +const defaultIcon = './question.svg' function Icon(props: Props): JSX.Element | null { - const [fallbackIcon, setFallbackIcon] = useState(''); - const { logoUri, symbol } = props; + const [fallbackIcon, setFallbackIcon] = useState('') + const { logoUri, symbol } = props const onError = () => { if (!fallbackIcon) { - setFallbackIcon(defaultIcon); + setFallbackIcon(defaultIcon) } - }; + } - return ; + return } -export default Icon; +export default Icon diff --git a/apps/drain-safe/src/components/Logo.tsx b/apps/drain-safe/src/components/Logo.tsx index 77b81cf76..afb1a6a82 100644 --- a/apps/drain-safe/src/components/Logo.tsx +++ b/apps/drain-safe/src/components/Logo.tsx @@ -1,14 +1,14 @@ -import React from 'react'; -import styled from 'styled-components'; +import React from 'react' +import styled from 'styled-components' const IconImg = styled.img` margin-right: 10px; height: 3em; width: auto; -`; +` function Logo(): JSX.Element { - return ; + return } -export default Logo; +export default Logo diff --git a/apps/drain-safe/src/components/SubmitButton.tsx b/apps/drain-safe/src/components/SubmitButton.tsx index 5bb6e51cb..30c204cfd 100644 --- a/apps/drain-safe/src/components/SubmitButton.tsx +++ b/apps/drain-safe/src/components/SubmitButton.tsx @@ -1,14 +1,20 @@ -import { Button } from '@gnosis.pm/safe-react-components'; -import Flex from './Flex'; +import { Button } from '@gnosis.pm/safe-react-components' +import Flex from './Flex' -function SubmitButton({ children, disabled }: { children: string; disabled?: boolean }): JSX.Element { +function SubmitButton({ + children, + disabled, +}: { + children: string + disabled?: boolean +}): JSX.Element { return ( - ); + ) } -export default SubmitButton; +export default SubmitButton diff --git a/apps/drain-safe/src/components/TimedComponent.tsx b/apps/drain-safe/src/components/TimedComponent.tsx index 472747e11..ab143cfc7 100644 --- a/apps/drain-safe/src/components/TimedComponent.tsx +++ b/apps/drain-safe/src/components/TimedComponent.tsx @@ -1,14 +1,14 @@ -import useTimeout from '../hooks/useTimeout'; +import useTimeout from '../hooks/useTimeout' type Props = { - onTimeout: () => void; - timeout: number; -}; + onTimeout: () => void + timeout: number +} const TimedComponent: React.FC = ({ onTimeout, timeout, children }) => { - useTimeout(onTimeout, timeout); + useTimeout(onTimeout, timeout) - return children as React.ReactElement; -}; + return children as React.ReactElement +} -export default TimedComponent; +export default TimedComponent diff --git a/apps/drain-safe/src/fonts.d.ts b/apps/drain-safe/src/fonts.d.ts index 7bbf68bbc..85a0dfc7e 100644 --- a/apps/drain-safe/src/fonts.d.ts +++ b/apps/drain-safe/src/fonts.d.ts @@ -1,2 +1,2 @@ -declare module '*.woff'; -declare module '*.woff2'; +declare module '*.woff' +declare module '*.woff2' diff --git a/apps/drain-safe/src/hooks/use-balances.ts b/apps/drain-safe/src/hooks/use-balances.ts index 475cac02d..6d7a95f85 100644 --- a/apps/drain-safe/src/hooks/use-balances.ts +++ b/apps/drain-safe/src/hooks/use-balances.ts @@ -1,45 +1,48 @@ -import { useState, useEffect, useCallback } from 'react'; -import { useSafeAppsSDK } from '@gnosis.pm/safe-apps-react-sdk'; -import { TokenBalance } from '@gnosis.pm/safe-apps-sdk'; -import { NATIVE_TOKEN } from '../utils/sdk-helpers'; +import { useState, useEffect, useCallback } from 'react' +import { useSafeAppsSDK } from '@gnosis.pm/safe-apps-react-sdk' +import { TokenBalance } from '@gnosis.pm/safe-apps-sdk' +import { NATIVE_TOKEN } from '../utils/sdk-helpers' export type BalancesType = { - error?: Error; - assets: TokenBalance[]; - selectedTokens: string[]; - setSelectedTokens: (tokens: string[]) => void; -}; + error?: Error + assets: TokenBalance[] + selectedTokens: string[] + setSelectedTokens: (tokens: string[]) => void +} const transferableTokens = (item: TokenBalance) => - item.tokenInfo.type !== NATIVE_TOKEN || (item.tokenInfo.type === NATIVE_TOKEN && Number(item.fiatBalance) !== 0); + item.tokenInfo.type !== NATIVE_TOKEN || + (item.tokenInfo.type === NATIVE_TOKEN && Number(item.fiatBalance) !== 0) function useBalances(safeAddress: string, chainId: number): BalancesType { - const { sdk } = useSafeAppsSDK(); - const [assets, setAssets] = useState([]); - const [selectedTokens, setSelectedTokens] = useState([]); - const [error, setError] = useState(); + const { sdk } = useSafeAppsSDK() + const [assets, setAssets] = useState([]) + const [selectedTokens, setSelectedTokens] = useState([]) + const [error, setError] = useState() const loadBalances = useCallback(async () => { if (!safeAddress || !chainId) { - return; + return } try { - const balances = await sdk.safe.experimental_getBalances({ currency: 'USD' }); - const assets = balances.items.filter(transferableTokens); + const balances = await sdk.safe.experimental_getBalances({ + currency: 'USD', + }) + const assets = balances.items.filter(transferableTokens) - setAssets(assets); - setSelectedTokens(assets.map((token: TokenBalance) => token.tokenInfo.address)); + setAssets(assets) + setSelectedTokens(assets.map((token: TokenBalance) => token.tokenInfo.address)) } catch (err) { - setError(err as Error); + setError(err as Error) } - }, [safeAddress, chainId, sdk]); + }, [safeAddress, chainId, sdk]) useEffect(() => { - loadBalances(); - }, [loadBalances]); + loadBalances() + }, [loadBalances]) - return { error, assets, selectedTokens, setSelectedTokens }; + return { error, assets, selectedTokens, setSelectedTokens } } -export default useBalances; +export default useBalances diff --git a/apps/drain-safe/src/hooks/useTimeout.ts b/apps/drain-safe/src/hooks/useTimeout.ts index 2eb70926d..6015efc26 100644 --- a/apps/drain-safe/src/hooks/useTimeout.ts +++ b/apps/drain-safe/src/hooks/useTimeout.ts @@ -1,21 +1,21 @@ -import { useEffect, useLayoutEffect, useRef } from 'react'; +import { useEffect, useLayoutEffect, useRef } from 'react' function useTimeout(callback: () => void, delay: number | null) { - const savedCallback = useRef(callback); + const savedCallback = useRef(callback) useLayoutEffect(() => { - savedCallback.current = callback; - }, [callback]); + savedCallback.current = callback + }, [callback]) useEffect(() => { if (!delay && delay !== 0) { - return; + return } - const id = setTimeout(() => savedCallback.current(), delay); + const id = setTimeout(() => savedCallback.current(), delay) - return () => clearTimeout(id); - }, [delay]); + return () => clearTimeout(id) + }, [delay]) } -export default useTimeout; +export default useTimeout diff --git a/apps/drain-safe/src/hooks/useWeb3.ts b/apps/drain-safe/src/hooks/useWeb3.ts index 2ec51928b..ed91285ff 100644 --- a/apps/drain-safe/src/hooks/useWeb3.ts +++ b/apps/drain-safe/src/hooks/useWeb3.ts @@ -1,41 +1,41 @@ -import { useEffect, useState } from 'react'; -import Web3 from 'web3'; -import { useSafeAppsSDK } from '@gnosis.pm/safe-apps-react-sdk'; +import { useEffect, useState } from 'react' +import Web3 from 'web3' +import { useSafeAppsSDK } from '@gnosis.pm/safe-apps-react-sdk' function useWeb3() { - const [web3, setWeb3] = useState(); - const { sdk } = useSafeAppsSDK(); + const [web3, setWeb3] = useState() + const { sdk } = useSafeAppsSDK() useEffect(() => { const setWeb3Instance = async () => { - const chainInfo = await sdk.safe.getChainInfo(); + const chainInfo = await sdk.safe.getChainInfo() if (!chainInfo) { - return; + return } - const rpcUrlGetter = rpcUrlGetterByNetwork[chainInfo.chainId as CHAINS]; + const rpcUrlGetter = rpcUrlGetterByNetwork[chainInfo.chainId as CHAINS] if (!rpcUrlGetter) { - throw Error(`RPC URL not defined for ${chainInfo.chainName} chain`); + throw Error(`RPC URL not defined for ${chainInfo.chainName} chain`) } - const rpcUrl = rpcUrlGetter(process.env.REACT_APP_RPC_TOKEN); + const rpcUrl = rpcUrlGetter(process.env.REACT_APP_RPC_TOKEN) - const web3Instance = new Web3(rpcUrl); + const web3Instance = new Web3(rpcUrl) - setWeb3(web3Instance); - }; + setWeb3(web3Instance) + } - setWeb3Instance(); - }, [sdk.safe]); + setWeb3Instance() + }, [sdk.safe]) return { web3, - }; + } } -export default useWeb3; +export default useWeb3 export enum CHAINS { MAINNET = '1', @@ -56,12 +56,12 @@ export enum CHAINS { } export const rpcUrlGetterByNetwork: { - [key in CHAINS]: null | ((token?: string) => string); + [key in CHAINS]: null | ((token?: string) => string) } = { - [CHAINS.MAINNET]: (token) => `https://mainnet.infura.io/v3/${token}`, + [CHAINS.MAINNET]: token => `https://mainnet.infura.io/v3/${token}`, [CHAINS.MORDEN]: null, [CHAINS.ROPSTEN]: null, - [CHAINS.RINKEBY]: (token) => `https://rinkeby.infura.io/v3/${token}`, + [CHAINS.RINKEBY]: token => `https://rinkeby.infura.io/v3/${token}`, [CHAINS.GOERLI]: null, [CHAINS.OPTIMISM]: () => 'https://mainnet.optimism.io', [CHAINS.KOVAN]: null, @@ -73,4 +73,4 @@ export const rpcUrlGetterByNetwork: { [CHAINS.AVALANCHE]: () => 'https://api.avax.network/ext/bc/C/rpc', [CHAINS.VOLTA]: () => 'https://volta-rpc.energyweb.org', [CHAINS.AURORA]: () => 'https://mainnet.aurora.dev', -}; +} diff --git a/apps/drain-safe/src/index.tsx b/apps/drain-safe/src/index.tsx index 08659ace4..c457c4d09 100644 --- a/apps/drain-safe/src/index.tsx +++ b/apps/drain-safe/src/index.tsx @@ -1,11 +1,11 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import { ThemeProvider } from 'styled-components'; -import { theme, Loader, Title } from '@gnosis.pm/safe-react-components'; -import SafeProvider from '@gnosis.pm/safe-apps-react-sdk'; +import React from 'react' +import ReactDOM from 'react-dom' +import { ThemeProvider } from 'styled-components' +import { theme, Loader, Title } from '@gnosis.pm/safe-react-components' +import SafeProvider from '@gnosis.pm/safe-apps-react-sdk' -import GlobalStyle from './GlobalStyle'; -import App from './components/App'; +import GlobalStyle from './GlobalStyle' +import App from './components/App' ReactDOM.render( @@ -24,4 +24,4 @@ ReactDOM.render( , document.getElementById('root'), -); +) diff --git a/apps/drain-safe/src/setupTests.ts b/apps/drain-safe/src/setupTests.ts index 666127af3..264828a90 100644 --- a/apps/drain-safe/src/setupTests.ts +++ b/apps/drain-safe/src/setupTests.ts @@ -1 +1 @@ -import '@testing-library/jest-dom/extend-expect'; +import '@testing-library/jest-dom/extend-expect' diff --git a/apps/drain-safe/src/utils/formatters.ts b/apps/drain-safe/src/utils/formatters.ts index 180827b0e..bf673c7d3 100644 --- a/apps/drain-safe/src/utils/formatters.ts +++ b/apps/drain-safe/src/utils/formatters.ts @@ -1,9 +1,9 @@ -import BigNumber from 'bignumber.js'; +import BigNumber from 'bignumber.js' export const formatTokenValue = (value: number | string, decimals: number): string => { - return new BigNumber(value).times(`1e-${decimals}`).toFixed(); -}; + return new BigNumber(value).times(`1e-${decimals}`).toFixed() +} export const formatCurrencyValue = (value: string, currency: string): string => { - return new Intl.NumberFormat('en-US', { style: 'currency', currency }).format(parseFloat(value)); -}; + return new Intl.NumberFormat('en-US', { style: 'currency', currency }).format(parseFloat(value)) +} diff --git a/apps/drain-safe/src/utils/sdk-helpers.ts b/apps/drain-safe/src/utils/sdk-helpers.ts index 5a2b4df1b..9e5e7e1f6 100644 --- a/apps/drain-safe/src/utils/sdk-helpers.ts +++ b/apps/drain-safe/src/utils/sdk-helpers.ts @@ -1,13 +1,16 @@ -import web3Utils, { AbiItem } from 'web3-utils'; -import abiCoder, { AbiCoder } from 'web3-eth-abi'; -import { BaseTransaction, TokenBalance, TokenType } from '@gnosis.pm/safe-apps-sdk'; -import erc20 from '../abis/erc20'; +import web3Utils, { AbiItem } from 'web3-utils' +import abiCoder, { AbiCoder } from 'web3-eth-abi' +import { BaseTransaction, TokenBalance, TokenType } from '@gnosis.pm/safe-apps-sdk' +import erc20 from '../abis/erc20' -export const NATIVE_TOKEN = TokenType['NATIVE_TOKEN']; +export const NATIVE_TOKEN = TokenType['NATIVE_TOKEN'] export function encodeTxData(method: AbiItem, recipient: string, amount: string): string { - const abi = abiCoder as unknown; // a bug in the web3-eth-abi types - return (abi as AbiCoder).encodeFunctionCall(method, [web3Utils.toChecksumAddress(recipient), amount]); + const abi = abiCoder as unknown // a bug in the web3-eth-abi types + return (abi as AbiCoder).encodeFunctionCall(method, [ + web3Utils.toChecksumAddress(recipient), + amount, + ]) } export function tokenToTx(recipient: string, item: TokenBalance): BaseTransaction { @@ -23,5 +26,5 @@ export function tokenToTx(recipient: string, item: TokenBalance): BaseTransactio to: web3Utils.toChecksumAddress(item.tokenInfo.address), value: '0', data: encodeTxData(erc20.transfer, recipient, item.balance), - }; + } } diff --git a/apps/drain-safe/src/utils/sort-helpers.ts b/apps/drain-safe/src/utils/sort-helpers.ts index 6f899296c..df3f852e3 100644 --- a/apps/drain-safe/src/utils/sort-helpers.ts +++ b/apps/drain-safe/src/utils/sort-helpers.ts @@ -1,37 +1,40 @@ function resolvePath(path: string, obj: any) { - const props = path.split('.'); - return props.reduce((prev, curr) => prev && prev[curr], obj); + const props = path.split('.') + return props.reduce((prev, curr) => prev && prev[curr], obj) } function descendingComparator(a: T, b: T, orderBy: string) { - let item1 = resolvePath(orderBy, a); - let item2 = resolvePath(orderBy, b); + let item1 = resolvePath(orderBy, a) + let item2 = resolvePath(orderBy, b) if (isNumeric(item1) && isNumeric(item2)) { - return item1 - item2; + return item1 - item2 } if (item2 < item1) { - return -1; + return -1 } if (item2 > item1) { - return 1; + return 1 } - return 0; + return 0 } function isNumeric(num: any) { - return !isNaN(num); + return !isNaN(num) } -export function getComparator(order: string, orderBy: string | undefined): (a: T, b: T) => number { +export function getComparator( + order: string, + orderBy: string | undefined, +): (a: T, b: T) => number { if (!orderBy) { - return () => 0; + return () => 0 } return order === 'desc' ? (a, b) => descendingComparator(a, b, orderBy) - : (a, b) => -descendingComparator(a, b, orderBy); + : (a, b) => -descendingComparator(a, b, orderBy) } diff --git a/apps/drain-safe/src/utils/test-helpers.tsx b/apps/drain-safe/src/utils/test-helpers.tsx index 1c88b7b70..1b9f3e4d1 100644 --- a/apps/drain-safe/src/utils/test-helpers.tsx +++ b/apps/drain-safe/src/utils/test-helpers.tsx @@ -1,5 +1,5 @@ -import { ThemeProvider } from 'styled-components'; -import { render } from '@testing-library/react'; +import { ThemeProvider } from 'styled-components' +import { render } from '@testing-library/react' export const mockTheme = { buttons: { @@ -43,7 +43,7 @@ export const mockTheme = { }, }, }, -}; +} export const mockTxsRequest = { txs: [ @@ -73,7 +73,7 @@ export const mockTxsRequest = { value: '0', }, ], -}; +} export const mockInitialBalances = [ { @@ -96,7 +96,8 @@ export const mockInitialBalances = [ decimals: 18, symbol: 'LINK', name: 'ChainLink Token', - logoUri: 'https://gnosis-safe-token-logos.s3.amazonaws.com/0x01BE23585060835E02B77ef475b0Cc51aA1e0709.png', + logoUri: + 'https://gnosis-safe-token-logos.s3.amazonaws.com/0x01BE23585060835E02B77ef475b0Cc51aA1e0709.png', }, balance: '10000000000000000000', fiatBalance: '32.17898', @@ -109,7 +110,8 @@ export const mockInitialBalances = [ decimals: 18, symbol: 'DAI', name: 'Dai Stablecoin', - logoUri: 'https://gnosis-safe-token-logos.s3.amazonaws.com/0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735.png', + logoUri: + 'https://gnosis-safe-token-logos.s3.amazonaws.com/0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735.png', }, balance: '342342323423000000000000000000', fiatBalance: '24.89904', @@ -122,7 +124,8 @@ export const mockInitialBalances = [ decimals: 18, symbol: 'MKR', name: 'Maker', - logoUri: 'https://gnosis-safe-token-logos.s3.amazonaws.com/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85.png', + logoUri: + 'https://gnosis-safe-token-logos.s3.amazonaws.com/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85.png', }, balance: '318438539290761', fiatBalance: '0.00000', @@ -135,16 +138,17 @@ export const mockInitialBalances = [ decimals: 18, symbol: 'UNI', name: 'Uniswap', - logoUri: 'https://gnosis-safe-token-logos.s3.amazonaws.com/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984.png', + logoUri: + 'https://gnosis-safe-token-logos.s3.amazonaws.com/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984.png', }, balance: '1050000000000000044', fiatBalance: '3971.92584', fiatConversion: '3782.786514637171', }, -]; +] export function renderWithProviders(ui: JSX.Element) { return { ...render({ui}), - }; + } } diff --git a/apps/ramp-network/config-overrides.js b/apps/ramp-network/config-overrides.js index cfe7814f0..f4ec1f4f0 100644 --- a/apps/ramp-network/config-overrides.js +++ b/apps/ramp-network/config-overrides.js @@ -1,8 +1,8 @@ -const webpack = require('webpack'); +const webpack = require('webpack') module.exports = { webpack: function (config, env) { - const fallback = config.resolve.fallback || {}; + const fallback = config.resolve.fallback || {} // https://github.com/ChainSafe/web3.js#web3-and-create-react-app Object.assign(fallback, { @@ -15,39 +15,39 @@ module.exports = { url: require.resolve('url'), // https://stackoverflow.com/questions/68707553/uncaught-referenceerror-buffer-is-not-defined buffer: require.resolve('buffer'), - }); + }) - config.resolve.fallback = fallback; + config.resolve.fallback = fallback config.plugins = (config.plugins || []).concat([ new webpack.ProvidePlugin({ process: 'process/browser', Buffer: ['buffer', 'Buffer'], }), - ]); + ]) // https://github.com/facebook/create-react-app/issues/11924 - config.ignoreWarnings = [/to parse source map/i]; + config.ignoreWarnings = [/to parse source map/i] - return config; + return config }, jest: function (config) { - return config; + return config }, devServer: function (configFunction) { return function (proxy, allowedHost) { - const config = configFunction(proxy, allowedHost); + const config = configFunction(proxy, allowedHost) config.headers = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET', 'Access-Control-Allow-Headers': 'X-Requested-With, content-type, Authorization', - }; + } - return config; - }; + return config + } }, paths: function (paths) { - return paths; + return paths }, -}; +} diff --git a/apps/ramp-network/src/App.tsx b/apps/ramp-network/src/App.tsx index 00c7c465d..c68286ad8 100644 --- a/apps/ramp-network/src/App.tsx +++ b/apps/ramp-network/src/App.tsx @@ -1,10 +1,10 @@ -import React, { useEffect, useState, useMemo } from 'react'; -import { useSafeAppsSDK } from '@gnosis.pm/safe-apps-react-sdk'; -import { Title, Loader, Text } from '@gnosis.pm/safe-react-components'; -import { ChainInfo } from '@gnosis.pm/safe-apps-sdk'; -import { goBack } from './utils'; -import { ASSETS_BY_CHAIN, getRampWidgetUrl, initializeRampWidget } from './ramp'; -import styled from 'styled-components'; +import React, { useEffect, useState, useMemo } from 'react' +import { useSafeAppsSDK } from '@gnosis.pm/safe-apps-react-sdk' +import { Title, Loader, Text } from '@gnosis.pm/safe-react-components' +import { ChainInfo } from '@gnosis.pm/safe-apps-sdk' +import { goBack } from './utils' +import { ASSETS_BY_CHAIN, getRampWidgetUrl, initializeRampWidget } from './ramp' +import styled from 'styled-components' const Container = styled.div` display: flex; @@ -12,7 +12,7 @@ const Container = styled.div` align-items: center; justify-content: center; height: 30em; -`; +` const NetworkNotSupported = ({ name }: { name: string }): React.ReactElement => { return ( @@ -20,27 +20,27 @@ const NetworkNotSupported = ({ name }: { name: string }): React.ReactElement => Network not supported Currently {name} is not supported - ); -}; + ) +} const SafeApp = (): React.ReactElement | null => { - const { sdk, safe } = useSafeAppsSDK(); - const [chainInfo, setChainInfo] = useState(); + const { sdk, safe } = useSafeAppsSDK() + const [chainInfo, setChainInfo] = useState() const isChainSupported = useMemo(() => { - return chainInfo && Object.keys(ASSETS_BY_CHAIN).includes(chainInfo.chainId); - }, [chainInfo]); + return chainInfo && Object.keys(ASSETS_BY_CHAIN).includes(chainInfo.chainId) + }, [chainInfo]) useEffect(() => { - (async () => { + ;(async () => { try { - const chainInfo = await sdk.safe.getChainInfo(); - setChainInfo(chainInfo); + const chainInfo = await sdk.safe.getChainInfo() + setChainInfo(chainInfo) } catch (e) { - console.error('Unable to get chain info:', e); + console.error('Unable to get chain info:', e) } - })(); - }, [sdk]); + })() + }, [sdk]) useEffect(() => { if (chainInfo && safe && isChainSupported) { @@ -49,19 +49,19 @@ const SafeApp = (): React.ReactElement | null => { address: safe.safeAddress, assets: ASSETS_BY_CHAIN[chainInfo.chainId], onClose: goBack, - }); + }) } - }, [chainInfo, safe, isChainSupported]); + }, [chainInfo, safe, isChainSupported]) if (!chainInfo || !safe) { - return ; + return } if (!isChainSupported) { - return ; + return } - return null; -}; + return null +} -export default SafeApp; +export default SafeApp diff --git a/apps/ramp-network/src/index.tsx b/apps/ramp-network/src/index.tsx index d3a40eebe..74255acc2 100644 --- a/apps/ramp-network/src/index.tsx +++ b/apps/ramp-network/src/index.tsx @@ -1,17 +1,17 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import { ThemeProvider } from 'styled-components'; -import { Loader, theme, Title } from '@gnosis.pm/safe-react-components'; -import SafeProvider from '@gnosis.pm/safe-apps-react-sdk'; +import React from 'react' +import ReactDOM from 'react-dom' +import { ThemeProvider } from 'styled-components' +import { Loader, theme, Title } from '@gnosis.pm/safe-react-components' +import SafeProvider from '@gnosis.pm/safe-apps-react-sdk' -import App from './App'; +import App from './App' const AppLoader = () => ( <> Waiting for Safe... -); +) ReactDOM.render( @@ -22,4 +22,4 @@ ReactDOM.render( , document.getElementById('root'), -); +) diff --git a/apps/ramp-network/src/ramp.ts b/apps/ramp-network/src/ramp.ts index 79413a1c5..fc45a6605 100644 --- a/apps/ramp-network/src/ramp.ts +++ b/apps/ramp-network/src/ramp.ts @@ -1,9 +1,9 @@ -import { ChainInfo } from '@gnosis.pm/safe-apps-sdk'; -import { RampInstantEvent, RampInstantSDK } from '@ramp-network/ramp-instant-sdk'; +import { ChainInfo } from '@gnosis.pm/safe-apps-sdk' +import { RampInstantEvent, RampInstantSDK } from '@ramp-network/ramp-instant-sdk' -const RINKEBY_STAGING_URL = 'https://ri-widget-staging.firebaseapp.com/'; -const WIDGET_CLOSE_EVENT = 'WIDGET_CLOSE'; -const PURCHASE_CREATED_EVENT = 'PURCHASE_CREATED'; +const RINKEBY_STAGING_URL = 'https://ri-widget-staging.firebaseapp.com/' +const WIDGET_CLOSE_EVENT = 'WIDGET_CLOSE' +const PURCHASE_CREATED_EVENT = 'PURCHASE_CREATED' export const ASSETS_BY_CHAIN: { [key: string]: string } = { '1': 'ETH_*,ERC20_*', @@ -12,18 +12,18 @@ export const ASSETS_BY_CHAIN: { [key: string]: string } = { '137': 'MATIC_*', '100': 'XDAI_*', '43114': 'AVAX_*', -}; +} export const getRampWidgetUrl = (chainInfo: ChainInfo) => { - return chainInfo?.chainId === '4' ? RINKEBY_STAGING_URL : ''; -}; + return chainInfo?.chainId === '4' ? RINKEBY_STAGING_URL : '' +} type RampWidgetInitializer = { - url?: string; - assets: string; - address: string; - onClose?: () => void; -}; + url?: string + assets: string + address: string + onClose?: () => void +} export const initializeRampWidget = ({ url, assets, address, onClose }: RampWidgetInitializer) => { return new RampInstantSDK({ @@ -35,14 +35,14 @@ export const initializeRampWidget = ({ url, assets, address, onClose }: RampWidg }) .on('*', (event: RampInstantEvent) => { if (event.type === WIDGET_CLOSE_EVENT) { - onClose?.(); + onClose?.() } if (event.type === PURCHASE_CREATED_EVENT) { // TODO: Send Analytics when the infra is ready // https://github.com/gnosis/safe-apps-sdk/issues/255 - console.log('PURCHASE_CREATED_EVENT', event); + console.log('PURCHASE_CREATED_EVENT', event) } }) - .show(); -}; + .show() +} diff --git a/apps/ramp-network/src/setupProxy.js b/apps/ramp-network/src/setupProxy.js index 89e88afa4..178eff9c5 100644 --- a/apps/ramp-network/src/setupProxy.js +++ b/apps/ramp-network/src/setupProxy.js @@ -2,12 +2,11 @@ // https://create-react-app.dev/docs/proxying-api-requests-in-development/#configuring-the-proxy-manually module.exports = function (app) { - app.use("/manifest.json", function (req, res, next) { + app.use('/manifest.json', function (req, res, next) { res.set({ - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Methods": "GET", - "Access-Control-Allow-Headers": - "X-Requested-With, content-type, Authorization", + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET', + 'Access-Control-Allow-Headers': 'X-Requested-With, content-type, Authorization', }) next() diff --git a/apps/ramp-network/src/utils.ts b/apps/ramp-network/src/utils.ts index d1ec1f49a..1870dd22a 100644 --- a/apps/ramp-network/src/utils.ts +++ b/apps/ramp-network/src/utils.ts @@ -1 +1 @@ -export const goBack = () => window.history.back(); +export const goBack = () => window.history.back() diff --git a/apps/tx-builder/config-overrides.js b/apps/tx-builder/config-overrides.js index cfe7814f0..f4ec1f4f0 100644 --- a/apps/tx-builder/config-overrides.js +++ b/apps/tx-builder/config-overrides.js @@ -1,8 +1,8 @@ -const webpack = require('webpack'); +const webpack = require('webpack') module.exports = { webpack: function (config, env) { - const fallback = config.resolve.fallback || {}; + const fallback = config.resolve.fallback || {} // https://github.com/ChainSafe/web3.js#web3-and-create-react-app Object.assign(fallback, { @@ -15,39 +15,39 @@ module.exports = { url: require.resolve('url'), // https://stackoverflow.com/questions/68707553/uncaught-referenceerror-buffer-is-not-defined buffer: require.resolve('buffer'), - }); + }) - config.resolve.fallback = fallback; + config.resolve.fallback = fallback config.plugins = (config.plugins || []).concat([ new webpack.ProvidePlugin({ process: 'process/browser', Buffer: ['buffer', 'Buffer'], }), - ]); + ]) // https://github.com/facebook/create-react-app/issues/11924 - config.ignoreWarnings = [/to parse source map/i]; + config.ignoreWarnings = [/to parse source map/i] - return config; + return config }, jest: function (config) { - return config; + return config }, devServer: function (configFunction) { return function (proxy, allowedHost) { - const config = configFunction(proxy, allowedHost); + const config = configFunction(proxy, allowedHost) config.headers = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET', 'Access-Control-Allow-Headers': 'X-Requested-With, content-type, Authorization', - }; + } - return config; - }; + return config + } }, paths: function (paths) { - return paths; + return paths }, -}; +} diff --git a/apps/tx-builder/package.json b/apps/tx-builder/package.json index 80fe2fe02..59e80c90f 100644 --- a/apps/tx-builder/package.json +++ b/apps/tx-builder/package.json @@ -5,7 +5,7 @@ "homepage": "/tx-builder", "dependencies": { "@gnosis.pm/safe-deployments": "^1.11.0", - "@gnosis.pm/safe-react-components": "^1.1.3", + "@gnosis.pm/safe-react-components": "^1.1.4", "@material-ui/core": "^4.12.3", "@material-ui/icons": "^4.11.0", "@material-ui/lab": "^4.0.0-alpha.60", diff --git a/apps/tx-builder/src/App.tsx b/apps/tx-builder/src/App.tsx index bf3d5666e..1bbd5ed56 100644 --- a/apps/tx-builder/src/App.tsx +++ b/apps/tx-builder/src/App.tsx @@ -1,19 +1,19 @@ -import { Routes, Route } from 'react-router-dom'; - -import Header from './components/Header'; -import CreateTransactions from './pages/CreateTransactions'; -import Dashboard from './pages/Dashboard'; -import EditTransactionLibrary from './pages/EditTransactionLibrary'; -import ReviewAndConfirm from './pages/ReviewAndConfirm'; -import SaveTransactionLibrary from './pages/SaveTransactionLibrary'; -import TransactionLibrary from './pages/TransactionLibrary'; +import { Routes, Route } from 'react-router-dom' + +import Header from './components/Header' +import CreateTransactions from './pages/CreateTransactions' +import Dashboard from './pages/Dashboard' +import EditTransactionLibrary from './pages/EditTransactionLibrary' +import ReviewAndConfirm from './pages/ReviewAndConfirm' +import SaveTransactionLibrary from './pages/SaveTransactionLibrary' +import TransactionLibrary from './pages/TransactionLibrary' import { HOME_PATH, EDIT_BATCH_PATH, REVIEW_AND_CONFIRM_PATH, SAVE_BATCH_PATH, TRANSACTION_LIBRARY_PATH, -} from './routes/routes'; +} from './routes/routes' const App = () => { return ( @@ -41,7 +41,7 @@ const App = () => { } /> - ); -}; + ) +} -export default App; +export default App diff --git a/apps/tx-builder/src/components/ChecksumWarning.tsx b/apps/tx-builder/src/components/ChecksumWarning.tsx index b4a32593f..82fc3d229 100644 --- a/apps/tx-builder/src/components/ChecksumWarning.tsx +++ b/apps/tx-builder/src/components/ChecksumWarning.tsx @@ -1,24 +1,26 @@ -import React from 'react'; -import MuiAlert from '@material-ui/lab/Alert'; -import MuiAlertTitle from '@material-ui/lab/AlertTitle'; -import styled from 'styled-components'; -import { useTransactionLibrary } from '../store'; +import React from 'react' +import MuiAlert from '@material-ui/lab/Alert' +import MuiAlertTitle from '@material-ui/lab/AlertTitle' +import styled from 'styled-components' +import { useTransactionLibrary } from '../store' const ChecksumWarning = () => { - const { hasChecksumWarning, setHasChecksumWarning } = useTransactionLibrary(); + const { hasChecksumWarning, setHasChecksumWarning } = useTransactionLibrary() if (!hasChecksumWarning) { - return null; + return null } return ( setHasChecksumWarning(false)}> - This batch contains some changed properties since you saved or downloaded it + + This batch contains some changed properties since you saved or downloaded it + - ); -}; + ) +} const ChecksumWrapper = styled.div` position: fixed; @@ -26,6 +28,6 @@ const ChecksumWrapper = styled.div` z-index: 10; background-color: transparent; height: 70px; -`; +` -export default ChecksumWarning; +export default ChecksumWarning diff --git a/apps/tx-builder/src/components/CreateNewBatchCard.tsx b/apps/tx-builder/src/components/CreateNewBatchCard.tsx index 5bfc75b90..d8c40f178 100644 --- a/apps/tx-builder/src/components/CreateNewBatchCard.tsx +++ b/apps/tx-builder/src/components/CreateNewBatchCard.tsx @@ -1,38 +1,38 @@ -import { useRef } from 'react'; -import { ButtonLink, Icon, Text } from '@gnosis.pm/safe-react-components'; -import { alpha } from '@material-ui/core'; -import Hidden from '@material-ui/core/Hidden'; -import styled from 'styled-components'; -import { useTheme } from '@material-ui/core/styles'; +import { useRef } from 'react' +import { ButtonLink, Icon, Text } from '@gnosis.pm/safe-react-components' +import { alpha } from '@material-ui/core' +import Hidden from '@material-ui/core/Hidden' +import styled from 'styled-components' +import { useTheme } from '@material-ui/core/styles' -import { ReactComponent as CreateNewBatchSVG } from '../assets/add-new-batch.svg'; -import useDropZone from '../hooks/useDropZone'; -import { useMediaQuery } from '@material-ui/core'; +import { ReactComponent as CreateNewBatchSVG } from '../assets/add-new-batch.svg' +import useDropZone from '../hooks/useDropZone' +import { useMediaQuery } from '@material-ui/core' type CreateNewBatchCardProps = { - onFileSelected: (file: File | null) => void; -}; + onFileSelected: (file: File | null) => void +} const CreateNewBatchCard = ({ onFileSelected }: CreateNewBatchCardProps) => { - const theme = useTheme(); - const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm')); + const theme = useTheme() + const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm')) - const fileRef = useRef(null); + const fileRef = useRef(null) const { isOverDropZone, isAcceptError, dropHandlers } = useDropZone((file: File | null) => { - onFileSelected(file); - }, '.json'); + onFileSelected(file) + }, '.json') const handleFileSelected = (event: any) => { - event.preventDefault(); + event.preventDefault() if (event.target.files.length) { - onFileSelected(event.target.files[0]); + onFileSelected(event.target.files[0]) } - }; + } const handleBrowse = function (event: any) { - event.preventDefault(); - fileRef.current?.click(); - }; + event.preventDefault() + fileRef.current?.click() + } return ( @@ -59,18 +59,29 @@ const CreateNewBatchCard = ({ onFileSelected }: CreateNewBatchCardProps) => { )} - + - ); -}; + ) +} -export default CreateNewBatchCard; +export default CreateNewBatchCard const Wrapper = styled.div<{ isSmallScreen: boolean }>` margin-top: ${({ isSmallScreen }) => (isSmallScreen ? '0' : '64px')}; -`; +` -const StyledDragAndDropFileContainer = styled.div<{ dragOver: Boolean; fullWidth: boolean; error: Boolean }>` +const StyledDragAndDropFileContainer = styled.div<{ + dragOver: Boolean + fullWidth: boolean + error: Boolean +}>` box-sizing: border-box; max-width: ${({ fullWidth }) => (fullWidth ? '100%' : '420px')}; border: 2px dashed ${({ theme, error }) => (error ? theme.colors.error : '#008c73')}; @@ -88,20 +99,20 @@ const StyledDragAndDropFileContainer = styled.div<{ dragOver: Boolean; fullWidth return ` transition: all 0.2s ease-in-out; transform: scale(1.05); - `; + ` } return ` border-color: ${error ? theme.colors.error : '#008c73'}; background-color: ${error ? alpha(theme.colors.error, 0.7) : '#eaf7f4'}; - `; + ` }} -`; +` const StyledText = styled(Text)<{ error?: Boolean }>` margin-left: 4px; color: ${({ error }) => (error ? '#FFF' : '#566976')}; -`; +` const StyledButtonLink = styled(ButtonLink)` padding: 0; @@ -110,4 +121,4 @@ const StyledButtonLink = styled(ButtonLink)` && > p { font-size: 16px; } -`; +` diff --git a/apps/tx-builder/src/components/EditTransactionModal.tsx b/apps/tx-builder/src/components/EditTransactionModal.tsx index 77043ee36..c6821acb1 100644 --- a/apps/tx-builder/src/components/EditTransactionModal.tsx +++ b/apps/tx-builder/src/components/EditTransactionModal.tsx @@ -1,6 +1,6 @@ -import { GenericModal, Button } from '@gnosis.pm/safe-react-components'; -import styled from 'styled-components'; -import { ProposedTransaction } from '../typings/models'; +import { GenericModal, Button } from '@gnosis.pm/safe-react-components' +import styled from 'styled-components' +import { ProposedTransaction } from '../typings/models' import SolidityForm, { CONTRACT_METHOD_INDEX_FIELD_NAME, CONTRACT_VALUES_FIELD_NAME, @@ -9,19 +9,19 @@ import SolidityForm, { parseFormToProposedTransaction, SolidityFormValuesTypes, TO_ADDRESS_FIELD_NAME, -} from './forms/SolidityForm'; -import { weiToEther } from '../utils'; +} from './forms/SolidityForm' +import { weiToEther } from '../utils' type EditTransactionModalProps = { - txIndex: number; - transaction: ProposedTransaction; - onSubmit: (newTransaction: ProposedTransaction) => void; - onDeleteTx: () => void; - onClose: () => void; - nativeCurrencySymbol: string | undefined; - networkPrefix: string | undefined; - getAddressFromDomain: (name: string) => Promise; -}; + txIndex: number + transaction: ProposedTransaction + onSubmit: (newTransaction: ProposedTransaction) => void + onDeleteTx: () => void + onClose: () => void + nativeCurrencySymbol: string | undefined + networkPrefix: string | undefined + getAddressFromDomain: (name: string) => Promise +} const EditTransactionModal = ({ txIndex, @@ -33,11 +33,11 @@ const EditTransactionModal = ({ networkPrefix, getAddressFromDomain, }: EditTransactionModalProps) => { - const { description, contractInterface } = transaction; + const { description, contractInterface } = transaction - const { customTransactionData, contractFieldsValues, contractMethodIndex } = description; + const { customTransactionData, contractFieldsValues, contractMethodIndex } = description - const isCustomHexDataTx = !!customTransactionData; + const isCustomHexDataTx = !!customTransactionData const initialFormValues: Partial = { [TO_ADDRESS_FIELD_NAME]: transaction.raw.to, @@ -45,7 +45,7 @@ const EditTransactionModal = ({ [CUSTOM_TRANSACTION_DATA_FIELD_NAME]: customTransactionData, [CONTRACT_METHOD_INDEX_FIELD_NAME]: contractMethodIndex, [CONTRACT_VALUES_FIELD_NAME]: contractFieldsValues, - }; + } const handleSubmit = (values: SolidityFormValuesTypes) => { const editedTransaction = parseFormToProposedTransaction( @@ -53,11 +53,11 @@ const EditTransactionModal = ({ contractInterface, nativeCurrencySymbol, networkPrefix, - ); + ) // keep the id of the transaction - onSubmit({ ...editedTransaction, id: transaction.id }); - }; + onSubmit({ ...editedTransaction, id: transaction.id }) + } return ( - ); -}; + ) +} const ButtonContainer = styled.div` display: flex; justify-content: space-between; margin-top: 15px; -`; +` const FormContainer = styled.div` width: 400px; @@ -105,6 +105,6 @@ const FormContainer = styled.div` border-radius: 8px; background-color: white; -`; +` -export default EditTransactionModal; +export default EditTransactionModal diff --git a/apps/tx-builder/src/components/EditableLabel.tsx b/apps/tx-builder/src/components/EditableLabel.tsx index e405b40a2..1e5fe0134 100644 --- a/apps/tx-builder/src/components/EditableLabel.tsx +++ b/apps/tx-builder/src/components/EditableLabel.tsx @@ -1,25 +1,27 @@ -import styled from 'styled-components'; +import styled from 'styled-components' type EditableLabelProps = { - children: React.ReactNode; - onEdit: (value: string) => void; -}; + children: React.ReactNode + onEdit: (value: string) => void +} const EditableLabel = ({ children, onEdit }: EditableLabelProps) => { return ( onEdit(event.target.innerText)} - onKeyPress={(event: any) => event.key === 'Enter' && event.target.blur() && event.preventDefault()} - onClick={(event) => event.stopPropagation()} + onBlur={event => onEdit(event.target.innerText)} + onKeyPress={(event: any) => + event.key === 'Enter' && event.target.blur() && event.preventDefault() + } + onClick={event => event.stopPropagation()} > {children} - ); -}; + ) +} -export default EditableLabel; +export default EditableLabel const EditableComponent = styled.div` font-family: Averta, 'Roboto', sans-serif; @@ -39,4 +41,4 @@ const EditableComponent = styled.div` &:focus { outline-color: #008c73; } -`; +` diff --git a/apps/tx-builder/src/components/Header.tsx b/apps/tx-builder/src/components/Header.tsx index b267e9e52..474ecb081 100644 --- a/apps/tx-builder/src/components/Header.tsx +++ b/apps/tx-builder/src/components/Header.tsx @@ -1,6 +1,6 @@ -import { FixedIcon, Icon, Text, Title, Tooltip } from '@gnosis.pm/safe-react-components'; -import { Link, useLocation, useNavigate } from 'react-router-dom'; -import styled from 'styled-components'; +import { FixedIcon, Icon, Text, Title, Tooltip } from '@gnosis.pm/safe-react-components' +import { Link, useLocation, useNavigate } from 'react-router-dom' +import styled from 'styled-components' import { CREATE_BATCH_PATH, @@ -8,42 +8,42 @@ import { HOME_PATH, SAVE_BATCH_PATH, TRANSACTION_LIBRARY_PATH, -} from '../routes/routes'; -import { useTransactionLibrary } from '../store'; -import ChecksumWarning from './ChecksumWarning'; +} from '../routes/routes' +import { useTransactionLibrary } from '../store' +import ChecksumWarning from './ChecksumWarning' const HELP_ARTICLE_LINK = - 'https://help.gnosis-safe.io/en/articles/4680071-create-a-batched-transaction-with-the-transaction-builder-safe-app'; + 'https://help.gnosis-safe.io/en/articles/4680071-create-a-batched-transaction-with-the-transaction-builder-safe-app' const goBackLabel: Record = { [CREATE_BATCH_PATH]: 'Back to Transaction Creation', [TRANSACTION_LIBRARY_PATH]: 'Back to Your Transaction Library', [EDIT_BATCH_PATH]: 'Back to Edit Batch', [SAVE_BATCH_PATH]: 'Back to Transaction Creation', -}; +} type LocationType = { - state: { from: string } | null; -}; + state: { from: string } | null +} const Header = () => { - const { pathname } = useLocation(); + const { pathname } = useLocation() - const navigate = useNavigate(); + const navigate = useNavigate() - const goBack = () => navigate(-1); + const goBack = () => navigate(-1) - const { batches } = useTransactionLibrary(); + const { batches } = useTransactionLibrary() - const isTransactionCreationPath = pathname === CREATE_BATCH_PATH; - const isSaveBatchPath = pathname === SAVE_BATCH_PATH; + const isTransactionCreationPath = pathname === CREATE_BATCH_PATH + const isSaveBatchPath = pathname === SAVE_BATCH_PATH - const showTitle = isTransactionCreationPath || isSaveBatchPath; - const showLinkToLibrary = isTransactionCreationPath || isSaveBatchPath; + const showTitle = isTransactionCreationPath || isSaveBatchPath + const showLinkToLibrary = isTransactionCreationPath || isSaveBatchPath - const { state } = useLocation() as LocationType; + const { state } = useLocation() as LocationType - const previousUrl = state?.from || CREATE_BATCH_PATH; + const previousUrl = state?.from || CREATE_BATCH_PATH return ( <> @@ -52,7 +52,13 @@ const Header = () => { <> {/* Transaction Builder Title */} Transaction Builder - + @@ -77,10 +83,10 @@ const Header = () => { - ); -}; + ) +} -export default Header; +export default Header const HeaderWrapper = styled.header` position: fixed; @@ -93,12 +99,12 @@ const HeaderWrapper = styled.header` height: 70px; padding: 0 40px; box-sizing: border-box; -`; +` const StyledTitle = styled(Title)` font-size: 20px; margin: 0 10px 0 0; -`; +` const StyledLink = styled(Link)` display: flex; @@ -106,18 +112,18 @@ const StyledLink = styled(Link)` color: #000000; font-size: 16px; text-decoration: none; -`; +` const StyledLeftLinkLabel = styled(Text)` margin-left: 8px; -`; +` const RigthLinkWrapper = styled.div` display: flex; flex-grow: 1; justify-content: flex-end; -`; +` const StyledRightLinkLabel = styled(Text)` margin-right: 8px; -`; +` diff --git a/apps/tx-builder/src/components/QuickTip.tsx b/apps/tx-builder/src/components/QuickTip.tsx index 7e1422556..ac1a46c48 100644 --- a/apps/tx-builder/src/components/QuickTip.tsx +++ b/apps/tx-builder/src/components/QuickTip.tsx @@ -1,24 +1,25 @@ -import { Icon } from '@gnosis.pm/safe-react-components'; -import MuiAlert from '@material-ui/lab/Alert'; -import MuiAlertTitle from '@material-ui/lab/AlertTitle'; -import React from 'react'; -import styled from 'styled-components'; +import { Icon } from '@gnosis.pm/safe-react-components' +import MuiAlert from '@material-ui/lab/Alert' +import MuiAlertTitle from '@material-ui/lab/AlertTitle' +import React from 'react' +import styled from 'styled-components' type QuickTipProps = { - onClose: () => void; -}; + onClose: () => void +} const QuickTip = ({ onClose }: QuickTipProps) => { return ( Quick Tip You can save your batches in your transaction library{' '} - (local browser storage) or{' '} - download the .json file to use - them later. + (local + browser storage) or{' '} + download the + .json file to use them later. - ); -}; + ) +} const StyledAlert = styled(MuiAlert)` && { @@ -33,18 +34,18 @@ const StyledAlert = styled(MuiAlert)` align-items: flex-start; } } -`; +` const StyledTitle = styled(MuiAlertTitle)` && { font-size: 14px; font-weight: bold; } -`; +` const StyledIcon = styled(Icon)` position: relative; top: 3px; -`; +` -export default QuickTip; +export default QuickTip diff --git a/apps/tx-builder/src/components/ShowMoreText.tsx b/apps/tx-builder/src/components/ShowMoreText.tsx index 6c91c7289..83e150bbc 100644 --- a/apps/tx-builder/src/components/ShowMoreText.tsx +++ b/apps/tx-builder/src/components/ShowMoreText.tsx @@ -1,15 +1,15 @@ -import { useState, SyntheticEvent } from 'react'; -import { Link } from '@gnosis.pm/safe-react-components'; +import { useState, SyntheticEvent } from 'react' +import { Link } from '@gnosis.pm/safe-react-components' type ShowMoreTextProps = { - children: string; - moreLabel?: string; - lessLabel?: string; - splitIndex?: number; -}; + children: string + moreLabel?: string + lessLabel?: string + splitIndex?: number +} -const SHOW_MORE = 'Show more'; -const SHOW_LESS = 'Show less'; +const SHOW_MORE = 'Show more' +const SHOW_LESS = 'Show less' export const ShowMoreText = ({ children, @@ -17,15 +17,15 @@ export const ShowMoreText = ({ lessLabel = SHOW_LESS, splitIndex = 50, }: ShowMoreTextProps) => { - const [expanded, setExpanded] = useState(false); + const [expanded, setExpanded] = useState(false) const handleToggle = (event: SyntheticEvent) => { - event.preventDefault(); - setExpanded(!expanded); - }; + event.preventDefault() + setExpanded(!expanded) + } if (children.length < splitIndex) { - return {children}; + return {children} } return ( @@ -33,5 +33,5 @@ export const ShowMoreText = ({ {expanded ? `${children} ` : `${children.substr(0, splitIndex)} ... `} {expanded ? lessLabel : moreLabel} - ); -}; + ) +} diff --git a/apps/tx-builder/src/components/TransactionBatchListItem.tsx b/apps/tx-builder/src/components/TransactionBatchListItem.tsx index c5a669416..53785d6ce 100644 --- a/apps/tx-builder/src/components/TransactionBatchListItem.tsx +++ b/apps/tx-builder/src/components/TransactionBatchListItem.tsx @@ -7,37 +7,37 @@ import { Icon, Text, Tooltip, -} from '@gnosis.pm/safe-react-components'; -import { AccordionDetails, IconButton } from '@material-ui/core'; -import { memo, useState } from 'react'; -import { DraggableProvided, DraggableStateSnapshot } from 'react-beautiful-dnd'; -import styled from 'styled-components'; -import DragIndicatorIcon from '@material-ui/icons/DragIndicator'; -import { ProposedTransaction } from '../typings/models'; -import TransactionDetails from './TransactionDetails'; -import { getTransactionText } from '../utils'; - -const UNKNOWN_POSITION_LABEL = '?'; -const minArrowSize = '12'; +} from '@gnosis.pm/safe-react-components' +import { AccordionDetails, IconButton } from '@material-ui/core' +import { memo, useState } from 'react' +import { DraggableProvided, DraggableStateSnapshot } from 'react-beautiful-dnd' +import styled from 'styled-components' +import DragIndicatorIcon from '@material-ui/icons/DragIndicator' +import { ProposedTransaction } from '../typings/models' +import TransactionDetails from './TransactionDetails' +import { getTransactionText } from '../utils' + +const UNKNOWN_POSITION_LABEL = '?' +const minArrowSize = '12' type TransactionProps = { - transaction: ProposedTransaction; - provided: DraggableProvided; - snapshot: DraggableStateSnapshot; - isLastTransaction: boolean; - showTransactionDetails: boolean; - index: number; - draggableTxIndexDestination: number | undefined; - draggableTxIndexOrigin: number | undefined; - reorderTransactions?: (sourceIndex: number, destinationIndex: number) => void; - networkPrefix: string | undefined; - replaceTransaction?: (newTransaction: ProposedTransaction, index: number) => void; - setTxIndexToEdit: (index: string) => void; - openEditTxModal: () => void; - removeTransaction?: (index: number) => void; - setTxIndexToRemove: (index: string) => void; - openDeleteTxModal: () => void; -}; + transaction: ProposedTransaction + provided: DraggableProvided + snapshot: DraggableStateSnapshot + isLastTransaction: boolean + showTransactionDetails: boolean + index: number + draggableTxIndexDestination: number | undefined + draggableTxIndexOrigin: number | undefined + reorderTransactions?: (sourceIndex: number, destinationIndex: number) => void + networkPrefix: string | undefined + replaceTransaction?: (newTransaction: ProposedTransaction, index: number) => void + setTxIndexToEdit: (index: string) => void + openEditTxModal: () => void + removeTransaction?: (index: number) => void + setTxIndexToRemove: (index: string) => void + openDeleteTxModal: () => void +} const TransactionBatchListItem = memo( ({ @@ -58,21 +58,21 @@ const TransactionBatchListItem = memo( setTxIndexToRemove, openDeleteTxModal, }: TransactionProps) => { - const { description } = transaction; - const { to } = description; + const { description } = transaction + const { to } = description - const transactionDescription = getTransactionText(description); + const transactionDescription = getTransactionText(description) - const [isTxExpanded, setTxExpanded] = useState(false); + const [isTxExpanded, setTxExpanded] = useState(false) const onClickShowTransactionDetails = () => { if (showTransactionDetails) { - setTxExpanded((isTxExpanded) => !isTxExpanded); + setTxExpanded(isTxExpanded => !isTxExpanded) } - }; - const isThisTxBeingDragging = snapshot.isDragging; + } + const isThisTxBeingDragging = snapshot.isDragging - const showArrowAdornment = !isLastTransaction && !isThisTxBeingDragging; + const showArrowAdornment = !isLastTransaction && !isThisTxBeingDragging // displayed order can change if the user uses the drag and drop feature const displayedTxPosition = getDisplayedTxPosition( @@ -80,7 +80,7 @@ const TransactionBatchListItem = memo( isThisTxBeingDragging, draggableTxIndexDestination, draggableTxIndexOrigin, - ); + ) return ( @@ -101,16 +101,30 @@ const TransactionBatchListItem = memo( TransitionProps={{ unmountOnExit: true }} >
- + {/* Drag & Drop Indicator */} {reorderTransactions && ( - + )} {/* Destination Address label */} - + {/* Transaction Description label */} {transactionDescription} @@ -123,10 +137,10 @@ const TransactionBatchListItem = memo( { - event.stopPropagation(); - setTxIndexToEdit(String(index)); - openEditTxModal(); + onClick={event => { + event.stopPropagation() + setTxIndexToEdit(String(index)) + openEditTxModal() }} > @@ -136,12 +150,18 @@ const TransactionBatchListItem = memo( {/* Delete transaction */} {removeTransaction && ( - + { - event.stopPropagation(); - setTxIndexToRemove(String(index)); - openDeleteTxModal(); + onClick={event => { + event.stopPropagation() + setTxIndexToRemove(String(index)) + openDeleteTxModal() }} size="medium" aria-label="Delete transaction" @@ -161,9 +181,9 @@ const TransactionBatchListItem = memo( arrow > { - event.stopPropagation(); - onClickShowTransactionDetails(); + onClick={event => { + event.stopPropagation() + onClickShowTransactionDetails() }} size="medium" aria-label="Expand transaction details" @@ -181,9 +201,9 @@ const TransactionBatchListItem = memo( - ); + ) }, -); +) const getDisplayedTxPosition = ( index: number, @@ -193,30 +213,32 @@ const getDisplayedTxPosition = ( ): string => { // we show the correct position in the transaction that is being dragged if (isDraggingThisTx) { - const isAwayFromDroppableZone = draggableTxIndexDestination === undefined; - return isAwayFromDroppableZone ? UNKNOWN_POSITION_LABEL : String(draggableTxIndexDestination + 1); + const isAwayFromDroppableZone = draggableTxIndexDestination === undefined + return isAwayFromDroppableZone + ? UNKNOWN_POSITION_LABEL + : String(draggableTxIndexDestination + 1) } // if a transaction is being dragged, we show the correct position in previous transactions if (index < Number(draggableTxIndexOrigin)) { // depending on the current destination we show the correct position - return index >= Number(draggableTxIndexDestination) ? `${index + 2}` : `${index + 1}`; + return index >= Number(draggableTxIndexDestination) ? `${index + 2}` : `${index + 1}` } // if a transaction is being dragged, we show the correct position in next transactions if (index > Number(draggableTxIndexOrigin)) { // depending on the current destination we show the correct position - return index > Number(draggableTxIndexDestination) ? `${index + 1}` : `${index}`; + return index > Number(draggableTxIndexDestination) ? `${index + 1}` : `${index}` } // otherwise we show the natural position - return `${index + 1}`; -}; + return `${index + 1}` +} const TransactionListItem = styled.li` display: flex; margin-bottom: 8px; -`; +` // transaction postion dot styles @@ -225,7 +247,7 @@ const PositionWrapper = styled.div` flex-direction: column; align-items: center; padding: 14px 10px 0 0; -`; +` const PositionDot = styled(Dot).withConfig({ shouldForwardProp: (prop, defaultValidatorFn) => defaultValidatorFn(prop), @@ -235,7 +257,7 @@ const PositionDot = styled(Dot).withConfig({ min-width: 24px; background-color: ${({ isDragging }) => (isDragging ? '#92c9be' : ' #e2e3e3')}; transition: background-color 0.5s linear; -`; +` const ArrowAdornment = styled.div` position: relative; @@ -268,12 +290,12 @@ const ArrowAdornment = styled.div` transform: rotate(45deg); } -`; +` // transaction description styles const StyledAccordion = styled(Accordion).withConfig({ - shouldForwardProp: (prop) => !['isDragging'].includes(prop), + shouldForwardProp: prop => !['isDragging'].includes(prop), })<{ isDragging: boolean }>` flex-grow: 1; @@ -298,7 +320,8 @@ const StyledAccordion = styled(Accordion).withConfig({ &.Mui-expanded { background-color: #effaf8; - border-color: ${({ isDragging, expanded }) => (isDragging || expanded ? '#92c9be' : '#e8e7e6')}; + border-color: ${({ isDragging, expanded }) => + isDragging || expanded ? '#92c9be' : '#e8e7e6'}; } } @@ -306,13 +329,13 @@ const StyledAccordion = styled(Accordion).withConfig({ max-width: 100%; align-items: center; } -`; +` const TransactionActionButton = styled(IconButton)` height: 32px; width: 32px; padding: 0; -`; +` const TransactionsDescription = styled(Text)` flex-grow: 1; @@ -321,11 +344,11 @@ const TransactionsDescription = styled(Text)` white-space: nowrap; overflow: hidden; text-overflow: ellipsis; -`; +` const DragAndDropIndicatorIcon = styled(DragIndicatorIcon)` color: #b2bbc0; margin-right: 4px; -`; +` -export default TransactionBatchListItem; +export default TransactionBatchListItem diff --git a/apps/tx-builder/src/components/TransactionDetails.tsx b/apps/tx-builder/src/components/TransactionDetails.tsx index f67494eed..5aaeb1c67 100644 --- a/apps/tx-builder/src/components/TransactionDetails.tsx +++ b/apps/tx-builder/src/components/TransactionDetails.tsx @@ -1,41 +1,60 @@ -import { ButtonLink, EthHashInfo, Text, Title } from '@gnosis.pm/safe-react-components'; -import React, { useEffect, useState } from 'react'; -import styled from 'styled-components'; +import { ButtonLink, EthHashInfo, Text, Title } from '@gnosis.pm/safe-react-components' +import React, { useEffect, useState } from 'react' +import styled from 'styled-components' -import useElementHeight from '../hooks/useElementHeight/useElementHeight'; -import { ProposedTransaction } from '../typings/models'; -import { weiToEther } from '../utils'; +import useElementHeight from '../hooks/useElementHeight/useElementHeight' +import { ProposedTransaction } from '../typings/models' +import { weiToEther } from '../utils' type TransactionDetailsProp = { - transaction: ProposedTransaction; -}; + transaction: ProposedTransaction +} const TransactionDetails = ({ transaction }: TransactionDetailsProp) => { - const { description, raw } = transaction; + const { description, raw } = transaction - const { to, value, data } = raw; - const { contractMethod, contractFieldsValues, customTransactionData, networkPrefix, nativeCurrencySymbol } = - description; + const { to, value, data } = raw + const { + contractMethod, + contractFieldsValues, + customTransactionData, + networkPrefix, + nativeCurrencySymbol, + } = description - const isCustomHexDataTx = !!customTransactionData; - const isContractInteractionTx = !!contractMethod && !isCustomHexDataTx; + const isCustomHexDataTx = !!customTransactionData + const isContractInteractionTx = !!contractMethod && !isCustomHexDataTx - const isTokenTransferTx = !isCustomHexDataTx && !isContractInteractionTx; + const isTokenTransferTx = !isCustomHexDataTx && !isContractInteractionTx return ( - {isTokenTransferTx ? `Transfer ${weiToEther(value)} ${nativeCurrencySymbol} to:` : 'Interact with:'} + {isTokenTransferTx + ? `Transfer ${weiToEther(value)} ${nativeCurrencySymbol} to:` + : 'Interact with:'} - + {/* to address */} to (address) - + {/* value */} @@ -59,9 +78,9 @@ const TransactionDetails = ({ transaction }: TransactionDetailsProp) => { {/* method inputs */} {contractMethod.inputs.map(({ name, type }, index) => { - const inputName = name || index; - const inputLabel = `${inputName} (${type})`; - const inputValue = contractFieldsValues?.[inputName]; + const inputName = name || index + const inputLabel = `${inputName} (${type})` + const inputValue = contractFieldsValues?.[inputName] return ( {/* input name */} @@ -71,22 +90,22 @@ const TransactionDetails = ({ transaction }: TransactionDetailsProp) => { {/* input value */} {inputValue} - ); + ) })} )} - ); -}; + ) +} -export default TransactionDetails; +export default TransactionDetails const Wrapper = styled.article` flex-grow: 1; padding: 0 16px; user-select: text; -`; +` const TxSummaryContainer = styled.div` display: grid; @@ -94,14 +113,14 @@ const TxSummaryContainer = styled.div` gap: 4px; margin-top: 16px; -`; +` const StyledTxTitle = styled(Title)` font-size: 16px; margin: 8px 0; font-weight: bold; line-height: initial; -`; +` const StyledMethodNameLabel = styled(Text)` padding-left: 4px; @@ -109,26 +128,26 @@ const StyledMethodNameLabel = styled(Text)` white-space: nowrap; overflow: hidden; text-overflow: ellipsis; -`; +` -const LINE_HEIGHT = 22; -const MAX_HEIGHT = 2 * LINE_HEIGHT; // 2 lines as max height +const LINE_HEIGHT = 22 +const MAX_HEIGHT = 2 * LINE_HEIGHT // 2 lines as max height const TxValueLabel = ({ children }: { children: React.ReactNode }) => { - const [showMore, setShowMore] = useState(false); - const [showEllipsis, setShowEllipsis] = useState(false); + const [showMore, setShowMore] = useState(false) + const [showEllipsis, setShowEllipsis] = useState(false) - const { height: containerHeight, elementRef } = useElementHeight(); + const { height: containerHeight, elementRef } = useElementHeight() // we show the Show more/less button if the height is more than 44px (the height of 2 lines) - const showMoreButton = containerHeight && containerHeight > MAX_HEIGHT; + const showMoreButton = containerHeight && containerHeight > MAX_HEIGHT // we show/hide ellipsis at the end of the second line if user clicks on "Show more" useEffect(() => { if (showMoreButton && !showMore) { - setShowEllipsis(true); + setShowEllipsis(true) } - }, [showMoreButton, showMore]); + }, [showMoreButton, showMore]) return (
@@ -139,16 +158,16 @@ const TxValueLabel = ({ children }: { children: React.ReactNode }) => { {/* show more/less button */} {showMoreButton && ( - setShowMore((showMore) => !showMore)}> + setShowMore(showMore => !showMore)}> {showMore ? 'Show less' : 'Show more'} )}
- ); -}; + ) +} const StyledTxValueLabel = styled(Text).withConfig({ - shouldForwardProp: (prop) => !['showMore'].includes(prop) || !['showEllipsis'].includes(prop), + shouldForwardProp: prop => !['showMore'].includes(prop) || !['showEllipsis'].includes(prop), })<{ showMore?: boolean; showEllipsis?: boolean }>` max-height: ${({ showMore }) => (showMore ? '100%' : `${MAX_HEIGHT + 1}px`)}; @@ -165,7 +184,7 @@ const StyledTxValueLabel = styled(Text).withConfig({ -webkit-line-clamp: 2; -webkit-box-orient: vertical; }`} -`; +` const StyledButtonLink = styled(ButtonLink)` padding: 0; @@ -174,4 +193,4 @@ const StyledButtonLink = styled(ButtonLink)` margin 0; } -`; +` diff --git a/apps/tx-builder/src/components/TransactionsBatchList.tsx b/apps/tx-builder/src/components/TransactionsBatchList.tsx index 8782befc9..85a056883 100644 --- a/apps/tx-builder/src/components/TransactionsBatchList.tsx +++ b/apps/tx-builder/src/components/TransactionsBatchList.tsx @@ -1,8 +1,8 @@ -import { isValidElement, useMemo, useState } from 'react'; -import { Dot, Text, Title, Icon, Tooltip } from '@gnosis.pm/safe-react-components'; +import { isValidElement, useMemo, useState } from 'react' +import { Dot, Text, Title, Icon, Tooltip } from '@gnosis.pm/safe-react-components' -import IconButton from '@material-ui/core/IconButton'; -import styled from 'styled-components'; +import IconButton from '@material-ui/core/IconButton' +import styled from 'styled-components' import { DragDropContext, Droppable, @@ -13,33 +13,33 @@ import { Draggable, DraggableProvided, DraggableStateSnapshot, -} from 'react-beautiful-dnd'; -import { ProposedTransaction } from '../typings/models'; -import useModal from '../hooks/useModal/useModal'; -import DeleteTransactionModal from './modals/DeleteTransactionModal'; -import DeleteBatchModal from './modals/DeleteBatchModal'; -import SaveBatchModal from './modals/SaveBatchModal'; -import EditTransactionModal from './EditTransactionModal'; -import { useNetwork, useTransactionLibrary } from '../store'; -import Item from './TransactionBatchListItem'; -import VirtualizedList from './VirtualizedList'; -import { getTransactionText } from '../utils'; +} from 'react-beautiful-dnd' +import { ProposedTransaction } from '../typings/models' +import useModal from '../hooks/useModal/useModal' +import DeleteTransactionModal from './modals/DeleteTransactionModal' +import DeleteBatchModal from './modals/DeleteBatchModal' +import SaveBatchModal from './modals/SaveBatchModal' +import EditTransactionModal from './EditTransactionModal' +import { useNetwork, useTransactionLibrary } from '../store' +import Item from './TransactionBatchListItem' +import VirtualizedList from './VirtualizedList' +import { getTransactionText } from '../utils' type TransactionsBatchListProps = { - transactions: ProposedTransaction[]; - showTransactionDetails: boolean; - showBatchHeader: boolean; - batchTitle?: string | React.ReactNode; - removeTransaction?: (index: number) => void; - saveBatch?: (name: string, transactions: ProposedTransaction[]) => void; - downloadBatch?: (name: string, transactions: ProposedTransaction[]) => void; - removeAllTransactions?: () => void; - replaceTransaction?: (newTransaction: ProposedTransaction, index: number) => void; - reorderTransactions?: (sourceIndex: number, destinationIndex: number) => void; -}; - -const TRANSACTION_LIST_DROPPABLE_ID = 'Transaction_List'; -const DROP_EVENT = 'DROP'; + transactions: ProposedTransaction[] + showTransactionDetails: boolean + showBatchHeader: boolean + batchTitle?: string | React.ReactNode + removeTransaction?: (index: number) => void + saveBatch?: (name: string, transactions: ProposedTransaction[]) => void + downloadBatch?: (name: string, transactions: ProposedTransaction[]) => void + removeAllTransactions?: () => void + replaceTransaction?: (newTransaction: ProposedTransaction, index: number) => void + reorderTransactions?: (sourceIndex: number, destinationIndex: number) => void +} + +const TRANSACTION_LIST_DROPPABLE_ID = 'Transaction_List' +const DROP_EVENT = 'DROP' const TransactionsBatchList = ({ transactions, @@ -54,60 +54,72 @@ const TransactionsBatchList = ({ batchTitle, }: TransactionsBatchListProps) => { // we need those states to display the correct position in each tx during the drag & drop - const { batch } = useTransactionLibrary(); - const [draggableTxIndexOrigin, setDraggableTxIndexOrigin] = useState(); - const [draggableTxIndexDestination, setDraggableTxIndexDestination] = useState(); + const { batch } = useTransactionLibrary() + const [draggableTxIndexOrigin, setDraggableTxIndexOrigin] = useState() + const [draggableTxIndexDestination, setDraggableTxIndexDestination] = useState() - const { networkPrefix, getAddressFromDomain, nativeCurrencySymbol } = useNetwork(); + const { networkPrefix, getAddressFromDomain, nativeCurrencySymbol } = useNetwork() const onDragStart = ({ source }: DragStart) => { - setDraggableTxIndexOrigin(source.index); - setDraggableTxIndexDestination(source.index); - }; + setDraggableTxIndexOrigin(source.index) + setDraggableTxIndexDestination(source.index) + } const onDragUpdate = ({ source, destination }: DragUpdate) => { - setDraggableTxIndexOrigin(source.index); - setDraggableTxIndexDestination(destination?.index); - }; + setDraggableTxIndexOrigin(source.index) + setDraggableTxIndexDestination(destination?.index) + } // we only perform the reorder if its present const onDragEnd = ({ reason, source, destination }: DropResult) => { - const sourceIndex = source.index; - const destinationIndex = destination?.index; + const sourceIndex = source.index + const destinationIndex = destination?.index - const isDropEvent = reason === DROP_EVENT; // because user can cancel the drag & drop - const hasTxPositionChanged = sourceIndex !== destinationIndex && destinationIndex !== undefined; + const isDropEvent = reason === DROP_EVENT // because user can cancel the drag & drop + const hasTxPositionChanged = sourceIndex !== destinationIndex && destinationIndex !== undefined - const shouldPerformTxReorder = isDropEvent && hasTxPositionChanged; + const shouldPerformTxReorder = isDropEvent && hasTxPositionChanged if (shouldPerformTxReorder) { - reorderTransactions?.(sourceIndex, destinationIndex); + reorderTransactions?.(sourceIndex, destinationIndex) } - setDraggableTxIndexOrigin(undefined); - setDraggableTxIndexDestination(undefined); - }; + setDraggableTxIndexOrigin(undefined) + setDraggableTxIndexDestination(undefined) + } // 5 modals needed: save batch modal, edit transaction modal, delete batch modal, delete transaction modal, download batch modal const { open: showDeleteBatchModal, openModal: openClearTransactions, closeModal: closeDeleteBatchModal, - } = useModal(); - const { open: showSaveBatchModal, openModal: openSaveBatchModal, closeModal: closeSaveBatchModal } = useModal(); - const { open: showDeleteTxModal, openModal: openDeleteTxModal, closeModal: closeDeleteTxModal } = useModal(); - const { open: showEditTxModal, openModal: openEditTxModal, closeModal: closeEditTxModal } = useModal(); + } = useModal() + const { + open: showSaveBatchModal, + openModal: openSaveBatchModal, + closeModal: closeSaveBatchModal, + } = useModal() + const { + open: showDeleteTxModal, + openModal: openDeleteTxModal, + closeModal: closeDeleteTxModal, + } = useModal() + const { + open: showEditTxModal, + openModal: openEditTxModal, + closeModal: closeEditTxModal, + } = useModal() - const [txIndexToRemove, setTxIndexToRemove] = useState(); - const [txIndexToEdit, setTxIndexToEdit] = useState(); + const [txIndexToRemove, setTxIndexToRemove] = useState() + const [txIndexToEdit, setTxIndexToEdit] = useState() const fileName = useMemo(() => { if (isValidElement(batchTitle)) { - return batchTitle.props.children; + return batchTitle.props.children } - return batchTitle || 'Untitled'; - }, [batchTitle]); + return batchTitle || 'Untitled' + }, [batchTitle]) return ( <> @@ -131,7 +143,13 @@ const TransactionsBatchList = ({ {/* Transactions Batch Actions */} {saveBatch && ( - + )} {downloadBatch && ( - + downloadBatch(fileName, transactions)}> @@ -151,7 +175,13 @@ const TransactionsBatchList = ({ )} {removeAllTransactions && ( - + @@ -162,7 +192,11 @@ const TransactionsBatchList = ({ {/* Standard Transactions List */} {transactions.length <= 20 && ( - + {(provided: DroppableProvided) => ( @@ -205,11 +239,19 @@ const TransactionsBatchList = ({ {/* Virtualized Transaction List */} {transactions.length > 20 && ( - + ( + renderClone={( + provided: DraggableProvided, + snapshot: DraggableStateSnapshot, + rubric, + ) => ( { - closeEditTxModal(); - replaceTransaction?.(updatedTransaction, Number(txIndexToEdit)); + closeEditTxModal() + replaceTransaction?.(updatedTransaction, Number(txIndexToEdit)) }} onDeleteTx={() => { - closeEditTxModal(); - removeTransaction?.(Number(txIndexToEdit)); + closeEditTxModal() + removeTransaction?.(Number(txIndexToEdit)) }} onClose={closeEditTxModal} networkPrefix={networkPrefix} @@ -300,8 +342,8 @@ const TransactionsBatchList = ({ { - closeDeleteBatchModal(); - removeAllTransactions(); + closeDeleteBatchModal() + removeAllTransactions() }} onClose={closeDeleteBatchModal} /> @@ -313,8 +355,8 @@ const TransactionsBatchList = ({ txIndex={Number(txIndexToRemove)} txDescription={getTransactionText(transactions[Number(txIndexToRemove)]?.description)} onClick={() => { - closeDeleteTxModal(); - removeTransaction?.(Number(txIndexToRemove)); + closeDeleteTxModal() + removeTransaction?.(Number(txIndexToRemove)) }} onClose={closeDeleteTxModal} /> @@ -324,24 +366,24 @@ const TransactionsBatchList = ({ {showSaveBatchModal && ( { - closeSaveBatchModal(); - saveBatch?.(name, transactions); + closeSaveBatchModal() + saveBatch?.(name, transactions) }} onClose={closeSaveBatchModal} /> )} - ); -}; + ) +} -export default TransactionsBatchList; +export default TransactionsBatchList // tx positions can change during drag & drop const TransactionsBatchWrapper = styled.section` width: 100%; user-select: none; -`; +` // batch header styles @@ -349,14 +391,14 @@ const TransactionHeader = styled.header` margin-top: 24px; display: flex; align-items: center; -`; +` const TransactionCounterDot = styled(Dot)` height: 24px; width: 24px; min-width: 24px; background-color: #566976; -`; +` const TransactionsTitle = styled(Title)` flex-grow: 1; @@ -367,7 +409,7 @@ const TransactionsTitle = styled(Title)` line-height: normal; display: flex; align-items: center; -`; +` const StyledHeaderIconButton = styled(IconButton)` &.MuiIconButton-root { @@ -375,11 +417,11 @@ const StyledHeaderIconButton = styled(IconButton)` background-color: white; margin-left: 8px; } -`; +` // transactions list styles const TransactionList = styled.ol` list-style: none; padding: 0; -`; +` diff --git a/apps/tx-builder/src/components/VirtualizedList.tsx b/apps/tx-builder/src/components/VirtualizedList.tsx index 6ddc059f2..c0da55b33 100644 --- a/apps/tx-builder/src/components/VirtualizedList.tsx +++ b/apps/tx-builder/src/components/VirtualizedList.tsx @@ -1,14 +1,18 @@ -import { memo, useEffect, useState } from 'react'; -import { Virtuoso } from 'react-virtuoso'; -import styled from 'styled-components'; +import { memo, useEffect, useState } from 'react' +import { Virtuoso } from 'react-virtuoso' +import styled from 'styled-components' type VirtualizedListProps = { - innerRef: any; - items: T[]; - renderItem: (item: T, index: number) => React.ReactNode; -}; + innerRef: any + items: T[] + renderItem: (item: T, index: number) => React.ReactNode +} -const VirtualizedList = ({ innerRef, items, renderItem }: VirtualizedListProps) => { +const VirtualizedList = ({ + innerRef, + items, + renderItem, +}: VirtualizedListProps) => { return ( ({ innerRef, items, renderItem }: Vir totalCount={items.length} overscan={100} /> - ); -}; + ) +} const HeightPreservingItem: React.FC = memo(({ children, ...props }: any) => { - const [size, setSize] = useState(0); - const knownSize = props['data-known-size']; + const [size, setSize] = useState(0) + const knownSize = props['data-known-size'] useEffect(() => { - setSize((prevSize) => { - return knownSize === 0 ? prevSize : knownSize; - }); - }, [knownSize]); + setSize(prevSize => { + return knownSize === 0 ? prevSize : knownSize + }) + }, [knownSize]) return ( {children} - ); -}); + ) +}) const HeightPreservingContainer = styled.div<{ size: number }>` - --child-height: ${(props) => `${props.size}px`}; + --child-height: ${props => `${props.size}px`}; &:empty { min-height: calc(var(--child-height)); box-sizing: border-box; } -`; +` -export default VirtualizedList; +export default VirtualizedList diff --git a/apps/tx-builder/src/components/forms/AddNewTransactionForm.tsx b/apps/tx-builder/src/components/forms/AddNewTransactionForm.tsx index 97c4e30f2..e5df78724 100644 --- a/apps/tx-builder/src/components/forms/AddNewTransactionForm.tsx +++ b/apps/tx-builder/src/components/forms/AddNewTransactionForm.tsx @@ -1,36 +1,45 @@ -import { Title, Button } from '@gnosis.pm/safe-react-components'; -import styled from 'styled-components'; +import { Title, Button } from '@gnosis.pm/safe-react-components' +import styled from 'styled-components' -import { ContractInterface } from '../../typings/models'; -import { isValidAddress } from '../../utils'; +import { ContractInterface } from '../../typings/models' +import { isValidAddress } from '../../utils' import SolidityForm, { CONTRACT_METHOD_INDEX_FIELD_NAME, SolidityFormValuesTypes, TO_ADDRESS_FIELD_NAME, parseFormToProposedTransaction, -} from './SolidityForm'; -import { useTransactions, useNetwork } from '../../store'; +} from './SolidityForm' +import { useTransactions, useNetwork } from '../../store' type AddNewTransactionFormProps = { - contract: ContractInterface | null; - to: string; - showHexEncodedData: boolean; -}; + contract: ContractInterface | null + to: string + showHexEncodedData: boolean +} -const AddNewTransactionForm = ({ contract, to, showHexEncodedData }: AddNewTransactionFormProps) => { +const AddNewTransactionForm = ({ + contract, + to, + showHexEncodedData, +}: AddNewTransactionFormProps) => { const initialFormValues = { [TO_ADDRESS_FIELD_NAME]: isValidAddress(to) ? to : '', [CONTRACT_METHOD_INDEX_FIELD_NAME]: '0', - }; + } - const { addTransaction } = useTransactions(); - const { networkPrefix, getAddressFromDomain, nativeCurrencySymbol } = useNetwork(); + const { addTransaction } = useTransactions() + const { networkPrefix, getAddressFromDomain, nativeCurrencySymbol } = useNetwork() const onSubmit = (values: SolidityFormValuesTypes) => { - const proposedTransaction = parseFormToProposedTransaction(values, contract, nativeCurrencySymbol, networkPrefix); + const proposedTransaction = parseFormToProposedTransaction( + values, + contract, + nativeCurrencySymbol, + networkPrefix, + ) - addTransaction(proposedTransaction); - }; + addTransaction(proposedTransaction) + } return ( <> @@ -54,13 +63,13 @@ const AddNewTransactionForm = ({ contract, to, showHexEncodedData }: AddNewTrans - ); -}; + ) +} -export default AddNewTransactionForm; +export default AddNewTransactionForm const ButtonContainer = styled.div` display: flex; justify-content: space-between; margin-top: 15px; -`; +` diff --git a/apps/tx-builder/src/components/forms/SolidityForm.tsx b/apps/tx-builder/src/components/forms/SolidityForm.tsx index 73b3be951..4ad7d15bc 100644 --- a/apps/tx-builder/src/components/forms/SolidityForm.tsx +++ b/apps/tx-builder/src/components/forms/SolidityForm.tsx @@ -1,7 +1,7 @@ -import { useEffect } from 'react'; -import { SubmitHandler, useForm } from 'react-hook-form'; -import { DevTool } from '@hookform/devtools'; -import { toChecksumAddress, toWei } from 'web3-utils'; +import { useEffect } from 'react' +import { SubmitHandler, useForm } from 'react-hook-form' +import { DevTool } from '@hookform/devtools' +import { toChecksumAddress, toWei } from 'web3-utils' import { ADDRESS_FIELD_TYPE, @@ -9,42 +9,42 @@ import { CUSTOM_TRANSACTION_DATA_FIELD_TYPE, NATIVE_AMOUNT_FIELD_TYPE, SolidityFieldTypes, -} from './fields/fields'; -import Field from './fields/Field'; -import { encodeToHexData, getInputTypeHelper } from '../../utils'; -import { ContractInterface, ProposedTransaction } from '../../typings/models'; +} from './fields/fields' +import Field from './fields/Field' +import { encodeToHexData, getInputTypeHelper } from '../../utils' +import { ContractInterface, ProposedTransaction } from '../../typings/models' -export const TO_ADDRESS_FIELD_NAME = 'toAddress'; -export const NATIVE_VALUE_FIELD_NAME = 'nativeAmount'; -export const CONTRACT_METHOD_INDEX_FIELD_NAME = 'contractMethodIndex'; -export const CONTRACT_VALUES_FIELD_NAME = 'contractFieldsValues'; -export const CUSTOM_TRANSACTION_DATA_FIELD_NAME = 'customTransactionData'; +export const TO_ADDRESS_FIELD_NAME = 'toAddress' +export const NATIVE_VALUE_FIELD_NAME = 'nativeAmount' +export const CONTRACT_METHOD_INDEX_FIELD_NAME = 'contractMethodIndex' +export const CONTRACT_VALUES_FIELD_NAME = 'contractFieldsValues' +export const CUSTOM_TRANSACTION_DATA_FIELD_NAME = 'customTransactionData' type SolidityFormPropsTypes = { - id: string; - networkPrefix: undefined | string; - getAddressFromDomain: (name: string) => Promise; - nativeCurrencySymbol: undefined | string; - contract: ContractInterface | null; - onSubmit: SubmitHandler; - initialValues?: Partial; - showHexToggler?: boolean; - children: React.ReactNode; - showHexEncodedData: boolean; -}; + id: string + networkPrefix: undefined | string + getAddressFromDomain: (name: string) => Promise + nativeCurrencySymbol: undefined | string + contract: ContractInterface | null + onSubmit: SubmitHandler + initialValues?: Partial + showHexToggler?: boolean + children: React.ReactNode + showHexEncodedData: boolean +} export type SolidityInitialFormValuesTypes = { - [TO_ADDRESS_FIELD_NAME]: string; - [CONTRACT_METHOD_INDEX_FIELD_NAME]: string; -}; + [TO_ADDRESS_FIELD_NAME]: string + [CONTRACT_METHOD_INDEX_FIELD_NAME]: string +} export type SolidityFormValuesTypes = { - [TO_ADDRESS_FIELD_NAME]: string; - [NATIVE_VALUE_FIELD_NAME]: string; - [CONTRACT_METHOD_INDEX_FIELD_NAME]: string; - [CONTRACT_VALUES_FIELD_NAME]: Record; - [CUSTOM_TRANSACTION_DATA_FIELD_NAME]: string; -}; + [TO_ADDRESS_FIELD_NAME]: string + [NATIVE_VALUE_FIELD_NAME]: string + [CONTRACT_METHOD_INDEX_FIELD_NAME]: string + [CONTRACT_VALUES_FIELD_NAME]: Record + [CUSTOM_TRANSACTION_DATA_FIELD_NAME]: string +} export const parseFormToProposedTransaction = ( values: SolidityFormValuesTypes, @@ -52,17 +52,18 @@ export const parseFormToProposedTransaction = ( nativeCurrencySymbol: string | undefined, networkPrefix: string | undefined, ): ProposedTransaction => { - const contractMethodIndex = values[CONTRACT_METHOD_INDEX_FIELD_NAME]; - const toAddress = values[TO_ADDRESS_FIELD_NAME]; - const tokenValue = values[NATIVE_VALUE_FIELD_NAME]; - const contractFieldsValues = values[CONTRACT_VALUES_FIELD_NAME]; - const customTransactionData = values[CUSTOM_TRANSACTION_DATA_FIELD_NAME]; + const contractMethodIndex = values[CONTRACT_METHOD_INDEX_FIELD_NAME] + const toAddress = values[TO_ADDRESS_FIELD_NAME] + const tokenValue = values[NATIVE_VALUE_FIELD_NAME] + const contractFieldsValues = values[CONTRACT_VALUES_FIELD_NAME] + const customTransactionData = values[CUSTOM_TRANSACTION_DATA_FIELD_NAME] - const contractMethod = contract?.methods[Number(contractMethodIndex)]; + const contractMethod = contract?.methods[Number(contractMethodIndex)] - const data = customTransactionData || encodeToHexData(contractMethod, contractFieldsValues) || '0x'; - const to = toChecksumAddress(toAddress); - const value = toWei(tokenValue || '0'); + const data = + customTransactionData || encodeToHexData(contractMethod, contractFieldsValues) || '0x' + const to = toChecksumAddress(toAddress) + const value = toWei(tokenValue || '0') return { id: new Date().getTime(), @@ -78,10 +79,10 @@ export const parseFormToProposedTransaction = ( networkPrefix, }, raw: { to, value, data }, - }; -}; + } +} -const isProdEnv = process.env.NODE_ENV === 'production'; +const isProdEnv = process.env.NODE_ENV === 'production' const SolidityForm = ({ id, @@ -105,28 +106,28 @@ const SolidityForm = ({ } = useForm({ defaultValues: initialValues, mode: 'onTouched', // This option allows you to configure the validation strategy before the user submits the form - }); + }) - const toAddress = watch(TO_ADDRESS_FIELD_NAME); - const contractMethodIndex = watch(CONTRACT_METHOD_INDEX_FIELD_NAME); - const nativeValue = watch(NATIVE_VALUE_FIELD_NAME); - const customTransactionData = watch(CUSTOM_TRANSACTION_DATA_FIELD_NAME); - const contractMethod = contract?.methods[Number(contractMethodIndex)]; + const toAddress = watch(TO_ADDRESS_FIELD_NAME) + const contractMethodIndex = watch(CONTRACT_METHOD_INDEX_FIELD_NAME) + const nativeValue = watch(NATIVE_VALUE_FIELD_NAME) + const customTransactionData = watch(CUSTOM_TRANSACTION_DATA_FIELD_NAME) + const contractMethod = contract?.methods[Number(contractMethodIndex)] - const contractFields = contractMethod?.inputs || []; - const showContractFields = !!contract && contract.methods.length > 0 && !showHexEncodedData; - const isPayableMethod = !!contract && contractMethod?.payable; + const contractFields = contractMethod?.inputs || [] + const showContractFields = !!contract && contract.methods.length > 0 && !showHexEncodedData + const isPayableMethod = !!contract && contractMethod?.payable - const isValueInputVisible = showHexEncodedData || !showContractFields || isPayableMethod; + const isValueInputVisible = showHexEncodedData || !showContractFields || isPayableMethod useEffect(() => { - const contractFieldsValues = getValues(CONTRACT_VALUES_FIELD_NAME); + const contractFieldsValues = getValues(CONTRACT_VALUES_FIELD_NAME) if (showHexEncodedData && contractMethod) { - const encodeData = encodeToHexData(contractMethod, contractFieldsValues); - setValue(CUSTOM_TRANSACTION_DATA_FIELD_TYPE, encodeData || ''); + const encodeData = encodeToHexData(contractMethod, contractFieldsValues) + setValue(CUSTOM_TRANSACTION_DATA_FIELD_TYPE, encodeData || '') } - }, [contractMethod, getValues, setValue, showHexEncodedData]); + }, [contractMethod, getValues, setValue, showHexEncodedData]) // Resets form to initial values if the user edited contract method and then switched to custom data and edited it useEffect(() => { @@ -140,15 +141,23 @@ const SolidityForm = ({ [TO_ADDRESS_FIELD_NAME]: toAddress, [CUSTOM_TRANSACTION_DATA_FIELD_NAME]: customTransactionData, [NATIVE_VALUE_FIELD_NAME]: nativeValue, - }); + }) } - }, [dirtyFields, reset, showHexEncodedData, customTransactionData, toAddress, nativeValue, initialValues]); + }, [ + dirtyFields, + reset, + showHexEncodedData, + customTransactionData, + toAddress, + nativeValue, + initialValues, + ]) useEffect(() => { if (isSubmitSuccessful) { - reset({ ...initialValues, [TO_ADDRESS_FIELD_NAME]: toAddress }); + reset({ ...initialValues, [TO_ADDRESS_FIELD_NAME]: toAddress }) } - }, [isSubmitSuccessful, reset, toAddress, initialValues]); + }, [isSubmitSuccessful, reset, toAddress, initialValues]) return ( <> @@ -202,8 +211,8 @@ const SolidityForm = ({ {/* Contract Fields */} {contractFields.map((contractField, index) => { - const name = `${CONTRACT_VALUES_FIELD_NAME}.${contractField.name || index}`; - const fieldType = getInputTypeHelper(contractField); + const name = `${CONTRACT_VALUES_FIELD_NAME}.${contractField.name || index}` + const fieldType = getInputTypeHelper(contractField) return ( showContractFields && ( @@ -222,7 +231,7 @@ const SolidityForm = ({ networkPrefix={networkPrefix} /> ) - ); + ) })} {/* Hex encoded textarea field */} @@ -245,7 +254,7 @@ const SolidityForm = ({ {/* set up the dev tool only in dev env */} {!isProdEnv && } - ); -}; + ) +} -export default SolidityForm; +export default SolidityForm diff --git a/apps/tx-builder/src/components/forms/fields/AddressContractField.tsx b/apps/tx-builder/src/components/forms/fields/AddressContractField.tsx index d93b5414e..194c74be5 100644 --- a/apps/tx-builder/src/components/forms/fields/AddressContractField.tsx +++ b/apps/tx-builder/src/components/forms/fields/AddressContractField.tsx @@ -1,5 +1,5 @@ -import { ReactElement } from 'react'; -import { AddressInput } from '@gnosis.pm/safe-react-components'; +import { ReactElement } from 'react' +import { AddressInput } from '@gnosis.pm/safe-react-components' const AddressContractField = ({ id, @@ -29,7 +29,7 @@ const AddressContractField = ({ onChangeAddress={onChange} showErrorsInTheLabel={false} /> - ); -}; + ) +} -export default AddressContractField; +export default AddressContractField diff --git a/apps/tx-builder/src/components/forms/fields/Field.tsx b/apps/tx-builder/src/components/forms/fields/Field.tsx index 01aed30da..e11b91ac4 100644 --- a/apps/tx-builder/src/components/forms/fields/Field.tsx +++ b/apps/tx-builder/src/components/forms/fields/Field.tsx @@ -1,6 +1,6 @@ -import { ReactElement } from 'react'; -import { Control, Controller } from 'react-hook-form'; -import { SelectItem } from '@gnosis.pm/safe-react-components/dist/inputs/Select'; +import { ReactElement } from 'react' +import { Control, Controller } from 'react-hook-form' +import { SelectItem } from '@gnosis.pm/safe-react-components/dist/inputs/Select' import { ADDRESS_FIELD_TYPE, @@ -9,61 +9,61 @@ import { CUSTOM_TRANSACTION_DATA_FIELD_TYPE, SolidityFieldTypes, CustomFieldTypes, -} from './fields'; -import AddressContractField from './AddressContractField'; -import SelectContractField from './SelectContractField'; -import TextareaContractField from './TextareaContractField'; -import TextContractField from './TextContractField'; -import validateField, { ValidationFunction } from '../validations/validateField'; +} from './fields' +import AddressContractField from './AddressContractField' +import SelectContractField from './SelectContractField' +import TextareaContractField from './TextareaContractField' +import TextContractField from './TextContractField' +import validateField, { ValidationFunction } from '../validations/validateField' const CUSTOM_SOLIDITY_COMPONENTS: CustomSolidityComponent = { [ADDRESS_FIELD_TYPE]: AddressContractField, [BOOLEAN_FIELD_TYPE]: SelectContractField, [CONTRACT_METHOD_FIELD_TYPE]: SelectContractField, [CUSTOM_TRANSACTION_DATA_FIELD_TYPE]: TextareaContractField, -}; +} const CUSTOM_DEFAULT_VALUES: CustomDefaultValueTypes = { [BOOLEAN_FIELD_TYPE]: 'true', [CONTRACT_METHOD_FIELD_TYPE]: '0', // first contract method as default -}; +} const BOOLEAN_DEFAULT_OPTIONS: SelectItem[] = [ { id: 'true', label: 'True' }, { id: 'false', label: 'False' }, -]; +] const DEFAULT_OPTIONS: DefaultOptionTypes = { [BOOLEAN_FIELD_TYPE]: BOOLEAN_DEFAULT_OPTIONS, -}; +} interface CustomDefaultValueTypes { - [key: string]: string; + [key: string]: string } interface CustomSolidityComponent { - [key: string]: (props: any) => ReactElement; + [key: string]: (props: any) => ReactElement } interface DefaultOptionTypes { - [key: string]: SelectItem[]; + [key: string]: SelectItem[] } type FieldProps = { - fieldType: SolidityFieldTypes | CustomFieldTypes; - control: Control; - id: string; - name: string; - label: string; - fullWidth?: boolean; - required?: boolean; - validations?: ValidationFunction[]; - getAddressFromDomain?: (name: string) => Promise; - networkPrefix?: string; - showErrorsInTheLabel?: boolean; - shouldUnregister?: boolean; - options?: SelectItem[]; -}; + fieldType: SolidityFieldTypes | CustomFieldTypes + control: Control + id: string + name: string + label: string + fullWidth?: boolean + required?: boolean + validations?: ValidationFunction[] + getAddressFromDomain?: (name: string) => Promise + networkPrefix?: string + showErrorsInTheLabel?: boolean + shouldUnregister?: boolean + options?: SelectItem[] +} const Field = ({ fieldType, @@ -76,7 +76,7 @@ const Field = ({ ...props }: FieldProps) => { // Component based on field type - const Component = CUSTOM_SOLIDITY_COMPONENTS[fieldType] || TextContractField; + const Component = CUSTOM_SOLIDITY_COMPONENTS[fieldType] || TextContractField // see https://react-hook-form.com/advanced-usage#ControlledmixedwithUncontrolledComponents return ( @@ -105,7 +105,7 @@ const Field = ({ /> )} /> - ); -}; + ) +} -export default Field; +export default Field diff --git a/apps/tx-builder/src/components/forms/fields/JsonField.tsx b/apps/tx-builder/src/components/forms/fields/JsonField.tsx index f937241ae..8c4c86e77 100644 --- a/apps/tx-builder/src/components/forms/fields/JsonField.tsx +++ b/apps/tx-builder/src/components/forms/fields/JsonField.tsx @@ -1,63 +1,71 @@ -import { useState, useCallback, ClipboardEvent } from 'react'; -import styled from 'styled-components'; -import { Icon, TextFieldInput, Tooltip, GenericModal, Text, Button, IconTypes } from '@gnosis.pm/safe-react-components'; -import IconButton from '@material-ui/core/IconButton'; -import { Box } from '@material-ui/core'; -import useModal from '../../../hooks/useModal/useModal'; - -const DEFAULT_ROWS = 4; +import { useState, useCallback, ClipboardEvent } from 'react' +import styled from 'styled-components' +import { + Icon, + TextFieldInput, + Tooltip, + GenericModal, + Text, + Button, + IconTypes, +} from '@gnosis.pm/safe-react-components' +import IconButton from '@material-ui/core/IconButton' +import { Box } from '@material-ui/core' +import useModal from '../../../hooks/useModal/useModal' + +const DEFAULT_ROWS = 4 type Props = { - id: string; - name: string; - label: string; - value: string; - onChange: (value: string) => void; -}; + id: string + name: string + label: string + value: string + onChange: (value: string) => void +} const JsonField = ({ id, name, label, value, onChange }: Props) => { - const { open: showReplaceModal, toggleModal } = useModal(); - const [tempAbi, setTempAbi] = useState(value); - const [isPrettified, setIsPrettified] = useState(false); - const hasError = isValidJSON(value) ? undefined : 'Invalid JSON value'; + const { open: showReplaceModal, toggleModal } = useModal() + const [tempAbi, setTempAbi] = useState(value) + const [isPrettified, setIsPrettified] = useState(false) + const hasError = isValidJSON(value) ? undefined : 'Invalid JSON value' const toggleFormatJSON = useCallback(() => { if (!value) { - return; + return } try { - onChange(JSON.stringify(JSON.parse(value), null, isPrettified ? 0 : 2)); - setIsPrettified(!isPrettified); + onChange(JSON.stringify(JSON.parse(value), null, isPrettified ? 0 : 2)) + setIsPrettified(!isPrettified) } catch (e) { - console.error(e); - onChange(value); + console.error(e) + onChange(value) } - }, [onChange, value, isPrettified]); + }, [onChange, value, isPrettified]) const changeAbi = useCallback(() => { - onChange(tempAbi); - setIsPrettified(false); - toggleModal(); - }, [tempAbi, onChange, toggleModal]); + onChange(tempAbi) + setIsPrettified(false) + toggleModal() + }, [tempAbi, onChange, toggleModal]) const handlePaste = useCallback( (event: ClipboardEvent) => { - event.preventDefault(); - event.stopPropagation(); + event.preventDefault() + event.stopPropagation() - const clipboardData = event.clipboardData; - const pastedData = clipboardData?.getData('Text') || ''; + const clipboardData = event.clipboardData + const pastedData = clipboardData?.getData('Text') || '' if (value && pastedData) { - setTempAbi(pastedData); - toggleModal(); + setTempAbi(pastedData) + toggleModal() } else { - onChange(pastedData); + onChange(pastedData) } }, [onChange, toggleModal, value], - ); + ) return ( <> @@ -73,8 +81,8 @@ const JsonField = ({ id, name, label, value, onChange }: Props) => { fullWidth hiddenLabel={false} onPaste={handlePaste} - onChange={(event) => { - onChange(event.target.value); + onChange={event => { + onChange(event.target.value) }} spellCheck={false} showErrorsInTheLabel={false} @@ -123,20 +131,20 @@ const JsonField = ({ id, name, label, value, onChange }: Props) => { /> )} - ); -}; + ) +} const isValidJSON = (value: string | undefined) => { if (value) { try { - JSON.parse(value); + JSON.parse(value) } catch { - return false; + return false } } - return true; -}; + return true +} const IconContainerButton = ({ tooltipLabel, @@ -144,30 +152,31 @@ const IconContainerButton = ({ onClick, error, }: { - tooltipLabel: string; - iconType: IconTypes; - onClick: () => void; - error: boolean; + tooltipLabel: string + iconType: IconTypes + onClick: () => void + error: boolean }) => ( -); +) const JSONFieldContainer = styled.div` position: relative; -`; +` const IconContainer = styled.div<{ error: boolean }>` position: absolute; top: -10px; right: 15px; - border: 1px solid ${({ theme, error }) => (error ? theme.colors.error : theme.colors.inputDefault)}; + border: 1px solid + ${({ theme, error }) => (error ? theme.colors.error : theme.colors.inputDefault)}; border-radius: 50%; background-color: #fff; -`; +` const StyledTextField = styled(TextFieldInput)` && { @@ -179,10 +188,10 @@ const StyledTextField = styled(TextFieldInput)` } } } -`; +` const StyledButton = styled(IconButton)` margin: 0 5px; -`; +` -export default JsonField; +export default JsonField diff --git a/apps/tx-builder/src/components/forms/fields/SelectContractField.tsx b/apps/tx-builder/src/components/forms/fields/SelectContractField.tsx index 4907f0602..528dde2fd 100644 --- a/apps/tx-builder/src/components/forms/fields/SelectContractField.tsx +++ b/apps/tx-builder/src/components/forms/fields/SelectContractField.tsx @@ -1,15 +1,21 @@ -import { Select } from '@gnosis.pm/safe-react-components'; -import { SelectItem } from '@gnosis.pm/safe-react-components/dist/inputs/Select'; +import { Select } from '@gnosis.pm/safe-react-components' +import { SelectItem } from '@gnosis.pm/safe-react-components/dist/inputs/Select' type SelectContractFieldTypes = { - options: SelectItem[]; - onChange: (id: string) => void; - value: string; - label: string; - name: string; -}; + options: SelectItem[] + onChange: (id: string) => void + value: string + label: string + name: string +} -const SelectContractField = ({ value, onChange, options, label, name }: SelectContractFieldTypes) => { +const SelectContractField = ({ + value, + onChange, + options, + label, + name, +}: SelectContractFieldTypes) => { return (