diff --git a/.storybook/__snapshots__/storyshots.test.js.snap b/.storybook/__snapshots__/storyshots.test.js.snap
index 66dd7686221..072ce8f7d78 100644
--- a/.storybook/__snapshots__/storyshots.test.js.snap
+++ b/.storybook/__snapshots__/storyshots.test.js.snap
@@ -23125,7 +23125,7 @@ exports[` Storyshots Organisms/SwapQuote SwapQuote 1`] = `
- Max TX Fee
+ Estimated Cost
- 15 minutes
+ 31 seconds
diff --git a/__tests__/fixtures.js b/__tests__/fixtures.js
index 9e69ffa5f3a..5927fa2f247 100644
--- a/__tests__/fixtures.js
+++ b/__tests__/fixtures.js
@@ -31,7 +31,8 @@ const PAGES = {
ADD_ACCOUNT_WEB3: `${FIXTURES_CONST.BASE_URL}/add-account/web3`,
SEND: `${FIXTURES_CONST.BASE_URL}/send`,
ADD_ACCOUNT: `${FIXTURES_CONST.BASE_URL}/add-account`,
- TX_STATUS: `${FIXTURES_CONST.BASE_URL}/tx-status`
+ TX_STATUS: `${FIXTURES_CONST.BASE_URL}/tx-status`,
+ SWAP: `${FIXTURES_CONST.BASE_URL}/swap`
};
const FIXTURE_ETHEREUM = 'Ethereum';
diff --git a/__tests__/swap-page.po.js b/__tests__/swap-page.po.js
new file mode 100644
index 00000000000..0f2075c6bf1
--- /dev/null
+++ b/__tests__/swap-page.po.js
@@ -0,0 +1,18 @@
+import { Selector, t } from 'testcafe';
+
+import BasePage from './base-page.po';
+import { FIXTURE_SEND_AMOUNT, PAGES } from './fixtures';
+
+export default class SwapPage extends BasePage {
+ async navigateToPage() {
+ this.navigateTo(PAGES.SWAP);
+ }
+
+ async waitPageLoaded(timeout) {
+ await this.waitForPage(PAGES.SWAP, timeout);
+ }
+
+ async fillForm() {
+ await t.typeText(Selector('input[name="swap-from"]').parent(), FIXTURE_SEND_AMOUNT);
+ }
+}
diff --git a/__tests__/swap.test.js b/__tests__/swap.test.js
new file mode 100644
index 00000000000..f0233713a8a
--- /dev/null
+++ b/__tests__/swap.test.js
@@ -0,0 +1,24 @@
+import { getByText } from '@testing-library/testcafe';
+
+import { injectLS } from './clientScripts';
+import { FIXTURE_LOCALSTORAGE_WITH_ONE_ACC, FIXTURES_CONST, PAGES } from './fixtures';
+import SwapPage from './swap-page.po';
+import { findByTKey } from './translation-utils';
+
+const swapPage = new SwapPage();
+
+fixture('Swap')
+ .clientScripts({ content: injectLS(FIXTURE_LOCALSTORAGE_WITH_ONE_ACC) })
+ .page(PAGES.SWAP);
+
+test('Can get swap quote', async (t) => {
+ await swapPage.waitPageLoaded();
+
+ /* Fill out form */
+ await swapPage.fillForm();
+ await t.wait(FIXTURES_CONST.TIMEOUT);
+
+ // Has received swap quote
+ const quote = await getByText(findByTKey('YOUR_QUOTE'));
+ await t.expect(quote).ok();
+});
diff --git a/jest_config/__fixtures__/index.ts b/jest_config/__fixtures__/index.ts
index 0686521d2b4..b32cea3e98a 100644
--- a/jest_config/__fixtures__/index.ts
+++ b/jest_config/__fixtures__/index.ts
@@ -75,5 +75,5 @@ export { fUserActions, fActionTemplates } from './userActions';
export { membershipApiResponse, accountWithMembership } from './membership';
export { default as APP_STATE } from './appState';
-export { fSwapQuote } from './swapQuote';
+export * from './swapQuote';
export { fBalances } from './balances';
diff --git a/jest_config/__fixtures__/swapQuote.ts b/jest_config/__fixtures__/swapQuote.ts
index 07f657d4317..d0308a9cb77 100644
--- a/jest_config/__fixtures__/swapQuote.ts
+++ b/jest_config/__fixtures__/swapQuote.ts
@@ -62,3 +62,67 @@ export const fSwapQuote = {
sellTokenToEthRate: '1547.962186957828746096',
buyTokenToEthRate: '1'
};
+
+export const fSwapQuoteReverse = {
+ price: '1.305257025416429946',
+ guaranteedPrice: '1.318309595670594246',
+ to: '0xdef1c0ded9bec7f1a1670819833240f027b25eff',
+ data:
+ '0x415565b0000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee0000000000000000000000009f8f72aa9304c8b593d555f12ef6589cc3a579a2000000000000000000000000000000000000000000000000124b8c194d5703b80000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee000000000000000000000000000000000000000000000000124b8c194d5703b800000000000000000000000000000000000000000000000000000000000000110000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000009f8f72aa9304c8b593d555f12ef6589cc3a579a200000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000de99870435440000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000442616c616e6365720000000000000000000000000000000000000000000000000000000000000000124b8c194d5703b80000000000000000000000000000000000000000000000000de998704354400000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000020000000000000000000000000987d7cc04652710b74fff380403f5c02f82e290a000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000009f8f72aa9304c8b593d555f12ef6589cc3a579a20000000000000000000000000000000000000000000000000008e1bc9bf04000000000000000000000000000d8d46494e200fa585fc98f86e6a5ea0dc1f18ad00000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000003000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000009f8f72aa9304c8b593d555f12ef6589cc3a579a2000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee0000000000000000000000000000000000000000000000000000000000000000869584cd000000000000000000000000100000000000000000000000000000000000001100000000000000000000000000000000000000000000008c9ad1818260a23bf6',
+ value: '1318301356235621304',
+ gas: '310000',
+ estimatedGas: '310000',
+ gasPrice: '66000000000',
+ protocolFee: '0',
+ minimumProtocolFee: '0',
+ buyTokenAddress: '0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2',
+ sellTokenAddress: '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee',
+ buyAmount: '1000000000000000000',
+ sellAmount: '1305248867560021093',
+ sources: [
+ { name: '0x', proportion: '0' },
+ { name: 'Uniswap', proportion: '0' },
+ { name: 'Uniswap_V2', proportion: '0' },
+ { name: 'Eth2Dai', proportion: '0' },
+ { name: 'Kyber', proportion: '0' },
+ { name: 'Curve', proportion: '0' },
+ { name: 'Balancer', proportion: '1' },
+ { name: 'Balancer_V2', proportion: '0' },
+ { name: 'Bancor', proportion: '0' },
+ { name: 'mStable', proportion: '0' },
+ { name: 'Mooniswap', proportion: '0' },
+ { name: 'Swerve', proportion: '0' },
+ { name: 'SnowSwap', proportion: '0' },
+ { name: 'SushiSwap', proportion: '0' },
+ { name: 'Shell', proportion: '0' },
+ { name: 'MultiHop', proportion: '0' },
+ { name: 'DODO', proportion: '0' },
+ { name: 'DODO_V2', proportion: '0' },
+ { name: 'CREAM', proportion: '0' },
+ { name: 'LiquidityProvider', proportion: '0' },
+ { name: 'CryptoCom', proportion: '0' },
+ { name: 'Linkswap', proportion: '0' },
+ { name: 'MakerPsm', proportion: '0' },
+ { name: 'KyberDMM', proportion: '0' },
+ { name: 'Smoothy', proportion: '0' },
+ { name: 'Component', proportion: '0' },
+ { name: 'Saddle', proportion: '0' },
+ { name: 'xSigma', proportion: '0' },
+ { name: 'Uniswap_V3', proportion: '0' }
+ ],
+ orders: [
+ {
+ makerToken: '0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2',
+ takerToken: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
+ makerAmount: '1002500000000000000',
+ takerAmount: '1305248867560021093',
+ fillData: { poolAddress: '0x987d7cc04652710b74fff380403f5c02f82e290a' },
+ source: 'Balancer',
+ sourcePathId: '0x646bfe1bbac7acdc36fb60378450d6f9c56d26c7b6c1e03f142777ee6194a987',
+ type: 0
+ }
+ ],
+ allowanceTarget: '0x0000000000000000000000000000000000000000',
+ sellTokenToEthRate: '1',
+ buyTokenToEthRate: '0.76609049757173667'
+};
diff --git a/src/components/TimeCountdown.tsx b/src/components/TimeCountdown.tsx
index bc1d8e0c97e..404b4d01099 100644
--- a/src/components/TimeCountdown.tsx
+++ b/src/components/TimeCountdown.tsx
@@ -9,7 +9,7 @@ const TimeCountdown = ({
value: number;
format?: string[];
}) => {
- const timeSince = (v: number) => formatTimeDuration(v, undefined, undefined, { format });
+ const timeSince = (v: number) => formatTimeDuration(v, Date.now() / 1000, false, { format });
const [countdown, setCountdown] = useState(timeSince(value));
useInterval(
diff --git a/src/features/SwapAssets/SwapAssetsFlow.test.tsx b/src/features/SwapAssets/SwapAssetsFlow.test.tsx
index 64b43564bd1..4294f43b45c 100644
--- a/src/features/SwapAssets/SwapAssetsFlow.test.tsx
+++ b/src/features/SwapAssets/SwapAssetsFlow.test.tsx
@@ -3,8 +3,9 @@ import React from 'react';
import mockAxios from 'jest-mock-axios';
import { fireEvent, simpleRender, waitFor } from 'test-utils';
-import { fAccounts, fAssets, fSwapQuote } from '@fixtures';
+import { fAccounts, fAssets, fSwapQuote, fSwapQuoteReverse } from '@fixtures';
import { StoreContext } from '@services/Store';
+import { truncate } from '@utils';
import SwapAssetsFlow from './SwapAssetsFlow';
@@ -16,7 +17,7 @@ function getComponent() {
assets: () => fAssets,
accounts: fAccounts,
userAssets: fAccounts.flatMap((a) => a.assets),
- getDefaultAccount: () => undefined
+ getDefaultAccount: () => fAccounts[0]
} as any) as any
}
>
@@ -25,6 +26,16 @@ function getComponent() {
);
}
+const tokenResponse = {
+ data: {
+ records: [fAssets[0], fAssets[13]].map((a) => ({
+ ...a,
+ symbol: a.ticker,
+ address: a.type === 'base' ? '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' : a.contractAddress
+ }))
+ }
+};
+
describe('SwapAssetsFlow', () => {
afterEach(() => {
mockAxios.reset();
@@ -32,15 +43,24 @@ describe('SwapAssetsFlow', () => {
it('selects default tokens', async () => {
const { getAllByText } = getComponent();
expect(mockAxios.get).toHaveBeenCalledWith('swap/v1/tokens');
- mockAxios.mockResponse({ data: { records: fAssets.map((a) => ({ ...a, symbol: a.ticker })) } });
+ mockAxios.mockResponse(tokenResponse);
await waitFor(() => expect(getAllByText(fAssets[0].ticker, { exact: false })).toBeDefined());
await waitFor(() => expect(getAllByText(fAssets[13].ticker, { exact: false })).toBeDefined());
});
+ it('selects default account', async () => {
+ const { getByText } = getComponent();
+ expect(mockAxios.get).toHaveBeenCalledWith('swap/v1/tokens');
+ mockAxios.mockResponse(tokenResponse);
+ await waitFor(() =>
+ expect(getByText(truncate(fAccounts[0].address), { exact: false })).toBeDefined()
+ );
+ });
+
it('calculates and shows to amount', async () => {
const { getAllByText, getAllByDisplayValue, container } = getComponent();
expect(mockAxios.get).toHaveBeenCalledWith('swap/v1/tokens');
- mockAxios.mockResponse({ data: { records: fAssets.map((a) => ({ ...a, symbol: a.ticker })) } });
+ mockAxios.mockResponse(tokenResponse);
await waitFor(() => expect(getAllByText(fAssets[0].ticker, { exact: false })).toBeDefined());
await waitFor(() => expect(getAllByText(fAssets[13].ticker, { exact: false })).toBeDefined());
mockAxios.reset();
@@ -56,4 +76,24 @@ describe('SwapAssetsFlow', () => {
expect(getAllByDisplayValue('0.000642566300455615', { exact: false })).toBeDefined()
);
});
+
+ it('calculates and shows from amount', async () => {
+ const { getAllByText, getAllByDisplayValue, container } = getComponent();
+ expect(mockAxios.get).toHaveBeenCalledWith('swap/v1/tokens');
+ mockAxios.mockResponse(tokenResponse);
+ await waitFor(() => expect(getAllByText(fAssets[0].ticker, { exact: false })).toBeDefined());
+ await waitFor(() => expect(getAllByText(fAssets[13].ticker, { exact: false })).toBeDefined());
+ mockAxios.reset();
+ fireEvent.change(container.querySelector('input[name="swap-to"]')!, {
+ target: { value: '1' }
+ });
+ await waitFor(() =>
+ expect(mockAxios.get).toHaveBeenCalledWith('swap/v1/quote', expect.anything())
+ );
+ mockAxios.mockResponse({ data: fSwapQuoteReverse });
+ await waitFor(() => expect(getAllByDisplayValue('1', { exact: false })).toBeDefined());
+ await waitFor(() =>
+ expect(getAllByDisplayValue('1.305248867560021093', { exact: false })).toBeDefined()
+ );
+ });
});
diff --git a/src/features/SwapAssets/SwapAssetsFlow.tsx b/src/features/SwapAssets/SwapAssetsFlow.tsx
index 6d5a322fdb0..5d80cd37f2d 100644
--- a/src/features/SwapAssets/SwapAssetsFlow.tsx
+++ b/src/features/SwapAssets/SwapAssetsFlow.tsx
@@ -61,7 +61,8 @@ const SwapAssetsFlow = (props: RouteComponentProps) => {
expiration,
approvalTx,
isEstimatingGas,
- tradeTx
+ tradeTx,
+ selectedNetwork
}: SwapFormState = formState;
const [assetPair, setAssetPair] = useState({});
@@ -109,7 +110,8 @@ const SwapAssetsFlow = (props: RouteComponentProps) => {
expiration,
approvalTx,
isEstimatingGas,
- isSubmitting
+ isSubmitting,
+ selectedNetwork
},
actions: {
handleFromAssetSelected,
@@ -133,7 +135,9 @@ const SwapAssetsFlow = (props: RouteComponentProps) => {
initWith(
() =>
Promise.resolve(
- (approvalTx ? [approvalTx, tradeTx] : [tradeTx]).map(appendSender(account.address))
+ (approvalTx ? [approvalTx, tradeTx!] : [tradeTx!]).map(
+ appendSender(account.address)
+ )
),
account,
account.network
diff --git a/src/features/SwapAssets/components/SwapAssets.test.tsx b/src/features/SwapAssets/components/SwapAssets.test.tsx
index 2d287962da5..1872046c6c4 100644
--- a/src/features/SwapAssets/components/SwapAssets.test.tsx
+++ b/src/features/SwapAssets/components/SwapAssets.test.tsx
@@ -10,6 +10,7 @@ import { LAST_CHANGED_AMOUNT } from '../types';
import SwapAssets from './SwapAssets';
const defaultProps: React.ComponentProps = {
+ selectedNetwork: 'Ethereum',
account: fAccounts[0],
assets: fAssets,
fromAsset: fAssets[0],
diff --git a/src/features/SwapAssets/components/SwapAssets.tsx b/src/features/SwapAssets/components/SwapAssets.tsx
index 1d0ffe41287..379b9beebd0 100644
--- a/src/features/SwapAssets/components/SwapAssets.tsx
+++ b/src/features/SwapAssets/components/SwapAssets.tsx
@@ -1,6 +1,5 @@
import React, { useCallback, useContext, useEffect, useState } from 'react';
-import { connect, ConnectedProps } from 'react-redux';
import styled from 'styled-components';
import {
@@ -18,15 +17,15 @@ import {
import { useRates } from '@services/Rates';
import { StoreContext } from '@services/Store';
import {
- AppState,
getBaseAssetByNetwork,
getIsDemoMode,
getSettings,
- selectDefaultNetwork
+ selectNetwork,
+ useSelector
} from '@store';
import { SPACING } from '@theme';
import translate, { translateRaw } from '@translations';
-import { Asset, ExtendedAsset, ISwapAsset, Network, StoreAccount } from '@types';
+import { Asset, ISwapAsset, StoreAccount } from '@types';
import { bigify, getTimeDifference, totalTxFeeToString, useInterval } from '@utils';
import { useDebounce } from '@vendor';
@@ -42,7 +41,7 @@ const StyledButton = styled(Button)`
}
`;
-type ISwapProps = SwapFormState & {
+type Props = SwapFormState & {
isSubmitting: boolean;
txError?: CustomError;
onSuccess(): void;
@@ -59,6 +58,7 @@ type ISwapProps = SwapFormState & {
const SwapAssets = (props: Props) => {
const {
+ selectedNetwork,
account,
fromAmount,
toAmount,
@@ -87,12 +87,14 @@ const SwapAssets = (props: Props) => {
tradeGasLimit,
gasPrice,
isEstimatingGas,
- expiration,
- isDemoMode,
- baseAsset,
- settings
+ expiration
} = props;
+ const settings = useSelector(getSettings);
+ const isDemoMode = useSelector(getIsDemoMode);
+ const network = useSelector(selectNetwork(selectedNetwork));
+ const baseAsset = useSelector(getBaseAssetByNetwork(network));
+
const [isExpired, setIsExpired] = useState(false);
const { accounts, userAssets } = useContext(StoreContext);
const { getAssetRate } = useRates();
@@ -145,18 +147,29 @@ const SwapAssets = (props: Props) => {
calculateNewToAmount(fromAmount);
}, [toAsset]);
+ const estimatedGasFee =
+ gasPrice &&
+ tradeGasLimit &&
+ totalTxFeeToString(
+ gasPrice,
+ bigify(tradeGasLimit).plus(approvalGasLimit ? approvalGasLimit : 0)
+ );
+
+ // Accounts with a balance of the chosen asset and base asset
+ const filteredAccounts = fromAsset
+ ? getAccountsWithAssetBalance(accounts, fromAsset, fromAmount, baseAsset.uuid, estimatedGasFee)
+ : [];
+
useEffect(() => {
if (
fromAmount &&
fromAsset &&
account &&
- !getAccountsWithAssetBalance(accounts, fromAsset, fromAmount).find(
- (a) => a.uuid === account.uuid
- )
+ !filteredAccounts.find((a) => a.uuid === account.uuid)
) {
handleAccountSelected(undefined);
}
- }, [fromAsset, fromAmount]);
+ }, [fromAsset, fromAmount, gasPrice, tradeGasLimit, approvalGasLimit]);
useEffect(() => {
handleRefreshQuote();
@@ -166,14 +179,6 @@ const SwapAssets = (props: Props) => {
handleGasLimitEstimation();
}, [approvalTx, account]);
- const estimatedGasFee =
- gasPrice &&
- tradeGasLimit &&
- totalTxFeeToString(
- gasPrice,
- bigify(tradeGasLimit).plus(approvalGasLimit ? approvalGasLimit : 0)
- );
-
useInterval(
() => {
if (!expiration) {
@@ -189,11 +194,6 @@ const SwapAssets = (props: Props) => {
[expiration]
);
- // Accounts with a balance of the chosen asset
- const filteredAccounts = fromAsset
- ? getAccountsWithAssetBalance(accounts, fromAsset, fromAmount, baseAsset.uuid, estimatedGasFee)
- : [];
-
return (
<>
@@ -242,6 +242,7 @@ const SwapAssets = (props: Props) => {
{
);
};
-const mapStateToProps = (state: AppState) => {
- const network = selectDefaultNetwork(state) as Network;
-
- return {
- isDemoMode: getIsDemoMode(state),
- baseAsset: getBaseAssetByNetwork(network)(state) as ExtendedAsset,
- settings: getSettings(state)
- };
-};
-
-const connector = connect(mapStateToProps);
-type Props = ConnectedProps & ISwapProps;
-
-export default connector(SwapAssets);
+export default SwapAssets;
diff --git a/src/features/SwapAssets/components/SwapQuote.stories.tsx b/src/features/SwapAssets/components/SwapQuote.stories.tsx
index 4935a0f3717..c12930bf6f3 100644
--- a/src/features/SwapAssets/components/SwapQuote.stories.tsx
+++ b/src/features/SwapAssets/components/SwapQuote.stories.tsx
@@ -1,8 +1,6 @@
import React from 'react';
-import { sub } from 'date-fns';
-
-import { DAIUUID, ETHUUID } from '@config';
+import { DAIUUID, DEX_TRADE_EXPIRATION, ETHUUID } from '@config';
import { fAssets, fSettings } from '@fixtures';
import { ISwapAsset, TTicker, TUuid } from '@types';
import { bigify, noOp } from '@utils';
@@ -30,7 +28,6 @@ const defaultProps = {
baseAssetRate: bigify('123'),
settings: fSettings,
isExpired: false,
- expiration: sub(new Date(), { minutes: 15 }), // Component displays a time-from so we provide a relative date.
estimatedGasFee: '123123',
handleRefreshQuote: noOp
};
@@ -38,7 +35,7 @@ const defaultProps = {
export default { title: 'Organisms/SwapQuote', component: SwapQuote };
const Template = (args: React.ComponentProps) => {
- return ;
+ return ;
};
export const Hello = Template.bind({});
diff --git a/src/features/SwapAssets/components/SwapQuote.tsx b/src/features/SwapAssets/components/SwapQuote.tsx
index 8d0d6bee864..2d892dd9846 100644
--- a/src/features/SwapAssets/components/SwapQuote.tsx
+++ b/src/features/SwapAssets/components/SwapQuote.tsx
@@ -94,7 +94,7 @@ export const SwapQuote = ({
- {translateRaw('MAX_TX_FEE')}
+ {translateRaw('ESTIMATED_COST')}
{!isExpired ? (
-
+
) : (
{translateRaw('EXPIRED')}
diff --git a/src/features/SwapAssets/stateFormFactory.tsx b/src/features/SwapAssets/stateFormFactory.tsx
index ca5f553484e..74aa075ea3c 100644
--- a/src/features/SwapAssets/stateFormFactory.tsx
+++ b/src/features/SwapAssets/stateFormFactory.tsx
@@ -1,9 +1,9 @@
import axios from 'axios';
-import { DEFAULT_NETWORK_TICKER, MYC_DEX_COMMISSION_RATE } from '@config';
+import { MYC_DEX_COMMISSION_RATE } from '@config';
import { checkRequiresApproval } from '@helpers';
import { DexAsset, DexService, getGasEstimate } from '@services';
-import { selectDefaultNetwork, useSelector } from '@store';
+import { selectNetwork, useSelector } from '@store';
import translate from '@translations';
import { ISwapAsset, ITxGasLimit, Network, StoreAccount } from '@types';
import {
@@ -20,6 +20,7 @@ import {
import { LAST_CHANGED_AMOUNT, SwapFormState } from './types';
const swapFormInitialState = {
+ selectedNetwork: 'Ethereum',
assets: [],
account: undefined,
fromAsset: undefined,
@@ -33,8 +34,10 @@ const swapFormInitialState = {
lastChangedAmount: LAST_CHANGED_AMOUNT.FROM
};
+const BASE_ASSET_ADDRESS = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee';
+
const SwapFormFactory: TUseStateReducerFactory = ({ state, setState }) => {
- const network = useSelector(selectDefaultNetwork) as Network;
+ const network = useSelector(selectNetwork(state.selectedNetwork)) as Network;
const fetchSwapAssets = async () => {
try {
@@ -42,22 +45,20 @@ const SwapFormFactory: TUseStateReducerFactory = ({ state, setSta
if (assets.length < 1) return;
// sort assets alphabetically
const newAssets = assets
- .map(
- ({ symbol, decimals, ...asset }: DexAsset): ISwapAsset => ({
- ...asset,
- ticker: symbol,
- decimal: decimals,
- uuid:
- symbol === DEFAULT_NETWORK_TICKER
- ? generateAssetUUID(network.chainId)
- : generateAssetUUID(network.chainId, asset.address)
- })
- )
+ .map(({ symbol, decimals, ...asset }: DexAsset) => ({
+ ...asset,
+ ticker: symbol,
+ decimal: decimals,
+ uuid:
+ asset.address === BASE_ASSET_ADDRESS
+ ? generateAssetUUID(network.chainId)
+ : generateAssetUUID(network.chainId, asset.address)
+ }))
.sort((asset1: ISwapAsset, asset2: ISwapAsset) =>
(asset1.ticker as string).localeCompare(asset2.ticker)
);
// set fromAsset to default (ETH)
- const fromAsset = newAssets.find((x: ISwapAsset) => x.ticker === network.baseUnit);
+ const fromAsset = newAssets.find((x) => x.address === BASE_ASSET_ADDRESS);
const toAsset = newAssets[0];
return [newAssets, fromAsset, toAsset];
} catch (e) {
@@ -128,6 +129,7 @@ const SwapFormFactory: TUseStateReducerFactory = ({ state, setSta
}));
const { price, sellAmount, ...rest } = await DexService.instance.getOrderDetailsTo(
+ network,
account?.address,
fromAsset,
toAsset,
@@ -197,6 +199,7 @@ const SwapFormFactory: TUseStateReducerFactory = ({ state, setSta
}));
const { price, buyAmount, ...rest } = await DexService.instance.getOrderDetailsFrom(
+ network,
account?.address,
fromAsset,
toAsset,
@@ -274,8 +277,10 @@ const SwapFormFactory: TUseStateReducerFactory = ({ state, setSta
approvalTx &&
(await checkRequiresApproval(network, approvalTx.to!, account.address, approvalTx.data!));
+ const { type, ...tx } = approvalTx;
+
const approvalGasLimit = inputGasLimitToHex(
- requiresApproval ? await getGasEstimate(network, approvalTx!) : '0'
+ requiresApproval ? await getGasEstimate(network, tx!) : '0'
) as ITxGasLimit;
setState((prevState: SwapFormState) => ({
diff --git a/src/features/SwapAssets/types.ts b/src/features/SwapAssets/types.ts
index fd937dcf315..9e55176deaa 100644
--- a/src/features/SwapAssets/types.ts
+++ b/src/features/SwapAssets/types.ts
@@ -1,6 +1,15 @@
import { BigNumber } from 'bignumber.js';
-import { ISwapAsset, ITxGasLimit, ITxGasPrice, ITxObject, ITxStatus, StoreAccount } from '@types';
+import {
+ ISwapAsset,
+ ITxGasLimit,
+ ITxGasPrice,
+ ITxObject,
+ ITxStatus,
+ ITxType,
+ NetworkId,
+ StoreAccount
+} from '@types';
export enum LAST_CHANGED_AMOUNT {
FROM = 'FROM_AMOUNT',
@@ -25,6 +34,7 @@ export interface SwapState {
}
export interface SwapFormState {
+ selectedNetwork: NetworkId;
account: StoreAccount;
assets: ISwapAsset[];
fromAsset: ISwapAsset;
@@ -42,9 +52,11 @@ export interface SwapFormState {
gasPrice?: ITxGasPrice;
approvalGasLimit?: ITxGasLimit;
tradeGasLimit?: ITxGasLimit;
- approvalTx?: Partial;
+ approvalTx?: Pick & {
+ type: ITxType;
+ };
expiration?: number;
- tradeTx?: Partial;
+ tradeTx?: Pick & { type: ITxType };
}
export interface IAssetPair {
diff --git a/src/hooks/useTxMulti/useTxMulti.spec.tsx b/src/hooks/useTxMulti/useTxMulti.spec.tsx
index 3851ce3416f..780a38f9988 100644
--- a/src/hooks/useTxMulti/useTxMulti.spec.tsx
+++ b/src/hooks/useTxMulti/useTxMulti.spec.tsx
@@ -193,17 +193,15 @@ describe('useTxMulti', () => {
await waitFor(() =>
expect(mockDispatch).toHaveBeenCalledWith(
actionWithPayload({
- ...fAccount,
- transactions: expect.arrayContaining([
- expect.objectContaining({
- amount: '0.0',
- asset: fAssets[1],
- baseAsset: fAssets[1],
- hash: '0x1',
- txType: ITxType.APPROVAL,
- status: ITxStatus.PENDING
- })
- ])
+ account: fAccount,
+ tx: expect.objectContaining({
+ amount: '0.0',
+ asset: fAssets[1],
+ baseAsset: fAssets[1],
+ hash: '0x1',
+ txType: ITxType.APPROVAL,
+ status: ITxStatus.PENDING
+ })
})
)
);
@@ -211,17 +209,15 @@ describe('useTxMulti', () => {
await waitFor(() =>
expect(mockDispatch).toHaveBeenCalledWith(
actionWithPayload({
- ...fAccount,
- transactions: expect.arrayContaining([
- expect.objectContaining({
- amount: '0.0',
- asset: fAssets[1],
- baseAsset: fAssets[1],
- hash: '0x2',
- txType: ITxType.PURCHASE_MEMBERSHIP,
- status: ITxStatus.PENDING
- })
- ])
+ account: fAccount,
+ tx: expect.objectContaining({
+ amount: '0.0',
+ asset: fAssets[1],
+ baseAsset: fAssets[1],
+ hash: '0x2',
+ txType: ITxType.PURCHASE_MEMBERSHIP,
+ status: ITxStatus.PENDING
+ })
})
)
);
diff --git a/src/services/ApiService/Dex/Dex.spec.ts b/src/services/ApiService/Dex/Dex.spec.ts
index 7041f29ad3f..24449ce9736 100644
--- a/src/services/ApiService/Dex/Dex.spec.ts
+++ b/src/services/ApiService/Dex/Dex.spec.ts
@@ -1,7 +1,7 @@
import mockAxios from 'jest-mock-axios';
-import { fAssets, fRopDAI, fSwapQuote } from '@fixtures';
-import { ITxData, ITxGasLimit, ITxGasPrice, ITxToAddress, ITxType, ITxValue } from '@types';
+import { fAssets, fNetwork, fRopDAI, fSwapQuote } from '@fixtures';
+import { ITxData, ITxGasPrice, ITxToAddress, ITxType, ITxValue } from '@types';
import { DexService } from '.';
import { formatTradeTx } from './Dex';
@@ -12,13 +12,19 @@ describe('SwapFlow', () => {
});
describe('getOrderDetails', () => {
it('returns the expected two transactions for a multi tx swap', async () => {
- const promise = DexService.instance.getOrderDetailsFrom(null, fRopDAI, fAssets[0], '1');
+ const promise = DexService.instance.getOrderDetailsFrom(
+ fNetwork,
+ null,
+ fRopDAI,
+ fAssets[0],
+ '1'
+ );
mockAxios.mockResponse({
data: { ...fSwapQuote }
});
const result = await promise;
expect(result.approvalTx).toStrictEqual({
- chainId: 1,
+ chainId: fNetwork.chainId,
data:
'0x095ea7b3000000000000000000000000def1c0ded9bec7f1a1670819833240f027b25eff0000000000000000000000000000000000000000000000000de0b6b3a7640000',
from: undefined,
@@ -28,11 +34,10 @@ describe('SwapFlow', () => {
value: '0x0'
});
expect(result.tradeTx).toStrictEqual({
- chainId: 1,
+ chainId: fNetwork.chainId,
data:
'0xd9627aa400000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000002429108b8f331000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee869584cd0000000000000000000000001000000000000000000000000000000000000011000000000000000000000000000000000000000000000096596a1ef6601a8b3a',
gasPrice: '0x23db1d8400',
- gasLimit: '0x21340',
to: '0xdef1c0ded9bec7f1a1670819833240f027b25eff',
type: 'SWAP',
value: '0x0'
@@ -47,14 +52,13 @@ describe('SwapFlow', () => {
to: '0xA65440C4CC83D70b44cF244a0da5373acA16a9cb' as ITxToAddress,
data: '0x5d46ec340000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000000d8775f648430679a709e98d2b0cb6250d2887ef00000000000000000000000000000000000000000000000000038d7ea4c680000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000002a00000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000e807dc3fe542f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000002a1530c4c41db0b0b2bb646cb5eb1a67b715866700000000000000000000000000000000000000000000000000000000000000010000000000000000000000002a1530c4c41db0b0b2bb646cb5eb1a67b715866700000000000000000000000000000000000000000000000000000000000000a4ddf7e1a700000000000000000000000000000000000000000000000000038d7ea4c68000000000000000000000000000000000000000000000000000000e807dc3fe542f0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000005e6275fe0000000000000000000000000d8775f648430679a709e98d2b0cb6250d2887ef000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a400000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000' as ITxData,
value: '50000' as ITxValue,
- gasLimit: '0x21340' as ITxGasLimit,
- gasPrice: '0x23db1d8400' as ITxGasPrice
+ gasPrice: '0x23db1d8400' as ITxGasPrice,
+ chainId: 1
})
).toEqual({
to: '0xA65440C4CC83D70b44cF244a0da5373acA16a9cb' as ITxToAddress,
data: '0x5d46ec340000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000000d8775f648430679a709e98d2b0cb6250d2887ef00000000000000000000000000000000000000000000000000038d7ea4c680000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000002a00000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000e807dc3fe542f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000002a1530c4c41db0b0b2bb646cb5eb1a67b715866700000000000000000000000000000000000000000000000000000000000000010000000000000000000000002a1530c4c41db0b0b2bb646cb5eb1a67b715866700000000000000000000000000000000000000000000000000000000000000a4ddf7e1a700000000000000000000000000000000000000000000000000038d7ea4c68000000000000000000000000000000000000000000000000000000e807dc3fe542f0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000005e6275fe0000000000000000000000000d8775f648430679a709e98d2b0cb6250d2887ef000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a400000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000' as ITxData,
value: '0xc350' as ITxValue,
- gasLimit: '0x21340' as ITxGasLimit,
gasPrice: '0x23db1d8400' as ITxGasPrice,
chainId: 1,
type: ITxType.SWAP
diff --git a/src/services/ApiService/Dex/Dex.ts b/src/services/ApiService/Dex/Dex.ts
index eb9af314e0a..98f843108fc 100644
--- a/src/services/ApiService/Dex/Dex.ts
+++ b/src/services/ApiService/Dex/Dex.ts
@@ -3,7 +3,6 @@ import axios, { AxiosInstance } from 'axios';
import {
DEFAULT_ASSET_DECIMAL,
- DEFAULT_NETWORK_CHAINID,
DEX_BASE_URL,
DEX_FEE_RECIPIENT,
DEX_TRADE_EXPIRATION,
@@ -17,6 +16,7 @@ import {
ITxObject,
ITxType,
ITxValue,
+ Network,
TAddress,
TTicker
} from '@types';
@@ -60,20 +60,23 @@ export default class DexService {
};
public getOrderDetailsFrom = async (
+ network: Network,
account: string | null,
from: ISwapAsset,
to: ISwapAsset,
fromAmount: string
- ) => this.getOrderDetails(account, from, to, fromAmount);
+ ) => this.getOrderDetails(network, account, from, to, fromAmount);
public getOrderDetailsTo = async (
+ network: Network,
account: string | null,
from: ISwapAsset,
to: ISwapAsset,
toAmount: string
- ) => this.getOrderDetails(account, from, to, undefined, toAmount);
+ ) => this.getOrderDetails(network, account, from, to, undefined, toAmount);
private getOrderDetails = async (
+ network: Network,
account: string | null,
sellToken: ISwapAsset,
buyToken: ISwapAsset,
@@ -96,7 +99,8 @@ export default class DexService {
: undefined,
feeRecipient: DEX_FEE_RECIPIENT,
buyTokenPercentageFee: MYC_DEX_COMMISSION_RATE,
- takerAddress: account ? account : undefined
+ takerAddress: account ? account : undefined,
+ skipValidation: true
},
cancelToken: new CancelToken(function executor(c) {
// An executor function receives a cancel function as a parameter
@@ -112,13 +116,15 @@ export default class DexService {
spenderAddress: data.allowanceTarget,
hexGasPrice: addHexPrefix(bigify(data.gasPrice).toString(16)) as ITxGasPrice,
baseTokenAmount: bigify(data.sellAmount),
- chainId: DEFAULT_NETWORK_CHAINID
+ chainId: network.chainId
}),
type: ITxType.APPROVAL
}
: undefined;
- const tradeGasLimit = addHexPrefix(bigify(data.gas).toString(16)) as ITxGasLimit;
+ const tradeGasLimit = addHexPrefix(
+ bigify(data.gas).multipliedBy(1.2).integerValue(7).toString(16)
+ ) as ITxGasLimit;
return {
price: bigify(data.price),
@@ -137,8 +143,8 @@ export default class DexService {
to: data.to,
data: data.data,
gasPrice: addHexPrefix(bigify(data.gasPrice).toString(16)) as ITxGasPrice,
- gasLimit: tradeGasLimit,
- value: data.value
+ value: data.value,
+ chainId: network.chainId
})
};
};
@@ -149,15 +155,14 @@ export const formatTradeTx = ({
data,
value,
gasPrice,
- gasLimit
-}: Pick) => {
+ chainId
+}: Pick) => {
return {
to,
data,
value: addHexPrefix(bigify(value || '0').toString(16)) as ITxValue,
- chainId: DEFAULT_NETWORK_CHAINID,
+ chainId,
gasPrice,
- gasLimit,
type: ITxType.SWAP
};
};
diff --git a/src/services/Store/Account/useAccounts.spec.tsx b/src/services/Store/Account/useAccounts.spec.tsx
index 9e6b8a53843..e4a05f27d08 100644
--- a/src/services/Store/Account/useAccounts.spec.tsx
+++ b/src/services/Store/Account/useAccounts.spec.tsx
@@ -70,8 +70,8 @@ describe('useAccounts', () => {
result.current.addTxToAccount(fAccounts[0], fTxReceipt);
expect(mockDispatch).toHaveBeenCalledWith(
actionWithPayload({
- ...fAccounts[0],
- transactions: [fTxReceipt]
+ account: fAccounts[0],
+ tx: fTxReceipt
})
);
});
diff --git a/src/services/Store/Account/useAccounts.tsx b/src/services/Store/Account/useAccounts.tsx
index bba49c074dc..00239c511e3 100644
--- a/src/services/Store/Account/useAccounts.tsx
+++ b/src/services/Store/Account/useAccounts.tsx
@@ -2,6 +2,7 @@ import { useSelector } from 'react-redux';
import {
addAccounts,
+ addTxToAccount as addTxToAccountRedux,
destroyAccount,
getAccounts,
updateAccount as updateAccountRedux,
@@ -39,13 +40,8 @@ function useAccounts() {
const updateAccount = (_: TUuid, account: IAccount) => dispatch(updateAccountRedux(account));
- const addTxToAccount = (accountData: IAccount, newTx: ITxReceipt) => {
- const newAccountData = {
- ...accountData,
- transactions: [...accountData.transactions.filter((tx) => tx.hash !== newTx.hash), newTx]
- };
- updateAccount(accountData.uuid, newAccountData);
- };
+ const addTxToAccount = (account: IAccount, tx: ITxReceipt) =>
+ dispatch(addTxToAccountRedux({ account, tx }));
const removeTxFromAccount = (accountData: IAccount, tx: ITxReceipt) => {
const newAccountData = {
diff --git a/src/services/Store/StoreProvider.tsx b/src/services/Store/StoreProvider.tsx
index 51561699c80..86bd3811f2a 100644
--- a/src/services/Store/StoreProvider.tsx
+++ b/src/services/Store/StoreProvider.tsx
@@ -14,7 +14,6 @@ import {
fetchAssets,
fetchMemberships,
isMyCryptoMember,
- scanTokens,
selectTxsByStatus,
useDispatch,
useSelector
@@ -28,7 +27,6 @@ import {
IAccountAdditionData,
IPendingTxReceipt,
ITxStatus,
- ITxType,
Network,
NetworkId,
StoreAccount,
@@ -58,12 +56,7 @@ import { getNewDefaultAssetTemplateByNetwork, getTotalByAsset, useAssets } from
import { getAccountsAssetsBalances } from './BalanceService';
import { useContacts } from './Contact';
import { findMultipleNextUnusedDefaultLabels } from './Contact/helpers';
-import {
- getStoreAccounts,
- getTxsFromAccount,
- isNotExcludedAsset,
- isTokenMigration
-} from './helpers';
+import { getStoreAccounts, getTxsFromAccount, isNotExcludedAsset } from './helpers';
import { getNetworkById, useNetworks } from './Network';
import { useSettings } from './Settings';
@@ -238,14 +231,6 @@ export const StoreProvider: React.FC = ({ children }) => {
txResponse.blockNumber
);
addTxToAccount(senderAccount, finishedTxReceipt);
- if (
- finishedTxReceipt.txType === ITxType.DEFIZAP ||
- isTokenMigration(finishedTxReceipt.txType)
- ) {
- dispatch(scanTokens({ accounts: [storeAccount] }));
- } else if (finishedTxReceipt.txType === ITxType.PURCHASE_MEMBERSHIP) {
- dispatch(fetchMemberships([storeAccount]));
- }
});
});
});
diff --git a/src/services/Store/store/account.slice.test.ts b/src/services/Store/store/account.slice.test.ts
index 2c454bb8b71..a8c342dd6e0 100644
--- a/src/services/Store/store/account.slice.test.ts
+++ b/src/services/Store/store/account.slice.test.ts
@@ -1,17 +1,22 @@
import { BigNumber } from '@ethersproject/bignumber';
-import { mockAppState } from 'test-utils';
+import { expectSaga, mockAppState } from 'test-utils';
import { ETHUUID, REPV2UUID } from '@config';
-import { fAccount, fAccounts, fSettings, fTransaction } from '@fixtures';
-import { IAccount, ITxReceipt, TUuid } from '@types';
+import { fAccount, fAccounts, fSettings, fTransaction, fTxReceipt } from '@fixtures';
+import { IAccount, ITxReceipt, ITxStatus, ITxType, TUuid } from '@types';
import {
+ addTxToAccount,
+ addTxToAccountWorker,
getAccounts,
initialState,
selectAccountTxs,
selectCurrentAccounts,
- default as slice
+ default as slice,
+ updateAccount
} from './account.slice';
+import { fetchMemberships } from './membership.slice';
+import { scanTokens } from './tokenScanning.slice';
const reducer = slice.reducer;
const { create, createMany, destroy, update, updateMany, reset, updateAssets } = slice.actions;
@@ -157,4 +162,49 @@ describe('AccountSlice', () => {
}
]);
});
+
+ describe('addTxToAccountWorker', () => {
+ it('updates account with tx', () => {
+ return expectSaga(addTxToAccountWorker, addTxToAccount({ account: fAccount, tx: fTxReceipt }))
+ .put(updateAccount({ ...fAccount, transactions: [fTxReceipt] }))
+ .silentRun();
+ });
+
+ it('scans for tokens if tx is a swap', () => {
+ return expectSaga(
+ addTxToAccountWorker,
+ addTxToAccount({
+ account: fAccount,
+ tx: { ...fTxReceipt, txType: ITxType.SWAP, status: ITxStatus.SUCCESS }
+ })
+ )
+ .put(scanTokens({ accounts: [fAccount] }))
+ .silentRun();
+ });
+
+ it('scans for membership if tx is a membership purchase', () => {
+ return expectSaga(
+ addTxToAccountWorker,
+ addTxToAccount({
+ account: fAccount,
+ tx: { ...fTxReceipt, txType: ITxType.PURCHASE_MEMBERSHIP, status: ITxStatus.SUCCESS }
+ })
+ )
+ .put(fetchMemberships([fAccount]))
+ .silentRun();
+ });
+
+ it('overwrites existing tx', () => {
+ const tx = { ...fTxReceipt, status: ITxStatus.SUCCESS };
+ return expectSaga(
+ addTxToAccountWorker,
+ addTxToAccount({
+ account: { ...fAccount, transactions: [fTxReceipt] },
+ tx
+ })
+ )
+ .put(updateAccount({ ...fAccount, transactions: [tx] }))
+ .silentRun();
+ });
+ });
});
diff --git a/src/services/Store/store/account.slice.ts b/src/services/Store/store/account.slice.ts
index 5b577762ec2..03cd9566cfd 100644
--- a/src/services/Store/store/account.slice.ts
+++ b/src/services/Store/store/account.slice.ts
@@ -1,21 +1,26 @@
import { BigNumber as EthersBN } from '@ethersproject/bignumber';
import { createAction, createSelector, createSlice, PayloadAction } from '@reduxjs/toolkit';
-import { put, select, takeLatest } from 'redux-saga/effects';
+import { all, put, select, takeLatest } from 'redux-saga/effects';
import {
AssetBalanceObject,
ExtendedAsset,
IAccount,
IProvidersMappings,
+ ITxReceipt,
ITxStatus,
+ ITxType,
LSKeys,
TUuid
} from '@types';
import { findIndex, propEq } from '@vendor';
+import { isTokenMigration } from '../helpers';
import { getAssetByUUID } from './asset.slice';
+import { fetchMemberships } from './membership.slice';
import { getAppState } from './selectors';
import { addAccountsToFavorites, getFavorites, getIsDemoMode } from './settings.slice';
+import { scanTokens } from './tokenScanning.slice';
export const initialState = [] as IAccount[];
@@ -135,12 +140,18 @@ export const getAccountsAssetsMappings = createSelector([getAccountsAssets], (as
* Actions
*/
export const addAccounts = createAction(`${slice.name}/addAccounts`);
+export const addTxToAccount = createAction<{ account: IAccount; tx: ITxReceipt }>(
+ `${slice.name}/addTxToAccount`
+);
/**
* Sagas
*/
export function* accountsSaga() {
- yield takeLatest(addAccounts.type, handleAddAccounts);
+ yield all([
+ takeLatest(addAccounts.type, handleAddAccounts),
+ takeLatest(addTxToAccount.type, addTxToAccountWorker)
+ ]);
}
export function* handleAddAccounts({ payload }: PayloadAction) {
@@ -154,3 +165,27 @@ export function* handleAddAccounts({ payload }: PayloadAction) {
yield put(addAccountsToFavorites(payload.map(({ uuid }) => uuid)));
}
}
+
+export function* addTxToAccountWorker({
+ payload: { account, tx: newTx }
+}: PayloadAction<{ account: IAccount; tx: ITxReceipt }>) {
+ const newAccountData = {
+ ...account,
+ transactions: [...account.transactions.filter((tx) => tx.hash !== newTx.hash), newTx]
+ };
+ yield put(updateAccount(newAccountData));
+
+ if (newTx.status !== ITxStatus.SUCCESS) {
+ return;
+ }
+
+ if (
+ newTx.txType === ITxType.DEFIZAP ||
+ isTokenMigration(newTx.txType) ||
+ newTx.txType === ITxType.SWAP
+ ) {
+ yield put(scanTokens({ accounts: [account] }));
+ } else if (newTx.txType === ITxType.PURCHASE_MEMBERSHIP) {
+ yield put(fetchMemberships([account]));
+ }
+}
diff --git a/src/services/Store/store/asset.slice.ts b/src/services/Store/store/asset.slice.ts
index d846d06e5ca..933725dc818 100644
--- a/src/services/Store/store/asset.slice.ts
+++ b/src/services/Store/store/asset.slice.ts
@@ -4,7 +4,7 @@ import { all, call, put, takeLatest } from 'redux-saga/effects';
import { EXCLUDED_ASSETS } from '@config';
import { MyCryptoApiService } from '@services';
import { ExtendedAsset, LSKeys, Network, TUuid } from '@types';
-import { filter, find, findIndex, map, mergeRight, pipe, propEq, toPairs } from '@vendor';
+import { filter, findIndex, map, mergeRight, pipe, propEq, toPairs } from '@vendor';
import { initialLegacyState } from './legacy.initialState';
import { appReset } from './root.reducer';
@@ -80,7 +80,7 @@ export default slice;
export const getAssets = createSelector([getAppState], (s) => s[slice.name]);
export const getBaseAssetByNetwork = (network: Network) =>
- createSelector(getAssets, find(propEq('uuid', network.baseAsset)));
+ createSelector(getAssets, (assets) => assets.find((asset) => asset.uuid === network.baseAsset)!);
export const getAssetByUUID = (uuid: TUuid) =>
createSelector([getAssets], (a) => a.find((asset) => asset.uuid === uuid));
diff --git a/src/services/Store/store/index.ts b/src/services/Store/store/index.ts
index 4bae6b301c1..81a17ae8a0d 100644
--- a/src/services/Store/store/index.ts
+++ b/src/services/Store/store/index.ts
@@ -23,7 +23,8 @@ export {
addAccounts,
selectCurrentAccounts,
selectAccountTxs,
- selectTxsByStatus
+ selectTxsByStatus,
+ addTxToAccount
} from './account.slice';
export {
createContact,
diff --git a/src/services/Store/store/network.slice.ts b/src/services/Store/store/network.slice.ts
index e36a4847156..027f79fd36f 100644
--- a/src/services/Store/store/network.slice.ts
+++ b/src/services/Store/store/network.slice.ts
@@ -4,7 +4,7 @@ import { all, call, put, select, takeLatest } from 'redux-saga/effects';
import { DEFAULT_NETWORK } from '@config';
import { EthersJS } from '@services/EthService/network/ethersJsProvider';
import { LSKeys, Network, NetworkId } from '@types';
-import { find, findIndex, propEq } from '@vendor';
+import { findIndex, propEq } from '@vendor';
import { initialLegacyState } from './legacy.initialState';
import { getAppState } from './selectors';
@@ -88,9 +88,7 @@ export default slice;
* Selectors
*/
-// @ts-expect-error: TS fails to infer correct type from find
-const findNetwork: (id: NetworkId) => (n: Network[]) => Network = (id: NetworkId) =>
- find(propEq('id', id));
+const findNetwork = (id: NetworkId) => (networks: Network[]) => networks.find((n) => n.id === id)!;
export const selectNetworks = createSelector([getAppState], (s) => s[slice.name]);
diff --git a/src/translations/lang/en.json b/src/translations/lang/en.json
index 92399a0de6d..438d4e50492 100644
--- a/src/translations/lang/en.json
+++ b/src/translations/lang/en.json
@@ -975,9 +975,10 @@
"GET_NEW_QUOTE": "Get New Quote",
"SWAP_FOR": "Swap $from for $to",
"SWAP_AMOUNT_TOOLTIP": "This is confirming the amounts you want to convert and receive.",
- "SWAP_TX_FEE_TOOLTIP": "This is an approximate maximum fee based off a combination of network and platform fees.",
+ "SWAP_TX_FEE_TOOLTIP": "The approximate amount you will pay to complete your swap. Includes platform fees, network fees and the cost to approve a token when necessary.",
"SWAP_EXPIRY_TOOLTIP": "Your quoted amount and rate will expire after a time. To refresh this, select \"Get New Quote.\"",
"EXPIRED": "Expired",
- "SWAP_INSUFFICIENT_FUNDS": "Insufficient funds for transaction"
+ "SWAP_INSUFFICIENT_FUNDS": "Insufficient funds for transaction",
+ "ESTIMATED_COST": "Estimated Cost"
}
-}
+}
\ No newline at end of file