Skip to content

Commit

Permalink
Added a warning icon if transfer cost is higher than token market val…
Browse files Browse the repository at this point in the history
…ue (#236)

* Added a warning msg if transfer cost is higher than token market value

* Added useWeb3 hook to Drain Safe app
  • Loading branch information
DaniSomoza committed Dec 14, 2021
1 parent 51a6e6c commit cb5feb8
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 15 deletions.
37 changes: 28 additions & 9 deletions apps/drain-safe/src/__tests__/App.test.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import React from 'react';
import { render, screen, waitFor, fireEvent } from '@testing-library/react';
import { screen, waitFor, fireEvent } from '@testing-library/react';
import { within } from '@testing-library/dom';
import { useSafeAppsSDK } from '@gnosis.pm/safe-apps-react-sdk';
import useBalances from '../hooks/use-balances';
import { mockTxsRequest, mockInitialBalances, renderWithProviders } from '../utils/test-helpers';
import App from '../components/App';

Expand Down Expand Up @@ -58,14 +56,14 @@ jest.mock('web3', () => {

describe('<App />', () => {
it('should render the tokens in the safe balance', async () => {
const { container } = renderWithProviders(<App />);
renderWithProviders(<App />);

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 () => {
const { container } = renderWithProviders(<App />);
renderWithProviders(<App />);
const { sdk } = useSafeAppsSDK();

await screen.findByText(/chainlink token/i);
Expand All @@ -75,7 +73,7 @@ describe('<App />', () => {
});

it('should drain the safe when submit button is clicked removing the tokens excluded by the user', async () => {
const { container } = renderWithProviders(<App />);
renderWithProviders(<App />);
const { sdk } = useSafeAppsSDK();
const checkboxes = await screen.findAllByRole('checkbox');

Expand All @@ -93,7 +91,7 @@ describe('<App />', () => {
});

it('should show an error if no recipient address is entered', async () => {
const { container } = renderWithProviders(<App />);
renderWithProviders(<App />);

await screen.findByText(/chainLink token/i);
fireEvent.click(screen.getByText(/transfer everything/i));
Expand All @@ -102,7 +100,7 @@ describe('<App />', () => {
});

it('should allow to order token by string prop', async () => {
const { container } = renderWithProviders(<App />);
renderWithProviders(<App />);

await screen.findByText(/chainlink token/i);
const assetColumnHeaderElement = screen.getByText(/asset/i);
Expand All @@ -124,7 +122,7 @@ describe('<App />', () => {
});

it('should allow to order token by numeric prop', async () => {
const { container } = renderWithProviders(<App />);
renderWithProviders(<App />);

await screen.findByText(/chainLink token/i);
const amountColumnHeaderElement = screen.getByText(/amount/i);
Expand All @@ -144,4 +142,25 @@ describe('<App />', () => {
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(<App />);

const warningTooltip =
/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('tbody tr');

// 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();

// 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();
});
});
});
20 changes: 19 additions & 1 deletion apps/drain-safe/src/components/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ 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';
Expand All @@ -26,6 +28,7 @@ const App: React.FC = () => {
const [toAddress, setToAddress] = useState<string>('');
const [isFinished, setFinished] = useState<boolean>(false);
const [error, setError] = useState<string>('');
const [gasPrice, setGasPrice] = useState<BigNumber>(new BigNumber(0));
const [networkPrefix, setNetworkPrefix] = useState<string>('');

const onError = (userMsg: string, err: Error) => {
Expand Down Expand Up @@ -117,6 +120,14 @@ const App: React.FC = () => {
}
}, [balancesError]);

useEffect(() => {
sdk.eth.getGasPrice().then((gasPrice) => {
setGasPrice(new BigNumber(gasPrice));
});
}, [sdk.eth]);

const ethFiatPrice = Number(assets[0]?.fiatConversion || 0);

useEffect(() => {
const getChainInfo = async () => {
try {
Expand All @@ -136,7 +147,14 @@ const App: React.FC = () => {
<Logo />
<Title size="md">Drain Account</Title>
</Flex>
<Balances assets={assets} exclude={excludedTokens} onExcludeChange={handleExcludeChange} />
<Balances
ethFiatPrice={ethFiatPrice}
gasPrice={gasPrice}
web3={web3}
assets={assets}
exclude={excludedTokens}
onExcludeChange={handleExcludeChange}
/>
{error && <Text size="lg">{error}</Text>}
{isFinished && (
<Text size="lg">
Expand Down
30 changes: 25 additions & 5 deletions apps/drain-safe/src/components/Balances.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { useCallback, useMemo, useState } from 'react';
import { Table, Checkbox, TableSortDirection } from '@gnosis.pm/safe-react-components';
import { formatTokenValue, formatCurrencyValue } from '../utils/formatters';
import Icon from './Icon';
import { TokenBalance, TokenInfo } from '@gnosis.pm/safe-apps-sdk';
import BigNumber from 'bignumber.js';
import Web3 from 'web3';

import { formatTokenValue } from '../utils/formatters';
import Icon from './Icon';
import Flex from './Flex';
import { useCallback, useMemo, useState } from 'react';
import { getComparator } from '../utils/sort-helpers';
import CurrencyCell from './CurrencyCell';

const CURRENCY = 'USD';

Expand All @@ -28,10 +32,16 @@ function Balances({
assets,
exclude,
onExcludeChange,
gasPrice,
ethFiatPrice,
web3,
}: {
assets: TokenBalance[];
exclude: string[];
ethFiatPrice: number;
web3: Web3 | undefined;
onExcludeChange: (address: string, checked: boolean) => void;
gasPrice: BigNumber;
}): JSX.Element {
const [orderBy, setOrderBy] = useState<string | undefined>();
const [order, setOrder] = useState<TableSortDirection>(TableSortDirection.asc);
Expand Down Expand Up @@ -80,7 +90,17 @@ function Balances({
},

{ content: formatTokenValue(item.balance, token.decimals) },
{ content: formatCurrencyValue(item.fiatBalance, CURRENCY) },
{
content: (
<CurrencyCell
web3={web3}
ethFiatPrice={ethFiatPrice}
gasPrice={gasPrice}
item={item}
currency={CURRENCY}
/>
),
},

{
content: (
Expand All @@ -95,7 +115,7 @@ function Balances({
],
};
}),
[assets, exclude, handleExclusion, order, orderBy],
[assets, exclude, handleExclusion, order, orderBy, ethFiatPrice, gasPrice, web3],
);

return (
Expand Down
78 changes: 78 additions & 0 deletions apps/drain-safe/src/components/CurrencyCell.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
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 Web3 from 'web3';

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,
currency,
gasPrice,
ethFiatPrice,
web3,
}: {
item: TokenBalance;
currency: string;
gasPrice: BigNumber;
ethFiatPrice: number;
web3: Web3 | undefined;
}) {
const label = formatCurrencyValue(item.fiatBalance, currency);
const [transferCostInFiat, setTransferCostInFiat] = useState(new BigNumber(0));

const { safe } = useSafeAppsSDK();

// Transfer cost estimation
useEffect(() => {
const estimateTransferCost = async () => {
try {
const sendTokenTx = tokenToTx(safe.safeAddress, item);

const estimatedTransferGas = await web3?.eth.estimateGas({ ...sendTokenTx, from: safe.safeAddress });

const gasCostInWei = gasPrice.multipliedBy(estimatedTransferGas || 21000);
const gasCostInEther = new BigNumber(web3Utils.fromWei(gasCostInWei.toString(), 'ether'));

const transferCostInFiat = gasCostInEther.multipliedBy(ethFiatPrice);

setTransferCostInFiat(transferCostInFiat);
} catch (e) {
console.log('Error: ', e);
}
};
estimateTransferCost();
}, [gasPrice, ethFiatPrice, item, web3, 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);

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 ? (
<Tooltip title={warningTooltip} placement="top">
<Flex>
{label}
<StyledIcon size="md" type="alert" color="warning" />
</Flex>
</Tooltip>
) : (
<Flex>{label}</Flex>
);
}

export default CurrencyCell;

const StyledIcon = styled(Icon)`
margin-left: 4px;
`;
8 changes: 8 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1627,6 +1627,14 @@
"@gnosis.pm/safe-react-gateway-sdk" "^2.5.6"
ethers "^5.4.7"

"@gnosis.pm/safe-apps-sdk@6.1.1":
version "6.1.1"
resolved "https://registry.yarnpkg.com/@gnosis.pm/safe-apps-sdk/-/safe-apps-sdk-6.1.1.tgz#6a4e19c475967a0d04a8c04559e05703484e2ca8"
integrity sha512-MI2gIdQUlu1NrDt5rF47xey1LSiQoy5Nb+vqn16PM5qVc3FjR+nPNG8+1yGlL96di31z+kyS2J8DzHiea7+TMA==
dependencies:
"@gnosis.pm/safe-react-gateway-sdk" "^2.5.6"
ethers "^5.4.7"

"@gnosis.pm/safe-react-components@^0.5.0":
version "0.5.0"
resolved "https://registry.yarnpkg.com/@gnosis.pm/safe-react-components/-/safe-react-components-0.5.0.tgz#ce72f3f0674d33bac507bf6409462655fb55f096"
Expand Down

0 comments on commit cb5feb8

Please sign in to comment.