Skip to content

Commit

Permalink
Update token transfer value in use value hook
Browse files Browse the repository at this point in the history
  • Loading branch information
pedronfigueiredo committed Oct 17, 2024
1 parent e3c31d5 commit 3d5e95a
Show file tree
Hide file tree
Showing 5 changed files with 202 additions and 146 deletions.
Original file line number Diff line number Diff line change
@@ -1,120 +1,126 @@
import { TransactionMeta } from '@metamask/transaction-controller';
import { Numeric } from '../../../../../../../shared/modules/Numeric';
import { genUnapprovedTokenTransferConfirmation } from '../../../../../../../test/data/confirmations/token-transfer';
import mockState from '../../../../../../../test/data/mock-state.json';
import { renderHookWithProvider } from '../../../../../../../test/lib/render-helpers';
// import useTokenExchangeRate from '../../../../../../components/app/currency-input/hooks/useTokenExchangeRate';
import { Numeric } from '../../../../../../../shared/modules/Numeric';
import { renderHookWithConfirmContextProvider } from '../../../../../../../test/lib/confirmations/render-helpers';
import useTokenExchangeRate from '../../../../../../components/app/currency-input/hooks/useTokenExchangeRate';
import { useTokenTracker } from '../../../../../../hooks/useTokenTracker';
import { useAssetDetails } from '../../../../hooks/useAssetDetails';
import { useTokenValues } from './use-token-values';
import { useDecodedTransactionData } from './useDecodedTransactionData';

jest.mock('../../../../hooks/useAssetDetails', () => ({
...jest.requireActual('../../../../hooks/useAssetDetails'),
useAssetDetails: jest.fn(),
}));

jest.mock('./useDecodedTransactionData', () => ({
...jest.requireActual('./useDecodedTransactionData'),
useDecodedTransactionData: jest.fn(),
}));

jest.mock(
'../../../../../../components/app/currency-input/hooks/useTokenExchangeRate',
() => jest.fn(),
);

jest.mock('../../../../../../hooks/useTokenTracker', () => ({
...jest.requireActual('../../../../../../hooks/useTokenTracker'),
useTokenTracker: jest.fn(),
}));

describe('useTokenValues', () => {
const useAssetDetailsMock = jest.mocked(useAssetDetails);
const useDecodedTransactionDataMock = jest.mocked(useDecodedTransactionData);
const useTokenExchangeRateMock = jest.mocked(useTokenExchangeRate);
const useTokenTrackerMock = jest.mocked(useTokenTracker);

const TEST_SELECTED_TOKEN = {
address: 'address',
decimals: 18,
symbol: 'symbol',
iconUrl: 'iconUrl',
image: 'image',
};

it('returns native and fiat balances', async () => {
(useTokenTrackerMock as jest.Mock).mockResolvedValue({
tokensWithBalances: [
{
address: '0x076146c765189d51be3160a2140cf80bfc73ad68',
balance: '1000000000000000000',
decimals: 18,
},
],
});

(useTokenExchangeRateMock as jest.Mock).mockResolvedValue(
new Numeric(1, 10),
);

const transactionMeta = genUnapprovedTokenTransferConfirmation(
{},
) as TransactionMeta;

const { result, waitForNextUpdate } = renderHookWithProvider(
() => useTokenValues(transactionMeta, TEST_SELECTED_TOKEN),
mockState,
);

await waitForNextUpdate();

expect(result.current).toEqual({
fiatDisplayValue: '$1.00',
tokenBalance: '1',
});
beforeEach(() => {
jest.resetAllMocks();
});

it('returns undefined native and fiat balances if no token with balances is returned', async () => {
(useTokenTrackerMock as jest.Mock).mockResolvedValue({
tokensWithBalances: [],
});

it('returns native and fiat balances', async () => {
(useAssetDetailsMock as jest.Mock).mockImplementation(() => ({
decimals: '10',
}));
(useDecodedTransactionDataMock as jest.Mock).mockImplementation(() => ({
pending: false,
value: {
data: [
{
name: 'transfer',
params: [
{
type: 'address',
value: '0x9bc5baF874d2DA8D216aE9f137804184EE5AfEF4',
},
{
type: 'uint256',
value: 70000000000,
},
],
},
],
source: 'FourByte',
},
}));
(useTokenExchangeRateMock as jest.Mock).mockResolvedValue(
new Numeric(1, 10),
new Numeric(0.91, 10),
);

const transactionMeta = genUnapprovedTokenTransferConfirmation(
{},
) as TransactionMeta;

const { result, waitForNextUpdate } = renderHookWithProvider(
() => useTokenValues(transactionMeta, TEST_SELECTED_TOKEN),
const { result, waitForNextUpdate } = renderHookWithConfirmContextProvider(
() => useTokenValues(transactionMeta),
mockState,
);

await waitForNextUpdate();

expect(result.current).toEqual({
fiatDisplayValue: undefined,
tokenBalance: undefined,
decodedTransferValue: 7,
fiatDisplayValue: '$6.37',
pending: false,
});
});

it('returns undefined fiat balance if no token rate is returned', async () => {
(useTokenTrackerMock as jest.Mock).mockResolvedValue({
tokensWithBalances: [
{
address: '0x076146c765189d51be3160a2140cf80bfc73ad68',
balance: '1000000000000000000',
decimals: 18,
},
],
});

(useAssetDetailsMock as jest.Mock).mockImplementation(() => ({
decimals: '10',
}));
(useDecodedTransactionDataMock as jest.Mock).mockImplementation(() => ({
pending: false,
value: {
data: [
{
name: 'transfer',
params: [
{
type: 'address',
value: '0x9bc5baF874d2DA8D216aE9f137804184EE5AfEF4',
},
{
type: 'uint256',
value: 70000000000,
},
],
},
],
source: 'FourByte',
},
}));
(useTokenExchangeRateMock as jest.Mock).mockResolvedValue(null);

const transactionMeta = genUnapprovedTokenTransferConfirmation(
{},
) as TransactionMeta;

const { result, waitForNextUpdate } = renderHookWithProvider(
() => useTokenValues(transactionMeta, TEST_SELECTED_TOKEN),
const { result, waitForNextUpdate } = renderHookWithConfirmContextProvider(
() => useTokenValues(transactionMeta),
mockState,
);

await waitForNextUpdate();

expect(result.current).toEqual({
decodedTransferValue: 7,
fiatDisplayValue: null,
tokenBalance: '1',
pending: false,
});
});
});
Original file line number Diff line number Diff line change
@@ -1,77 +1,64 @@
import { TransactionMeta } from '@metamask/transaction-controller';
import { isHexString } from '@metamask/utils';
import { BigNumber } from 'bignumber.js';
import { isBoolean } from 'lodash';
import { useMemo, useState } from 'react';
import { calcTokenAmount } from '../../../../../../../shared/lib/transactions-controller-utils';
import { toChecksumHexAddress } from '../../../../../../../shared/modules/hexstring-utils';
import { Numeric } from '../../../../../../../shared/modules/Numeric';
import useTokenExchangeRate from '../../../../../../components/app/currency-input/hooks/useTokenExchangeRate';
import { useFiatFormatter } from '../../../../../../hooks/useFiatFormatter';
import { useTokenTracker } from '../../../../../../hooks/useTokenTracker';
import { SelectedToken } from '../shared/selected-token';
import { useAssetDetails } from '../../../../hooks/useAssetDetails';
import { useDecodedTransactionData } from './useDecodedTransactionData';

export const useTokenValues = (
transactionMeta: TransactionMeta,
selectedToken: SelectedToken,
) => {
const [tokensWithBalances, setTokensWithBalances] = useState<
{ balance: string; address: string; decimals: number; string: string }[]
>([]);
export const useTokenValues = (transactionMeta: TransactionMeta) => {
const { decimals } = useAssetDetails(
transactionMeta.txParams.to,
transactionMeta.txParams.from,
transactionMeta.txParams.data,
);

const fetchTokenBalances = async () => {
const result: {
tokensWithBalances: {
balance: string;
address: string;
decimals: number;
string: string;
}[];
} = await useTokenTracker({
tokens: [selectedToken],
address: undefined,
});
const decodedResponse = useDecodedTransactionData();
const { value, pending } = decodedResponse;

setTokensWithBalances(result.tokensWithBalances);
};
const decodedTransferValue = useMemo(() => {
if (!value || !decimals) {
return 0;
}

fetchTokenBalances();
const paramIndex = value.data[0].params.findIndex(
(param) =>
param.value !== undefined &&
!isHexString(param.value) &&
param.value.length === undefined &&
!isBoolean(param.value),
);
if (paramIndex === -1) {
return 0;
}

return new BigNumber(value.data[0].params[paramIndex].value.toString())
.dividedBy(new BigNumber(10).pow(Number(decimals)))
.toNumber();
}, [value, decimals]);

const [exchangeRate, setExchangeRate] = useState<Numeric | undefined>();
const fetchExchangeRate = async () => {
const result = await useTokenExchangeRate(transactionMeta?.txParams?.to);

setExchangeRate(result);
};

fetchExchangeRate();

const tokenBalance = useMemo(() => {
const tokenWithBalance = tokensWithBalances.find(
(token: {
balance: string;
address: string;
decimals: number;
string: string;
}) =>
toChecksumHexAddress(token.address) ===
toChecksumHexAddress(transactionMeta?.txParams?.to as string),
);

if (!tokenWithBalance) {
return undefined;
}

return calcTokenAmount(tokenWithBalance.balance, tokenWithBalance.decimals);
}, [tokensWithBalances]);

const fiatValue =
exchangeRate && tokenBalance && exchangeRate.times(tokenBalance).toNumber();

exchangeRate &&
decodedTransferValue &&
exchangeRate.times(decodedTransferValue, 10).toNumber();
const fiatFormatter = useFiatFormatter();

const fiatDisplayValue =
fiatValue && fiatFormatter(fiatValue, { shorten: true });

return {
decodedTransferValue,
fiatDisplayValue,
tokenBalance: tokenBalance && String(tokenBalance.toNumber()),
pending,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,47 @@
exports[`<SendHeading /> renders component 1`] = `
<div>
<div
class="mm-box mm-box--padding-4 mm-box--display-flex mm-box--flex-direction-column mm-box--justify-content-center mm-box--align-items-center"
class="mm-box mm-box--display-flex mm-box--justify-content-center mm-box--align-items-center"
>
<div
class="mm-box mm-text mm-avatar-base mm-avatar-base--size-xl mm-avatar-token mm-text--body-lg-medium mm-text--text-transform-uppercase mm-box--display-flex mm-box--justify-content-center mm-box--align-items-center mm-box--color-text-muted mm-box--background-color-overlay-default mm-box--rounded-full"
<svg
class="preloader__icon"
fill="none"
height="20"
viewBox="0 0 16 16"
width="20"
xmlns="http://www.w3.org/2000/svg"
>
?
</div>
<h2
class="mm-box mm-text mm-text--heading-lg mm-box--margin-top-3 mm-box--color-inherit"
>
Unknown
</h2>
<path
clip-rule="evenodd"
d="M8 13.7143C4.84409 13.7143 2.28571 11.1559 2.28571 8C2.28571 4.84409 4.84409 2.28571 8 2.28571C11.1559 2.28571 13.7143 4.84409 13.7143 8C13.7143 11.1559 11.1559 13.7143 8 13.7143ZM8 16C3.58172 16 0 12.4183 0 8C0 3.58172 3.58172 0 8 0C12.4183 0 16 3.58172 16 8C16 12.4183 12.4183 16 8 16Z"
fill="var(--color-primary-muted)"
fill-rule="evenodd"
/>
<mask
height="16"
id="mask0"
mask-type="alpha"
maskUnits="userSpaceOnUse"
width="16"
x="0"
y="0"
>
<path
clip-rule="evenodd"
d="M8 13.7143C4.84409 13.7143 2.28571 11.1559 2.28571 8C2.28571 4.84409 4.84409 2.28571 8 2.28571C11.1559 2.28571 13.7143 4.84409 13.7143 8C13.7143 11.1559 11.1559 13.7143 8 13.7143ZM8 16C3.58172 16 0 12.4183 0 8C0 3.58172 3.58172 0 8 0C12.4183 0 16 3.58172 16 8C16 12.4183 12.4183 16 8 16Z"
fill="var(--color-primary-default)"
fill-rule="evenodd"
/>
</mask>
<g
mask="url(#mask0)"
>
<path
d="M6.85718 17.9999V11.4285V8.28564H-4.85711V17.9999H6.85718Z"
fill="var(--color-primary-default)"
/>
</g>
</svg>
</div>
</div>
`;
Loading

0 comments on commit 3d5e95a

Please sign in to comment.