diff --git a/.env b/.env index 5afb3a2..4930468 100644 --- a/.env +++ b/.env @@ -1,2 +1,2 @@ REACT_APP_NODE_CONFIG_ENV=staging -REACT_APP_INFURA_KEY='' +REACT_APP_INFURA_KEY=e9d88f4871b04add8ab3dbfd71b70877 diff --git a/.gitignore b/.gitignore index 6cabc33..59ae3a8 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,8 @@ node_modules src/react-app-env.d.ts public/js/bundle.js build +.env .env* !.env-template yarn-error.log -.vscode \ No newline at end of file +.vscode diff --git a/package.json b/package.json index 2cb5a09..0705aeb 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "react-toastify": "^7.0.0", "redux": "^4.0.5", "redux-devtools-extension": "^2.13.8", + "redux-persist": "^6.0.0", "redux-thunk": "^2.3.0", "styled-components": "^5.3.1", "typescript": "^4.3.5", @@ -72,6 +73,8 @@ "browserslist": { "production": [ ">0.2%", + "not ie <= 99", + "not android <= 4.4.4", "not dead", "not op_mini all" ], diff --git a/src/components/Bridge/TransferPanel/FeeInfo.tsx b/src/components/Bridge/TransferPanel/FeeInfo.tsx index 5b83391..ea08e8c 100644 --- a/src/components/Bridge/TransferPanel/FeeInfo.tsx +++ b/src/components/Bridge/TransferPanel/FeeInfo.tsx @@ -4,12 +4,13 @@ import styled from 'styled-components'; import { useAppSelector } from '../../../utils/hooks'; import { getChainsFromDirection, getChainName } from '../../../utils/common'; import { Chain, Channel, SwapDirection } from '../../../types/types'; -import { Asset } from '../../../types/Asset'; +import { Asset, decimals } from '../../../types/Asset'; import { ACTIVE_CHANNEL, PERMITTED_ETH_NETWORK } from '../../../config'; - +import { utils } from 'ethers'; import ToolTip from '../../ToolTip/ToolTip'; import { updateFees } from '../../../redux/actions/bridge'; import { useDispatch } from 'react-redux'; +import BigNumber from 'bignumber.js'; const toDotCurrency = { symbol: 'DOT', text: 'ERC20 DOT' }; const toETHCurrency = { symbol: 'ETH', text: 'Parachain ETH' }; @@ -26,7 +27,7 @@ type Props = { const FeeInfo = ({ className, setError }: Props) => { const { - assets, swapDirection, fees + assets, swapDirection, fees, depositAmount, selectedAsset } = useAppSelector((state) => state.bridge); const dispatch = useDispatch(); @@ -47,10 +48,26 @@ const FeeInfo = ({ className, setError }: Props) => { currency = toETHCurrency; break; } - const asset = assets.find((asset) => asset.symbol === currency.symbol); const balance = asset ? asset.balance[chains.from] : 0; + let balanceError = false; + let isFeeSufficient = true; + if(selectedAsset?.symbol === asset?.symbol) { + + const decimalMap = decimals(selectedAsset, swapDirection); + const depositedAmount = new BigNumber( + // make sure we are comparing the same units + utils.parseUnits( + depositAmount || '0', decimalMap.from, + ).toString(), + ); + const transactionFees = utils.parseUnits( + fee || '0', decimalMap.from, + ).toString(); + const differenceValue = new BigNumber(balance).minus(depositedAmount); + isFeeSufficient = (differenceValue).isGreaterThanOrEqualTo(new BigNumber(transactionFees)); + } if (fee !== null && Number(balance) < Number(fee)) { balanceError = true; } @@ -58,7 +75,6 @@ const FeeInfo = ({ className, setError }: Props) => { useEffect(() => { const checkFeeBalance = (assets: Asset[], swapDirection: SwapDirection) => { const chains = getChainsFromDirection(swapDirection); - let fee: string | null; let currency: any; switch (chains.to) { @@ -71,10 +87,8 @@ const FeeInfo = ({ className, setError }: Props) => { currency = toETHCurrency; break; } - const asset = assets.find((asset) => asset.symbol === currency.symbol); - const balance = asset ? asset.balance[chains.from] : 0; - if (fee !== null && Number(balance) < Number(fee)) { + if (fee !== null && (!isFeeSufficient || Number(balance) < Number(fee))) { setCBError(TRANSFER_FEE_ERROR); } else { @@ -83,7 +97,7 @@ const FeeInfo = ({ className, setError }: Props) => { }; checkFeeBalance(assets, swapDirection); - }, [setCBError, assets, swapDirection, fees.erc20dot, fees.parachainEth]); + }, [setCBError, assets, swapDirection,isFeeSufficient, fees.erc20dot, fees.parachainEth]); useEffect(() => { dispatch(updateFees()); diff --git a/src/components/Bridge/TransferPanel/ParachainDisplay.tsx b/src/components/Bridge/TransferPanel/ParachainDisplay.tsx index eed0249..7c07755 100644 --- a/src/components/Bridge/TransferPanel/ParachainDisplay.tsx +++ b/src/components/Bridge/TransferPanel/ParachainDisplay.tsx @@ -21,12 +21,11 @@ const ParachainDisplay = ({ className }: ChainDisplayProps) => { const { parachainId } = useAppSelector((state) => state.bridge); - const selectParachain = (event:any) => { - PARACHAIN_LIST.filter((parachain) => { - if(parachain.parachainId == event.target.value) { - dispatch(setTransactionFee(parachain.transactionFee)); - } - }) + const selectParachain = (event: any) => { + PARACHAIN_LIST.filter( + (parachain) => parachain.parachainId == event.target.value + && dispatch(setTransactionFee(parachain.transactionFee)), + ); dispatch(setParaChainId(event.target.value)); } diff --git a/src/components/Bridge/TransferPanel/SelectedFungibleToken.tsx b/src/components/Bridge/TransferPanel/SelectedFungibleToken.tsx index 188e4b1..b361c7e 100644 --- a/src/components/Bridge/TransferPanel/SelectedFungibleToken.tsx +++ b/src/components/Bridge/TransferPanel/SelectedFungibleToken.tsx @@ -5,7 +5,8 @@ import styled from 'styled-components'; import BigNumber from 'bignumber.js'; import { utils } from 'ethers'; import { - setDepositAmount + setDepositAmount, + initializeTokens } from '../../../redux/actions/bridge'; import { @@ -40,6 +41,11 @@ const SelectedFungibleToken = ({ className, openAssetSelector, setError }: Props const dispatch = useDispatch(); const decimalMap = decimals(selectedAsset, swapDirection); + const handleAssetSelector = () => { + openAssetSelector(); + dispatch(initializeTokens()); + } + useEffect(() => { }, [dispatch]); const setStableError = useCallback(setError,[]); @@ -94,7 +100,7 @@ const SelectedFungibleToken = ({ className, openAssetSelector, setError }: Props type="number" onPillClick={handleMaxClicked} /> - openAssetSelector()}> + {selectedAsset?.symbol} diff --git a/src/components/TransactionsListModal/TransactionListModal.style.ts b/src/components/TransactionsListModal/TransactionListModal.style.ts index cd1e9bb..e4fb638 100644 --- a/src/components/TransactionsListModal/TransactionListModal.style.ts +++ b/src/components/TransactionsListModal/TransactionListModal.style.ts @@ -7,15 +7,19 @@ export const Wrapper = styled.section` export const Heading = styled.h2` text-align: center; + top: 0; + position: fixed; `; export const List = styled.ul` - margin: 0; - padding: 0; + padding: 53px 15px; width: 100%; gap: 25px; display: flex; flex-direction: column; + max-height: 300px + overflow-y: scroll; + overflow-x: hidden; `; export const Button = styled.button` diff --git a/src/components/TransactionsListModal/TransactionListModal.tsx b/src/components/TransactionsListModal/TransactionListModal.tsx index 6ad8357..6b845c0 100644 --- a/src/components/TransactionsListModal/TransactionListModal.tsx +++ b/src/components/TransactionsListModal/TransactionListModal.tsx @@ -68,4 +68,5 @@ export default styled(TransactionListModal)` min-width: 480px; align-items: center; width: 100%; + max-height: 350px; `; diff --git a/src/config-local.tsx b/src/config-local.tsx index d78975c..5405543 100644 --- a/src/config-local.tsx +++ b/src/config-local.tsx @@ -26,6 +26,12 @@ export default { // Allow health check to skip blocks for performance HEALTH_CHECK_POLKADOT_POLL_SKIP_BLOCKS: 500, + // Time in milliseconds for setInterval + SET_INTERVAL_TIME : 5000, + + // Provider for listening ethereum events + ETHEREUM_WEB_SOCKET_PROVIDER: 'ws://localhost:8546', + // Polkadotjs API Provider POLKADOT_API_PROVIDER: 'ws://localhost:11144', POLKADOT_RELAY_API_PROVIDER: 'ws://localhost:9944', @@ -39,7 +45,7 @@ export default { SNOWBRIDGE_EXPLORER_URL: 'https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A11144#/explorer', PERMITTED_ETH_NETWORK: 'private', - PERMITTED_ETH_NETWORK_ID: '0x???', + PERMITTED_ETH_NETWORK_ID: '0xf', BASIC_CHANNEL_ID: 0, INCENTIVIZED_CHANNEL_ID: 1, diff --git a/src/config-staging.tsx b/src/config-staging.tsx index 07afa0b..4321cfe 100644 --- a/src/config-staging.tsx +++ b/src/config-staging.tsx @@ -26,6 +26,12 @@ export default { // Allow health check to skip blocks for performance HEALTH_CHECK_POLKADOT_POLL_SKIP_BLOCKS: 1000, + // Time in milliseconds for setInterval + SET_INTERVAL_TIME: 5000, + + // Provider for listening ethereum events + ETHEREUM_WEB_SOCKET_PROVIDER: `wss://ropsten.infura.io/ws/v3/${process.env.REACT_APP_INFURA_KEY}`, + // Polkadotjs API Provider POLKADOT_API_PROVIDER: 'wss://parachain-rpc.snowbridge.network', POLKADOT_RELAY_API_PROVIDER: 'wss://polkadot-rpc.snowbridge.network', diff --git a/src/config.tsx b/src/config.tsx index 66281aa..da96c16 100644 --- a/src/config.tsx +++ b/src/config.tsx @@ -22,6 +22,8 @@ export const { HEALTH_CHECK_POLKADOT_POLL_MAX_BLOCKS, HEALTH_CHECK_ETHEREUM_POLL_MAX_BLOCKS, HEALTH_CHECK_POLKADOT_POLL_SKIP_BLOCKS, + SET_INTERVAL_TIME, + ETHEREUM_WEB_SOCKET_PROVIDER, POLKADOT_API_PROVIDER, POLKADOT_RELAY_API_PROVIDER, REQUIRED_ETH_CONFIRMATIONS, diff --git a/src/contractAddresses.json b/src/contractAddresses.json index 27976a6..c53a82f 100644 --- a/src/contractAddresses.json +++ b/src/contractAddresses.json @@ -1,24 +1,13 @@ { - "ScaleCodec": "0x86D9aC0Bab011917f57B9E9607833b4340F9D4F8", - "MerkleProof": "0xD184c103F7acc340847eEE82a0B909E3358bc28d", - "Bitfield": "0x992B9df075935E522EC7950F37eC8557e86f6fdb", - "ParachainLightClient": "0x2ffA5ecdBe006d30397c7636d3e015EEE251369F", - "ValidatorRegistry": "0xFc97A6197dc90bef6bbEFD672742Ed75E9768553", - "SimplifiedMMRVerification": "0xEDa338E4dC46038493b885327842fD3E301CaB39", - "BeefyLightClient": "0x87d1f7fdfEe7f651FaBc8bFCB6E086C278b77A7d", - "BasicInboundChannel": "0x774667629726ec1FaBEbCEc0D9139bD1C8f72a23", - "IncentivizedInboundChannel": "0x83428c7db9815f482a39a1715684dCF755021997", - "BasicOutboundChannel": "0xF8F7758FbcEfd546eAEff7dE24AFf666B6228e73", - "IncentivizedOutboundChannel": "0xEE9170ABFbf9421Ad6DD07F6BDec9D89F2B581E0", - "ETHApp": "0xB1185EDE04202fE62D38F5db72F71e38Ff3E8305", - "DOTApp": "0x8cF6147918A5CBb672703F879f385036f8793a24", - "ERC20App": "0x3f0839385DB9cBEa8E73AdA6fa0CFe07E321F61d", - "TestToken": "0x440eDFFA1352B13227e8eE646f3Ea37456deC701", - "ERC721App": "0x54D6643762E46036b3448659791adAf554225541", - "TestToken721": "0x4283d8996E5a7F4BEa58c6052b1471a2a9524C87", - "TestToken721Enumerable": "0x3f839E70117C64744930De8567Ae7A5363487cA3", - "MaliciousDOTApp": "0xdAF13FA1997b9649b2bCC553732c67887A68022C", - "SnowDOTAddress": "0x0c8df76790248eD9045415882cdC1eF924E23216", - "name": "localhost", - "chainId": "15" -} \ No newline at end of file + "BasicOutboundChannel": "0xaC79a7da8e15C0591AcE1D4AB9CDFC3607538F82", + "BasicInboundChannel": "0x0727be345ca6EEE30dAd360d0c5b70E999ac902D", + "IncentivizedInboundChannel": "0x7F7Fa25e3eb272F9439E4da0f837061E845dd389", + "IncentivizedOutboundChannel": "0x8261F5C60aa9A5e3A2d2F4e0e5bEADa9906E5cf2", + "ETHApp": "0x27d369eD6f515E924D363E67F8C175a84B16Da73", + "ERC20App": "0xE756038a457f990E8cDd781B6e4f40765bFe613a", + "DOTApp": "0xe99FA30e32500Ae0C723403916b765540EC9cd2b", + "ERC721App": "0xdA3C5796feF00Bb677ef72aE0dCe2511aD350f77", + "SnowDOTAddress": "0x2a7e15303f745e41f3d1cc5e3fb353bb29d2119b", + "name": "ropsten", + "chainId": "3" +} diff --git a/src/index.tsx b/src/index.tsx index f2d6431..8c053d5 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -5,14 +5,16 @@ import { ThemeProvider } from 'styled-components'; import App from './App'; import GlobalStyle from './styles/globalStyle'; import { basic } from './styles/theme'; -import store from './redux/store'; - +import { store, persistor } from './redux/store'; +import { PersistGate } from 'redux-persist/integration/react' ReactDOM.render( - + + + , diff --git a/src/net/eth.ts b/src/net/eth.ts index bafeca0..6974137 100644 --- a/src/net/eth.ts +++ b/src/net/eth.ts @@ -7,12 +7,15 @@ import { web3FromSource } from '@polkadot/extension-dapp'; import { PromiEvent } from 'web3-core'; import Api, { ss58ToU8 } from './api'; import Polkadot from './polkadot'; +import { store } from '../redux/store'; +import { pendingEventTransactions } from '../utils/common'; import Onboard from 'bnc-onboard' // Import Contracts -import { +import { PERMITTED_ETH_NETWORK_ID, - INFURA_KEY, - CONTRACT_ADDRESS + INFURA_KEY, + CONTRACT_ADDRESS, + SET_INTERVAL_TIME } from '../config'; /* tslint:disable */ @@ -46,14 +49,17 @@ import { import { fetchEthAddress } from '../redux/actions/EthTransactions'; import { Channel } from '../types/types'; import { getChannelID } from '../utils/common'; +import { handleTransaction, handlePolkadotMissedEvents, handleEthereumMissedEvents } from '../redux/actions/transactions'; +import { subscribeEthereumEvents } from "../redux/actions/net"; +import { persistor } from '../redux/store'; -const wallets: any[] =[ - { walletName: "metamask", preferred: true }, - { - walletName: "walletConnect", - infuraKey: INFURA_KEY, - preferred: true - }, +const wallets: any[] = [ + { walletName: "metamask", preferred: true }, + { + walletName: "walletConnect", + infuraKey: INFURA_KEY, + preferred: true + }, ] export default class Eth extends Api { public static loadContracts(dispatch: Dispatch, web3: Web3): void { @@ -104,74 +110,104 @@ export default class Eth extends Api { // Web3 API connector public static async connect(dispatch: Dispatch): Promise { const connectionComplete = async () => { - let web3:any; - let provider:any; - try{ - const onboard = Onboard({ - networkId: parseInt(PERMITTED_ETH_NETWORK_ID), // [Integer] The Ethereum network ID your Dapp uses. - subscriptions: { - wallet: (wallet: any) => { - web3= new Web3(wallet.provider) - dispatch(setWeb3(web3)); - provider= wallet.provider - - web3.eth.net.getNetworkType() - .then((network: string) => dispatch(setMetamaskNetwork(network))); - } - }, - walletSelect: { - wallets: wallets - } - }); - let walletselected = await onboard.walletSelect() - while(! walletselected ){ - walletselected = await onboard.walletSelect(); - } - let readyToTransact = await onboard.walletCheck(); - if (!readyToTransact) { - window.location.reload(); + let web3: any; + let provider: any; + try { + const onboard = Onboard({ + networkId: parseInt(PERMITTED_ETH_NETWORK_ID), // [Integer] The Ethereum network ID your Dapp uses. + subscriptions: { + wallet: (wallet: any) => { + web3 = new Web3(wallet.provider) + dispatch(setWeb3(web3)); + provider = wallet.provider + + web3.eth.net.getNetworkType() + .then((network: string) => dispatch(setMetamaskNetwork(network))); } - // Set contracts - Eth.loadContracts(dispatch, web3); - // fetch addresses - await dispatch(fetchEthAddress()); - console.log('- Eth connected'); + }, + walletSelect: { + wallets: wallets + } + }); + let walletselected = await onboard.walletSelect() + while (!walletselected) { + walletselected = await onboard.walletSelect(); } - catch{ - throw new Error('Something went wrong'); + let readyToTransact = await onboard.walletCheck(); + if (!readyToTransact) { + window.location.reload(); } - return provider; - }; - try{ - const provider= await connectionComplete(); - if (provider) { - provider.on('accountsChanged', async (accounts: string[]) => { - if (accounts[0]) { - await dispatch(setEthAddress(accounts[0])); - dispatch(initializeTokens()); - } else { - setEthAddress(); - } - }); - - provider.on('disconnect', async () => { - setEthAddress(); - }); - - provider.on('chainChanged', () => { - window.location.reload(); - }); - - provider.on('disconnect', () => { - window.location.reload(); - }); - } else { - throw new Error('Please connect wallet'); + // Set contracts + Eth.loadContracts(dispatch, web3); + // fetch addresses + await dispatch(fetchEthAddress()); + // Ethereum event for inbound channel contracts + dispatch(subscribeEthereumEvents()); + + //Obtain transaction list + let stateData: any = store.getState(); + if (stateData && stateData.transactions && stateData.transactions.transactions.length > 0) { + let pendingTxCount = pendingEventTransactions(stateData.transactions.transactions); + if (pendingTxCount === 0) { + persistor.purge(); + } + else { + // Handelling transaction callback and events + let interval = setInterval(() => { + + let transactions = store.getState().transactions.transactions; + pendingTxCount = pendingEventTransactions(transactions); + if (pendingTxCount === 0) + clearInterval(interval); + + //handling the lost callback of Ethereum and Polkadot transactions + dispatch(handleTransaction(web3)); + + // handles the missed Polkadot events (MessageDispatch) for the transactions for the basic/Incentivized channel and updates tx-nonce by comaring with latest channel nonce. + dispatch(handlePolkadotMissedEvents()); + + // handles the missed Ethereum events (MessgaeDispatched) for the transactions for basic/Incentivized Ethereum contracts and updates tx-nonce by comparing with latest channel nonce. + dispatch(handleEthereumMissedEvents(web3)); + + }, SET_INTERVAL_TIME); + } } + console.log('- Eth connected'); + } + catch { + throw new Error('Something went wrong'); + } + return provider; + }; + try { + const provider = await connectionComplete(); + if (provider) { + provider.on('accountsChanged', async (accounts: string[]) => { + if (accounts[0]) { + await dispatch(setEthAddress(accounts[0])); + dispatch(initializeTokens()); + } else { + setEthAddress(); + } + }); + + provider.on('disconnect', async () => { + setEthAddress(); + }); + + provider.on('chainChanged', () => { + window.location.reload(); + }); + + provider.on('disconnect', () => { + window.location.reload(); + }); + } else { + throw new Error('Please connect wallet'); + } } - catch (error) - { - console.log('error==',error); + catch (error) { + console.log('error==', error); } } @@ -258,7 +294,7 @@ export default class Eth extends Api { erc20Contract: Contract, erc721AppContract: Contract, channel: Channel, - transactionFee:string + transactionFee: string ): PromiEvent { try { const polkadotRecipientBytes: Uint8Array = ss58ToU8( @@ -285,7 +321,7 @@ export default class Eth extends Api { // call ether contract for ether if (isEther(asset)) { return ethContract.methods - .lock(polkadotRecipientBytes, channelId, parachainId,transactionFee) + .lock(polkadotRecipientBytes, channelId, parachainId, transactionFee) .send({ from: sender, gas: 500000, diff --git a/src/net/polkadot.ts b/src/net/polkadot.ts index 4864217..a0441ef 100644 --- a/src/net/polkadot.ts +++ b/src/net/polkadot.ts @@ -26,7 +26,7 @@ import Api, { ss58ToU8 } from './api'; import { Asset, isDot, isErc20 } from '../types/Asset'; import { updateBalances } from '../redux/actions/bridge'; -import { Channel } from '../types/types'; +import { Channel, PolkadotTxConfirmation } from '../types/types'; import { getChannelID } from '../utils/common'; import { AssetBalance } from '../types/types'; @@ -248,4 +248,38 @@ export default class Polkadot extends Api { return polkadotApi.tx.erc721App.burn(channelId, subTokenId, ethRecipient) .signAndSend(account.address, { signer: injector.signer }, callback); } + + //Fetch the polkadot transaction confirmation status and nonce value via hash. + public static async getTransactionConfirmation( + polkadotApi: ApiPromise, + transactionHash: string, + blockNumber: number + ): Promise { + let nonce: string = ''; + let istxFound = false; + let latestpolkadotBlock = await polkadotApi.rpc.chain.getBlock(); + let latestblockNumber = latestpolkadotBlock.block.header.number.toNumber(); + for (let index = blockNumber; index <= latestblockNumber; index++) { + const blockHash = await polkadotApi.rpc.chain.getBlockHash(index); + const signedBlock = await polkadotApi.rpc.chain.getBlock(blockHash); + let txStatus = false; + + // the hash for each extrinsic in the block + signedBlock.block.extrinsics.forEach(async (ex) => { + txStatus = ex.hash.toHex() === transactionHash ? true : false; + if (txStatus) { + if (ex.isSigned) { + const allRecords: any = await polkadotApi.query.system.events.at(signedBlock.block.header.hash); + nonce = allRecords[5].event.data[0].toString(); + istxFound = true; + } + return { istxFound: istxFound, nonce: nonce }; + } + }); + + if (istxFound) + return { istxFound: istxFound, nonce: nonce }; + } + return { istxFound: false, nonce: '' }; + } } diff --git a/src/redux/actions/EthTransactions.ts b/src/redux/actions/EthTransactions.ts index 63eaa6b..88a1d42 100644 --- a/src/redux/actions/EthTransactions.ts +++ b/src/redux/actions/EthTransactions.ts @@ -116,6 +116,9 @@ export const unlockEthAsset = (amount: string): // set pending to open pending tx status modal await dispatch(setPendingTransaction(pendingTransaction)); + let latestpolkadotBlock = await polkadotApi.rpc.chain.getBlock(); + let blockNumber = latestpolkadotBlock.block.header.number.toNumber(); + const unsub = await EthApi.unlock( amount, selectedAsset!, @@ -129,8 +132,7 @@ export const unlockEthAsset = (amount: string): unsub, pendingTransaction, dispatch, - incentivizedInboundChannelContract!, - basicChannelContract!, + blockNumber ); // tx will be updated in handlePolkadotTransactionEvents diff --git a/src/redux/actions/PolkadotTransactions.ts b/src/redux/actions/PolkadotTransactions.ts index fda4717..5692fc5 100644 --- a/src/redux/actions/PolkadotTransactions.ts +++ b/src/redux/actions/PolkadotTransactions.ts @@ -32,8 +32,6 @@ export const lockPolkadotAsset = ( ethAddress, polkadotApi, polkadotAddress, - basicChannelContract, - incentivizedInboundChannelContract, } = state.net; const { selectedAsset, @@ -53,6 +51,9 @@ export const lockPolkadotAsset = ( const token = selectedAsset?.token as NonFungibleToken; const subTokenId = Number(token.subId); + let latestpolkadotBlock = await polkadotApi?.rpc.chain.getBlock(); + let blockNumber = latestpolkadotBlock?.block.header.number.toNumber(); + if (isNonFungible(selectedAsset!)) { const unsub = await Polkadot.burnERC721( polkadotApi!, subTokenId, polkadotAddress!, ethAddress!, ACTIVE_CHANNEL, @@ -62,8 +63,7 @@ export const lockPolkadotAsset = ( unsub!, pendingTransaction, dispatch, - incentivizedInboundChannelContract!, - basicChannelContract!, + blockNumber! ); // tx will be updated in handlePolkadotTransactionEvents @@ -92,8 +92,7 @@ export const lockPolkadotAsset = ( unsub!, pendingTransaction, dispatch, - incentivizedInboundChannelContract!, - basicChannelContract!, + blockNumber! ); // tx will be updated in handlePolkadotTransactionEvents diff --git a/src/redux/actions/net.ts b/src/redux/actions/net.ts index 5010e9e..89f3b1b 100644 --- a/src/redux/actions/net.ts +++ b/src/redux/actions/net.ts @@ -1,6 +1,6 @@ /* eslint-disable no-console */ /* eslint-disable @typescript-eslint/ban-types */ - +import Web3 from 'web3'; import { ThunkAction, ThunkDispatch } from 'redux-thunk'; import { AnyAction } from 'redux'; import _ from 'lodash'; @@ -12,6 +12,15 @@ import { netSlice, } from '../reducers/net'; import { Channel } from '../../types/types'; +import { transactionsSlice } from '../../redux/reducers/transactions'; +import { + CONTRACT_ADDRESS, + ETHEREUM_WEB_SOCKET_PROVIDER +} from '../../config' + +export const { + ethMessageDispatched +} = transactionsSlice.actions; export const { setAppDotContract, @@ -57,3 +66,59 @@ export const subscribeEvents = (): throw new Error('Polkadot API not connected'); } }; + +//Subscribe to events of Inbound channel ethereum contracts. +export const subscribeEthereumEvents = (): + ThunkAction, {}, {}, AnyAction> => async ( + dispatch: ThunkDispatch<{}, {}, AnyAction>, getState, + ): Promise => { + try { + var web3 = new Web3( + new Web3.providers.WebsocketProvider(ETHEREUM_WEB_SOCKET_PROVIDER) + ); + + let channel: Channel; + let basicInChannelLogFields = [ + { + indexed: false, + internalType: "uint64", + name: "nonce", + type: "uint64", + }, + { + indexed: false, + internalType: "bool", + name: "result", + type: "bool", + }, + ]; + web3.eth.subscribe('logs', { + address: [CONTRACT_ADDRESS.BasicInboundChannel, CONTRACT_ADDRESS.IncentivizedInboundChannel], + topics: ['0x504b093d860dc827c72a879d052fd8ac6b4c2af80c5f3a634654f172690bf10a'] + }, function (error: any, event: any) { + if (error) { + return '' + } + const decodedEvent = web3.eth.abi.decodeLog( + basicInChannelLogFields, + event.data, + event.topics, + ); + if (event.address === CONTRACT_ADDRESS.BasicInboundChannel) + channel = Channel.BASIC + + if (event.address === CONTRACT_ADDRESS.IncentivizedInboundChannel) + channel = Channel.INCENTIVIZED + + dispatch( + ethMessageDispatched({ + nonce: decodedEvent.nonce, + channel + }), + ); + }); + } + catch (error) { + console.error("errorMessage", error); + } +}; diff --git a/src/redux/actions/transactions.ts b/src/redux/actions/transactions.ts index acb3e71..97d7417 100644 --- a/src/redux/actions/transactions.ts +++ b/src/redux/actions/transactions.ts @@ -7,19 +7,18 @@ import { ThunkAction, ThunkDispatch } from 'redux-thunk'; import { Contract } from 'web3-eth-contract'; import { PromiEvent } from 'web3-core'; import Web3 from 'web3'; +import { ApiPromise } from '@polkadot/api'; import { REQUIRED_ETH_CONFIRMATIONS, CONTRACT_ADDRESS } from '../../config'; import { Asset, decimals, isDot, - isNonFungible, symbols, + AssetType, } from '../../types/Asset'; import { Chain, SwapDirection, Channel } from '../../types/types'; -import { AssetType } from '../../types/Asset'; import { RootState } from '../store'; import { - MessageDispatchedEvent, Transaction, TransactionStatus, transactionsSlice, @@ -27,8 +26,16 @@ import { import { doEthTransfer } from './EthTransactions'; import { doPolkadotTransfer } from './PolkadotTransactions'; import { notify } from './notifications'; -import { setShowConfirmTransactionModal, setShowTransactionListModal } from './bridge'; -import { updateSelectedAsset } from '../../redux/actions/bridge'; +import { + setShowConfirmTransactionModal, + setShowTransactionListModal, + updateBalances, + updateSelectedAsset, +} from './bridge'; +import { subscribeEthereumEvents } from './net'; +import * as BasicInboundChannel from '../../contracts/BasicInboundChannel.json'; +import * as IncentivizedInboundChannel from '../../contracts/IncentivizedInboundChannel.json'; +import Polkadot from '../../net/polkadot'; export const { addTransaction, @@ -40,6 +47,7 @@ export const { setPendingTransaction, setTransactionStatus, updateTransaction, + updateTransactionNotifyConfirmation, } = transactionsSlice.actions; export const updateConfirmations = ( @@ -106,6 +114,7 @@ export function createTransaction( error: '', nonce: '', channel, + isNotifyConfirmed: false, }; return pendingTransaction; @@ -124,19 +133,15 @@ export function handlePolkadotTransactionEvents( unsub: () => void, // function to unsubscribe from polkadot transaction events transaction: Transaction, // the transaction we are updating for each event dispatch: Dispatch, - incentivizedChannelContract: Contract, - basicChannelContract: Contract, + blockNumber: number, ): Transaction { const pendingTransaction = { ...transaction }; if (result.status.isReady) { - // result.status.hash - this is the call hash not the tx hash - // this is not unique and leads to duplicate keys in our transactions list. - // rather than waiting for the tx to be included in the block to read the tx hash - // we just generate a random number and treat that as the tx hash instead - // so we can track and display the 'submitting to chain' status - const hash = (Math.random() * 100).toString(); - pendingTransaction.hash = hash; + window.onbeforeunload = function () { return '' }; + + pendingTransaction.nearbyBlockNumber = blockNumber + pendingTransaction.hash = result.txHash.toString(); dispatch( addTransaction( @@ -149,11 +154,11 @@ export function handlePolkadotTransactionEvents( } if (result.status.isInBlock) { - let nonce = result.events[0].event.data[0].toString(); - if (isDot(transaction.asset) && !isNonFungible(transaction.asset)) { - nonce = result.events[1].event.data[0].toString(); - } + if (result && result.dispatchError) + return pendingTransaction; + + let nonce = result.events[3].event.data[0].toString(); pendingTransaction.nonce = nonce; pendingTransaction.status = TransactionStatus.WAITING_FOR_RELAY; @@ -167,33 +172,9 @@ export function handlePolkadotTransactionEvents( ), ); - const handleChannelMessageDispatched = (channel: Channel) => (event: MessageDispatchedEvent) => { - if ( - event.returnValues.nonce === nonce - ) { - dispatch( - ethMessageDispatched({ - nonce: event.returnValues.nonce, - dispatchTransactionNonce: pendingTransaction.nonce!, - channel - }), - ); - } - }; - - // subscribe to ETH dispatch event - // eslint-disable-next-line no-unused-expressions - incentivizedChannelContract - .events - .MessageDispatched({}) - .on('data', handleChannelMessageDispatched(Channel.INCENTIVIZED)); - - // TODO: replace with incentivized channel? - // eslint-disable-next-line no-unused-expressions - basicChannelContract - .events - .MessageDispatched({}) - .on('data', handleChannelMessageDispatched(Channel.BASIC)); + dispatch(subscribeEthereumEvents()); + + window.onbeforeunload = null; return pendingTransaction; } @@ -207,12 +188,22 @@ export function handlePolkadotTransactionEvents( status: TransactionStatus.WAITING_FOR_RELAY, }), ); + + // updating the token balance + dispatch(updateBalances()); + // unsubscribe from transaction events if (unsub) { unsub(); } } if (result.dispatchError) { + dispatch( + setError({ + hash: pendingTransaction.hash, + error: result.dispatchError + }) + ); alert("Error with dispatchable - see polkadotjs explorer for more info: " + result.dispatchError) } return pendingTransaction; @@ -267,93 +258,18 @@ export function handleEthereumTransactionEvents( ), ); }) - .on('receipt', async (receipt: any) => { - console.log('Transaction receipt received', receipt); - const basicOutChannelLogFields = [ - { - type: 'address', - name: 'source', - }, - { - type: 'uint64', - name: 'nonce', - }, - { - type: 'bytes', - name: 'payload', - }, - ]; - const incentivizedOutChannelLogFields = [ - { - type: 'address', - name: 'source', - }, - { - type: 'uint64', - name: 'nonce', - }, - { - type: 'uint256', - name: 'fee', - }, - { - type: 'bytes', - name: 'payload', - }, - ]; - let nonce; - - Object.keys(receipt.events).forEach((eventKey: any) => { - const event = receipt.events[eventKey]; - if (event.address === CONTRACT_ADDRESS.BasicOutboundChannel) { - const decodedEvent = web3.eth.abi.decodeLog( - basicOutChannelLogFields, - event.raw.data, - event.raw.topics, - ); - nonce = decodedEvent.nonce; - } - if (event.address === CONTRACT_ADDRESS.IncentivizedOutboundChannel) { - const decodedEvent = web3.eth.abi.decodeLog( - incentivizedOutChannelLogFields, - event.raw.data, - event.raw.topics, - ); - nonce = decodedEvent.nonce; - } - }) - - if (!nonce) { - return - } - dispatch( - setNonce({ - hash: transactionHash, - nonce, - }), - ); + .on('receipt', async (receipt: any) => { + dispatch(handleTransaction(web3)); }) + .on( 'confirmation', ( confirmation: number, receipt: any, ) => { - // update transaction confirmations - dispatch( - updateConfirmations(receipt.transactionHash, confirmation), - ); - - if (confirmation === REQUIRED_ETH_CONFIRMATIONS) { - dispatch(notify({ - text: `Transactions confirmed after ${confirmation} confirmations`, - color: 'success', - })); - console.log('call transactionEvent.off()'); - // TODO: call this - // transactionEvent.off('confirmation'); - } + dispatch(handleTransaction(web3)); }, ) .on('error', (error: any) => { @@ -409,7 +325,272 @@ export function handlePolkadotTransactionErrors( ...pendingTransaction, status: TransactionStatus.REJECTED, error: error.message, + }), + ); + } +} +// This will be used in handleTransaction to fetch the tx receipt and block confirmation for transaction +export async function handleEthTransaction( + hash: string, + web3: Web3, + dispatch: Dispatch, + istxConfirmed: any +) { + //get the transaction receipt + let txReceipt = await web3.eth.getTransactionReceipt(hash); + + //if pending then return + if (!txReceipt) + return ''; + + //status-FALSE when EVM reverted the transaction. + if (!txReceipt.status){ + dispatch( + setError({ + hash: hash, + error: 'transaction failed' }) ); + return -1; + } + + //Fetch current block number + let currentBlock = await web3.eth.getBlockNumber() + let confirmation = txReceipt.blockNumber === null ? 0 : currentBlock - txReceipt.blockNumber + + //Should not be called when tx is confirmed + if (istxConfirmed) { + handleEthTxRecipt(txReceipt, dispatch, web3) + } + + if (confirmation > 0 && istxConfirmed) { + // updating the token balance on UI + dispatch(updateBalances()); + + handleEthTxConfirmation(txReceipt, dispatch, confirmation); + } +} + +//This function is used to obtain and set required nonce from events via use of receipt. +export function handleEthTxRecipt(receipt: any, dispatch: Dispatch, + web3: Web3) { + + const basicOutChannelLogFields = [ + { + type: 'address', + name: 'source', + }, + { + type: 'uint64', + name: 'nonce', + }, + { + type: 'bytes', + name: 'payload', + }, + ]; + const incentivizedOutChannelLogFields = [ + { + type: 'address', + name: 'source', + }, + { + type: 'uint64', + name: 'nonce', + }, + { + type: 'uint256', + name: 'fee', + }, + { + type: 'bytes', + name: 'payload', + }, + ]; + let nonce; + receipt.logs.forEach((log: any) => { + const event = log; + + if (event.address === CONTRACT_ADDRESS.BasicOutboundChannel) { + const decodedEvent = web3.eth.abi.decodeLog( + basicOutChannelLogFields, + event.data, + event.topics, + ); + nonce = decodedEvent.nonce; + } + if (event.address === CONTRACT_ADDRESS.IncentivizedOutboundChannel) { + const decodedEvent = web3.eth.abi.decodeLog( + incentivizedOutChannelLogFields, + event.data, + event.topics, + ); + nonce = decodedEvent.nonce; + } + }) + if (!nonce) { + return + } + + dispatch( + setNonce({ + hash: receipt.transactionHash, + nonce, + }), + ); +} + +// This will be used in handleEthTransaction for update the eth tx block confirmation for tx. +export async function handleEthTxConfirmation( + receipt: any, + dispatch: Dispatch, + confirmation: number, +) { + // update transaction confirmations + dispatch(updateConfirmations(receipt.transactionHash, confirmation)); +} + +// This is used for handling the lost callback of Ethereum and Polkadot transactions +// fetch the tx-receipt, confirmation status, confirmation count,nonce for pending transaction via Hash. +export const handleTransaction = (web3: Web3): ThunkAction, {}, {}, AnyAction> => async ( + dispatch: ThunkDispatch<{}, {}, AnyAction>, + getState, +): Promise => { + const state = getState() as RootState; + + const { polkadotApi } = state.net; + + const pendingEThTransactions = state.transactions.transactions.filter( + (transaction) => transaction.status < TransactionStatus.WAITING_FOR_RELAY + && transaction.status !== TransactionStatus.REJECTED + && transaction.direction === SwapDirection.EthereumToPolkadot, + ); + const pendingPolkaDotTransactions = state.transactions.transactions.filter( + (transaction) => transaction.status < TransactionStatus.WAITING_FOR_RELAY + && transaction.status !== TransactionStatus.REJECTED + && transaction.direction === SwapDirection.PolkadotToEthereum, + ); + + const confirmationEThTransactions = state.transactions.transactions.filter( + (transaction) => transaction.status === TransactionStatus.WAITING_FOR_RELAY + && transaction.direction === SwapDirection.EthereumToPolkadot, + ); + + if (confirmationEThTransactions.length > 0) { + confirmationEThTransactions.forEach((tx: any) => { + if (tx.isNotifyConfirmed === false) { + dispatch( + updateTransactionNotifyConfirmation({ + hash: tx.hash, + }), + ); + dispatch( + notify({ + text: `Transactions confirmed after ${tx.confirmations} confirmations`, + color: 'success', + }), + ); + } + }); + } + + if (polkadotApi) { + if (pendingPolkaDotTransactions.length > 0) { + pendingPolkaDotTransactions.map((tx: any) => handlepolkadotTransaction(state, tx.hash, tx.nearbyBlockNumber, polkadotApi, dispatch)) + } + } + + if (pendingEThTransactions.length > 0) { + pendingEThTransactions.map((tx: any) => handleEthTransaction( + tx.hash, + web3, + dispatch, + tx.status < TransactionStatus.WAITING_FOR_RELAY, + )); + } +}; + +//Handle the missed Polkadot events (MessageDispatch) for the transactions for the basic/Incentivized channel. +//and updates the transaction status accordingly. +export const handlePolkadotMissedEvents = (): + ThunkAction, {}, {}, AnyAction> => async ( + dispatch: ThunkDispatch<{}, {}, AnyAction>, + getState, + ): Promise => { + const state = getState() as RootState; + const { polkadotApi } = state.net; + if (polkadotApi) { + const incentivizeLatestNonce = Number(await polkadotApi.query['incentivizedInboundChannel'].nonce()); + const basicLatestNonce = Number(await polkadotApi.query['basicInboundChannel'].nonce()); + const pendingTransactions = state.transactions.transactions.filter((transaction) => transaction.status >= TransactionStatus.WAITING_FOR_RELAY && transaction.status < TransactionStatus.DISPATCHED && transaction.direction === SwapDirection.EthereumToPolkadot && Number(transaction.nonce) <= (transaction.channel === Channel.INCENTIVIZED ? incentivizeLatestNonce : basicLatestNonce)); + + pendingTransactions.map((tx: Transaction) => { + const nonce = tx.nonce ? tx.nonce : '' + const channel = tx.channel === Channel.BASIC ? Channel.BASIC : Channel.INCENTIVIZED + + dispatch(parachainMessageDispatched({ nonce, channel }) + ) + }) + } +} + +// Handle the missed event of the inbound channel when user closed the application. +export const handleEthereumMissedEvents = ( + web3: Web3 +): + ThunkAction, {}, {}, AnyAction> => async ( + dispatch: ThunkDispatch<{}, {}, AnyAction>, + getState, + ): Promise => { + const state = getState() as RootState; + + const incentivizeInboundContract = new web3.eth.Contract( + IncentivizedInboundChannel.abi as any, + CONTRACT_ADDRESS.IncentivizedInboundChannel, + ); + + const basicInboundContract = new web3.eth.Contract( + BasicInboundChannel.abi as any, + CONTRACT_ADDRESS.BasicInboundChannel, + ); + + const incentivizeInboundLatestNonce = Number(await incentivizeInboundContract.methods.nonce().call()); + const basicInboundLatestNonce = Number(await basicInboundContract.methods.nonce().call()); + + const missedEventTransactions = state.transactions.transactions.filter((transaction) => transaction.status >= TransactionStatus.WAITING_FOR_RELAY && transaction.status < TransactionStatus.DISPATCHED && transaction.direction === SwapDirection.PolkadotToEthereum && Number(transaction.nonce) <= (transaction.channel === Channel.INCENTIVIZED ? incentivizeInboundLatestNonce : basicInboundLatestNonce)); + + missedEventTransactions.map((tx: Transaction) => { + if (tx.nonce) { + const nonce = tx.nonce + const channel = tx.channel === Channel.BASIC ? Channel.BASIC : Channel.INCENTIVIZED + + dispatch(ethMessageDispatched({ nonce, channel })) + } + }) + } + +//This function hanle the lost callback for polkadot transactions and +//contains logic to fetch the transaction confirmation and nonce state. +export async function handlepolkadotTransaction( + state: any, + hash: string, + blockNumber: number, + polkadotApi: ApiPromise, + dispatch: Dispatch +) { + let TxDetail = await Polkadot.getTransactionConfirmation(polkadotApi, hash, blockNumber) + if (TxDetail.istxFound) { + dispatch( + setNonce({ + hash: hash, + nonce: TxDetail.nonce, + }), + ); + dispatch( + setTransactionStatus({ + hash: hash, + status: TransactionStatus.WAITING_FOR_RELAY, + }), + ); } } diff --git a/src/redux/reducers/transactions.ts b/src/redux/reducers/transactions.ts index 47cf56f..b99fa2e 100644 --- a/src/redux/reducers/transactions.ts +++ b/src/redux/reducers/transactions.ts @@ -5,7 +5,7 @@ import { REQUIRED_ETH_CONFIRMATIONS } from '../../config'; import { Asset } from '../../types/Asset'; import { Chain, SwapDirection, Channel } from '../../types/types'; import { RootState } from '../store'; - +import { nonCircularPayload,nonCircularUpdateTxPayload } from '../../utils/common' export enum TransactionStatus { // used for error states REJECTED = -1, @@ -44,6 +44,10 @@ export interface Transaction { asset: Asset; direction: SwapDirection channel: Channel; + isNotifyConfirmed: boolean; + //@TODO block No near which will be used to fetch polkadot transaction confirmation + nearbyBlockNumber?: number; + } // Interface for the Ethereum 'MessageDispatched' event, @@ -73,10 +77,11 @@ export const transactionsSlice = createSlice({ initialState, reducers: { addTransaction: (state, action: PayloadAction) => { + const payload = nonCircularPayload(action); // append to tx list - state.transactions.push(action.payload); + state.transactions.push(payload); // update pending tx - state.pendingTransaction = action.payload; + state.pendingTransaction = payload; }, setConfirmations: (state, action: PayloadAction<{ confirmations: number, hash: string }>) => { const getTransactionStatus = (transaction: Transaction): TransactionStatus => { @@ -110,7 +115,15 @@ export const transactionsSlice = createSlice({ } }, updateTransaction: (state, action: PayloadAction<{ hash: string, update: Partial }>) => { - state.transactions = state.transactions.map((tx) => (tx.hash === action.payload.hash ? { ...tx, ...action.payload.update } : tx)); + const payload = nonCircularUpdateTxPayload(action.payload.update); + state.transactions = state.transactions.map((tx) => (tx.hash === action.payload.hash ? { ...tx, ...payload } : tx)); + }, + updateTransactionNotifyConfirmation: (state, action: PayloadAction<{ hash: string }>) => { + const transaction = state.transactions.filter((tx) => tx.hash === action.payload.hash)[0]; + + if (transaction) { + transaction.isNotifyConfirmed = true; + } }, setNonce: (state, action: PayloadAction<{ hash: string, nonce: string }>) => { const transaction = state.transactions.filter((tx) => tx.hash === action.payload.hash)[0]; @@ -130,7 +143,8 @@ export const transactionsSlice = createSlice({ parachainMessageDispatched: (state, action: PayloadAction<{ nonce: string, channel: Channel }>) => { const transaction = state.transactions.filter((tx) => { return tx.nonce === action.payload.nonce && - tx.channel === action.payload.channel + tx.channel === action.payload.channel && + tx.direction === SwapDirection.EthereumToPolkadot })[0]; if (transaction) { transaction.isMinted = true; @@ -142,18 +156,20 @@ export const transactionsSlice = createSlice({ // // const transaction = state.transactions.filter((tx) => tx.nonce === action.payload.nonce)[0]; // }, setPendingTransaction: (state, action: PayloadAction) => { - state.pendingTransaction = action.payload; + const payload = nonCircularPayload(action); + state.pendingTransaction = payload; }, ethMessageDispatched: (state, action: PayloadAction<{ - nonce: string, dispatchTransactionNonce: string, channel: Channel, + nonce: string, channel: Channel, }>) => { const transaction = state.transactions.filter((tx) => { return tx.nonce === action.payload.nonce && - tx.channel === action.payload.channel + tx.channel === action.payload.channel && + tx.direction === SwapDirection.PolkadotToEthereum })[0]; if (transaction) { transaction.status = TransactionStatus.DISPATCHED; - transaction.dispatchTransactionHash = action.payload.dispatchTransactionNonce; + transaction.nonce = transaction.nonce } }, }, diff --git a/src/redux/store.ts b/src/redux/store.ts index 1812d83..669089b 100644 --- a/src/redux/store.ts +++ b/src/redux/store.ts @@ -6,19 +6,35 @@ import ERC20Transactions from './reducers/ERC20Transactions'; import ERC721Transactions from './reducers/ERC721Transactions'; import bridgeReducer from './reducers/bridge'; import bridgeHealthReducer from './reducers/bridgeHealth'; +import { combineReducers } from 'redux'; +import { persistReducer, persistStore } from 'redux-persist'; +import localStorage from 'redux-persist/lib/storage'; + + +const persistConfig = { + key: 'root', + storage: localStorage, + whitelist: ['transactions'] +}; + +const reducers = combineReducers({ + net, + transactions, + ERC20Transactions, + ERC721Transactions, + bridge: bridgeReducer, + bridgeHealth: bridgeHealthReducer, +}); + +const persistedReducer = persistReducer(persistConfig, reducers); const store = configureStore({ - reducer: { - net, - transactions, - ERC20Transactions, - ERC721Transactions, - bridge: bridgeReducer, - bridgeHealth: bridgeHealthReducer, - }, + reducer: persistedReducer, middleware: [thunkMiddleware], }); -export default store; +let persistor = persistStore(store); + +export { store, persistor }; export type RootState = ReturnType export type AppDispatch = typeof store.dispatch diff --git a/src/types/types.ts b/src/types/types.ts index 9882ade..bc4e3f9 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -51,3 +51,8 @@ export interface AssetBalance { isAssetFound: boolean, amount: string }; + +export interface PolkadotTxConfirmation { + istxFound: boolean, + nonce: string +}; \ No newline at end of file diff --git a/src/utils/common.ts b/src/utils/common.ts index 8e09712..4216af7 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -1,7 +1,8 @@ import { BASIC_CHANNEL_ID, INCENTIVIZED_CHANNEL_ID, REQUIRED_ETH_CONFIRMATIONS } from '../config'; -import { Transaction } from '../redux/reducers/transactions'; +import { Transaction,TransactionStatus } from '../redux/reducers/transactions'; import { SwapDirection, Chain, Channel } from '../types/types'; import { isNonFungible, NonFungibleToken, Asset } from '../types/Asset'; +import { PayloadAction } from '@reduxjs/toolkit'; /** * Shortens a wallet address, showing X number of letters, an ellipsis, and @@ -60,6 +61,11 @@ export const pendingTransactions = (transactions: Transaction[]) (t) => t.confirmations < REQUIRED_ETH_CONFIRMATIONS, ).length; +export const pendingEventTransactions = (transactions: Transaction[]) + : number => transactions.filter( + (t) => t.status !== TransactionStatus.DISPATCHED && t.status !== TransactionStatus.REJECTED, + ).length; + export const getChainsFromDirection = (swapDirection: SwapDirection): { from: Chain, to: Chain } => ( swapDirection === SwapDirection.EthereumToPolkadot ? { from: Chain.ETHEREUM, to: Chain.POLKADOT } @@ -98,3 +104,19 @@ export const getChannelID = (channel: Channel): number => { return INCENTIVIZED_CHANNEL_ID; } } + +//Remove property named as pipes causing circular JSON issue in redux persist +export const nonCircularPayload = (action: PayloadAction): Transaction => { + action.payload = JSON.parse(JSON.stringify(action.payload,(key,value) => { + return key === 'pipes' ? null : value; + })) + return action.payload; +} + +//Remove property named as pipes causing circular JSON issue in redux persist for the case of update transaction +export const nonCircularUpdateTxPayload = (update: Partial): any => { + update = JSON.parse(JSON.stringify(update,(key,value) => { + return key === 'pipes' ? null : value; + })) + return update; +} diff --git a/yarn.lock b/yarn.lock index b68b8fe..7e6977f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -33,6 +33,11 @@ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.14.7.tgz#7b047d7a3a89a67d2258dc61f604f098f1bc7e08" integrity sha512-nS6dZaISCXJ3+518CWiBfEr//gHyMO02uDxBkXTKZDN5POruCnOZ1N4YBRZDCabwF8nZMWBpRxIicmXtBs+fvw== +"@babel/compat-data@^7.14.5", "@babel/compat-data@^7.14.7": + version "7.17.0" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.17.0.tgz#86850b8597ea6962089770952075dcaabb8dba34" + integrity sha512-392byTlpGWXMv4FbyWw3sAZ/FrW/DrwqLGXpy0mbyNe9Taqv1mg9yON5/o0cnr8XYCkFTZbC1eV+c+LAROgrng== + "@babel/compat-data@^7.16.4", "@babel/compat-data@^7.16.8": version "7.16.8" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.16.8.tgz#31560f9f29fdf1868de8cb55049538a1b9732a60" @@ -90,14 +95,23 @@ jsesc "^2.5.1" source-map "^0.5.0" -"@babel/helper-annotate-as-pure@^7.16.0", "@babel/helper-annotate-as-pure@^7.16.7": +"@babel/generator@^7.17.3": + version "7.17.3" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.17.3.tgz#a2c30b0c4f89858cb87050c3ffdfd36bdf443200" + integrity sha512-+R6Dctil/MgUsZsZAkYgK+ADNSZzJRRy0TvY65T71z/CR854xHQ1EweBYXdfT+HNeN7w0cSJJEzgxZMv40pxsg== + dependencies: + "@babel/types" "^7.17.0" + jsesc "^2.5.1" + source-map "^0.5.0" + +"@babel/helper-annotate-as-pure@^7.14.5", "@babel/helper-annotate-as-pure@^7.16.0", "@babel/helper-annotate-as-pure@^7.16.7": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz#bb2339a7534a9c128e3102024c60760a3a7f3862" integrity sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw== dependencies: "@babel/types" "^7.16.7" -"@babel/helper-builder-binary-assignment-operator-visitor@^7.16.7": +"@babel/helper-builder-binary-assignment-operator-visitor@^7.14.5", "@babel/helper-builder-binary-assignment-operator-visitor@^7.16.7": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.16.7.tgz#38d138561ea207f0f69eb1626a418e4f7e6a580b" integrity sha512-C6FdbRaxYjwVu/geKW4ZeQ0Q31AftgRcdSnZ5/jsH6BzCJbtvXvhpfkbkThYSuutZA7nCXpPR6AD9zd1dprMkA== @@ -115,7 +129,7 @@ browserslist "^4.16.6" semver "^6.3.0" -"@babel/helper-compilation-targets@^7.16.7": +"@babel/helper-compilation-targets@^7.14.5", "@babel/helper-compilation-targets@^7.16.7": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.7.tgz#06e66c5f299601e6c7da350049315e83209d551b" integrity sha512-mGojBwIWcwGD6rfqgRXVlVYmPAv7eOpIemUG3dGnDdCY4Pae70ROij3XmfrH6Fa1h1aiDylpglbZyktfzyo/hA== @@ -138,6 +152,14 @@ "@babel/helper-replace-supers" "^7.16.7" "@babel/helper-split-export-declaration" "^7.16.7" +"@babel/helper-create-regexp-features-plugin@^7.14.5": + version "7.17.0" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.17.0.tgz#1dcc7d40ba0c6b6b25618997c5dbfd310f186fe1" + integrity sha512-awO2So99wG6KnlE+TPs6rn83gCz5WlEePJDTnLEqbchMVrBeAujURVphRdigsk094VhvZehFoNOihSlcBjwsXA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.16.7" + regexpu-core "^5.0.1" + "@babel/helper-create-regexp-features-plugin@^7.16.7": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.16.7.tgz#0cb82b9bac358eb73bfbd73985a776bfa6b14d48" @@ -174,7 +196,7 @@ dependencies: "@babel/types" "^7.16.7" -"@babel/helper-function-name@^7.16.7": +"@babel/helper-function-name@^7.14.5", "@babel/helper-function-name@^7.16.7": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz#f1ec51551fb1c8956bc8dd95f38523b6cf375f8f" integrity sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA== @@ -190,7 +212,7 @@ dependencies: "@babel/types" "^7.16.7" -"@babel/helper-hoist-variables@^7.16.7": +"@babel/helper-hoist-variables@^7.14.5", "@babel/helper-hoist-variables@^7.16.7": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz#86bcb19a77a509c7b77d0e22323ef588fa58c246" integrity sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg== @@ -204,13 +226,27 @@ dependencies: "@babel/types" "^7.16.7" -"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.16.0", "@babel/helper-module-imports@^7.16.7", "@babel/helper-module-imports@^7.8.3": +"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.14.5", "@babel/helper-module-imports@^7.16.0", "@babel/helper-module-imports@^7.16.7", "@babel/helper-module-imports@^7.8.3": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz#25612a8091a999704461c8a222d0efec5d091437" integrity sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg== dependencies: "@babel/types" "^7.16.7" +"@babel/helper-module-transforms@^7.14.5": + version "7.17.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.17.6.tgz#3c3b03cc6617e33d68ef5a27a67419ac5199ccd0" + integrity sha512-2ULmRdqoOMpdvkbT8jONrZML/XALfzxlb052bldftkicAUy8AxSCkD5trDPQcwHNmolcl7wP6ehNqMlyUw6AaA== + dependencies: + "@babel/helper-environment-visitor" "^7.16.7" + "@babel/helper-module-imports" "^7.16.7" + "@babel/helper-simple-access" "^7.16.7" + "@babel/helper-split-export-declaration" "^7.16.7" + "@babel/helper-validator-identifier" "^7.16.7" + "@babel/template" "^7.16.7" + "@babel/traverse" "^7.17.3" + "@babel/types" "^7.17.0" + "@babel/helper-module-transforms@^7.16.7", "@babel/helper-module-transforms@^7.9.0": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.16.7.tgz#7665faeb721a01ca5327ddc6bba15a5cb34b6a41" @@ -225,7 +261,7 @@ "@babel/traverse" "^7.16.7" "@babel/types" "^7.16.7" -"@babel/helper-optimise-call-expression@^7.16.7": +"@babel/helper-optimise-call-expression@^7.14.5", "@babel/helper-optimise-call-expression@^7.16.7": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.7.tgz#a34e3560605abbd31a18546bd2aad3e6d9a174f2" integrity sha512-EtgBhg7rd/JcnpZFXpBy0ze1YRfdm7BnBX4uKMBd3ixa3RGAE002JZB66FJyNH7g0F38U05pXmA5P8cBh7z+1w== @@ -237,6 +273,15 @@ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz#aa3a8ab4c3cceff8e65eb9e73d87dc4ff320b2f5" integrity sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA== +"@babel/helper-remap-async-to-generator@^7.14.5", "@babel/helper-remap-async-to-generator@^7.16.8": + version "7.16.8" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.8.tgz#29ffaade68a367e2ed09c90901986918d25e57e3" + integrity sha512-fm0gH7Flb8H51LqJHy3HJ3wnE1+qtYR2A99K06ahwrawLdOFsCEWjZOrYricXJHoPSudNKxrMBUPEIPxiIIvBw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.16.7" + "@babel/helper-wrap-function" "^7.16.8" + "@babel/types" "^7.16.8" + "@babel/helper-remap-async-to-generator@^7.16.7": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.7.tgz#5ce2416990d55eb6e099128338848ae8ffa58a9a" @@ -246,16 +291,7 @@ "@babel/helper-wrap-function" "^7.16.7" "@babel/types" "^7.16.7" -"@babel/helper-remap-async-to-generator@^7.16.8": - version "7.16.8" - resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.8.tgz#29ffaade68a367e2ed09c90901986918d25e57e3" - integrity sha512-fm0gH7Flb8H51LqJHy3HJ3wnE1+qtYR2A99K06ahwrawLdOFsCEWjZOrYricXJHoPSudNKxrMBUPEIPxiIIvBw== - dependencies: - "@babel/helper-annotate-as-pure" "^7.16.7" - "@babel/helper-wrap-function" "^7.16.8" - "@babel/types" "^7.16.8" - -"@babel/helper-replace-supers@^7.16.7": +"@babel/helper-replace-supers@^7.14.5", "@babel/helper-replace-supers@^7.16.7": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.16.7.tgz#e9f5f5f32ac90429c1a4bdec0f231ef0c2838ab1" integrity sha512-y9vsWilTNaVnVh6xiJfABzsNpgDPKev9HnAgz6Gb1p6UUwf9NepdlsV7VXGCftJM+jqD5f7JIEubcpLjZj5dBw== @@ -266,33 +302,33 @@ "@babel/traverse" "^7.16.7" "@babel/types" "^7.16.7" -"@babel/helper-simple-access@^7.16.7": +"@babel/helper-simple-access@^7.14.5", "@babel/helper-simple-access@^7.16.7": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.16.7.tgz#d656654b9ea08dbb9659b69d61063ccd343ff0f7" integrity sha512-ZIzHVyoeLMvXMN/vok/a4LWRy8G2v205mNP0XOuf9XRLyX5/u9CnVulUtDgUTama3lT+bf/UqucuZjqiGuTS1g== dependencies: "@babel/types" "^7.16.7" -"@babel/helper-skip-transparent-expression-wrappers@^7.16.0": +"@babel/helper-skip-transparent-expression-wrappers@^7.14.5", "@babel/helper-skip-transparent-expression-wrappers@^7.16.0": version "7.16.0" resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.16.0.tgz#0ee3388070147c3ae051e487eca3ebb0e2e8bb09" integrity sha512-+il1gTy0oHwUsBQZyJvukbB4vPMdcYBrFHa0Uc4AizLxbq6BOYC51Rv4tWocX9BLBDLZ4kc6qUFpQ6HRgL+3zw== dependencies: "@babel/types" "^7.16.0" -"@babel/helper-split-export-declaration@^7.16.7": +"@babel/helper-split-export-declaration@^7.14.5", "@babel/helper-split-export-declaration@^7.16.7": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz#0b648c0c42da9d3920d85ad585f2778620b8726b" integrity sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw== dependencies: "@babel/types" "^7.16.7" -"@babel/helper-validator-identifier@^7.16.7": +"@babel/helper-validator-identifier@^7.14.5", "@babel/helper-validator-identifier@^7.16.7": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz#e8c602438c4a8195751243da9031d1607d247cad" integrity sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw== -"@babel/helper-validator-option@^7.16.7": +"@babel/helper-validator-option@^7.14.5", "@babel/helper-validator-option@^7.16.7": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz#b203ce62ce5fe153899b617c08957de860de4d23" integrity sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ== @@ -326,7 +362,7 @@ "@babel/traverse" "^7.16.7" "@babel/types" "^7.16.7" -"@babel/highlight@^7.16.7": +"@babel/highlight@^7.14.5", "@babel/highlight@^7.16.7": version "7.16.10" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.16.10.tgz#744f2eb81579d6eea753c227b0f570ad785aba88" integrity sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw== @@ -354,6 +390,11 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.16.10.tgz#aba1b1cb9696a24a19f59c41af9cf17d1c716a88" integrity sha512-Sm/S9Or6nN8uiFsQU1yodyDW3MWXQhFeqzMPM+t8MJjM+pLsnFVxFZzkpXKvUXh+Gz9cbMoYYs484+Jw/NTEFQ== +"@babel/parser@^7.17.3": + version "7.17.3" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.17.3.tgz#b07702b982990bf6fdc1da5049a23fece4c5c3d0" + integrity sha512-7yJPvPV+ESz2IUTPbOL+YkIGyCqOyNIzdguKQuJGnH7bg1WTIifuM21YqokFt/THWh1AkCRn9IgoykTRCBVpzA== + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.16.7": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.16.7.tgz#4eda6d6c2a0aa79c70fa7b6da67763dfe2141050" @@ -1090,7 +1131,7 @@ "@babel/helper-plugin-utils" "^7.14.5" "@babel/helper-replace-supers" "^7.14.5" -"@babel/plugin-transform-parameters@^7.16.7", "@babel/plugin-transform-parameters@^7.8.7": +"@babel/plugin-transform-parameters@^7.14.5", "@babel/plugin-transform-parameters@^7.16.7", "@babel/plugin-transform-parameters@^7.8.7": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.16.7.tgz#a1721f55b99b736511cb7e0152f61f17688f331f" integrity sha512-AT3MufQ7zZEhU2hwOA11axBnExW0Lszu4RL/tAlUJBuNoRak+wehQW8h6KcXOcgjY42fHtDxswuMhMjFEuv/aw== @@ -1559,6 +1600,22 @@ debug "^4.1.0" globals "^11.1.0" +"@babel/traverse@^7.17.3": + version "7.17.3" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.17.3.tgz#0ae0f15b27d9a92ba1f2263358ea7c4e7db47b57" + integrity sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw== + dependencies: + "@babel/code-frame" "^7.16.7" + "@babel/generator" "^7.17.3" + "@babel/helper-environment-visitor" "^7.16.7" + "@babel/helper-function-name" "^7.16.7" + "@babel/helper-hoist-variables" "^7.16.7" + "@babel/helper-split-export-declaration" "^7.16.7" + "@babel/parser" "^7.17.3" + "@babel/types" "^7.17.0" + debug "^4.1.0" + globals "^11.1.0" + "@babel/types@^7.0.0", "@babel/types@^7.3.0", "@babel/types@^7.4.0", "@babel/types@^7.4.4", "@babel/types@^7.7.0", "@babel/types@^7.9.0": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.16.7.tgz#4ed19d51f840ed4bd5645be6ce40775fecf03159" @@ -1575,6 +1632,14 @@ "@babel/helper-validator-identifier" "^7.16.7" to-fast-properties "^2.0.0" +"@babel/types@^7.17.0": + version "7.17.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.17.0.tgz#a826e368bccb6b3d84acd76acad5c0d87342390b" + integrity sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw== + dependencies: + "@babel/helper-validator-identifier" "^7.16.7" + to-fast-properties "^2.0.0" + "@cnakazawa/watch@^1.0.3": version "1.0.4" resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a" @@ -5380,6 +5445,17 @@ browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.17.5, browserslist@^4 node-releases "^2.0.1" picocolors "^1.0.0" +browserslist@^4.16.6: + version "4.19.3" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.19.3.tgz#29b7caad327ecf2859485f696f9604214bedd383" + integrity sha512-XK3X4xtKJ+Txj8G5c30B4gsm71s69lqXlkYui4s6EkKxuv49qjYlY6oVd+IFJ73d4YymtM3+djvvt/R/iJwwDg== + dependencies: + caniuse-lite "^1.0.30001312" + electron-to-chromium "^1.4.71" + escalade "^3.1.1" + node-releases "^2.0.2" + picocolors "^1.0.0" + bs58@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/bs58/-/bs58-3.1.0.tgz#d4c26388bf4804cac714141b1945aa47e5eb248e" @@ -5679,6 +5755,11 @@ caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001035, can resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001304.tgz" integrity sha512-bdsfZd6K6ap87AGqSHJP/s1V+U6Z5lyrcbBu3ovbCCf8cSYpwTtGrCBObMpJqwxfTbLW6YTIdbb1jEeTelcpYQ== +caniuse-lite@^1.0.30001312: + version "1.0.30001312" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001312.tgz#e11eba4b87e24d22697dae05455d5aea28550d5f" + integrity sha512-Wiz1Psk2MEK0pX3rUzWaunLTZzqS2JYZFzNKqAiJGiuxIjRPLgV6+VDPOg6lQOUxmDwhTlh198JsTTi8Hzw6aQ== + canvas-renderer@~2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/canvas-renderer/-/canvas-renderer-2.2.0.tgz#512151f5494aaac5270802fba22599785114716d" @@ -7145,6 +7226,11 @@ electron-to-chromium@^1.3.378, electron-to-chromium@^1.4.17: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.49.tgz#5b6a3dc032590beef4be485a4b0b3fe7d0e3dfd7" integrity sha512-k/0t1TRfonHIp8TJKfjBu2cKj8MqYTiEpOhci+q7CVEE5xnCQnx1pTa+V8b/sdhe4S3PR4p4iceEQWhGrKQORQ== +electron-to-chromium@^1.4.71: + version "1.4.72" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.72.tgz#19b871f1da8be8199b2330d694fc84fcdb72ecd9" + integrity sha512-9LkRQwjW6/wnSfevR21a3k8sOJ+XWSH7kkzs9/EUenKmuDkndP3W9y1yCZpOxufwGbX3JV8glZZSDb4o95zwXQ== + elliptic@6.5.2: version "6.5.2" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.2.tgz#05c5678d7173c049d8ca433552224a495d0e3762" @@ -12213,6 +12299,11 @@ node-releases@^2.0.1: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.1.tgz#3d1d395f204f1f2f29a54358b9fb678765ad2fc5" integrity sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA== +node-releases@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.2.tgz#7139fe71e2f4f11b47d4d2986aaf8c48699e0c01" + integrity sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg== + normalize-hex@0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/normalize-hex/-/normalize-hex-0.0.2.tgz#5491c43759db2f06b7168d8419f4925c271ab27e" @@ -14460,6 +14551,11 @@ redux-devtools-extension@^2.13.8: resolved "https://registry.yarnpkg.com/redux-devtools-extension/-/redux-devtools-extension-2.13.9.tgz#6b764e8028b507adcb75a1cae790f71e6be08ae7" integrity sha512-cNJ8Q/EtjhQaZ71c8I9+BPySIBVEKssbPpskBfsXqb8HJ002A3KRVHfeRzwRo6mGPqsm7XuHTqNSNeS1Khig0A== +redux-persist@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/redux-persist/-/redux-persist-6.0.0.tgz#b4d2972f9859597c130d40d4b146fecdab51b3a8" + integrity sha512-71LLMbUq2r02ng2We9S215LtPu3fY0KgaGE0k8WRgl6RkqxtGfl7HUozz1Dftwsb0D/5mZ8dwAaPbtnzfvbEwQ== + redux-thunk@^2.3.0, redux-thunk@^2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.4.1.tgz#0dd8042cf47868f4b29699941de03c9301a75714" @@ -14472,6 +14568,13 @@ redux@^4.0.0, redux@^4.0.5, redux@^4.1.2: dependencies: "@babel/runtime" "^7.9.2" +regenerate-unicode-properties@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz#7f442732aa7934a3740c779bb9b3340dccc1fb56" + integrity sha512-vn5DU6yg6h8hP/2OkQo3K7uVILvY4iu0oI4t3HFa81UPkhGJwkRwM10JEc3upjdhHjs/k8GJY1sRBhk5sr69Bw== + dependencies: + regenerate "^1.4.2" + regenerate-unicode-properties@^9.0.0: version "9.0.0" resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-9.0.0.tgz#54d09c7115e1f53dc2314a974b32c1c344efe326" @@ -14544,11 +14647,28 @@ regexpu-core@^4.7.1: unicode-match-property-ecmascript "^2.0.0" unicode-match-property-value-ecmascript "^2.0.0" +regexpu-core@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.0.1.tgz#c531122a7840de743dcf9c83e923b5560323ced3" + integrity sha512-CriEZlrKK9VJw/xQGJpQM5rY88BtuL8DM+AEwvcThHilbxiTAy8vq4iJnd2tqq8wLmjbGZzP7ZcKFjbGkmEFrw== + dependencies: + regenerate "^1.4.2" + regenerate-unicode-properties "^10.0.1" + regjsgen "^0.6.0" + regjsparser "^0.8.2" + unicode-match-property-ecmascript "^2.0.0" + unicode-match-property-value-ecmascript "^2.0.0" + regjsgen@^0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.2.tgz#92ff295fb1deecbf6ecdab2543d207e91aa33733" integrity sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A== +regjsgen@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.6.0.tgz#83414c5354afd7d6627b16af5f10f41c4e71808d" + integrity sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA== + regjsparser@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.7.0.tgz#a6b667b54c885e18b52554cb4960ef71187e9968" @@ -14556,6 +14676,13 @@ regjsparser@^0.7.0: dependencies: jsesc "~0.5.0" +regjsparser@^0.8.2: + version "0.8.4" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.8.4.tgz#8a14285ffcc5de78c5b95d62bbf413b6bc132d5f" + integrity sha512-J3LABycON/VNEu3abOviqGHuB/LOtOQj8SKmfP9anY5GfAVw/SPjwzSjxGjbZXIxbGfqTHtJw58C2Li/WkStmA== + dependencies: + jsesc "~0.5.0" + relateurl@^0.2.7: version "0.2.7" resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9"