Automatically detect network by searching the transaction in both chains (#377)
This commit is contained in:
parent
4a727dc159
commit
691e4294ae
@ -1,10 +1,9 @@
|
||||
import React, { useState, FormEvent, useEffect } from 'react'
|
||||
import React, { useState, FormEvent } from 'react'
|
||||
import styled from 'styled-components'
|
||||
import { FormSubmitParams } from './MainPage'
|
||||
import { useStateProvider } from '../state/StateProvider'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { Button } from './commons/Button'
|
||||
import { RadioButtonLabel, RadioButtonContainer } from './commons/RadioButton'
|
||||
import { TransactionSelector } from './TransactionSelector'
|
||||
import { TransactionReceipt } from 'web3-eth'
|
||||
|
||||
const LabelText = styled.label`
|
||||
line-height: 36px;
|
||||
@ -23,33 +22,21 @@ const Input = styled.input`
|
||||
}
|
||||
`
|
||||
|
||||
export const Form = ({
|
||||
onSubmit,
|
||||
lastUsedChain
|
||||
}: {
|
||||
onSubmit: ({ chainId, txHash }: FormSubmitParams) => void
|
||||
lastUsedChain: number
|
||||
}) => {
|
||||
const { home, foreign, loading } = useStateProvider()
|
||||
const { chainId: paramChainId, txHash: paramTxHash } = useParams()
|
||||
const [chainId, setChainId] = useState(lastUsedChain)
|
||||
export const Form = ({ onSubmit }: { onSubmit: ({ chainId, txHash, receipt }: FormSubmitParams) => void }) => {
|
||||
const [txHash, setTxHash] = useState('')
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
if (!paramChainId) {
|
||||
setChainId(lastUsedChain > 0 ? lastUsedChain : foreign.chainId)
|
||||
} else {
|
||||
setChainId(parseInt(paramChainId))
|
||||
setTxHash(paramTxHash)
|
||||
}
|
||||
},
|
||||
[foreign.chainId, paramChainId, paramTxHash, lastUsedChain]
|
||||
)
|
||||
const [searchTx, setSearchTx] = useState(false)
|
||||
|
||||
const formSubmit = (e: FormEvent) => {
|
||||
e.preventDefault()
|
||||
onSubmit({ chainId, txHash })
|
||||
setSearchTx(true)
|
||||
}
|
||||
|
||||
const onSelected = (chainId: number, receipt: TransactionReceipt) => {
|
||||
onSubmit({ chainId, txHash, receipt })
|
||||
}
|
||||
|
||||
if (searchTx) {
|
||||
return <TransactionSelector txHash={txHash} onSelected={onSelected} />
|
||||
}
|
||||
|
||||
return (
|
||||
@ -72,32 +59,6 @@ export const Form = ({
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{!loading && (
|
||||
<div className="row is-center">
|
||||
<RadioButtonContainer className="is-vertical-align" onClick={() => setChainId(foreign.chainId)}>
|
||||
<input
|
||||
className="is-marginless"
|
||||
type="radio"
|
||||
name="network"
|
||||
value={foreign.name}
|
||||
checked={chainId === foreign.chainId}
|
||||
onChange={() => setChainId(foreign.chainId)}
|
||||
/>
|
||||
<RadioButtonLabel htmlFor={foreign.name}>{foreign.name}</RadioButtonLabel>
|
||||
</RadioButtonContainer>
|
||||
<RadioButtonContainer className="is-vertical-align" onClick={() => setChainId(home.chainId)}>
|
||||
<input
|
||||
className="is-marginless"
|
||||
type="radio"
|
||||
name="network"
|
||||
value={home.name}
|
||||
checked={chainId === home.chainId}
|
||||
onChange={() => setChainId(home.chainId)}
|
||||
/>
|
||||
<RadioButtonLabel htmlFor={home.name}>{home.name}</RadioButtonLabel>
|
||||
</RadioButtonContainer>
|
||||
</div>
|
||||
)}
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import { Route, useHistory } from 'react-router-dom'
|
||||
import { Form } from './Form'
|
||||
import { StatusContainer } from './StatusContainer'
|
||||
import { useStateProvider } from '../state/StateProvider'
|
||||
import { TransactionReceipt } from 'web3-eth'
|
||||
|
||||
const StyledMainPage = styled.div`
|
||||
text-align: center;
|
||||
@ -34,24 +35,24 @@ const HeaderContainer = styled.header`
|
||||
export interface FormSubmitParams {
|
||||
chainId: number
|
||||
txHash: string
|
||||
receipt: TransactionReceipt
|
||||
}
|
||||
|
||||
export const MainPage = () => {
|
||||
const history = useHistory()
|
||||
const { home, foreign } = useStateProvider()
|
||||
const [selectedChainId, setSelectedChainId] = useState(0)
|
||||
const [networkName, setNetworkName] = useState('')
|
||||
const [receipt, setReceipt] = useState<Maybe<TransactionReceipt>>(null)
|
||||
|
||||
const setNetworkData = (chainId: number) => {
|
||||
const network = chainId === home.chainId ? home.name : foreign.name
|
||||
|
||||
setNetworkName(network)
|
||||
setSelectedChainId(chainId)
|
||||
}
|
||||
|
||||
const onFormSubmit = ({ chainId, txHash }: FormSubmitParams) => {
|
||||
const onFormSubmit = ({ chainId, txHash, receipt }: FormSubmitParams) => {
|
||||
setNetworkData(chainId)
|
||||
|
||||
setReceipt(receipt)
|
||||
history.push(`/${chainId}/${txHash}`)
|
||||
}
|
||||
|
||||
@ -72,10 +73,16 @@ export const MainPage = () => {
|
||||
</HeaderContainer>
|
||||
</Header>
|
||||
<div className="container">
|
||||
<Route exact path={['/']} children={<Form onSubmit={onFormSubmit} lastUsedChain={selectedChainId} />} />
|
||||
<Route exact path={['/']} children={<Form onSubmit={onFormSubmit} />} />
|
||||
<Route
|
||||
path={['/:chainId/:txHash/:messageIdParam', '/:chainId/:txHash']}
|
||||
children={<StatusContainer onBackToMain={resetNetworkHeader} setNetworkFromParams={setNetworkFromParams} />}
|
||||
children={
|
||||
<StatusContainer
|
||||
onBackToMain={resetNetworkHeader}
|
||||
setNetworkFromParams={setNetworkFromParams}
|
||||
receiptParam={receipt}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</StyledMainPage>
|
||||
|
47
alm/src/components/NetworkTransactionSelector.tsx
Normal file
47
alm/src/components/NetworkTransactionSelector.tsx
Normal file
@ -0,0 +1,47 @@
|
||||
import React, { useState } from 'react'
|
||||
import { Button } from './commons/Button'
|
||||
import { RadioButtonLabel, RadioButtonContainer } from './commons/RadioButton'
|
||||
import { useStateProvider } from '../state/StateProvider'
|
||||
|
||||
export const NetworkTransactionSelector = ({ onNetworkSelected }: { onNetworkSelected: (chainId: number) => void }) => {
|
||||
const { home, foreign } = useStateProvider()
|
||||
const [chainId, setChainId] = useState(home.chainId)
|
||||
|
||||
const networks = [home, foreign]
|
||||
|
||||
const onSelect = () => {
|
||||
onNetworkSelected(chainId)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>The transaction was found in both networks, please select one:</p>
|
||||
<div className="row is-center">
|
||||
<div className="col-3-lg col-12 is-marginless">
|
||||
{networks.map((network, i) => (
|
||||
<RadioButtonContainer
|
||||
className="row is-center is-vertical-align"
|
||||
key={i}
|
||||
onClick={() => setChainId(network.chainId)}
|
||||
>
|
||||
<input
|
||||
className="is-marginless"
|
||||
type="radio"
|
||||
name="message"
|
||||
value={network.chainId}
|
||||
checked={network.chainId === chainId}
|
||||
onChange={() => setChainId(network.chainId)}
|
||||
/>
|
||||
<RadioButtonLabel htmlFor={i.toString()}>{network.name}</RadioButtonLabel>
|
||||
</RadioButtonContainer>
|
||||
))}
|
||||
</div>
|
||||
<div className="col-3-lg col-12 is-marginless">
|
||||
<Button className="button outline" onClick={onSelect}>
|
||||
Select
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -10,6 +10,7 @@ import { ExplorerTxLink } from './commons/ExplorerTxLink'
|
||||
import { ConfirmationsContainer } from './ConfirmationsContainer'
|
||||
import { LeftArrow } from './commons/LeftArrow'
|
||||
import styled from 'styled-components'
|
||||
import { TransactionReceipt } from 'web3-eth'
|
||||
|
||||
const BackButton = styled.button`
|
||||
color: var(--button-color);
|
||||
@ -28,9 +29,10 @@ const BackLabel = styled.label`
|
||||
export interface StatusContainerParam {
|
||||
onBackToMain: () => void
|
||||
setNetworkFromParams: (chainId: number) => void
|
||||
receiptParam: Maybe<TransactionReceipt>
|
||||
}
|
||||
|
||||
export const StatusContainer = ({ onBackToMain, setNetworkFromParams }: StatusContainerParam) => {
|
||||
export const StatusContainer = ({ onBackToMain, setNetworkFromParams, receiptParam }: StatusContainerParam) => {
|
||||
const { home, foreign } = useStateProvider()
|
||||
const history = useHistory()
|
||||
const { chainId, txHash, messageIdParam } = useParams()
|
||||
@ -39,7 +41,8 @@ export const StatusContainer = ({ onBackToMain, setNetworkFromParams }: StatusCo
|
||||
|
||||
const { messages, receipt, status, description, timestamp, loading } = useTransactionStatus({
|
||||
txHash: validParameters ? txHash : '',
|
||||
chainId: validParameters ? parseInt(chainId) : 0
|
||||
chainId: validParameters ? parseInt(chainId) : 0,
|
||||
receiptParam
|
||||
})
|
||||
|
||||
const selectedMessageId = messageIdParam === undefined || messages[messageIdParam] === undefined ? -1 : messageIdParam
|
||||
|
47
alm/src/components/TransactionSelector.tsx
Normal file
47
alm/src/components/TransactionSelector.tsx
Normal file
@ -0,0 +1,47 @@
|
||||
import React, { useEffect } from 'react'
|
||||
import { useTransactionFinder } from '../hooks/useTransactionFinder'
|
||||
import { useStateProvider } from '../state/StateProvider'
|
||||
import { TRANSACTION_STATUS } from '../config/constants'
|
||||
import { TransactionReceipt } from 'web3-eth'
|
||||
import { Loading } from './commons/Loading'
|
||||
import { NetworkTransactionSelector } from './NetworkTransactionSelector'
|
||||
|
||||
export const TransactionSelector = ({
|
||||
txHash,
|
||||
onSelected
|
||||
}: {
|
||||
txHash: string
|
||||
onSelected: (chainId: number, receipt: TransactionReceipt) => void
|
||||
}) => {
|
||||
const { home, foreign } = useStateProvider()
|
||||
const { receipt: homeReceipt, status: homeStatus } = useTransactionFinder({ txHash, web3: home.web3 })
|
||||
const { receipt: foreignReceipt, status: foreignStatus } = useTransactionFinder({ txHash, web3: foreign.web3 })
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
if (!home.chainId || !foreign.chainId) return
|
||||
if (homeStatus === TRANSACTION_STATUS.FOUND && foreignStatus === TRANSACTION_STATUS.NOT_FOUND) {
|
||||
if (!homeReceipt) return
|
||||
onSelected(home.chainId, homeReceipt)
|
||||
} else if (foreignStatus === TRANSACTION_STATUS.FOUND && homeStatus === TRANSACTION_STATUS.NOT_FOUND) {
|
||||
if (!foreignReceipt) return
|
||||
onSelected(foreign.chainId, foreignReceipt)
|
||||
}
|
||||
},
|
||||
[homeReceipt, homeStatus, foreignReceipt, foreignStatus, home.chainId, foreign.chainId, onSelected]
|
||||
)
|
||||
|
||||
const onSelectedNetwork = (chainId: number) => {
|
||||
const chain = chainId === home.chainId ? home.chainId : foreign.chainId
|
||||
const receipt = chainId === home.chainId ? homeReceipt : foreignReceipt
|
||||
|
||||
if (!receipt) return
|
||||
onSelected(chain, receipt)
|
||||
}
|
||||
|
||||
if (foreignStatus === TRANSACTION_STATUS.FOUND && homeStatus === TRANSACTION_STATUS.FOUND) {
|
||||
return <NetworkTransactionSelector onNetworkSelected={onSelectedNetwork} />
|
||||
}
|
||||
|
||||
return <Loading />
|
||||
}
|
@ -32,7 +32,9 @@ export const TRANSACTION_STATUS = {
|
||||
SUCCESS_ONE_MESSAGE: 'SUCCESS_ONE_MESSAGE',
|
||||
SUCCESS_NO_MESSAGES: 'SUCCESS_NO_MESSAGES',
|
||||
FAILED: 'FAILED',
|
||||
NOT_FOUND: 'NOT_FOUND'
|
||||
FOUND: 'FOUND',
|
||||
NOT_FOUND: 'NOT_FOUND',
|
||||
UNDEFINED: 'UNDEFINED'
|
||||
}
|
||||
|
||||
export const CONFIRMATIONS_STATUS = {
|
||||
|
56
alm/src/hooks/useTransactionFinder.ts
Normal file
56
alm/src/hooks/useTransactionFinder.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { TransactionReceipt } from 'web3-eth'
|
||||
import { HOME_RPC_POLLING_INTERVAL, TRANSACTION_STATUS } from '../config/constants'
|
||||
import Web3 from 'web3'
|
||||
|
||||
export const useTransactionFinder = ({ txHash, web3 }: { txHash: string; web3: Maybe<Web3> }) => {
|
||||
const [status, setStatus] = useState(TRANSACTION_STATUS.UNDEFINED)
|
||||
const [receipt, setReceipt] = useState<Maybe<TransactionReceipt>>(null)
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
if (!txHash || !web3) return
|
||||
|
||||
const subscriptions: number[] = []
|
||||
|
||||
const unsubscribe = () => {
|
||||
subscriptions.forEach(s => {
|
||||
clearTimeout(s)
|
||||
})
|
||||
}
|
||||
|
||||
const getReceipt = async (
|
||||
web3: Web3,
|
||||
txHash: string,
|
||||
setReceipt: Function,
|
||||
setStatus: Function,
|
||||
subscriptions: number[]
|
||||
) => {
|
||||
const txReceipt = await web3.eth.getTransactionReceipt(txHash)
|
||||
setReceipt(txReceipt)
|
||||
|
||||
if (!txReceipt) {
|
||||
setStatus(TRANSACTION_STATUS.NOT_FOUND)
|
||||
const timeoutId = setTimeout(
|
||||
() => getReceipt(web3, txHash, setReceipt, setStatus, subscriptions),
|
||||
HOME_RPC_POLLING_INTERVAL
|
||||
)
|
||||
subscriptions.push(timeoutId)
|
||||
} else {
|
||||
setStatus(TRANSACTION_STATUS.FOUND)
|
||||
}
|
||||
}
|
||||
|
||||
getReceipt(web3, txHash, setReceipt, setStatus, subscriptions)
|
||||
return () => {
|
||||
unsubscribe()
|
||||
}
|
||||
},
|
||||
[txHash, web3]
|
||||
)
|
||||
|
||||
return {
|
||||
status,
|
||||
receipt
|
||||
}
|
||||
}
|
@ -6,7 +6,15 @@ import { useStateProvider } from '../state/StateProvider'
|
||||
import { getHomeMessagesFromReceipt, getForeignMessagesFromReceipt, MessageObject, getBlock } from '../utils/web3'
|
||||
import useInterval from '@use-it/interval'
|
||||
|
||||
export const useTransactionStatus = ({ txHash, chainId }: { txHash: string; chainId: number }) => {
|
||||
export const useTransactionStatus = ({
|
||||
txHash,
|
||||
chainId,
|
||||
receiptParam
|
||||
}: {
|
||||
txHash: string
|
||||
chainId: number
|
||||
receiptParam: Maybe<TransactionReceipt>
|
||||
}) => {
|
||||
const { home, foreign } = useStateProvider()
|
||||
const [messages, setMessages] = useState<Array<MessageObject>>([])
|
||||
const [status, setStatus] = useState('')
|
||||
@ -37,7 +45,14 @@ export const useTransactionStatus = ({ txHash, chainId }: { txHash: string; chai
|
||||
const isHome = chainId === home.chainId
|
||||
const web3 = isHome ? home.web3 : foreign.web3
|
||||
|
||||
const txReceipt = await web3.eth.getTransactionReceipt(txHash)
|
||||
let txReceipt
|
||||
|
||||
if (receiptParam) {
|
||||
txReceipt = receiptParam
|
||||
} else {
|
||||
txReceipt = await web3.eth.getTransactionReceipt(txHash)
|
||||
}
|
||||
|
||||
setReceipt(txReceipt)
|
||||
|
||||
if (!txReceipt) {
|
||||
@ -92,7 +107,17 @@ export const useTransactionStatus = ({ txHash, chainId }: { txHash: string; chai
|
||||
unsubscribe()
|
||||
}
|
||||
},
|
||||
[txHash, chainId, home.chainId, foreign.chainId, home.web3, foreign.web3, home.bridgeAddress, foreign.bridgeAddress]
|
||||
[
|
||||
txHash,
|
||||
chainId,
|
||||
home.chainId,
|
||||
foreign.chainId,
|
||||
home.web3,
|
||||
foreign.web3,
|
||||
home.bridgeAddress,
|
||||
foreign.bridgeAddress,
|
||||
receiptParam
|
||||
]
|
||||
)
|
||||
|
||||
return {
|
||||
|
@ -53,7 +53,7 @@ export const getValidatorSuccessTransaction = (
|
||||
getSuccessTransactions: (args: GetFailedTransactionParams) => Promise<APITransaction[]>
|
||||
) => async (validatorData: BasicConfirmationParam): Promise<ConfirmationParam> => {
|
||||
const { validator } = validatorData
|
||||
const validatorCacheKey = `${CACHE_KEY_SUCCESS}${validatorData.validator}`
|
||||
const validatorCacheKey = `${CACHE_KEY_SUCCESS}${validatorData.validator}-${messageData}`
|
||||
const fromCache = validatorsCache.getData(validatorCacheKey)
|
||||
|
||||
if (fromCache && fromCache.txHash) {
|
||||
@ -100,7 +100,7 @@ export const getValidatorFailedTransaction = (
|
||||
timestamp: number,
|
||||
getFailedTransactions: (args: GetFailedTransactionParams) => Promise<APITransaction[]>
|
||||
) => async (validatorData: BasicConfirmationParam): Promise<ConfirmationParam> => {
|
||||
const validatorCacheKey = `${CACHE_KEY_FAILED}${validatorData.validator}`
|
||||
const validatorCacheKey = `${CACHE_KEY_FAILED}${validatorData.validator}-${messageData}`
|
||||
const failedFromCache = validatorsCache.getData(validatorCacheKey)
|
||||
|
||||
if (failedFromCache && failedFromCache.txHash) {
|
||||
|
@ -80,7 +80,7 @@ export const getFinalizationEvent = async (
|
||||
})
|
||||
setPendingExecution(true)
|
||||
} else {
|
||||
const validatorExecutionCacheKey = `${CACHE_KEY_EXECUTION_FAILED}${validator}`
|
||||
const validatorExecutionCacheKey = `${CACHE_KEY_EXECUTION_FAILED}${validator}-${message.id}`
|
||||
const failedFromCache = validatorsCache.get(validatorExecutionCacheKey)
|
||||
|
||||
if (!failedFromCache) {
|
||||
|
Loading…
Reference in New Issue
Block a user