diff --git a/.gitignore b/.gitignore
index 7cea0539..9655fe72 100644
--- a/.gitignore
+++ b/.gitignore
@@ -49,3 +49,6 @@ monitor/responses/*
monitor/cache/*
!monitor/cache/.gitkeep
!monitor/.gitkeep
+
+# Local Netlify folder
+.netlify
\ No newline at end of file
diff --git a/alm/package.json b/alm/package.json
index dfee20a9..41ede67e 100644
--- a/alm/package.json
+++ b/alm/package.json
@@ -3,6 +3,7 @@
"version": "0.1.0",
"private": true,
"dependencies": {
+ "@ethersproject/bignumber": ">=5.0.0-beta.130",
"@react-hook/window-size": "^3.0.6",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
@@ -15,6 +16,8 @@
"@types/react-router-dom": "^5.1.5",
"@types/styled-components": "^5.1.0",
"@use-it/interval": "^0.1.3",
+ "@web3-react/core": "^6.1.1",
+ "@web3-react/injected-connector": "^6.0.7",
"customize-cra": "^1.0.0",
"date-fns": "^2.14.0",
"dotenv": "^8.2.0",
@@ -27,8 +30,9 @@
"react-scripts": "3.0.1",
"styled-components": "^5.1.1",
"typescript": "^3.5.2",
- "web3": "1.2.7",
- "web3-eth-contract": "1.2.7"
+ "web3": "1.2.11",
+ "web3-eth-contract": "1.2.11",
+ "web3-utils": "1.2.11"
},
"scripts": {
"start": "yarn createSnapshots && ./load-env.sh react-app-rewired start",
diff --git a/alm/public/_redirects b/alm/public/_redirects
new file mode 100644
index 00000000..78f7f206
--- /dev/null
+++ b/alm/public/_redirects
@@ -0,0 +1 @@
+/* /index.html 200
\ No newline at end of file
diff --git a/alm/src/App.tsx b/alm/src/App.tsx
index 3ab3eaf6..81f3bc0c 100644
--- a/alm/src/App.tsx
+++ b/alm/src/App.tsx
@@ -1,14 +1,18 @@
import React from 'react'
import { BrowserRouter } from 'react-router-dom'
+import { Web3ReactProvider } from '@web3-react/core'
+import Web3 from 'web3'
import { MainPage } from './components/MainPage'
import { StateProvider } from './state/StateProvider'
function App() {
return (
-
-
-
+ new Web3(provider)}>
+
+
+
+
)
}
diff --git a/alm/src/components/ConfirmationsContainer.tsx b/alm/src/components/ConfirmationsContainer.tsx
index 4edc9488..981d62ef 100644
--- a/alm/src/components/ConfirmationsContainer.tsx
+++ b/alm/src/components/ConfirmationsContainer.tsx
@@ -44,12 +44,19 @@ export interface ConfirmationsContainerParams {
export const ConfirmationsContainer = ({ message, receipt, fromHome, timestamp }: ConfirmationsContainerParams) => {
const {
- home: { name: homeName },
+ home: { name: homeName, confirmations },
foreign: { name: foreignName }
} = useStateProvider()
const { requiredSignatures, validatorList } = useValidatorContract({ fromHome, receipt })
const { blockConfirmations } = useBlockConfirmations({ fromHome, receipt })
- const { confirmations, status, executionData, signatureCollected, waitingBlocksResolved } = useMessageConfirmations({
+ const {
+ status,
+ executionData,
+ signatureCollected,
+ waitingBlocksResolved,
+ setExecutionData,
+ executionEventsFetched
+ } = useMessageConfirmations({
message,
receipt,
fromHome,
@@ -102,7 +109,16 @@ export const ConfirmationsContainer = ({ message, receipt, fromHome, timestamp }
validatorList={validatorList}
waitingBlocksResolved={waitingBlocksResolved}
/>
- {signatureCollected && }
+ {signatureCollected && (
+
+ )}
)
diff --git a/alm/src/components/ExecutionConfirmation.tsx b/alm/src/components/ExecutionConfirmation.tsx
index d1af0a90..445b98b8 100644
--- a/alm/src/components/ExecutionConfirmation.tsx
+++ b/alm/src/components/ExecutionConfirmation.tsx
@@ -1,24 +1,43 @@
import React from 'react'
import { formatTimestamp, formatTxHash, getExplorerTxUrl } from '../utils/networks'
import { useWindowWidth } from '@react-hook/window-size'
-import { SEARCHING_TX, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants'
+import { SEARCHING_TX, VALIDATOR_CONFIRMATION_STATUS, ALM_HOME_TO_FOREIGN_MANUAL_EXECUTION } from '../config/constants'
import { SimpleLoading } from './commons/Loading'
import styled from 'styled-components'
import { ExecutionData } from '../hooks/useMessageConfirmations'
import { GreyLabel, RedLabel, SuccessLabel } from './commons/Labels'
import { ExplorerTxLink } from './commons/ExplorerTxLink'
import { Thead, AgeTd, StatusTd } from './commons/Table'
+import { ManualExecutionButton } from './ManualExecutionButton'
const StyledExecutionConfirmation = styled.div`
margin-top: 30px;
`
export interface ExecutionConfirmationParams {
+ messageData: string
executionData: ExecutionData
+ setExecutionData: Function
+ requiredSignatures: number
isHome: boolean
+ executionEventsFetched: boolean
}
-export const ExecutionConfirmation = ({ executionData, isHome }: ExecutionConfirmationParams) => {
+export const ExecutionConfirmation = ({
+ messageData,
+ executionData,
+ setExecutionData,
+ requiredSignatures,
+ isHome,
+ executionEventsFetched
+}: ExecutionConfirmationParams) => {
+ const availableManualExecution =
+ !isHome &&
+ (executionData.status === VALIDATOR_CONFIRMATION_STATUS.WAITING ||
+ (executionData.status === VALIDATOR_CONFIRMATION_STATUS.UNDEFINED &&
+ executionEventsFetched &&
+ !!executionData.validator))
+ const requiredManualExecution = availableManualExecution && ALM_HOME_TO_FOREIGN_MANUAL_EXECUTION
const windowWidth = useWindowWidth()
const txExplorerLink = getExplorerTxUrl(executionData.txHash, isHome)
@@ -48,26 +67,46 @@ export const ExecutionConfirmation = ({ executionData, isHome }: ExecutionConfir
- Executed by |
+ {requiredManualExecution ? 'Execution info' : 'Executed by'} |
Status |
- Age |
+ {!requiredManualExecution && Age | }
+ {availableManualExecution && Actions | }
- {formattedValidator ? formattedValidator : } |
- {getExecutionStatusElement(executionData.status)}
-
- {executionData.timestamp > 0 ? (
-
- {formatTimestamp(executionData.timestamp)}
-
- ) : executionData.status === VALIDATOR_CONFIRMATION_STATUS.WAITING ? (
- ''
+
+ {requiredManualExecution ? (
+ 'Manual user action is required to complete the operation'
+ ) : formattedValidator ? (
+ formattedValidator
) : (
- SEARCHING_TX
+
)}
-
+ |
+ {getExecutionStatusElement(executionData.status)}
+ {!requiredManualExecution && (
+
+ {executionData.timestamp > 0 ? (
+
+ {formatTimestamp(executionData.timestamp)}
+
+ ) : executionData.status === VALIDATOR_CONFIRMATION_STATUS.WAITING ? (
+ ''
+ ) : (
+ SEARCHING_TX
+ )}
+
+ )}
+ {availableManualExecution && (
+
+
+ |
+ )}
diff --git a/alm/src/components/MainPage.tsx b/alm/src/components/MainPage.tsx
index 88ced815..9697d65c 100644
--- a/alm/src/components/MainPage.tsx
+++ b/alm/src/components/MainPage.tsx
@@ -8,6 +8,7 @@ import { TransactionReceipt } from 'web3-eth'
import { InfoAlert } from './commons/InfoAlert'
import { ExplorerTxLink } from './commons/ExplorerTxLink'
import { FOREIGN_NETWORK_NAME, HOME_NETWORK_NAME } from '../config/constants'
+import { ErrorAlert } from './commons/ErrorAlert'
const StyledMainPage = styled.div`
text-align: center;
@@ -51,7 +52,7 @@ export interface FormSubmitParams {
export const MainPage = () => {
const history = useHistory()
- const { home, foreign } = useStateProvider()
+ const { home, foreign, error, setError } = useStateProvider()
const [networkName, setNetworkName] = useState('')
const [receipt, setReceipt] = useState>(null)
const [showInfoAlert, setShowInfoAlert] = useState(false)
@@ -131,6 +132,7 @@ export const MainPage = () => {
)}
+ {error && setError('')} error={error} />}
} />
{
+ const { home, foreign, setError } = useStateProvider()
+ const { library, activate, account, active } = useWeb3React()
+ const [manualExecution, setManualExecution] = useState(false)
+ const disabled =
+ home.confirmations.filter(({ signature }) => signature && signature.startsWith('0x')).length < requiredSignatures
+
+ useEffect(
+ () => {
+ if (!manualExecution || !foreign.chainId) return
+
+ if (!active) {
+ activate(new InjectedConnector({ supportedChainIds: [foreign.chainId] }), e => {
+ if (e.message.includes('Unsupported chain id')) {
+ setError(INCORRECT_CHAIN_ERROR)
+ const { ethereum } = window as any
+
+ // remove the error message after chain is correctly changed to the foreign one
+ const listener = (chainId: string) => {
+ if (parseInt(chainId.slice(2), 16) === foreign.chainId) {
+ ethereum.removeListener('chainChanged', listener)
+ setError((error: string) => (error === INCORRECT_CHAIN_ERROR ? '' : error))
+ }
+ }
+ ethereum.on('chainChanged', listener)
+ } else {
+ setError(e.message)
+ }
+ setManualExecution(false)
+ })
+ return
+ }
+
+ if (!library || !foreign.bridgeContract || !home.confirmations) return
+
+ const collectedSignatures = home.confirmations
+ .map(confirmation => confirmation.signature!)
+ .filter(signature => signature && signature.startsWith('0x'))
+ const signatures = packSignatures(collectedSignatures.map(signatureToVRS))
+ const data = foreign.bridgeContract.methods.executeSignatures(messageData, signatures).encodeABI()
+ setManualExecution(false)
+
+ library.eth
+ .sendTransaction({
+ from: account,
+ to: foreign.bridgeAddress,
+ data
+ })
+ .on('transactionHash', (txHash: string) =>
+ setExecutionData({
+ status: VALIDATOR_CONFIRMATION_STATUS.PENDING,
+ validator: account,
+ txHash,
+ timestamp: Math.floor(new Date().getTime() / 1000.0),
+ executionResult: false
+ })
+ )
+ .on('error', (e: Error) => setError(e.message))
+ },
+ [
+ manualExecution,
+ library,
+ activate,
+ active,
+ account,
+ foreign.chainId,
+ foreign.bridgeAddress,
+ foreign.bridgeContract,
+ setError,
+ messageData,
+ home.confirmations,
+ setExecutionData
+ ]
+ )
+
+ return (
+
+ setManualExecution(true)}>
+ Execute
+
+
+ )
+}
diff --git a/alm/src/components/commons/CloseIcon.tsx b/alm/src/components/commons/CloseIcon.tsx
index 702e3edf..73242115 100644
--- a/alm/src/components/commons/CloseIcon.tsx
+++ b/alm/src/components/commons/CloseIcon.tsx
@@ -1,6 +1,6 @@
import React from 'react'
-export const CloseIcon = () => (
+export const CloseIcon = ({ color }: { color?: string }) => (
)
diff --git a/alm/src/components/commons/ErrorAlert.tsx b/alm/src/components/commons/ErrorAlert.tsx
new file mode 100644
index 00000000..af3e3f59
--- /dev/null
+++ b/alm/src/components/commons/ErrorAlert.tsx
@@ -0,0 +1,31 @@
+import React from 'react'
+import styled from 'styled-components'
+import { InfoIcon } from './InfoIcon'
+import { CloseIcon } from './CloseIcon'
+
+const StyledErrorAlert = styled.div`
+ border: 1px solid var(--failed-color);
+ border-radius: 4px;
+ margin-bottom: 20px;
+ padding-top: 10px;
+`
+
+const CloseIconContainer = styled.div`
+ cursor: pointer;
+`
+
+const TextContainer = styled.div`
+ flex-direction: column;
+`
+
+export const ErrorAlert = ({ onClick, error }: { onClick: () => void; error: string }) => (
+
+
+
+ {error}
+
+
+
+
+
+)
diff --git a/alm/src/components/commons/InfoIcon.tsx b/alm/src/components/commons/InfoIcon.tsx
index f8d84f92..9fd62c6e 100644
--- a/alm/src/components/commons/InfoIcon.tsx
+++ b/alm/src/components/commons/InfoIcon.tsx
@@ -1,6 +1,6 @@
import React from 'react'
-export const InfoIcon = () => (
+export const InfoIcon = ({ color }: { color?: string }) => (