Compare commits
56 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ffd88f6cd0 | ||
|
|
caf2e2b4d3 | ||
|
|
d5d0c8f56a | ||
|
|
dc27bd6caa | ||
|
|
ab814f831c | ||
|
|
691e4294ae | ||
|
|
4a727dc159 | ||
|
|
2ca07e998a | ||
|
|
0eb7c41278 | ||
|
|
9e9e891db8 | ||
|
|
d228bb7ea9 | ||
|
|
d2606997a3 | ||
|
|
3c956ab9ec | ||
|
|
9b3e6a51a9 | ||
|
|
d512b9850f | ||
|
|
bcdf691000 | ||
|
|
8e10a5d609 | ||
|
|
da60edae4b | ||
|
|
e7eb8ec758 | ||
|
|
571dec8022 | ||
|
|
4db62d721d | ||
|
|
8d6acd0339 | ||
|
|
c013cc7378 | ||
|
|
d6e39f34af | ||
|
|
3b368ce644 | ||
|
|
c1d58c2908 | ||
|
|
d17e9e0eea | ||
|
|
a2c678d0a2 | ||
|
|
4bd3576691 | ||
|
|
d3576f5a79 | ||
|
|
62f9a080c9 | ||
|
|
10f67168a7 | ||
|
|
f90f888ae4 | ||
|
|
84508e2b84 | ||
|
|
2369e876aa | ||
|
|
4117f29a74 | ||
|
|
1921087ad1 | ||
|
|
b4abbc4910 | ||
|
|
bcbe34a839 | ||
|
|
738442e4cf | ||
|
|
64c5a5670f | ||
|
|
c8c589536b | ||
|
|
7b0edff624 | ||
|
|
b02b879b02 | ||
|
|
5b4b01a79b | ||
|
|
881fafe9a8 | ||
|
|
c1ed6f21e6 | ||
|
|
8ae0fa82d5 | ||
|
|
dbf3d3d90d | ||
|
|
8977ed6d3b | ||
|
|
df0dc1c313 | ||
|
|
7b2bebbcf0 | ||
|
|
85104d67c0 | ||
|
|
2a3d7c8e08 | ||
|
|
e6052f162a | ||
|
|
52ed4e85e2 |
@@ -245,7 +245,7 @@ jobs:
|
||||
- tokenbridge-orb/yarn-install-cached-on-machine
|
||||
- run:
|
||||
name: Prepare the infrastructure
|
||||
command: e2e-commons/up.sh deploy << parameters.scenario-name >>
|
||||
command: e2e-commons/up.sh deploy << parameters.scenario-name >> blocks
|
||||
no_output_timeout: 50m
|
||||
- tokenbridge-orb/wait-for-oracle:
|
||||
redis-key: << parameters.redis-key >>
|
||||
@@ -256,7 +256,6 @@ jobs:
|
||||
name: Run the ui-e2e tests
|
||||
command: |
|
||||
nvm use default;
|
||||
node ./e2e-commons/scripts/blocks.js &
|
||||
cd ui-e2e; yarn mocha -g "<< parameters.ui-e2e-grep >>" -b ./test.js
|
||||
- when:
|
||||
condition: << parameters.oracle-e2e-script >>
|
||||
@@ -315,3 +314,8 @@ workflows:
|
||||
scenario-name: amb
|
||||
redis-key: amb-collected-signatures:lastProcessedBlock
|
||||
oracle-e2e-script: "amb"
|
||||
- ultimate:
|
||||
name: "ultimate: amb stake erc to erc"
|
||||
scenario-name: ultimate-amb-stake-erc-to-erc
|
||||
redis-key: amb-collected-signatures:lastProcessedBlock
|
||||
ui-e2e-grep: "AMB-STAKE-ERC-TO-ERC"
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
**/*.md
|
||||
|
||||
contracts/test
|
||||
contracts/build
|
||||
oracle/test
|
||||
oracle/**/*.png
|
||||
oracle/**/*.jpg
|
||||
audit
|
||||
|
||||
@@ -2,3 +2,5 @@ node_modules
|
||||
submodules
|
||||
coverage
|
||||
lib
|
||||
dist
|
||||
build
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -6,6 +6,7 @@ coverage
|
||||
|
||||
# production
|
||||
build
|
||||
dist
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
@@ -48,4 +49,5 @@ __pycache__
|
||||
|
||||
#monitor
|
||||
monitor/responses/*
|
||||
!monitor/.gitkeep
|
||||
monitor/configs/*.env
|
||||
!monitor/.gitkeep
|
||||
|
||||
@@ -43,6 +43,7 @@ ORACLE_VALIDATOR_ADDRESS | The public address of the bridge validator | hexideci
|
||||
name | description | value
|
||||
--- | --- | ---
|
||||
UI_TITLE | The title for the bridge UI page. `%c` will be replaced by the name of the network. | string
|
||||
UI_OG_TITLE | The meta title for the deployed bridge page. | string
|
||||
UI_DESCRIPTION | The meta description for the deployed bridge page. | string
|
||||
UI_NATIVE_TOKEN_DISPLAY_NAME | name of the home native coin | string
|
||||
UI_HOME_NETWORK_DISPLAY_NAME | name to be displayed for home network | string
|
||||
@@ -56,7 +57,8 @@ UI_FOREIGN_EXPLORER_ADDRESS_TEMPLATE | template link to address on foreign explo
|
||||
UI_HOME_GAS_PRICE_UPDATE_INTERVAL | An interval in milliseconds used to get the updated gas price value either from the oracle or from the Home Bridge contract. | integer
|
||||
UI_FOREIGN_GAS_PRICE_UPDATE_INTERVAL | An interval in milliseconds used to get the updated gas price value either from the oracle or from the Foreign Bridge contract. | integer
|
||||
UI_PORT | The port for the UI app. | integer
|
||||
UI_STYLES | The set of styles to render the bridge UI page. Currently only `classic` is implemented | classic
|
||||
UI_STYLES | The set of styles to render the bridge UI page. | core/classic/stake
|
||||
UI_PUBLIC_URL | The public url for the deployed bridge page | string
|
||||
|
||||
|
||||
## Monitor configuration
|
||||
@@ -70,3 +72,4 @@ MONITOR_VALIDATOR_FOREIGN_TX_LIMIT | Average gas usage of a transaction sent by
|
||||
MONITOR_TX_NUMBER_THRESHOLD | If estimated number of transaction is equal to or below this value, the monitor will report that the validator has less funds than it is required. | integer
|
||||
MONITOR_PORT | The port for the Monitor. | integer
|
||||
MONITOR_BRIDGE_NAME | The name to be used in the url path for the bridge | string
|
||||
MONITOR_CACHE_EVENTS | If set to true, monitor will cache obtained events for other workers runs
|
||||
|
||||
@@ -29,6 +29,8 @@ Sub-repositories maintained within this monorepo are listed below.
|
||||
| [Deployment-E2E](deployment-e2e/README.md) | End to end tests for the Deployment |
|
||||
| [Commons](commons/README.md) | Interfaces, constants and utilities shared between the sub-repositories |
|
||||
| [E2E-Commons](e2e-commons/README.md) | Common utilities and configuration used in end to end tests |
|
||||
| [ALM](alm/README.md) | DApp interface tool for AMB Live Monitoring |
|
||||
| [Burner-wallet-plugin](burner-wallet-plugin/README.md) | TokenBridge Burner Wallet 2 Plugin |
|
||||
|
||||
Additionally there are [Smart Contracts](https://github.com/poanetwork/tokenbridge-contracts) used to manage bridge validators, collect signatures, and confirm asset relay and disposal.
|
||||
|
||||
@@ -66,11 +68,13 @@ Clone the repository:
|
||||
git clone https://github.com/poanetwork/tokenbridge
|
||||
```
|
||||
|
||||
Initialize submodules, install dependencies, compile the Smart Contracts:
|
||||
If there is no need to build docker images for the TokenBridge components (oracle, monitor, UI), initialize submodules, install dependencies, compile the Smart Contracts:
|
||||
```
|
||||
yarn initialize
|
||||
```
|
||||
|
||||
Then refer to the corresponding README files to get information about particular TokenBridge component.
|
||||
|
||||
## Linting
|
||||
|
||||
Running linter for all JS projects:
|
||||
|
||||
15
alm/.env.example
Normal file
15
alm/.env.example
Normal file
@@ -0,0 +1,15 @@
|
||||
COMMON_HOME_BRIDGE_ADDRESS=0xFe446bEF1DbF7AFE24E81e05BC8B271C1BA9a560
|
||||
COMMON_FOREIGN_BRIDGE_ADDRESS=0xFe446bEF1DbF7AFE24E81e05BC8B271C1BA9a560
|
||||
|
||||
COMMON_HOME_RPC_URL=https://sokol.poa.network
|
||||
COMMON_FOREIGN_RPC_URL=https://kovan.infura.io/v3/
|
||||
|
||||
ALM_HOME_NETWORK_NAME=Sokol Testnet
|
||||
ALM_FOREIGN_NETWORK_NAME=Kovan Testnet
|
||||
|
||||
ALM_HOME_EXPLORER_TX_TEMPLATE=https://blockscout.com/poa/sokol/tx/%s
|
||||
ALM_FOREIGN_EXPLORER_TX_TEMPLATE=https://blockscout.com/eth/kovan/tx/%s
|
||||
|
||||
ALM_HOME_EXPLORER_API=https://blockscout.com/poa/sokol/api
|
||||
ALM_FOREIGN_EXPLORER_API=https://kovan.etherscan.io/api?apikey=YourApiKeyToken
|
||||
PORT=8080
|
||||
6
alm/.eslintrc.js
Normal file
6
alm/.eslintrc.js
Normal file
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
extends: [
|
||||
"react-app",
|
||||
"../.eslintrc"
|
||||
]
|
||||
}
|
||||
23
alm/.gitignore
vendored
Normal file
23
alm/.gitignore
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
29
alm/Dockerfile
Normal file
29
alm/Dockerfile
Normal file
@@ -0,0 +1,29 @@
|
||||
FROM node:12 as alm-builder
|
||||
|
||||
WORKDIR /mono
|
||||
COPY package.json .
|
||||
COPY contracts/package.json ./contracts/
|
||||
COPY commons/package.json ./commons/
|
||||
COPY alm/package.json ./alm/
|
||||
COPY yarn.lock .
|
||||
RUN yarn install --production --frozen-lockfile
|
||||
|
||||
COPY ./contracts ./contracts
|
||||
RUN yarn run compile:contracts
|
||||
RUN mv ./contracts/build ./ && rm -rf ./contracts/* ./contracts/.[!.]* && mv ./build ./contracts/
|
||||
|
||||
COPY ./commons ./commons
|
||||
|
||||
COPY ./alm ./alm
|
||||
ARG DOT_ENV_PATH=./alm/.env
|
||||
COPY ${DOT_ENV_PATH} ./alm/.env
|
||||
|
||||
WORKDIR /mono/alm
|
||||
RUN yarn run build
|
||||
|
||||
|
||||
FROM node:12 as alm-production
|
||||
RUN yarn global add serve
|
||||
WORKDIR /app
|
||||
COPY --from=alm-builder /mono/alm/build .
|
||||
CMD serve -p $PORT -s .
|
||||
46
alm/README.md
Normal file
46
alm/README.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# AMB Live Monitoring
|
||||
|
||||
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
|
||||
|
||||
## Available Scripts
|
||||
|
||||
In the project directory, you can run:
|
||||
|
||||
### `yarn start`
|
||||
|
||||
Runs the app in the development mode.<br />
|
||||
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
|
||||
|
||||
The page will reload if you make edits.<br />
|
||||
You will also see any lint errors in the console.
|
||||
|
||||
### `yarn test`
|
||||
|
||||
Launches the test runner in the interactive watch mode.<br />
|
||||
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
|
||||
|
||||
### `yarn build`
|
||||
|
||||
Builds the app for production to the `build` folder.<br />
|
||||
It correctly bundles React in production mode and optimizes the build for the best performance.
|
||||
|
||||
The build is minified and the filenames include the hashes.<br />
|
||||
Your app is ready to be deployed!
|
||||
|
||||
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
|
||||
|
||||
### `yarn eject`
|
||||
|
||||
**Note: this is a one-way operation. Once you `eject`, you can’t go back!**
|
||||
|
||||
If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
|
||||
|
||||
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
|
||||
|
||||
You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
|
||||
|
||||
## Learn More
|
||||
|
||||
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
|
||||
|
||||
To learn React, check out the [React documentation](https://reactjs.org/).
|
||||
9
alm/config-overrides.js
Normal file
9
alm/config-overrides.js
Normal file
@@ -0,0 +1,9 @@
|
||||
const { override, disableEsLint } = require('customize-cra')
|
||||
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin')
|
||||
|
||||
const disableModuleScopePlugin = () => config => {
|
||||
config.resolve.plugins = config.resolve.plugins.filter(plugin => !(plugin instanceof ModuleScopePlugin))
|
||||
return config
|
||||
}
|
||||
|
||||
module.exports = override(disableEsLint(), disableModuleScopePlugin())
|
||||
14
alm/docker-compose.yml
Normal file
14
alm/docker-compose.yml
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
version: '2.4'
|
||||
services:
|
||||
alm:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: alm/Dockerfile
|
||||
ports:
|
||||
- "${PORT}:${PORT}"
|
||||
env_file: ./.env
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
restart: unless-stopped
|
||||
entrypoint: serve -p ${PORT} -s .
|
||||
16
alm/load-env.sh
Executable file
16
alm/load-env.sh
Executable file
@@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
|
||||
while read line; do
|
||||
if [ "$line" = "" ]; then
|
||||
: # Skip empty lines
|
||||
elif [[ "$line" =~ \#.* ]]; then
|
||||
: # Skip comment lines
|
||||
elif [[ "$line" =~ "UI_PORT"* ]]; then
|
||||
eval $line
|
||||
export PORT="$UI_PORT"
|
||||
else
|
||||
export "REACT_APP_$line"
|
||||
fi
|
||||
done < '.env'
|
||||
|
||||
$*
|
||||
57
alm/package.json
Normal file
57
alm/package.json
Normal file
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"name": "alm",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@react-hook/window-size": "^3.0.6",
|
||||
"@testing-library/jest-dom": "^4.2.4",
|
||||
"@testing-library/react": "^9.3.2",
|
||||
"@testing-library/user-event": "^7.1.2",
|
||||
"@types/jest": "^24.0.0",
|
||||
"@types/node": "^12.0.0",
|
||||
"@types/promise-retry": "^1.1.3",
|
||||
"@types/react": "^16.9.0",
|
||||
"@types/react-dom": "^16.9.0",
|
||||
"@types/react-router-dom": "^5.1.5",
|
||||
"@types/styled-components": "^5.1.0",
|
||||
"@use-it/interval": "^0.1.3",
|
||||
"customize-cra": "^1.0.0",
|
||||
"date-fns": "^2.14.0",
|
||||
"fast-memoize": "^2.5.2",
|
||||
"promise-retry": "^2.0.1",
|
||||
"react": "^16.13.1",
|
||||
"react-app-rewired": "^2.1.6",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-scripts": "3.0.1",
|
||||
"styled-components": "^5.1.1",
|
||||
"typescript": "^3.5.2",
|
||||
"web3": "1.2.7",
|
||||
"web3-eth-contract": "1.2.7"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "./load-env.sh react-app-rewired start",
|
||||
"build": "./load-env.sh react-app-rewired build",
|
||||
"test": "react-app-rewired test",
|
||||
"eject": "react-app-rewired eject",
|
||||
"lint": "eslint '*/**/*.{js,ts,tsx}' --ignore-path ../.eslintignore"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "react-app"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint-plugin-prettier": "^3.1.3"
|
||||
}
|
||||
}
|
||||
BIN
alm/public/favicon.ico
Normal file
BIN
alm/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.1 KiB |
45
alm/public/index.html
Normal file
45
alm/public/index.html
Normal file
@@ -0,0 +1,45 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="AMB Live Monitoring"
|
||||
/>
|
||||
<link rel="stylesheet" href="https://unpkg.com/chota@latest">
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,700" rel="stylesheet">
|
||||
<title>AMB Live Monitoring</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
||||
15
alm/public/manifest.json
Normal file
15
alm/public/manifest.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"short_name": "ALM",
|
||||
"name": "AMB Live Monitoring",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
3
alm/public/robots.txt
Normal file
3
alm/public/robots.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
||||
5
alm/src/App.test.tsx
Normal file
5
alm/src/App.test.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import React from 'react'
|
||||
|
||||
test('renders learn react link', () => {
|
||||
// Removed basic test from setup. Keeping this so CI passes until we add unit tests.
|
||||
})
|
||||
16
alm/src/App.tsx
Normal file
16
alm/src/App.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import React from 'react'
|
||||
import { BrowserRouter } from 'react-router-dom'
|
||||
import { MainPage } from './components/MainPage'
|
||||
import { StateProvider } from './state/StateProvider'
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<StateProvider>
|
||||
<MainPage />
|
||||
</StateProvider>
|
||||
</BrowserRouter>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
||||
319
alm/src/abis/BridgeValidators.ts
Normal file
319
alm/src/abis/BridgeValidators.ts
Normal file
@@ -0,0 +1,319 @@
|
||||
import { AbiItem } from 'web3-utils'
|
||||
|
||||
const abi: AbiItem[] = [
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'validatorCount',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'getBridgeValidatorsInterfacesVersion',
|
||||
outputs: [
|
||||
{
|
||||
name: 'major',
|
||||
type: 'uint64'
|
||||
},
|
||||
{
|
||||
name: 'minor',
|
||||
type: 'uint64'
|
||||
},
|
||||
{
|
||||
name: 'patch',
|
||||
type: 'uint64'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'pure',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'isInitialized',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'bool'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'validatorList',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'address[]'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: false,
|
||||
inputs: [
|
||||
{
|
||||
name: '_requiredSignatures',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
name: 'setRequiredSignatures',
|
||||
outputs: [],
|
||||
payable: false,
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'requiredSignatures',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [
|
||||
{
|
||||
name: '_address',
|
||||
type: 'address'
|
||||
}
|
||||
],
|
||||
name: 'getNextValidator',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'address'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'owner',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'address'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [
|
||||
{
|
||||
name: '_validator',
|
||||
type: 'address'
|
||||
}
|
||||
],
|
||||
name: 'isValidatorDuty',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'bool'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'deployedAtBlock',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'F_ADDR',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'address'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: false,
|
||||
inputs: [
|
||||
{
|
||||
name: 'newOwner',
|
||||
type: 'address'
|
||||
}
|
||||
],
|
||||
name: 'transferOwnership',
|
||||
outputs: [],
|
||||
payable: false,
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [
|
||||
{
|
||||
name: '_validator',
|
||||
type: 'address'
|
||||
}
|
||||
],
|
||||
name: 'isValidator',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'bool'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
anonymous: false,
|
||||
inputs: [
|
||||
{
|
||||
indexed: true,
|
||||
name: 'validator',
|
||||
type: 'address'
|
||||
}
|
||||
],
|
||||
name: 'ValidatorAdded',
|
||||
type: 'event'
|
||||
},
|
||||
{
|
||||
anonymous: false,
|
||||
inputs: [
|
||||
{
|
||||
indexed: true,
|
||||
name: 'validator',
|
||||
type: 'address'
|
||||
}
|
||||
],
|
||||
name: 'ValidatorRemoved',
|
||||
type: 'event'
|
||||
},
|
||||
{
|
||||
anonymous: false,
|
||||
inputs: [
|
||||
{
|
||||
indexed: false,
|
||||
name: 'requiredSignatures',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
name: 'RequiredSignaturesChanged',
|
||||
type: 'event'
|
||||
},
|
||||
{
|
||||
anonymous: false,
|
||||
inputs: [
|
||||
{
|
||||
indexed: false,
|
||||
name: 'previousOwner',
|
||||
type: 'address'
|
||||
},
|
||||
{
|
||||
indexed: false,
|
||||
name: 'newOwner',
|
||||
type: 'address'
|
||||
}
|
||||
],
|
||||
name: 'OwnershipTransferred',
|
||||
type: 'event'
|
||||
},
|
||||
{
|
||||
constant: false,
|
||||
inputs: [
|
||||
{
|
||||
name: '_requiredSignatures',
|
||||
type: 'uint256'
|
||||
},
|
||||
{
|
||||
name: '_initialValidators',
|
||||
type: 'address[]'
|
||||
},
|
||||
{
|
||||
name: '_owner',
|
||||
type: 'address'
|
||||
}
|
||||
],
|
||||
name: 'initialize',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'bool'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: false,
|
||||
inputs: [
|
||||
{
|
||||
name: '_validator',
|
||||
type: 'address'
|
||||
}
|
||||
],
|
||||
name: 'addValidator',
|
||||
outputs: [],
|
||||
payable: false,
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: false,
|
||||
inputs: [
|
||||
{
|
||||
name: '_validator',
|
||||
type: 'address'
|
||||
}
|
||||
],
|
||||
name: 'removeValidator',
|
||||
outputs: [],
|
||||
payable: false,
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function'
|
||||
}
|
||||
]
|
||||
|
||||
export default abi
|
||||
589
alm/src/abis/ForeignAMB.ts
Normal file
589
alm/src/abis/ForeignAMB.ts
Normal file
@@ -0,0 +1,589 @@
|
||||
import { AbiItem } from 'web3-utils'
|
||||
|
||||
const abi: AbiItem[] = [
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'transactionHash',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'bytes32'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [
|
||||
{
|
||||
name: '_txHash',
|
||||
type: 'bytes32'
|
||||
}
|
||||
],
|
||||
name: 'relayedMessages',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'bool'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: false,
|
||||
inputs: [
|
||||
{
|
||||
name: '_sourceChainId',
|
||||
type: 'uint256'
|
||||
},
|
||||
{
|
||||
name: '_destinationChainId',
|
||||
type: 'uint256'
|
||||
},
|
||||
{
|
||||
name: '_validatorContract',
|
||||
type: 'address'
|
||||
},
|
||||
{
|
||||
name: '_maxGasPerTx',
|
||||
type: 'uint256'
|
||||
},
|
||||
{
|
||||
name: '_gasPrice',
|
||||
type: 'uint256'
|
||||
},
|
||||
{
|
||||
name: '_requiredBlockConfirmations',
|
||||
type: 'uint256'
|
||||
},
|
||||
{
|
||||
name: '_owner',
|
||||
type: 'address'
|
||||
}
|
||||
],
|
||||
name: 'initialize',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'bool'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'isInitialized',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'bool'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'requiredBlockConfirmations',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: false,
|
||||
inputs: [
|
||||
{
|
||||
name: '_data',
|
||||
type: 'bytes'
|
||||
},
|
||||
{
|
||||
name: '_signatures',
|
||||
type: 'bytes'
|
||||
}
|
||||
],
|
||||
name: 'executeSignatures',
|
||||
outputs: [],
|
||||
payable: false,
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [
|
||||
{
|
||||
name: '_data',
|
||||
type: 'bytes'
|
||||
}
|
||||
],
|
||||
name: 'getMinimumGasUsage',
|
||||
outputs: [
|
||||
{
|
||||
name: 'gas',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'pure',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [
|
||||
{
|
||||
name: '_messageId',
|
||||
type: 'bytes32'
|
||||
}
|
||||
],
|
||||
name: 'failedMessageReceiver',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'address'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'getBridgeMode',
|
||||
outputs: [
|
||||
{
|
||||
name: '_data',
|
||||
type: 'bytes4'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'pure',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: false,
|
||||
inputs: [
|
||||
{
|
||||
name: '_sourceChainId',
|
||||
type: 'uint256'
|
||||
},
|
||||
{
|
||||
name: '_destinationChainId',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
name: 'setChainIds',
|
||||
outputs: [],
|
||||
payable: false,
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [
|
||||
{
|
||||
name: '_messageId',
|
||||
type: 'bytes32'
|
||||
}
|
||||
],
|
||||
name: 'failedMessageSender',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'address'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'messageId',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'bytes32'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: false,
|
||||
inputs: [
|
||||
{
|
||||
name: '_token',
|
||||
type: 'address'
|
||||
},
|
||||
{
|
||||
name: '_to',
|
||||
type: 'address'
|
||||
}
|
||||
],
|
||||
name: 'claimTokens',
|
||||
outputs: [],
|
||||
payable: false,
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: false,
|
||||
inputs: [
|
||||
{
|
||||
name: '_maxGasPerTx',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
name: 'setMaxGasPerTx',
|
||||
outputs: [],
|
||||
payable: false,
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'requiredSignatures',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'owner',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'address'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'validatorContract',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'address'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'deployedAtBlock',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'getBridgeInterfacesVersion',
|
||||
outputs: [
|
||||
{
|
||||
name: 'major',
|
||||
type: 'uint64'
|
||||
},
|
||||
{
|
||||
name: 'minor',
|
||||
type: 'uint64'
|
||||
},
|
||||
{
|
||||
name: 'patch',
|
||||
type: 'uint64'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'pure',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'messageSourceChainId',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: false,
|
||||
inputs: [
|
||||
{
|
||||
name: '_blockConfirmations',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
name: 'setRequiredBlockConfirmations',
|
||||
outputs: [],
|
||||
payable: false,
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: false,
|
||||
inputs: [
|
||||
{
|
||||
name: '_gasPrice',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
name: 'setGasPrice',
|
||||
outputs: [],
|
||||
payable: false,
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [
|
||||
{
|
||||
name: '_messageId',
|
||||
type: 'bytes32'
|
||||
}
|
||||
],
|
||||
name: 'messageCallStatus',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'bool'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'messageSender',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'address'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: false,
|
||||
inputs: [
|
||||
{
|
||||
name: '_contract',
|
||||
type: 'address'
|
||||
},
|
||||
{
|
||||
name: '_data',
|
||||
type: 'bytes'
|
||||
},
|
||||
{
|
||||
name: '_gas',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
name: 'requireToPassMessage',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'bytes32'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [
|
||||
{
|
||||
name: '_messageId',
|
||||
type: 'bytes32'
|
||||
}
|
||||
],
|
||||
name: 'failedMessageDataHash',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'bytes32'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'maxGasPerTx',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: false,
|
||||
inputs: [
|
||||
{
|
||||
name: 'newOwner',
|
||||
type: 'address'
|
||||
}
|
||||
],
|
||||
name: 'transferOwnership',
|
||||
outputs: [],
|
||||
payable: false,
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'gasPrice',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
anonymous: false,
|
||||
inputs: [
|
||||
{
|
||||
indexed: true,
|
||||
name: 'messageId',
|
||||
type: 'bytes32'
|
||||
},
|
||||
{
|
||||
indexed: false,
|
||||
name: 'encodedData',
|
||||
type: 'bytes'
|
||||
}
|
||||
],
|
||||
name: 'UserRequestForAffirmation',
|
||||
type: 'event'
|
||||
},
|
||||
{
|
||||
anonymous: false,
|
||||
inputs: [
|
||||
{
|
||||
indexed: true,
|
||||
name: 'sender',
|
||||
type: 'address'
|
||||
},
|
||||
{
|
||||
indexed: true,
|
||||
name: 'executor',
|
||||
type: 'address'
|
||||
},
|
||||
{
|
||||
indexed: true,
|
||||
name: 'messageId',
|
||||
type: 'bytes32'
|
||||
},
|
||||
{
|
||||
indexed: false,
|
||||
name: 'status',
|
||||
type: 'bool'
|
||||
}
|
||||
],
|
||||
name: 'RelayedMessage',
|
||||
type: 'event'
|
||||
},
|
||||
{
|
||||
anonymous: false,
|
||||
inputs: [
|
||||
{
|
||||
indexed: false,
|
||||
name: 'gasPrice',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
name: 'GasPriceChanged',
|
||||
type: 'event'
|
||||
},
|
||||
{
|
||||
anonymous: false,
|
||||
inputs: [
|
||||
{
|
||||
indexed: false,
|
||||
name: 'requiredBlockConfirmations',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
name: 'RequiredBlockConfirmationChanged',
|
||||
type: 'event'
|
||||
},
|
||||
{
|
||||
anonymous: false,
|
||||
inputs: [
|
||||
{
|
||||
indexed: false,
|
||||
name: 'previousOwner',
|
||||
type: 'address'
|
||||
},
|
||||
{
|
||||
indexed: false,
|
||||
name: 'newOwner',
|
||||
type: 'address'
|
||||
}
|
||||
],
|
||||
name: 'OwnershipTransferred',
|
||||
type: 'event'
|
||||
}
|
||||
]
|
||||
|
||||
export default abi
|
||||
777
alm/src/abis/HomeAMB.ts
Normal file
777
alm/src/abis/HomeAMB.ts
Normal file
@@ -0,0 +1,777 @@
|
||||
import { AbiItem } from 'web3-utils'
|
||||
|
||||
const abi: AbiItem[] = [
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'transactionHash',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'bytes32'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [
|
||||
{
|
||||
name: '_message',
|
||||
type: 'bytes32'
|
||||
}
|
||||
],
|
||||
name: 'numMessagesSigned',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [
|
||||
{
|
||||
name: '_hash',
|
||||
type: 'bytes32'
|
||||
},
|
||||
{
|
||||
name: '_index',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
name: 'signature',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'bytes'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: false,
|
||||
inputs: [
|
||||
{
|
||||
name: '_sourceChainId',
|
||||
type: 'uint256'
|
||||
},
|
||||
{
|
||||
name: '_destinationChainId',
|
||||
type: 'uint256'
|
||||
},
|
||||
{
|
||||
name: '_validatorContract',
|
||||
type: 'address'
|
||||
},
|
||||
{
|
||||
name: '_maxGasPerTx',
|
||||
type: 'uint256'
|
||||
},
|
||||
{
|
||||
name: '_gasPrice',
|
||||
type: 'uint256'
|
||||
},
|
||||
{
|
||||
name: '_requiredBlockConfirmations',
|
||||
type: 'uint256'
|
||||
},
|
||||
{
|
||||
name: '_owner',
|
||||
type: 'address'
|
||||
}
|
||||
],
|
||||
name: 'initialize',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'bool'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'isInitialized',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'bool'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'requiredBlockConfirmations',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [
|
||||
{
|
||||
name: '_data',
|
||||
type: 'bytes'
|
||||
}
|
||||
],
|
||||
name: 'getMinimumGasUsage',
|
||||
outputs: [
|
||||
{
|
||||
name: 'gas',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'pure',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [
|
||||
{
|
||||
name: '_messageId',
|
||||
type: 'bytes32'
|
||||
}
|
||||
],
|
||||
name: 'failedMessageReceiver',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'address'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'getBridgeMode',
|
||||
outputs: [
|
||||
{
|
||||
name: '_data',
|
||||
type: 'bytes4'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'pure',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: false,
|
||||
inputs: [
|
||||
{
|
||||
name: '_sourceChainId',
|
||||
type: 'uint256'
|
||||
},
|
||||
{
|
||||
name: '_destinationChainId',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
name: 'setChainIds',
|
||||
outputs: [],
|
||||
payable: false,
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [
|
||||
{
|
||||
name: '_hash',
|
||||
type: 'bytes32'
|
||||
}
|
||||
],
|
||||
name: 'message',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'bytes'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [
|
||||
{
|
||||
name: '_messageId',
|
||||
type: 'bytes32'
|
||||
}
|
||||
],
|
||||
name: 'failedMessageSender',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'address'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: false,
|
||||
inputs: [
|
||||
{
|
||||
name: 'signature',
|
||||
type: 'bytes'
|
||||
},
|
||||
{
|
||||
name: 'message',
|
||||
type: 'bytes'
|
||||
}
|
||||
],
|
||||
name: 'submitSignature',
|
||||
outputs: [],
|
||||
payable: false,
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'messageId',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'bytes32'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: false,
|
||||
inputs: [
|
||||
{
|
||||
name: '_token',
|
||||
type: 'address'
|
||||
},
|
||||
{
|
||||
name: '_to',
|
||||
type: 'address'
|
||||
}
|
||||
],
|
||||
name: 'claimTokens',
|
||||
outputs: [],
|
||||
payable: false,
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [
|
||||
{
|
||||
name: '_hash',
|
||||
type: 'bytes32'
|
||||
}
|
||||
],
|
||||
name: 'numAffirmationsSigned',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [
|
||||
{
|
||||
name: '_hash',
|
||||
type: 'bytes32'
|
||||
}
|
||||
],
|
||||
name: 'affirmationsSigned',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'bool'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: false,
|
||||
inputs: [
|
||||
{
|
||||
name: '_maxGasPerTx',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
name: 'setMaxGasPerTx',
|
||||
outputs: [],
|
||||
payable: false,
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'requiredSignatures',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'owner',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'address'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [
|
||||
{
|
||||
name: '_message',
|
||||
type: 'bytes32'
|
||||
}
|
||||
],
|
||||
name: 'messagesSigned',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'bool'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'validatorContract',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'address'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'deployedAtBlock',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'getBridgeInterfacesVersion',
|
||||
outputs: [
|
||||
{
|
||||
name: 'major',
|
||||
type: 'uint64'
|
||||
},
|
||||
{
|
||||
name: 'minor',
|
||||
type: 'uint64'
|
||||
},
|
||||
{
|
||||
name: 'patch',
|
||||
type: 'uint64'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'pure',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'messageSourceChainId',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: false,
|
||||
inputs: [
|
||||
{
|
||||
name: '_blockConfirmations',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
name: 'setRequiredBlockConfirmations',
|
||||
outputs: [],
|
||||
payable: false,
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: false,
|
||||
inputs: [
|
||||
{
|
||||
name: '_gasPrice',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
name: 'setGasPrice',
|
||||
outputs: [],
|
||||
payable: false,
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [
|
||||
{
|
||||
name: '_messageId',
|
||||
type: 'bytes32'
|
||||
}
|
||||
],
|
||||
name: 'messageCallStatus',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'bool'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'messageSender',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'address'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: false,
|
||||
inputs: [
|
||||
{
|
||||
name: '_contract',
|
||||
type: 'address'
|
||||
},
|
||||
{
|
||||
name: '_data',
|
||||
type: 'bytes'
|
||||
},
|
||||
{
|
||||
name: '_gas',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
name: 'requireToPassMessage',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'bytes32'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [
|
||||
{
|
||||
name: '_messageId',
|
||||
type: 'bytes32'
|
||||
}
|
||||
],
|
||||
name: 'failedMessageDataHash',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'bytes32'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'maxGasPerTx',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: false,
|
||||
inputs: [
|
||||
{
|
||||
name: 'message',
|
||||
type: 'bytes'
|
||||
}
|
||||
],
|
||||
name: 'executeAffirmation',
|
||||
outputs: [],
|
||||
payable: false,
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: false,
|
||||
inputs: [
|
||||
{
|
||||
name: 'newOwner',
|
||||
type: 'address'
|
||||
}
|
||||
],
|
||||
name: 'transferOwnership',
|
||||
outputs: [],
|
||||
payable: false,
|
||||
stateMutability: 'nonpayable',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [],
|
||||
name: 'gasPrice',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'view',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
constant: true,
|
||||
inputs: [
|
||||
{
|
||||
name: '_number',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
name: 'isAlreadyProcessed',
|
||||
outputs: [
|
||||
{
|
||||
name: '',
|
||||
type: 'bool'
|
||||
}
|
||||
],
|
||||
payable: false,
|
||||
stateMutability: 'pure',
|
||||
type: 'function'
|
||||
},
|
||||
{
|
||||
anonymous: false,
|
||||
inputs: [
|
||||
{
|
||||
indexed: true,
|
||||
name: 'messageId',
|
||||
type: 'bytes32'
|
||||
},
|
||||
{
|
||||
indexed: false,
|
||||
name: 'encodedData',
|
||||
type: 'bytes'
|
||||
}
|
||||
],
|
||||
name: 'UserRequestForSignature',
|
||||
type: 'event'
|
||||
},
|
||||
{
|
||||
anonymous: false,
|
||||
inputs: [
|
||||
{
|
||||
indexed: true,
|
||||
name: 'sender',
|
||||
type: 'address'
|
||||
},
|
||||
{
|
||||
indexed: true,
|
||||
name: 'executor',
|
||||
type: 'address'
|
||||
},
|
||||
{
|
||||
indexed: true,
|
||||
name: 'messageId',
|
||||
type: 'bytes32'
|
||||
},
|
||||
{
|
||||
indexed: false,
|
||||
name: 'status',
|
||||
type: 'bool'
|
||||
}
|
||||
],
|
||||
name: 'AffirmationCompleted',
|
||||
type: 'event'
|
||||
},
|
||||
{
|
||||
anonymous: false,
|
||||
inputs: [
|
||||
{
|
||||
indexed: true,
|
||||
name: 'signer',
|
||||
type: 'address'
|
||||
},
|
||||
{
|
||||
indexed: false,
|
||||
name: 'messageHash',
|
||||
type: 'bytes32'
|
||||
}
|
||||
],
|
||||
name: 'SignedForUserRequest',
|
||||
type: 'event'
|
||||
},
|
||||
{
|
||||
anonymous: false,
|
||||
inputs: [
|
||||
{
|
||||
indexed: true,
|
||||
name: 'signer',
|
||||
type: 'address'
|
||||
},
|
||||
{
|
||||
indexed: false,
|
||||
name: 'messageHash',
|
||||
type: 'bytes32'
|
||||
}
|
||||
],
|
||||
name: 'SignedForAffirmation',
|
||||
type: 'event'
|
||||
},
|
||||
{
|
||||
anonymous: false,
|
||||
inputs: [
|
||||
{
|
||||
indexed: false,
|
||||
name: 'authorityResponsibleForRelay',
|
||||
type: 'address'
|
||||
},
|
||||
{
|
||||
indexed: false,
|
||||
name: 'messageHash',
|
||||
type: 'bytes32'
|
||||
},
|
||||
{
|
||||
indexed: false,
|
||||
name: 'NumberOfCollectedSignatures',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
name: 'CollectedSignatures',
|
||||
type: 'event'
|
||||
},
|
||||
{
|
||||
anonymous: false,
|
||||
inputs: [
|
||||
{
|
||||
indexed: false,
|
||||
name: 'gasPrice',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
name: 'GasPriceChanged',
|
||||
type: 'event'
|
||||
},
|
||||
{
|
||||
anonymous: false,
|
||||
inputs: [
|
||||
{
|
||||
indexed: false,
|
||||
name: 'requiredBlockConfirmations',
|
||||
type: 'uint256'
|
||||
}
|
||||
],
|
||||
name: 'RequiredBlockConfirmationChanged',
|
||||
type: 'event'
|
||||
},
|
||||
{
|
||||
anonymous: false,
|
||||
inputs: [
|
||||
{
|
||||
indexed: false,
|
||||
name: 'previousOwner',
|
||||
type: 'address'
|
||||
},
|
||||
{
|
||||
indexed: false,
|
||||
name: 'newOwner',
|
||||
type: 'address'
|
||||
}
|
||||
],
|
||||
name: 'OwnershipTransferred',
|
||||
type: 'event'
|
||||
}
|
||||
]
|
||||
|
||||
export default abi
|
||||
3
alm/src/abis/index.ts
Normal file
3
alm/src/abis/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export { default as HOME_AMB_ABI } from './HomeAMB'
|
||||
export { default as FOREIGN_AMB_ABI } from './ForeignAMB'
|
||||
export { default as BRIDGE_VALIDATORS_ABI } from './BridgeValidators'
|
||||
85
alm/src/components/ConfirmationsContainer.tsx
Normal file
85
alm/src/components/ConfirmationsContainer.tsx
Normal file
@@ -0,0 +1,85 @@
|
||||
import React from 'react'
|
||||
import { TransactionReceipt } from 'web3-eth'
|
||||
import { useMessageConfirmations } from '../hooks/useMessageConfirmations'
|
||||
import { MessageObject } from '../utils/web3'
|
||||
import styled from 'styled-components'
|
||||
import { CONFIRMATIONS_STATUS } from '../config/constants'
|
||||
import { CONFIRMATIONS_STATUS_LABEL } from '../config/descriptions'
|
||||
import { SimpleLoading } from './commons/Loading'
|
||||
import { ValidatorsConfirmations } from './ValidatorsConfirmations'
|
||||
import { getConfirmationsStatusDescription } from '../utils/networks'
|
||||
import { useStateProvider } from '../state/StateProvider'
|
||||
import { ExecutionConfirmation } from './ExecutionConfirmation'
|
||||
import { useValidatorContract } from '../hooks/useValidatorContract'
|
||||
import { useBlockConfirmations } from '../hooks/useBlockConfirmations'
|
||||
|
||||
const StatusLabel = styled.label`
|
||||
font-weight: bold;
|
||||
font-size: 18px;
|
||||
`
|
||||
|
||||
const StatusResultLabel = styled.label`
|
||||
font-size: 18px;
|
||||
padding-left: 10px;
|
||||
`
|
||||
|
||||
const StyledConfirmationContainer = styled.div`
|
||||
background-color: var(--bg-color);
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
`
|
||||
|
||||
const StatusDescription = styled.div`
|
||||
padding-top: 10px;
|
||||
`
|
||||
|
||||
export interface ConfirmationsContainerParams {
|
||||
message: MessageObject
|
||||
receipt: Maybe<TransactionReceipt>
|
||||
fromHome: boolean
|
||||
timestamp: number
|
||||
}
|
||||
|
||||
export const ConfirmationsContainer = ({ message, receipt, fromHome, timestamp }: ConfirmationsContainerParams) => {
|
||||
const {
|
||||
home: { name: homeName },
|
||||
foreign: { name: foreignName }
|
||||
} = useStateProvider()
|
||||
const { requiredSignatures, validatorList } = useValidatorContract({ fromHome, receipt })
|
||||
const { blockConfirmations } = useBlockConfirmations({ fromHome, receipt })
|
||||
const { confirmations, status, executionData, signatureCollected } = useMessageConfirmations({
|
||||
message,
|
||||
receipt,
|
||||
fromHome,
|
||||
timestamp,
|
||||
requiredSignatures,
|
||||
validatorList,
|
||||
blockConfirmations
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="row is-center">
|
||||
<StyledConfirmationContainer className="col-9">
|
||||
<div className="row is-center">
|
||||
<StatusLabel>Status:</StatusLabel>
|
||||
<StatusResultLabel>
|
||||
{status !== CONFIRMATIONS_STATUS.UNDEFINED ? CONFIRMATIONS_STATUS_LABEL[status] : <SimpleLoading />}
|
||||
</StatusResultLabel>
|
||||
</div>
|
||||
<StatusDescription className="row is-center">
|
||||
<p className="col-10">
|
||||
{status !== CONFIRMATIONS_STATUS.UNDEFINED
|
||||
? getConfirmationsStatusDescription(status, homeName, foreignName)
|
||||
: ''}
|
||||
</p>
|
||||
</StatusDescription>
|
||||
<ValidatorsConfirmations
|
||||
confirmations={confirmations}
|
||||
requiredSignatures={requiredSignatures}
|
||||
validatorList={validatorList}
|
||||
/>
|
||||
{signatureCollected && <ExecutionConfirmation executionData={executionData} isHome={!fromHome} />}
|
||||
</StyledConfirmationContainer>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
69
alm/src/components/ExecutionConfirmation.tsx
Normal file
69
alm/src/components/ExecutionConfirmation.tsx
Normal file
@@ -0,0 +1,69 @@
|
||||
import React from 'react'
|
||||
import { formatTimestamp, formatTxHash, getExplorerTxUrl } from '../utils/networks'
|
||||
import { useWindowWidth } from '@react-hook/window-size'
|
||||
import { VALIDATOR_CONFIRMATION_STATUS } 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'
|
||||
|
||||
const Thead = styled.thead`
|
||||
border-bottom: 2px solid #9e9e9e;
|
||||
`
|
||||
|
||||
const StyledExecutionConfirmation = styled.div`
|
||||
margin-top: 30px;
|
||||
`
|
||||
|
||||
export interface ExecutionConfirmationParams {
|
||||
executionData: ExecutionData
|
||||
isHome: boolean
|
||||
}
|
||||
|
||||
export const ExecutionConfirmation = ({ executionData, isHome }: ExecutionConfirmationParams) => {
|
||||
const windowWidth = useWindowWidth()
|
||||
|
||||
const txExplorerLink = getExplorerTxUrl(executionData.txHash, isHome)
|
||||
const formattedValidator =
|
||||
windowWidth < 850 && executionData.validator ? formatTxHash(executionData.validator) : executionData.validator
|
||||
|
||||
const getExecutionStatusElement = (validatorStatus = '') => {
|
||||
switch (validatorStatus) {
|
||||
case VALIDATOR_CONFIRMATION_STATUS.SUCCESS:
|
||||
return <SuccessLabel>{validatorStatus}</SuccessLabel>
|
||||
case VALIDATOR_CONFIRMATION_STATUS.FAILED:
|
||||
return <RedLabel>{validatorStatus}</RedLabel>
|
||||
case VALIDATOR_CONFIRMATION_STATUS.PENDING:
|
||||
case VALIDATOR_CONFIRMATION_STATUS.WAITING:
|
||||
return <GreyLabel>{validatorStatus}</GreyLabel>
|
||||
default:
|
||||
return <SimpleLoading />
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledExecutionConfirmation>
|
||||
<table>
|
||||
<Thead>
|
||||
<tr>
|
||||
<th>Executed by</th>
|
||||
<th className="text-center">Status</th>
|
||||
<th className="text-center">Age</th>
|
||||
</tr>
|
||||
</Thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{formattedValidator ? formattedValidator : <SimpleLoading />}</td>
|
||||
<td className="text-center">{getExecutionStatusElement(executionData.status)}</td>
|
||||
<td className="text-center">
|
||||
<ExplorerTxLink href={txExplorerLink} target="_blank">
|
||||
{executionData.timestamp > 0 ? formatTimestamp(executionData.timestamp) : ''}
|
||||
</ExplorerTxLink>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</StyledExecutionConfirmation>
|
||||
)
|
||||
}
|
||||
64
alm/src/components/Form.tsx
Normal file
64
alm/src/components/Form.tsx
Normal file
@@ -0,0 +1,64 @@
|
||||
import React, { useState, FormEvent } from 'react'
|
||||
import styled from 'styled-components'
|
||||
import { FormSubmitParams } from './MainPage'
|
||||
import { Button } from './commons/Button'
|
||||
import { TransactionSelector } from './TransactionSelector'
|
||||
import { TransactionReceipt } from 'web3-eth'
|
||||
|
||||
const LabelText = styled.label`
|
||||
line-height: 36px;
|
||||
max-width: 140px;
|
||||
`
|
||||
|
||||
const Input = styled.input`
|
||||
background-color: var(--bg-color);
|
||||
color: var(--font-color);
|
||||
max-width: 100%;
|
||||
border-color: var(--color-primary) !important;
|
||||
&:hover,
|
||||
&:active,
|
||||
&:focus {
|
||||
border-color: var(--button-color) !important;
|
||||
}
|
||||
`
|
||||
|
||||
export const Form = ({ onSubmit }: { onSubmit: ({ chainId, txHash, receipt }: FormSubmitParams) => void }) => {
|
||||
const [txHash, setTxHash] = useState('')
|
||||
const [searchTx, setSearchTx] = useState(false)
|
||||
|
||||
const formSubmit = (e: FormEvent) => {
|
||||
e.preventDefault()
|
||||
setSearchTx(true)
|
||||
}
|
||||
|
||||
const onSelected = (chainId: number, receipt: TransactionReceipt) => {
|
||||
onSubmit({ chainId, txHash, receipt })
|
||||
}
|
||||
|
||||
if (searchTx) {
|
||||
return <TransactionSelector txHash={txHash} onSelected={onSelected} />
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={formSubmit}>
|
||||
<div className="row is-center">
|
||||
<LabelText className="col-2">Bridgeable tx hash:</LabelText>
|
||||
<div className="col-7">
|
||||
<Input
|
||||
placeholder="Enter transaction hash"
|
||||
type="text"
|
||||
onChange={e => setTxHash(e.target.value)}
|
||||
required
|
||||
pattern="^0x[a-fA-F0-9]{64}$"
|
||||
value={txHash}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-1">
|
||||
<Button className="button outline" type="submit">
|
||||
Check
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
90
alm/src/components/MainPage.tsx
Normal file
90
alm/src/components/MainPage.tsx
Normal file
@@ -0,0 +1,90 @@
|
||||
import React, { useState } from 'react'
|
||||
import styled from 'styled-components'
|
||||
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;
|
||||
min-height: 100vh;
|
||||
`
|
||||
|
||||
const Header = styled.header`
|
||||
background-color: #001529;
|
||||
color: #ffffff;
|
||||
margin-bottom: 50px;
|
||||
`
|
||||
|
||||
const HeaderContainer = styled.header`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-size: 16px;
|
||||
height: 64px;
|
||||
line-height: 64px;
|
||||
padding: 0 50px;
|
||||
|
||||
@media (max-width: 600px) {
|
||||
padding: 0 20px;
|
||||
}
|
||||
`
|
||||
|
||||
export interface FormSubmitParams {
|
||||
chainId: number
|
||||
txHash: string
|
||||
receipt: TransactionReceipt
|
||||
}
|
||||
|
||||
export const MainPage = () => {
|
||||
const history = useHistory()
|
||||
const { home, foreign } = useStateProvider()
|
||||
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)
|
||||
}
|
||||
|
||||
const onFormSubmit = ({ chainId, txHash, receipt }: FormSubmitParams) => {
|
||||
setNetworkData(chainId)
|
||||
setReceipt(receipt)
|
||||
history.push(`/${chainId}/${txHash}`)
|
||||
}
|
||||
|
||||
const resetNetworkHeader = () => {
|
||||
setNetworkName('')
|
||||
}
|
||||
|
||||
const setNetworkFromParams = (chainId: number) => {
|
||||
setNetworkData(chainId)
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledMainPage>
|
||||
<Header>
|
||||
<HeaderContainer>
|
||||
<span>AMB Live Monitoring</span>
|
||||
<span>{networkName}</span>
|
||||
</HeaderContainer>
|
||||
</Header>
|
||||
<div className="container">
|
||||
<Route exact path={['/']} children={<Form onSubmit={onFormSubmit} />} />
|
||||
<Route
|
||||
path={['/:chainId/:txHash/:messageIdParam', '/:chainId/:txHash']}
|
||||
children={
|
||||
<StatusContainer
|
||||
onBackToMain={resetNetworkHeader}
|
||||
setNetworkFromParams={setNetworkFromParams}
|
||||
receiptParam={receipt}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</StyledMainPage>
|
||||
)
|
||||
}
|
||||
47
alm/src/components/MessageSelector.tsx
Normal file
47
alm/src/components/MessageSelector.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import React, { useState } from 'react'
|
||||
import { Button } from './commons/Button'
|
||||
import { RadioButtonLabel, RadioButtonContainer } from './commons/RadioButton'
|
||||
import { useWindowWidth } from '@react-hook/window-size'
|
||||
import { formatTxHashExtended } from '../utils/networks'
|
||||
import { MessageObject } from '../utils/web3'
|
||||
|
||||
export interface MessageSelectorParams {
|
||||
messages: Array<MessageObject>
|
||||
onMessageSelected: (index: number) => void
|
||||
}
|
||||
|
||||
export const MessageSelector = ({ messages, onMessageSelected }: MessageSelectorParams) => {
|
||||
const [messageIndex, setMessageIndex] = useState(0)
|
||||
const windowWidth = useWindowWidth()
|
||||
|
||||
const onSelect = () => {
|
||||
onMessageSelected(messageIndex)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="row is-center">
|
||||
<div className="col-7-lg col-12 is-marginless">
|
||||
{messages.map((message, i) => (
|
||||
<RadioButtonContainer className="row is-center is-vertical-align" key={i} onClick={() => setMessageIndex(i)}>
|
||||
<input
|
||||
className="is-marginless"
|
||||
type="radio"
|
||||
name="message"
|
||||
value={i}
|
||||
checked={i === messageIndex}
|
||||
onChange={() => setMessageIndex(i)}
|
||||
/>
|
||||
<RadioButtonLabel htmlFor={i.toString()}>
|
||||
{windowWidth < 700 ? formatTxHashExtended(message.id) : message.id}
|
||||
</RadioButtonLabel>
|
||||
</RadioButtonContainer>
|
||||
))}
|
||||
</div>
|
||||
<div className="col-1-lg col-12 is-marginless">
|
||||
<Button className="button outline" onClick={onSelect}>
|
||||
Select
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
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>
|
||||
)
|
||||
}
|
||||
122
alm/src/components/StatusContainer.tsx
Normal file
122
alm/src/components/StatusContainer.tsx
Normal file
@@ -0,0 +1,122 @@
|
||||
import React, { useEffect } from 'react'
|
||||
import { Link, useHistory, useParams } from 'react-router-dom'
|
||||
import { useTransactionStatus } from '../hooks/useTransactionStatus'
|
||||
import { formatTxHash, getExplorerTxUrl, getTransactionStatusDescription, validTxHash } from '../utils/networks'
|
||||
import { TRANSACTION_STATUS } from '../config/constants'
|
||||
import { MessageSelector } from './MessageSelector'
|
||||
import { Loading } from './commons/Loading'
|
||||
import { useStateProvider } from '../state/StateProvider'
|
||||
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);
|
||||
border-color: var(--font-color);
|
||||
margin-top: 10px;
|
||||
&:focus {
|
||||
outline: var(--button-color);
|
||||
}
|
||||
`
|
||||
|
||||
const BackLabel = styled.label`
|
||||
margin-left: 5px;
|
||||
cursor: pointer;
|
||||
`
|
||||
|
||||
export interface StatusContainerParam {
|
||||
onBackToMain: () => void
|
||||
setNetworkFromParams: (chainId: number) => void
|
||||
receiptParam: Maybe<TransactionReceipt>
|
||||
}
|
||||
|
||||
export const StatusContainer = ({ onBackToMain, setNetworkFromParams, receiptParam }: StatusContainerParam) => {
|
||||
const { home, foreign } = useStateProvider()
|
||||
const history = useHistory()
|
||||
const { chainId, txHash, messageIdParam } = useParams()
|
||||
const validChainId = chainId === home.chainId.toString() || chainId === foreign.chainId.toString()
|
||||
const validParameters = validChainId && validTxHash(txHash)
|
||||
|
||||
const { messages, receipt, status, description, timestamp, loading } = useTransactionStatus({
|
||||
txHash: validParameters ? txHash : '',
|
||||
chainId: validParameters ? parseInt(chainId) : 0,
|
||||
receiptParam
|
||||
})
|
||||
|
||||
const selectedMessageId = messageIdParam === undefined || messages[messageIdParam] === undefined ? -1 : messageIdParam
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
if (validChainId) {
|
||||
setNetworkFromParams(parseInt(chainId))
|
||||
}
|
||||
},
|
||||
[validChainId, chainId, setNetworkFromParams]
|
||||
)
|
||||
|
||||
if (!validParameters && home.chainId && foreign.chainId) {
|
||||
return (
|
||||
<div>
|
||||
<p>
|
||||
Chain Id: {chainId} and/or Transaction Hash: {txHash} are not valid
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
return <Loading />
|
||||
}
|
||||
|
||||
const onMessageSelected = (messageId: number) => {
|
||||
history.push(`/${chainId}/${txHash}/${messageId}`)
|
||||
}
|
||||
|
||||
const displayMessageSelector = status === TRANSACTION_STATUS.SUCCESS_MULTIPLE_MESSAGES && selectedMessageId === -1
|
||||
const multiMessageSelected = status === TRANSACTION_STATUS.SUCCESS_MULTIPLE_MESSAGES && selectedMessageId !== -1
|
||||
const displayReference = multiMessageSelected ? messages[selectedMessageId].id : txHash
|
||||
const formattedMessageId = formatTxHash(displayReference)
|
||||
|
||||
const displayedDescription = multiMessageSelected
|
||||
? getTransactionStatusDescription(TRANSACTION_STATUS.SUCCESS_ONE_MESSAGE, timestamp)
|
||||
: description
|
||||
|
||||
const isHome = chainId === home.chainId.toString()
|
||||
const txExplorerLink = getExplorerTxUrl(txHash, isHome)
|
||||
const displayExplorerLink = status !== TRANSACTION_STATUS.NOT_FOUND
|
||||
|
||||
const displayConfirmations = status === TRANSACTION_STATUS.SUCCESS_ONE_MESSAGE || multiMessageSelected
|
||||
const messageToConfirm =
|
||||
messages.length > 1 ? messages[selectedMessageId] : messages.length > 0 ? messages[0] : { id: '', data: '' }
|
||||
return (
|
||||
<div>
|
||||
{status && (
|
||||
<p>
|
||||
The request{' '}
|
||||
{displayExplorerLink && (
|
||||
<ExplorerTxLink href={txExplorerLink} target="blank">
|
||||
{formattedMessageId}
|
||||
</ExplorerTxLink>
|
||||
)}
|
||||
{!displayExplorerLink && <label>{formattedMessageId}</label>} {displayedDescription}
|
||||
</p>
|
||||
)}
|
||||
{displayMessageSelector && <MessageSelector messages={messages} onMessageSelected={onMessageSelected} />}
|
||||
{displayConfirmations && (
|
||||
<ConfirmationsContainer message={messageToConfirm} receipt={receipt} fromHome={isHome} timestamp={timestamp} />
|
||||
)}
|
||||
<div className="row is-center">
|
||||
<div className="col-9">
|
||||
<Link to="/" onClick={onBackToMain}>
|
||||
<BackButton className="button outline is-left">
|
||||
<LeftArrow />
|
||||
<BackLabel>Search another transaction</BackLabel>
|
||||
</BackButton>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
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 />
|
||||
}
|
||||
91
alm/src/components/ValidatorsConfirmations.tsx
Normal file
91
alm/src/components/ValidatorsConfirmations.tsx
Normal file
@@ -0,0 +1,91 @@
|
||||
import React from 'react'
|
||||
import { formatTimestamp, formatTxHash, getExplorerTxUrl } from '../utils/networks'
|
||||
import { useWindowWidth } from '@react-hook/window-size'
|
||||
import { VALIDATOR_CONFIRMATION_STATUS } from '../config/constants'
|
||||
import { SimpleLoading } from './commons/Loading'
|
||||
import styled from 'styled-components'
|
||||
import { ConfirmationParam } from '../hooks/useMessageConfirmations'
|
||||
import { GreyLabel, RedLabel, SuccessLabel } from './commons/Labels'
|
||||
import { ExplorerTxLink } from './commons/ExplorerTxLink'
|
||||
|
||||
const Thead = styled.thead`
|
||||
border-bottom: 2px solid #9e9e9e;
|
||||
`
|
||||
|
||||
const RequiredConfirmations = styled.label`
|
||||
font-size: 14px;
|
||||
`
|
||||
|
||||
export interface ValidatorsConfirmationsParams {
|
||||
confirmations: Array<ConfirmationParam>
|
||||
requiredSignatures: number
|
||||
validatorList: string[]
|
||||
}
|
||||
|
||||
export const ValidatorsConfirmations = ({
|
||||
confirmations,
|
||||
requiredSignatures,
|
||||
validatorList
|
||||
}: ValidatorsConfirmationsParams) => {
|
||||
const windowWidth = useWindowWidth()
|
||||
|
||||
const getValidatorStatusElement = (validatorStatus = '') => {
|
||||
switch (validatorStatus) {
|
||||
case VALIDATOR_CONFIRMATION_STATUS.SUCCESS:
|
||||
return <SuccessLabel>{validatorStatus}</SuccessLabel>
|
||||
case VALIDATOR_CONFIRMATION_STATUS.FAILED:
|
||||
return <RedLabel>{validatorStatus}</RedLabel>
|
||||
case VALIDATOR_CONFIRMATION_STATUS.PENDING:
|
||||
case VALIDATOR_CONFIRMATION_STATUS.WAITING:
|
||||
case VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED:
|
||||
return <GreyLabel>{validatorStatus}</GreyLabel>
|
||||
default:
|
||||
return <SimpleLoading />
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<table>
|
||||
<Thead>
|
||||
<tr>
|
||||
<th>Validator</th>
|
||||
<th className="text-center">Status</th>
|
||||
<th className="text-center">Age</th>
|
||||
</tr>
|
||||
</Thead>
|
||||
<tbody>
|
||||
{validatorList.map((validator, i) => {
|
||||
const filteredConfirmation = confirmations.filter(c => c.validator === validator)
|
||||
const confirmation = filteredConfirmation.length > 0 ? filteredConfirmation[0] : null
|
||||
const displayedStatus = confirmation && confirmation.status ? confirmation.status : ''
|
||||
const explorerLink = confirmation && confirmation.txHash ? getExplorerTxUrl(confirmation.txHash, true) : ''
|
||||
const elementIfNoTimestamp =
|
||||
displayedStatus !== VALIDATOR_CONFIRMATION_STATUS.WAITING &&
|
||||
displayedStatus !== VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED ? (
|
||||
<SimpleLoading />
|
||||
) : (
|
||||
''
|
||||
)
|
||||
return (
|
||||
<tr key={i}>
|
||||
<td>{windowWidth < 850 ? formatTxHash(validator) : validator}</td>
|
||||
<td className="text-center">{getValidatorStatusElement(displayedStatus)}</td>
|
||||
<td className="text-center">
|
||||
<ExplorerTxLink href={explorerLink} target="_blank">
|
||||
{confirmation && confirmation.timestamp > 0
|
||||
? formatTimestamp(confirmation.timestamp)
|
||||
: elementIfNoTimestamp}
|
||||
</ExplorerTxLink>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
<RequiredConfirmations>
|
||||
{requiredSignatures} of {validatorList.length} confirmations required
|
||||
</RequiredConfirmations>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
10
alm/src/components/commons/Button.tsx
Normal file
10
alm/src/components/commons/Button.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import styled from 'styled-components'
|
||||
|
||||
export const Button = styled.button`
|
||||
height: 36px;
|
||||
color: var(--button-color);
|
||||
border-color: var(--button-color);
|
||||
&:focus {
|
||||
outline: var(--button-color);
|
||||
}
|
||||
`
|
||||
7
alm/src/components/commons/ExplorerTxLink.tsx
Normal file
7
alm/src/components/commons/ExplorerTxLink.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
import styled from 'styled-components'
|
||||
|
||||
export const ExplorerTxLink = styled.a`
|
||||
color: var(--link-color);
|
||||
text-decoration: underline;
|
||||
font-weight: bold;
|
||||
`
|
||||
22
alm/src/components/commons/Labels.tsx
Normal file
22
alm/src/components/commons/Labels.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import styled from 'styled-components'
|
||||
|
||||
export const SuccessLabel = styled.label`
|
||||
color: var(--success-color);
|
||||
background-color: var(--success-bg-color);
|
||||
padding: 0.4rem 0.7rem;
|
||||
border-radius: 4px;
|
||||
`
|
||||
|
||||
export const GreyLabel = styled.label`
|
||||
color: var(--not-required-color);
|
||||
background-color: var(--not-required-bg-color);
|
||||
padding: 0.4rem 0.7rem;
|
||||
border-radius: 4px;
|
||||
`
|
||||
|
||||
export const RedLabel = styled.label`
|
||||
color: var(--failed-color);
|
||||
background-color: var(--failed-bg-color);
|
||||
padding: 0.4rem 0.7rem;
|
||||
border-radius: 4px;
|
||||
`
|
||||
20
alm/src/components/commons/LeftArrow.tsx
Normal file
20
alm/src/components/commons/LeftArrow.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import React from 'react'
|
||||
import { useContext } from 'react'
|
||||
import { ThemeContext } from 'styled-components'
|
||||
|
||||
export const LeftArrow = () => {
|
||||
const themeContext = useContext(ThemeContext)
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
version="1.1"
|
||||
id="mdi-arrow-left"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill={themeContext.buttonColor}
|
||||
>
|
||||
<path d="M20,11V13H8L13.5,18.5L12.08,19.92L4.16,12L12.08,4.08L13.5,5.5L8,11H20Z" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
165
alm/src/components/commons/Loading.tsx
Normal file
165
alm/src/components/commons/Loading.tsx
Normal file
@@ -0,0 +1,165 @@
|
||||
import React, { useContext } from 'react'
|
||||
import { ThemeContext } from 'styled-components'
|
||||
|
||||
export interface LoadingParams {
|
||||
width?: string
|
||||
height?: string
|
||||
displayMessage?: boolean
|
||||
}
|
||||
|
||||
export const Loading = ({ width = '50px', height = '50px', displayMessage = true }: LoadingParams) => {
|
||||
const themeContext = useContext(ThemeContext)
|
||||
return (
|
||||
<div className="row is-center">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
style={{ background: 'none', display: 'block', shapeRendering: 'auto' }}
|
||||
width={width}
|
||||
height={height}
|
||||
viewBox="0 0 100 100"
|
||||
preserveAspectRatio="xMidYMid"
|
||||
>
|
||||
<g transform="rotate(0 50 50)">
|
||||
<rect x="47" y="24" rx="3" ry="6" width="6" height="12" fill={themeContext.buttonColor}>
|
||||
<animate
|
||||
attributeName="opacity"
|
||||
values="1;0"
|
||||
keyTimes="0;1"
|
||||
dur="1s"
|
||||
begin="-0.9166666666666666s"
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
</rect>
|
||||
</g>
|
||||
<g transform="rotate(30 50 50)">
|
||||
<rect x="47" y="24" rx="3" ry="6" width="6" height="12" fill={themeContext.buttonColor}>
|
||||
<animate
|
||||
attributeName="opacity"
|
||||
values="1;0"
|
||||
keyTimes="0;1"
|
||||
dur="1s"
|
||||
begin="-0.8333333333333334s"
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
</rect>
|
||||
</g>
|
||||
<g transform="rotate(60 50 50)">
|
||||
<rect x="47" y="24" rx="3" ry="6" width="6" height="12" fill={themeContext.buttonColor}>
|
||||
<animate
|
||||
attributeName="opacity"
|
||||
values="1;0"
|
||||
keyTimes="0;1"
|
||||
dur="1s"
|
||||
begin="-0.75s"
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
</rect>
|
||||
</g>
|
||||
<g transform="rotate(90 50 50)">
|
||||
<rect x="47" y="24" rx="3" ry="6" width="6" height="12" fill={themeContext.buttonColor}>
|
||||
<animate
|
||||
attributeName="opacity"
|
||||
values="1;0"
|
||||
keyTimes="0;1"
|
||||
dur="1s"
|
||||
begin="-0.6666666666666666s"
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
</rect>
|
||||
</g>
|
||||
<g transform="rotate(120 50 50)">
|
||||
<rect x="47" y="24" rx="3" ry="6" width="6" height="12" fill={themeContext.buttonColor}>
|
||||
<animate
|
||||
attributeName="opacity"
|
||||
values="1;0"
|
||||
keyTimes="0;1"
|
||||
dur="1s"
|
||||
begin="-0.5833333333333334s"
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
</rect>
|
||||
</g>
|
||||
<g transform="rotate(150 50 50)">
|
||||
<rect x="47" y="24" rx="3" ry="6" width="6" height="12" fill={themeContext.buttonColor}>
|
||||
<animate
|
||||
attributeName="opacity"
|
||||
values="1;0"
|
||||
keyTimes="0;1"
|
||||
dur="1s"
|
||||
begin="-0.5s"
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
</rect>
|
||||
</g>
|
||||
<g transform="rotate(180 50 50)">
|
||||
<rect x="47" y="24" rx="3" ry="6" width="6" height="12" fill={themeContext.buttonColor}>
|
||||
<animate
|
||||
attributeName="opacity"
|
||||
values="1;0"
|
||||
keyTimes="0;1"
|
||||
dur="1s"
|
||||
begin="-0.4166666666666667s"
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
</rect>
|
||||
</g>
|
||||
<g transform="rotate(210 50 50)">
|
||||
<rect x="47" y="24" rx="3" ry="6" width="6" height="12" fill={themeContext.buttonColor}>
|
||||
<animate
|
||||
attributeName="opacity"
|
||||
values="1;0"
|
||||
keyTimes="0;1"
|
||||
dur="1s"
|
||||
begin="-0.3333333333333333s"
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
</rect>
|
||||
</g>
|
||||
<g transform="rotate(240 50 50)">
|
||||
<rect x="47" y="24" rx="3" ry="6" width="6" height="12" fill={themeContext.buttonColor}>
|
||||
<animate
|
||||
attributeName="opacity"
|
||||
values="1;0"
|
||||
keyTimes="0;1"
|
||||
dur="1s"
|
||||
begin="-0.25s"
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
</rect>
|
||||
</g>
|
||||
<g transform="rotate(270 50 50)">
|
||||
<rect x="47" y="24" rx="3" ry="6" width="6" height="12" fill={themeContext.buttonColor}>
|
||||
<animate
|
||||
attributeName="opacity"
|
||||
values="1;0"
|
||||
keyTimes="0;1"
|
||||
dur="1s"
|
||||
begin="-0.16666666666666666s"
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
</rect>
|
||||
</g>
|
||||
<g transform="rotate(300 50 50)">
|
||||
<rect x="47" y="24" rx="3" ry="6" width="6" height="12" fill={themeContext.buttonColor}>
|
||||
<animate
|
||||
attributeName="opacity"
|
||||
values="1;0"
|
||||
keyTimes="0;1"
|
||||
dur="1s"
|
||||
begin="-0.08333333333333333s"
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
</rect>
|
||||
</g>
|
||||
<g transform="rotate(330 50 50)">
|
||||
<rect x="47" y="24" rx="3" ry="6" width="6" height="12" fill={themeContext.buttonColor}>
|
||||
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="0s" repeatCount="indefinite" />
|
||||
</rect>
|
||||
</g>
|
||||
</svg>
|
||||
{displayMessage && <label style={{ color: themeContext.buttonColor }}>Loading...</label>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const SimpleLoading = () => <Loading width="30px" height="30px" displayMessage={false} />
|
||||
9
alm/src/components/commons/RadioButton.tsx
Normal file
9
alm/src/components/commons/RadioButton.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import styled from 'styled-components'
|
||||
|
||||
export const RadioButtonLabel = styled.label`
|
||||
padding-left: 5px;
|
||||
`
|
||||
|
||||
export const RadioButtonContainer = styled.div`
|
||||
padding: 10px;
|
||||
`
|
||||
59
alm/src/config/constants.ts
Normal file
59
alm/src/config/constants.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
export const HOME_BRIDGE_ADDRESS: string = process.env.REACT_APP_COMMON_HOME_BRIDGE_ADDRESS || ''
|
||||
export const FOREIGN_BRIDGE_ADDRESS: string = process.env.REACT_APP_COMMON_FOREIGN_BRIDGE_ADDRESS || ''
|
||||
|
||||
export const HOME_RPC_URL: string = process.env.REACT_APP_COMMON_HOME_RPC_URL || ''
|
||||
export const FOREIGN_RPC_URL: string = process.env.REACT_APP_COMMON_FOREIGN_RPC_URL || ''
|
||||
|
||||
export const HOME_NETWORK_NAME: string = process.env.REACT_APP_ALM_HOME_NETWORK_NAME || ''
|
||||
export const FOREIGN_NETWORK_NAME: string = process.env.REACT_APP_ALM_FOREIGN_NETWORK_NAME || ''
|
||||
|
||||
export const HOME_EXPLORER_TX_TEMPLATE: string = process.env.REACT_APP_ALM_HOME_EXPLORER_TX_TEMPLATE || ''
|
||||
export const FOREIGN_EXPLORER_TX_TEMPLATE: string = process.env.REACT_APP_ALM_FOREIGN_EXPLORER_TX_TEMPLATE || ''
|
||||
|
||||
export const HOME_EXPLORER_API: string = process.env.REACT_APP_ALM_HOME_EXPLORER_API || ''
|
||||
export const FOREIGN_EXPLORER_API: string = process.env.REACT_APP_ALM_FOREIGN_EXPLORER_API || ''
|
||||
|
||||
export const HOME_RPC_POLLING_INTERVAL: number = 5000
|
||||
export const FOREIGN_RPC_POLLING_INTERVAL: number = 5000
|
||||
export const BLOCK_RANGE: number = 50
|
||||
export const ONE_DAY_TIMESTAMP: number = 86400
|
||||
export const THREE_DAYS_TIMESTAMP: number = 259200
|
||||
|
||||
export const EXECUTE_AFFIRMATION_HASH = 'e7a2c01f'
|
||||
export const SUBMIT_SIGNATURE_HASH = '630cea8e'
|
||||
export const EXECUTE_SIGNATURES_HASH = '3f7658fd'
|
||||
|
||||
export const CACHE_KEY_SUCCESS = 'success-confirmation-validator-'
|
||||
export const CACHE_KEY_FAILED = 'failed-confirmation-validator-'
|
||||
export const CACHE_KEY_EXECUTION_FAILED = 'failed-execution-validator-'
|
||||
|
||||
export const TRANSACTION_STATUS = {
|
||||
SUCCESS_MULTIPLE_MESSAGES: 'SUCCESS_MULTIPLE_MESSAGES',
|
||||
SUCCESS_ONE_MESSAGE: 'SUCCESS_ONE_MESSAGE',
|
||||
SUCCESS_NO_MESSAGES: 'SUCCESS_NO_MESSAGES',
|
||||
FAILED: 'FAILED',
|
||||
FOUND: 'FOUND',
|
||||
NOT_FOUND: 'NOT_FOUND',
|
||||
UNDEFINED: 'UNDEFINED'
|
||||
}
|
||||
|
||||
export const CONFIRMATIONS_STATUS = {
|
||||
SUCCESS: 'SUCCESS',
|
||||
SUCCESS_MESSAGE_FAILED: 'SUCCESS_MESSAGE_FAILED',
|
||||
EXECUTION_FAILED: 'EXECUTION_FAILED',
|
||||
EXECUTION_PENDING: 'EXECUTION_PENDING',
|
||||
EXECUTION_WAITING: 'EXECUTION_WAITING',
|
||||
FAILED: 'FAILED',
|
||||
PENDING: 'PENDING',
|
||||
WAITING: 'WAITING',
|
||||
UNDEFINED: 'UNDEFINED'
|
||||
}
|
||||
|
||||
export const VALIDATOR_CONFIRMATION_STATUS = {
|
||||
SUCCESS: 'Success',
|
||||
FAILED: 'Failed',
|
||||
PENDING: 'Pending',
|
||||
WAITING: 'Waiting',
|
||||
NOT_REQUIRED: 'Not required',
|
||||
UNDEFINED: 'UNDEFINED'
|
||||
}
|
||||
36
alm/src/config/descriptions.ts
Normal file
36
alm/src/config/descriptions.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
// %t will be replaced by the time -> x minutes/hours/days ago
|
||||
export const TRANSACTION_STATUS_DESCRIPTION: { [key: string]: string } = {
|
||||
SUCCESS_MULTIPLE_MESSAGES: 'was initiated %t and contains several bridge messages. Specify one of them:',
|
||||
SUCCESS_ONE_MESSAGE: 'was initiated %t',
|
||||
SUCCESS_NO_MESSAGES: 'execution succeeded %t but it does not contain any bridge messages',
|
||||
FAILED: 'failed %t',
|
||||
NOT_FOUND: 'was not found'
|
||||
}
|
||||
|
||||
export const CONFIRMATIONS_STATUS_LABEL: { [key: string]: string } = {
|
||||
SUCCESS: 'Success',
|
||||
SUCCESS_MESSAGE_FAILED: 'Success',
|
||||
EXECUTION_FAILED: 'Execution failed',
|
||||
EXECUTION_PENDING: 'Execution pending',
|
||||
EXECUTION_WAITING: 'Execution waiting',
|
||||
FAILED: 'Failed',
|
||||
PENDING: 'Pending',
|
||||
WAITING: 'Waiting'
|
||||
}
|
||||
|
||||
// %homeChain will be replaced by the home network name
|
||||
// %foreignChain will be replaced by the foreign network name
|
||||
export const CONFIRMATIONS_STATUS_DESCRIPTION: { [key: string]: string } = {
|
||||
SUCCESS: '',
|
||||
SUCCESS_MESSAGE_FAILED:
|
||||
'Signatures have been collected in the %homeChain and they were successfully sent to the %foreignChain but the contained message execution failed.',
|
||||
EXECUTION_FAILED:
|
||||
'Signatures have been collected in the %homeChain and they were sent to the %foreignChain but the transaction with signatures failed',
|
||||
EXECUTION_PENDING:
|
||||
'Signatures have been collected in the %homeChain and they were sent to the %foreignChain but the transaction is in the pending state (transactions congestion or low gas price)',
|
||||
EXECUTION_WAITING: 'Execution waiting',
|
||||
FAILED:
|
||||
'Some validators sent improper transactions as so they were failed, collected confirmations are not enough to execute the relay request',
|
||||
PENDING: 'Some confirmations are in pending state',
|
||||
WAITING: 'Validators are waiting for the chain finalization'
|
||||
}
|
||||
1
alm/src/global.d.ts
vendored
Normal file
1
alm/src/global.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
declare type Maybe<T> = T | null
|
||||
38
alm/src/hooks/useBlockConfirmations.ts
Normal file
38
alm/src/hooks/useBlockConfirmations.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { TransactionReceipt } from 'web3-eth'
|
||||
import { useStateProvider } from '../state/StateProvider'
|
||||
import { Contract } from 'web3-eth-contract'
|
||||
import { getRequiredBlockConfirmations } from '../utils/contract'
|
||||
|
||||
export interface UseBlockConfirmationsParams {
|
||||
fromHome: boolean
|
||||
receipt: Maybe<TransactionReceipt>
|
||||
}
|
||||
|
||||
export const useBlockConfirmations = ({ receipt, fromHome }: UseBlockConfirmationsParams) => {
|
||||
const [blockConfirmations, setBlockConfirmations] = useState(0)
|
||||
|
||||
const { home, foreign } = useStateProvider()
|
||||
|
||||
const callRequireBlockConfirmations = async (
|
||||
contract: Contract,
|
||||
receipt: TransactionReceipt,
|
||||
setResult: Function
|
||||
) => {
|
||||
const result = await getRequiredBlockConfirmations(contract, receipt.blockNumber)
|
||||
setResult(result)
|
||||
}
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
const bridgeContract = fromHome ? home.bridgeContract : foreign.bridgeContract
|
||||
if (!bridgeContract || !receipt) return
|
||||
callRequireBlockConfirmations(bridgeContract, receipt, setBlockConfirmations)
|
||||
},
|
||||
[home.bridgeContract, foreign.bridgeContract, receipt, fromHome]
|
||||
)
|
||||
|
||||
return {
|
||||
blockConfirmations
|
||||
}
|
||||
}
|
||||
38
alm/src/hooks/useBridgeContracts.ts
Normal file
38
alm/src/hooks/useBridgeContracts.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { HOME_AMB_ABI, FOREIGN_AMB_ABI } from '../abis'
|
||||
import { FOREIGN_BRIDGE_ADDRESS, HOME_BRIDGE_ADDRESS } from '../config/constants'
|
||||
import { Contract } from 'web3-eth-contract'
|
||||
import Web3 from 'web3'
|
||||
|
||||
export interface useBridgeContractsParams {
|
||||
homeWeb3: Web3
|
||||
foreignWeb3: Web3
|
||||
}
|
||||
|
||||
export const useBridgeContracts = ({ homeWeb3, foreignWeb3 }: useBridgeContractsParams) => {
|
||||
const [homeBridge, setHomeBridge] = useState<Maybe<Contract>>(null)
|
||||
const [foreignBridge, setForeignBridge] = useState<Maybe<Contract>>(null)
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
if (!homeWeb3) return
|
||||
const homeContract = new homeWeb3.eth.Contract(HOME_AMB_ABI, HOME_BRIDGE_ADDRESS)
|
||||
setHomeBridge(homeContract)
|
||||
},
|
||||
[homeWeb3]
|
||||
)
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
if (!foreignWeb3) return
|
||||
const foreignContract = new foreignWeb3.eth.Contract(FOREIGN_AMB_ABI, FOREIGN_BRIDGE_ADDRESS)
|
||||
setForeignBridge(foreignContract)
|
||||
},
|
||||
[foreignWeb3]
|
||||
)
|
||||
|
||||
return {
|
||||
homeBridge,
|
||||
foreignBridge
|
||||
}
|
||||
}
|
||||
357
alm/src/hooks/useMessageConfirmations.ts
Normal file
357
alm/src/hooks/useMessageConfirmations.ts
Normal file
@@ -0,0 +1,357 @@
|
||||
import { useStateProvider } from '../state/StateProvider'
|
||||
import { TransactionReceipt } from 'web3-eth'
|
||||
import { MessageObject } from '../utils/web3'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { EventData } from 'web3-eth-contract'
|
||||
import { getAffirmationsSigned, getMessagesSigned } from '../utils/contract'
|
||||
import {
|
||||
BLOCK_RANGE,
|
||||
CONFIRMATIONS_STATUS,
|
||||
FOREIGN_RPC_POLLING_INTERVAL,
|
||||
HOME_RPC_POLLING_INTERVAL,
|
||||
VALIDATOR_CONFIRMATION_STATUS
|
||||
} from '../config/constants'
|
||||
import { homeBlockNumberProvider, foreignBlockNumberProvider } from '../services/BlockNumberProvider'
|
||||
import { checkSignaturesWaitingForBLocks } from '../utils/signatureWaitingForBlocks'
|
||||
import { getCollectedSignaturesEvent } from '../utils/getCollectedSignaturesEvent'
|
||||
import { checkWaitingBlocksForExecution } from '../utils/executionWaitingForBlocks'
|
||||
import { getConfirmationsForTx } from '../utils/getConfirmationsForTx'
|
||||
import { getFinalizationEvent } from '../utils/getFinalizationEvent'
|
||||
import {
|
||||
getValidatorFailedTransactionsForMessage,
|
||||
getExecutionFailedTransactionForMessage,
|
||||
getValidatorPendingTransactionsForMessage,
|
||||
getExecutionPendingTransactionsForMessage,
|
||||
getValidatorSuccessTransactionsForMessage
|
||||
} from '../utils/explorer'
|
||||
|
||||
export interface useMessageConfirmationsParams {
|
||||
message: MessageObject
|
||||
receipt: Maybe<TransactionReceipt>
|
||||
fromHome: boolean
|
||||
timestamp: number
|
||||
requiredSignatures: number
|
||||
validatorList: string[]
|
||||
blockConfirmations: number
|
||||
}
|
||||
|
||||
export interface BasicConfirmationParam {
|
||||
validator: string
|
||||
status: string
|
||||
}
|
||||
|
||||
export interface ConfirmationParam extends BasicConfirmationParam {
|
||||
txHash: string
|
||||
timestamp: number
|
||||
}
|
||||
|
||||
export interface ExecutionData {
|
||||
status: string
|
||||
validator: string
|
||||
txHash: string
|
||||
timestamp: number
|
||||
executionResult: boolean
|
||||
}
|
||||
|
||||
export const useMessageConfirmations = ({
|
||||
message,
|
||||
receipt,
|
||||
fromHome,
|
||||
timestamp,
|
||||
requiredSignatures,
|
||||
validatorList,
|
||||
blockConfirmations
|
||||
}: useMessageConfirmationsParams) => {
|
||||
const { home, foreign } = useStateProvider()
|
||||
const [confirmations, setConfirmations] = useState<Array<ConfirmationParam>>([])
|
||||
const [status, setStatus] = useState(CONFIRMATIONS_STATUS.UNDEFINED)
|
||||
const [waitingBlocks, setWaitingBlocks] = useState(false)
|
||||
const [waitingBlocksResolved, setWaitingBlocksResolved] = useState(false)
|
||||
const [signatureCollected, setSignatureCollected] = useState(false)
|
||||
const [collectedSignaturesEvent, setCollectedSignaturesEvent] = useState<Maybe<EventData>>(null)
|
||||
const [executionData, setExecutionData] = useState<ExecutionData>({
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
||||
validator: '',
|
||||
txHash: '',
|
||||
timestamp: 0,
|
||||
executionResult: false
|
||||
})
|
||||
const [waitingBlocksForExecution, setWaitingBlocksForExecution] = useState(false)
|
||||
const [waitingBlocksForExecutionResolved, setWaitingBlocksForExecutionResolved] = useState(false)
|
||||
const [failedConfirmations, setFailedConfirmations] = useState(false)
|
||||
const [failedExecution, setFailedExecution] = useState(false)
|
||||
const [pendingConfirmations, setPendingConfirmations] = useState(false)
|
||||
const [pendingExecution, setPendingExecution] = useState(false)
|
||||
|
||||
// Check if the validators are waiting for block confirmations to verify the message
|
||||
useEffect(
|
||||
() => {
|
||||
if (!receipt || !blockConfirmations) return
|
||||
|
||||
const subscriptions: Array<number> = []
|
||||
|
||||
const unsubscribe = () => {
|
||||
subscriptions.forEach(s => {
|
||||
clearTimeout(s)
|
||||
})
|
||||
}
|
||||
|
||||
const blockProvider = fromHome ? homeBlockNumberProvider : foreignBlockNumberProvider
|
||||
const interval = fromHome ? HOME_RPC_POLLING_INTERVAL : FOREIGN_RPC_POLLING_INTERVAL
|
||||
const web3 = fromHome ? home.web3 : foreign.web3
|
||||
blockProvider.start(web3)
|
||||
|
||||
const targetBlock = receipt.blockNumber + blockConfirmations
|
||||
|
||||
checkSignaturesWaitingForBLocks(
|
||||
targetBlock,
|
||||
setWaitingBlocks,
|
||||
setWaitingBlocksResolved,
|
||||
validatorList,
|
||||
setConfirmations,
|
||||
blockProvider,
|
||||
interval,
|
||||
subscriptions
|
||||
)
|
||||
|
||||
return () => {
|
||||
unsubscribe()
|
||||
blockProvider.stop()
|
||||
}
|
||||
},
|
||||
[blockConfirmations, foreign.web3, fromHome, validatorList, home.web3, receipt]
|
||||
)
|
||||
|
||||
// The collected signature event is only fetched once the signatures are collected on tx from home to foreign, to calculate if
|
||||
// the execution tx on the foreign network is waiting for block confirmations
|
||||
// This is executed if the message is in Home to Foreign direction only
|
||||
useEffect(
|
||||
() => {
|
||||
if (!fromHome || !receipt || !home.web3 || !signatureCollected) return
|
||||
|
||||
const subscriptions: Array<number> = []
|
||||
|
||||
const unsubscribe = () => {
|
||||
subscriptions.forEach(s => {
|
||||
clearTimeout(s)
|
||||
})
|
||||
}
|
||||
|
||||
homeBlockNumberProvider.start(home.web3)
|
||||
|
||||
const fromBlock = receipt.blockNumber
|
||||
const toBlock = fromBlock + BLOCK_RANGE
|
||||
const messageHash = home.web3.utils.soliditySha3Raw(message.data)
|
||||
|
||||
getCollectedSignaturesEvent(
|
||||
home.web3,
|
||||
home.bridgeContract,
|
||||
fromBlock,
|
||||
toBlock,
|
||||
messageHash,
|
||||
setCollectedSignaturesEvent,
|
||||
subscriptions
|
||||
)
|
||||
|
||||
return () => {
|
||||
unsubscribe()
|
||||
homeBlockNumberProvider.stop()
|
||||
}
|
||||
},
|
||||
[fromHome, home.bridgeContract, home.web3, message.data, receipt, signatureCollected]
|
||||
)
|
||||
|
||||
// Check if the responsible validator is waiting for block confirmations to execute the message on foreign network
|
||||
// This is executed if the message is in Home to Foreign direction only
|
||||
useEffect(
|
||||
() => {
|
||||
if (!fromHome || !home.web3 || !receipt || !collectedSignaturesEvent || !blockConfirmations) return
|
||||
|
||||
const subscriptions: Array<number> = []
|
||||
|
||||
const unsubscribe = () => {
|
||||
subscriptions.forEach(s => {
|
||||
clearTimeout(s)
|
||||
})
|
||||
}
|
||||
|
||||
homeBlockNumberProvider.start(home.web3)
|
||||
const targetBlock = collectedSignaturesEvent.blockNumber + blockConfirmations
|
||||
|
||||
checkWaitingBlocksForExecution(
|
||||
homeBlockNumberProvider,
|
||||
HOME_RPC_POLLING_INTERVAL,
|
||||
targetBlock,
|
||||
collectedSignaturesEvent,
|
||||
setWaitingBlocksForExecution,
|
||||
setWaitingBlocksForExecutionResolved,
|
||||
setExecutionData,
|
||||
subscriptions
|
||||
)
|
||||
|
||||
return () => {
|
||||
unsubscribe()
|
||||
homeBlockNumberProvider.stop()
|
||||
}
|
||||
},
|
||||
[collectedSignaturesEvent, fromHome, blockConfirmations, home.web3, receipt]
|
||||
)
|
||||
|
||||
// Checks if validators verified the message
|
||||
// To avoid making extra requests, this is only executed when validators finished waiting for blocks confirmations
|
||||
useEffect(
|
||||
() => {
|
||||
if (!waitingBlocksResolved || !timestamp || !requiredSignatures) return
|
||||
|
||||
const subscriptions: Array<number> = []
|
||||
|
||||
const unsubscribe = () => {
|
||||
subscriptions.forEach(s => {
|
||||
clearTimeout(s)
|
||||
})
|
||||
}
|
||||
|
||||
const confirmationContractMethod = fromHome ? getMessagesSigned : getAffirmationsSigned
|
||||
|
||||
getConfirmationsForTx(
|
||||
message.data,
|
||||
home.web3,
|
||||
validatorList,
|
||||
home.bridgeContract,
|
||||
confirmationContractMethod,
|
||||
setConfirmations,
|
||||
requiredSignatures,
|
||||
setSignatureCollected,
|
||||
waitingBlocksResolved,
|
||||
subscriptions,
|
||||
timestamp,
|
||||
getValidatorFailedTransactionsForMessage,
|
||||
setFailedConfirmations,
|
||||
getValidatorPendingTransactionsForMessage,
|
||||
setPendingConfirmations,
|
||||
getValidatorSuccessTransactionsForMessage
|
||||
)
|
||||
|
||||
return () => {
|
||||
unsubscribe()
|
||||
}
|
||||
},
|
||||
[
|
||||
fromHome,
|
||||
message.data,
|
||||
home.web3,
|
||||
validatorList,
|
||||
home.bridgeContract,
|
||||
requiredSignatures,
|
||||
waitingBlocksResolved,
|
||||
timestamp
|
||||
]
|
||||
)
|
||||
|
||||
// Gets finalization event to display the information about the execution of the message
|
||||
// In a message from Home to Foreign it will be executed after finishing waiting for block confirmations for the execution transaction on Foreign
|
||||
// In a message from Foreign to Home it will be executed after finishing waiting for block confirmations of the message request
|
||||
useEffect(
|
||||
() => {
|
||||
if ((fromHome && !waitingBlocksForExecutionResolved) || (!fromHome && !waitingBlocksResolved)) return
|
||||
|
||||
const subscriptions: Array<number> = []
|
||||
|
||||
const unsubscribe = () => {
|
||||
subscriptions.forEach(s => {
|
||||
clearTimeout(s)
|
||||
})
|
||||
}
|
||||
|
||||
const contractEvent = fromHome ? 'RelayedMessage' : 'AffirmationCompleted'
|
||||
const bridgeContract = fromHome ? foreign.bridgeContract : home.bridgeContract
|
||||
const providedWeb3 = fromHome ? foreign.web3 : home.web3
|
||||
const interval = fromHome ? FOREIGN_RPC_POLLING_INTERVAL : HOME_RPC_POLLING_INTERVAL
|
||||
|
||||
getFinalizationEvent(
|
||||
bridgeContract,
|
||||
contractEvent,
|
||||
providedWeb3,
|
||||
setExecutionData,
|
||||
waitingBlocksResolved,
|
||||
message,
|
||||
interval,
|
||||
subscriptions,
|
||||
timestamp,
|
||||
collectedSignaturesEvent,
|
||||
getExecutionFailedTransactionForMessage,
|
||||
setFailedExecution,
|
||||
getExecutionPendingTransactionsForMessage,
|
||||
setPendingExecution
|
||||
)
|
||||
|
||||
return () => {
|
||||
unsubscribe()
|
||||
}
|
||||
},
|
||||
[
|
||||
fromHome,
|
||||
foreign.bridgeContract,
|
||||
home.bridgeContract,
|
||||
message,
|
||||
foreign.web3,
|
||||
home.web3,
|
||||
waitingBlocksResolved,
|
||||
waitingBlocksForExecutionResolved,
|
||||
timestamp,
|
||||
collectedSignaturesEvent
|
||||
]
|
||||
)
|
||||
|
||||
// Sets the message status based in the collected information
|
||||
useEffect(
|
||||
() => {
|
||||
if (executionData.status === VALIDATOR_CONFIRMATION_STATUS.SUCCESS) {
|
||||
const newStatus = executionData.executionResult
|
||||
? CONFIRMATIONS_STATUS.SUCCESS
|
||||
: CONFIRMATIONS_STATUS.SUCCESS_MESSAGE_FAILED
|
||||
setStatus(newStatus)
|
||||
} else if (signatureCollected) {
|
||||
if (fromHome) {
|
||||
if (waitingBlocksForExecution) {
|
||||
setStatus(CONFIRMATIONS_STATUS.EXECUTION_WAITING)
|
||||
} else if (failedExecution) {
|
||||
setStatus(CONFIRMATIONS_STATUS.EXECUTION_FAILED)
|
||||
} else if (pendingExecution) {
|
||||
setStatus(CONFIRMATIONS_STATUS.EXECUTION_PENDING)
|
||||
} else {
|
||||
setStatus(CONFIRMATIONS_STATUS.UNDEFINED)
|
||||
}
|
||||
} else {
|
||||
setStatus(CONFIRMATIONS_STATUS.UNDEFINED)
|
||||
}
|
||||
} else if (waitingBlocks) {
|
||||
setStatus(CONFIRMATIONS_STATUS.WAITING)
|
||||
} else if (failedConfirmations) {
|
||||
setStatus(CONFIRMATIONS_STATUS.FAILED)
|
||||
} else if (pendingConfirmations) {
|
||||
setStatus(CONFIRMATIONS_STATUS.PENDING)
|
||||
} else {
|
||||
setStatus(CONFIRMATIONS_STATUS.UNDEFINED)
|
||||
}
|
||||
},
|
||||
[
|
||||
executionData,
|
||||
fromHome,
|
||||
signatureCollected,
|
||||
waitingBlocks,
|
||||
waitingBlocksForExecution,
|
||||
failedConfirmations,
|
||||
failedExecution,
|
||||
pendingConfirmations,
|
||||
pendingExecution
|
||||
]
|
||||
)
|
||||
|
||||
return {
|
||||
confirmations,
|
||||
status,
|
||||
signatureCollected,
|
||||
executionData
|
||||
}
|
||||
}
|
||||
27
alm/src/hooks/useNetwork.ts
Normal file
27
alm/src/hooks/useNetwork.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { getWeb3 } from '../utils/web3'
|
||||
|
||||
export const useNetwork = (url: string) => {
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [chainId, setChainId] = useState(0)
|
||||
const web3 = getWeb3(url)
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
setLoading(true)
|
||||
const getChainId = async () => {
|
||||
const id = await web3.eth.getChainId()
|
||||
setChainId(id)
|
||||
setLoading(false)
|
||||
}
|
||||
getChainId()
|
||||
},
|
||||
[web3.eth]
|
||||
)
|
||||
|
||||
return {
|
||||
web3,
|
||||
chainId,
|
||||
loading
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
131
alm/src/hooks/useTransactionStatus.ts
Normal file
131
alm/src/hooks/useTransactionStatus.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { TransactionReceipt } from 'web3-eth'
|
||||
import { HOME_RPC_POLLING_INTERVAL, TRANSACTION_STATUS } from '../config/constants'
|
||||
import { getTransactionStatusDescription } from '../utils/networks'
|
||||
import { useStateProvider } from '../state/StateProvider'
|
||||
import { getHomeMessagesFromReceipt, getForeignMessagesFromReceipt, MessageObject, getBlock } from '../utils/web3'
|
||||
import useInterval from '@use-it/interval'
|
||||
|
||||
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('')
|
||||
const [description, setDescription] = useState('')
|
||||
const [receipt, setReceipt] = useState<Maybe<TransactionReceipt>>(null)
|
||||
const [timestamp, setTimestamp] = useState(0)
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
// Update description so the time displayed is accurate
|
||||
useInterval(() => {
|
||||
if (!status || !timestamp || !description) return
|
||||
setDescription(getTransactionStatusDescription(status, timestamp))
|
||||
}, 30000)
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
const subscriptions: Array<number> = []
|
||||
|
||||
const unsubscribe = () => {
|
||||
subscriptions.forEach(s => {
|
||||
clearTimeout(s)
|
||||
})
|
||||
}
|
||||
|
||||
const getReceipt = async () => {
|
||||
if (!chainId || !txHash || !home.chainId || !foreign.chainId || !home.web3 || !foreign.web3) return
|
||||
setLoading(true)
|
||||
const isHome = chainId === home.chainId
|
||||
const web3 = isHome ? home.web3 : foreign.web3
|
||||
|
||||
let txReceipt
|
||||
|
||||
if (receiptParam) {
|
||||
txReceipt = receiptParam
|
||||
} else {
|
||||
txReceipt = await web3.eth.getTransactionReceipt(txHash)
|
||||
}
|
||||
|
||||
setReceipt(txReceipt)
|
||||
|
||||
if (!txReceipt) {
|
||||
setStatus(TRANSACTION_STATUS.NOT_FOUND)
|
||||
setDescription(getTransactionStatusDescription(TRANSACTION_STATUS.NOT_FOUND))
|
||||
setMessages([{ id: txHash, data: '' }])
|
||||
const timeoutId = setTimeout(() => getReceipt(), HOME_RPC_POLLING_INTERVAL)
|
||||
subscriptions.push(timeoutId)
|
||||
} else {
|
||||
const blockNumber = txReceipt.blockNumber
|
||||
const block = await getBlock(web3, blockNumber)
|
||||
const blockTimestamp = typeof block.timestamp === 'string' ? parseInt(block.timestamp) : block.timestamp
|
||||
setTimestamp(blockTimestamp)
|
||||
|
||||
if (txReceipt.status) {
|
||||
let bridgeMessages: Array<MessageObject>
|
||||
if (isHome) {
|
||||
bridgeMessages = getHomeMessagesFromReceipt(txReceipt, home.web3, home.bridgeAddress)
|
||||
} else {
|
||||
bridgeMessages = getForeignMessagesFromReceipt(txReceipt, foreign.web3, foreign.bridgeAddress)
|
||||
}
|
||||
|
||||
if (bridgeMessages.length === 0) {
|
||||
setMessages([{ id: txHash, data: '' }])
|
||||
setStatus(TRANSACTION_STATUS.SUCCESS_NO_MESSAGES)
|
||||
setDescription(getTransactionStatusDescription(TRANSACTION_STATUS.SUCCESS_NO_MESSAGES, blockTimestamp))
|
||||
} else if (bridgeMessages.length === 1) {
|
||||
setMessages(bridgeMessages)
|
||||
setStatus(TRANSACTION_STATUS.SUCCESS_ONE_MESSAGE)
|
||||
setDescription(getTransactionStatusDescription(TRANSACTION_STATUS.SUCCESS_ONE_MESSAGE, blockTimestamp))
|
||||
} else {
|
||||
setMessages(bridgeMessages)
|
||||
setStatus(TRANSACTION_STATUS.SUCCESS_MULTIPLE_MESSAGES)
|
||||
setDescription(
|
||||
getTransactionStatusDescription(TRANSACTION_STATUS.SUCCESS_MULTIPLE_MESSAGES, blockTimestamp)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
setStatus(TRANSACTION_STATUS.FAILED)
|
||||
setDescription(getTransactionStatusDescription(TRANSACTION_STATUS.FAILED, blockTimestamp))
|
||||
}
|
||||
}
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
// unsubscribe from previous txHash
|
||||
unsubscribe()
|
||||
|
||||
getReceipt()
|
||||
return () => {
|
||||
// unsubscribe when unmount component
|
||||
unsubscribe()
|
||||
}
|
||||
},
|
||||
[
|
||||
txHash,
|
||||
chainId,
|
||||
home.chainId,
|
||||
foreign.chainId,
|
||||
home.web3,
|
||||
foreign.web3,
|
||||
home.bridgeAddress,
|
||||
foreign.bridgeAddress,
|
||||
receiptParam
|
||||
]
|
||||
)
|
||||
|
||||
return {
|
||||
messages,
|
||||
status,
|
||||
description,
|
||||
receipt,
|
||||
timestamp,
|
||||
loading
|
||||
}
|
||||
}
|
||||
68
alm/src/hooks/useValidatorContract.ts
Normal file
68
alm/src/hooks/useValidatorContract.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Contract } from 'web3-eth-contract'
|
||||
import Web3 from 'web3'
|
||||
import { getRequiredSignatures, getValidatorAddress, getValidatorList } from '../utils/contract'
|
||||
import { BRIDGE_VALIDATORS_ABI } from '../abis'
|
||||
import { useStateProvider } from '../state/StateProvider'
|
||||
import { TransactionReceipt } from 'web3-eth'
|
||||
|
||||
export interface useValidatorContractParams {
|
||||
fromHome: boolean
|
||||
receipt: Maybe<TransactionReceipt>
|
||||
}
|
||||
|
||||
export const useValidatorContract = ({ receipt, fromHome }: useValidatorContractParams) => {
|
||||
const [validatorContract, setValidatorContract] = useState<Maybe<Contract>>(null)
|
||||
const [requiredSignatures, setRequiredSignatures] = useState(0)
|
||||
const [validatorList, setValidatorList] = useState([])
|
||||
|
||||
const { home, foreign } = useStateProvider()
|
||||
|
||||
const callValidatorContract = async (bridgeContract: Maybe<Contract>, web3: Web3, setValidatorContract: Function) => {
|
||||
if (!web3 || !bridgeContract) return
|
||||
const address = await getValidatorAddress(bridgeContract)
|
||||
const contract = new web3.eth.Contract(BRIDGE_VALIDATORS_ABI, address)
|
||||
setValidatorContract(contract)
|
||||
}
|
||||
|
||||
const callRequiredSignatures = async (
|
||||
contract: Maybe<Contract>,
|
||||
receipt: TransactionReceipt,
|
||||
setResult: Function
|
||||
) => {
|
||||
if (!contract) return
|
||||
const result = await getRequiredSignatures(contract, receipt.blockNumber)
|
||||
setResult(result)
|
||||
}
|
||||
|
||||
const callValidatorList = async (contract: Maybe<Contract>, receipt: TransactionReceipt, setResult: Function) => {
|
||||
if (!contract) return
|
||||
const result = await getValidatorList(contract, receipt.blockNumber)
|
||||
setResult(result)
|
||||
}
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
const web3 = fromHome ? home.web3 : foreign.web3
|
||||
const bridgeContract = fromHome ? home.bridgeContract : foreign.bridgeContract
|
||||
|
||||
if (!web3 || !bridgeContract) return
|
||||
callValidatorContract(bridgeContract, web3, setValidatorContract)
|
||||
},
|
||||
[home.web3, foreign.web3, home.bridgeContract, foreign.bridgeContract, fromHome]
|
||||
)
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
if (!receipt) return
|
||||
callRequiredSignatures(validatorContract, receipt, setRequiredSignatures)
|
||||
callValidatorList(validatorContract, receipt, setValidatorList)
|
||||
},
|
||||
[validatorContract, receipt]
|
||||
)
|
||||
|
||||
return {
|
||||
requiredSignatures,
|
||||
validatorList
|
||||
}
|
||||
}
|
||||
16
alm/src/index.tsx
Normal file
16
alm/src/index.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import { ThemeProvider } from 'styled-components'
|
||||
import { GlobalStyle } from './themes/GlobalStyle'
|
||||
import App from './App'
|
||||
import Light from './themes/Light'
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<ThemeProvider theme={Light}>
|
||||
<GlobalStyle />
|
||||
<App />
|
||||
</ThemeProvider>
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root')
|
||||
)
|
||||
1
alm/src/react-app-env.d.ts
vendored
Normal file
1
alm/src/react-app-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="react-scripts" />
|
||||
64
alm/src/services/BlockNumberProvider.ts
Normal file
64
alm/src/services/BlockNumberProvider.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import Web3 from 'web3'
|
||||
import differenceInMilliseconds from 'date-fns/differenceInMilliseconds'
|
||||
import { HOME_RPC_POLLING_INTERVAL } from '../config/constants'
|
||||
|
||||
export class BlockNumberProvider {
|
||||
private running: number
|
||||
private web3: Maybe<Web3>
|
||||
private ref: number | undefined
|
||||
private value: Maybe<number>
|
||||
private lastValueTimestamp: Maybe<Date>
|
||||
private readonly interval: number
|
||||
|
||||
constructor(interval = 5000) {
|
||||
this.running = 0
|
||||
this.web3 = null
|
||||
this.ref = undefined
|
||||
this.value = null
|
||||
this.lastValueTimestamp = null
|
||||
this.interval = interval
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
start(web3: Maybe<Web3>) {
|
||||
if (!this.running) {
|
||||
clearTimeout(this.ref)
|
||||
this.web3 = web3
|
||||
this.running = this.running + 1
|
||||
this.fetchLastBlock()
|
||||
} else {
|
||||
this.running = this.running + 1
|
||||
}
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.running = this.running - 1
|
||||
|
||||
if (!this.running) {
|
||||
clearTimeout(this.ref)
|
||||
this.ref = undefined
|
||||
this.web3 = null
|
||||
}
|
||||
}
|
||||
|
||||
get() {
|
||||
return this.value
|
||||
}
|
||||
|
||||
private async fetchLastBlock() {
|
||||
if (!this.web3) return
|
||||
const now = new Date()
|
||||
const distance = differenceInMilliseconds(now, this.lastValueTimestamp || 0)
|
||||
|
||||
if (distance >= this.interval) {
|
||||
this.value = await this.web3.eth.getBlockNumber()
|
||||
this.lastValueTimestamp = now
|
||||
}
|
||||
|
||||
this.ref = setTimeout(() => this.fetchLastBlock(), this.interval)
|
||||
}
|
||||
}
|
||||
|
||||
export const homeBlockNumberProvider = new BlockNumberProvider(HOME_RPC_POLLING_INTERVAL)
|
||||
export const foreignBlockNumberProvider = new BlockNumberProvider(HOME_RPC_POLLING_INTERVAL)
|
||||
29
alm/src/services/ValidatorsCache.ts
Normal file
29
alm/src/services/ValidatorsCache.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { ConfirmationParam } from '../hooks/useMessageConfirmations'
|
||||
|
||||
class ValidatorsCache {
|
||||
private readonly store: { [key: string]: boolean }
|
||||
private readonly dataStore: { [key: string]: ConfirmationParam }
|
||||
|
||||
constructor() {
|
||||
this.store = {}
|
||||
this.dataStore = {}
|
||||
}
|
||||
|
||||
get(key: string) {
|
||||
return this.store[key]
|
||||
}
|
||||
|
||||
set(key: string, value: boolean) {
|
||||
this.store[key] = value
|
||||
}
|
||||
|
||||
getData(key: string) {
|
||||
return this.dataStore[key]
|
||||
}
|
||||
|
||||
setData(key: string, value: ConfirmationParam) {
|
||||
this.dataStore[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
export default new ValidatorsCache()
|
||||
5
alm/src/setupTests.ts
Normal file
5
alm/src/setupTests.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
||||
// allows you to do things like:
|
||||
// expect(element).toHaveTextContent(/react/i)
|
||||
// learn more: https://github.com/testing-library/jest-dom
|
||||
import '@testing-library/jest-dom/extend-expect'
|
||||
78
alm/src/state/StateProvider.tsx
Normal file
78
alm/src/state/StateProvider.tsx
Normal file
@@ -0,0 +1,78 @@
|
||||
import React, { createContext, ReactNode } from 'react'
|
||||
import { useNetwork } from '../hooks/useNetwork'
|
||||
import {
|
||||
HOME_RPC_URL,
|
||||
FOREIGN_RPC_URL,
|
||||
HOME_BRIDGE_ADDRESS,
|
||||
FOREIGN_BRIDGE_ADDRESS,
|
||||
HOME_NETWORK_NAME,
|
||||
FOREIGN_NETWORK_NAME
|
||||
} from '../config/constants'
|
||||
import Web3 from 'web3'
|
||||
import { useBridgeContracts } from '../hooks/useBridgeContracts'
|
||||
import { Contract } from 'web3-eth-contract'
|
||||
|
||||
export interface BaseNetworkParams {
|
||||
chainId: number
|
||||
name: string
|
||||
web3: Maybe<Web3>
|
||||
bridgeAddress: string
|
||||
bridgeContract: Maybe<Contract>
|
||||
}
|
||||
|
||||
export interface StateContext {
|
||||
home: BaseNetworkParams
|
||||
foreign: BaseNetworkParams
|
||||
loading: boolean
|
||||
}
|
||||
|
||||
const initialState = {
|
||||
home: {
|
||||
chainId: 0,
|
||||
name: '',
|
||||
web3: null,
|
||||
bridgeAddress: HOME_BRIDGE_ADDRESS,
|
||||
bridgeContract: null
|
||||
},
|
||||
foreign: {
|
||||
chainId: 0,
|
||||
name: '',
|
||||
web3: null,
|
||||
bridgeAddress: FOREIGN_BRIDGE_ADDRESS,
|
||||
bridgeContract: null
|
||||
},
|
||||
loading: true
|
||||
}
|
||||
|
||||
const StateContext = createContext<StateContext>(initialState)
|
||||
|
||||
export const StateProvider = ({ children }: { children: ReactNode }) => {
|
||||
const homeNetwork = useNetwork(HOME_RPC_URL)
|
||||
const foreignNetwork = useNetwork(FOREIGN_RPC_URL)
|
||||
const { homeBridge, foreignBridge } = useBridgeContracts({
|
||||
homeWeb3: homeNetwork.web3,
|
||||
foreignWeb3: foreignNetwork.web3
|
||||
})
|
||||
|
||||
const value = {
|
||||
home: {
|
||||
bridgeAddress: HOME_BRIDGE_ADDRESS,
|
||||
name: HOME_NETWORK_NAME,
|
||||
bridgeContract: homeBridge,
|
||||
...homeNetwork
|
||||
},
|
||||
foreign: {
|
||||
bridgeAddress: FOREIGN_BRIDGE_ADDRESS,
|
||||
name: FOREIGN_NETWORK_NAME,
|
||||
bridgeContract: foreignBridge,
|
||||
...foreignNetwork
|
||||
},
|
||||
loading: homeNetwork.loading || foreignNetwork.loading
|
||||
}
|
||||
|
||||
return <StateContext.Provider value={value}>{children}</StateContext.Provider>
|
||||
}
|
||||
|
||||
export const useStateProvider = (): StateContext => {
|
||||
return React.useContext(StateContext)
|
||||
}
|
||||
22
alm/src/themes/Dark.tsx
Normal file
22
alm/src/themes/Dark.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
const theme = {
|
||||
backgroundColor: '#121212',
|
||||
fontColor: '#f5f5f5',
|
||||
buttonColor: '#f5f5f5',
|
||||
colorPrimary: '#272727',
|
||||
colorGrey: '#272727',
|
||||
colorLightGrey: '#272727',
|
||||
linkColor: '#ffffff',
|
||||
success: {
|
||||
textColor: '#00c9a7',
|
||||
backgroundColor: '#004d40'
|
||||
},
|
||||
notRequired: {
|
||||
textColor: '#bdbdbd',
|
||||
backgroundColor: '#424242'
|
||||
},
|
||||
failed: {
|
||||
textColor: '#EF5350',
|
||||
backgroundColor: '#4E342E'
|
||||
}
|
||||
}
|
||||
export default theme
|
||||
32
alm/src/themes/GlobalStyle.tsx
Normal file
32
alm/src/themes/GlobalStyle.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import { createGlobalStyle } from 'styled-components'
|
||||
|
||||
import theme from './Light'
|
||||
|
||||
type ThemeType = typeof theme
|
||||
|
||||
export const GlobalStyle = createGlobalStyle<{ theme: ThemeType }>`
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
:root {
|
||||
--bg-color: ${props => props.theme.backgroundColor};
|
||||
--font-color: ${props => props.theme.fontColor};
|
||||
--button-color: ${props => props.theme.buttonColor};
|
||||
--color-primary: ${props => props.theme.colorPrimary};
|
||||
--color-grey: ${props => props.theme.colorGrey};
|
||||
--color-lightGrey: ${props => props.theme.colorLightGrey};
|
||||
--link-color: ${props => props.theme.linkColor};
|
||||
--success-color: ${props => props.theme.success.textColor};
|
||||
--success-bg-color: ${props => props.theme.success.backgroundColor};
|
||||
--not-required-color: ${props => props.theme.notRequired.textColor};
|
||||
--not-required-bg-color: ${props => props.theme.notRequired.backgroundColor};
|
||||
--failed-color: ${props => props.theme.failed.textColor};
|
||||
--failed-bg-color: ${props => props.theme.failed.backgroundColor};
|
||||
}
|
||||
`
|
||||
22
alm/src/themes/Light.ts
Normal file
22
alm/src/themes/Light.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
const theme = {
|
||||
backgroundColor: '#FFFFFF',
|
||||
fontColor: 'rgba(0, 0, 0, 0.65)',
|
||||
buttonColor: '#1890ff',
|
||||
colorPrimary: '#BDBDBD',
|
||||
colorGrey: '#1890ff',
|
||||
colorLightGrey: '#1890ff',
|
||||
linkColor: '#1890ff',
|
||||
success: {
|
||||
textColor: '#388E3C',
|
||||
backgroundColor: 'rgba(0,201,167,.1)'
|
||||
},
|
||||
notRequired: {
|
||||
textColor: '#77838f',
|
||||
backgroundColor: 'rgba(119,131,143,.1)'
|
||||
},
|
||||
failed: {
|
||||
textColor: '#de4437',
|
||||
backgroundColor: 'rgba(222,68,55,.1)'
|
||||
}
|
||||
}
|
||||
export default theme
|
||||
68
alm/src/utils/contract.ts
Normal file
68
alm/src/utils/contract.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { Contract } from 'web3-eth-contract'
|
||||
|
||||
export const getRequiredBlockConfirmations = async (contract: Contract, blockNumber: number) => {
|
||||
const events = await contract.getPastEvents('RequiredBlockConfirmationChanged', {
|
||||
fromBlock: 0,
|
||||
toBlock: blockNumber
|
||||
})
|
||||
|
||||
let blockConfirmations
|
||||
if (events.length > 0) {
|
||||
// Use the value from last event before the transaction
|
||||
const event = events[events.length - 1]
|
||||
blockConfirmations = event.returnValues.requiredBlockConfirmations
|
||||
} else {
|
||||
// This is a special case where RequiredBlockConfirmationChanged was not emitted during initialization in early versions of AMB
|
||||
// of Sokol - Kovan. In this case the current value is used.
|
||||
blockConfirmations = await contract.methods.requiredBlockConfirmations().call()
|
||||
}
|
||||
return parseInt(blockConfirmations)
|
||||
}
|
||||
|
||||
export const getValidatorAddress = (contract: Contract) => contract.methods.validatorContract().call()
|
||||
|
||||
export const getRequiredSignatures = async (contract: Contract, blockNumber: number) => {
|
||||
const events = await contract.getPastEvents('RequiredSignaturesChanged', {
|
||||
fromBlock: 0,
|
||||
toBlock: blockNumber
|
||||
})
|
||||
|
||||
// Use the value form last event before the transaction
|
||||
const event = events[events.length - 1]
|
||||
const { requiredSignatures } = event.returnValues
|
||||
return parseInt(requiredSignatures)
|
||||
}
|
||||
|
||||
export const getValidatorList = async (contract: Contract, blockNumber: number) => {
|
||||
let currentList: string[] = await contract.methods.validatorList().call()
|
||||
const [added, removed] = await Promise.all([
|
||||
contract.getPastEvents('ValidatorAdded', {
|
||||
fromBlock: blockNumber
|
||||
}),
|
||||
contract.getPastEvents('ValidatorRemoved', {
|
||||
fromBlock: blockNumber
|
||||
})
|
||||
])
|
||||
|
||||
// Ordered desc
|
||||
const orderedEvents = [...added, ...removed].sort(({ blockNumber: prev }, { blockNumber: next }) => next - prev)
|
||||
|
||||
// Stored as a Set to avoid duplicates
|
||||
const validatorList = new Set(currentList)
|
||||
|
||||
orderedEvents.forEach(e => {
|
||||
const { validator } = e.returnValues
|
||||
if (e.event === 'ValidatorRemoved') {
|
||||
validatorList.add(validator)
|
||||
} else if (e.event === 'ValidatorAdded') {
|
||||
validatorList.delete(validator)
|
||||
}
|
||||
})
|
||||
|
||||
return Array.from(validatorList)
|
||||
}
|
||||
|
||||
export const getMessagesSigned = (contract: Contract, hash: string) => contract.methods.messagesSigned(hash).call()
|
||||
|
||||
export const getAffirmationsSigned = (contract: Contract, hash: string) =>
|
||||
contract.methods.affirmationsSigned(hash).call()
|
||||
58
alm/src/utils/executionWaitingForBlocks.ts
Normal file
58
alm/src/utils/executionWaitingForBlocks.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { BlockNumberProvider } from '../services/BlockNumberProvider'
|
||||
import { VALIDATOR_CONFIRMATION_STATUS } from '../config/constants'
|
||||
import { EventData } from 'web3-eth-contract'
|
||||
|
||||
export const checkWaitingBlocksForExecution = async (
|
||||
blockProvider: BlockNumberProvider,
|
||||
interval: number,
|
||||
targetBlock: number,
|
||||
collectedSignaturesEvent: EventData,
|
||||
setWaitingBlocksForExecution: Function,
|
||||
setWaitingBlocksForExecutionResolved: Function,
|
||||
setExecutionData: Function,
|
||||
subscriptions: number[]
|
||||
) => {
|
||||
const currentBlock = blockProvider.get()
|
||||
|
||||
if (currentBlock && currentBlock >= targetBlock) {
|
||||
setExecutionData({
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.UNDEFINED,
|
||||
validator: collectedSignaturesEvent.returnValues.authorityResponsibleForRelay,
|
||||
txHash: '',
|
||||
timestamp: 0,
|
||||
executionResult: false
|
||||
})
|
||||
setWaitingBlocksForExecution(false)
|
||||
setWaitingBlocksForExecutionResolved(true)
|
||||
blockProvider.stop()
|
||||
} else {
|
||||
let nextInterval = interval
|
||||
if (!currentBlock) {
|
||||
nextInterval = 500
|
||||
} else {
|
||||
setWaitingBlocksForExecution(true)
|
||||
setExecutionData({
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.WAITING,
|
||||
validator: collectedSignaturesEvent.returnValues.authorityResponsibleForRelay,
|
||||
txHash: '',
|
||||
timestamp: 0,
|
||||
executionResult: false
|
||||
})
|
||||
}
|
||||
const timeoutId = setTimeout(
|
||||
() =>
|
||||
checkWaitingBlocksForExecution(
|
||||
blockProvider,
|
||||
interval,
|
||||
targetBlock,
|
||||
collectedSignaturesEvent,
|
||||
setWaitingBlocksForExecution,
|
||||
setWaitingBlocksForExecutionResolved,
|
||||
setExecutionData,
|
||||
subscriptions
|
||||
),
|
||||
nextInterval
|
||||
)
|
||||
subscriptions.push(timeoutId)
|
||||
}
|
||||
}
|
||||
280
alm/src/utils/explorer.ts
Normal file
280
alm/src/utils/explorer.ts
Normal file
@@ -0,0 +1,280 @@
|
||||
import {
|
||||
EXECUTE_AFFIRMATION_HASH,
|
||||
EXECUTE_SIGNATURES_HASH,
|
||||
FOREIGN_EXPLORER_API,
|
||||
HOME_EXPLORER_API,
|
||||
SUBMIT_SIGNATURE_HASH
|
||||
} from '../config/constants'
|
||||
|
||||
export interface APITransaction {
|
||||
timeStamp: string
|
||||
isError: string
|
||||
input: string
|
||||
to: string
|
||||
hash: string
|
||||
}
|
||||
|
||||
export interface APIPendingTransaction {
|
||||
input: string
|
||||
to: string
|
||||
hash: string
|
||||
}
|
||||
|
||||
export interface PendingTransactionsParams {
|
||||
account: string
|
||||
api: string
|
||||
}
|
||||
|
||||
export interface AccountTransactionsParams {
|
||||
account: string
|
||||
to: string
|
||||
startTimestamp: number
|
||||
endTimestamp: number
|
||||
api: string
|
||||
}
|
||||
|
||||
export interface GetFailedTransactionParams {
|
||||
account: string
|
||||
to: string
|
||||
messageData: string
|
||||
startTimestamp: number
|
||||
endTimestamp: number
|
||||
}
|
||||
|
||||
export interface GetPendingTransactionParams {
|
||||
account: string
|
||||
to: string
|
||||
messageData: string
|
||||
}
|
||||
|
||||
export const fetchAccountTransactionsFromBlockscout = async ({
|
||||
account,
|
||||
to,
|
||||
startTimestamp,
|
||||
endTimestamp,
|
||||
api
|
||||
}: AccountTransactionsParams): Promise<APITransaction[]> => {
|
||||
const url = `${api}?module=account&action=txlist&address=${account}&filterby=from=${account}&to=${to}&starttimestamp=${startTimestamp}&endtimestamp=${endTimestamp}`
|
||||
|
||||
try {
|
||||
const result = await fetch(url).then(res => res.json())
|
||||
if (result.status === '0') {
|
||||
return []
|
||||
}
|
||||
|
||||
return result.result
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
export const getBlockByTimestampUrl = (api: string, timestamp: number) =>
|
||||
`${api}&module=block&action=getblocknobytime×tamp=${timestamp}&closest=before`
|
||||
|
||||
export const fetchAccountTransactionsFromEtherscan = async ({
|
||||
account,
|
||||
to,
|
||||
startTimestamp,
|
||||
endTimestamp,
|
||||
api
|
||||
}: AccountTransactionsParams): Promise<APITransaction[]> => {
|
||||
const startBlockUrl = getBlockByTimestampUrl(api, startTimestamp)
|
||||
const endBlockUrl = getBlockByTimestampUrl(api, endTimestamp)
|
||||
let fromBlock = 0
|
||||
let toBlock = 9999999999999
|
||||
try {
|
||||
const [fromBlockResult, toBlockResult] = await Promise.all([
|
||||
fetch(startBlockUrl).then(res => res.json()),
|
||||
fetch(endBlockUrl).then(res => res.json())
|
||||
])
|
||||
|
||||
if (fromBlockResult.status !== '0') {
|
||||
fromBlock = parseInt(fromBlockResult.result)
|
||||
}
|
||||
|
||||
if (toBlockResult.status !== '0') {
|
||||
toBlock = parseInt(toBlockResult.result)
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
return []
|
||||
}
|
||||
|
||||
const url = `${api}&module=account&action=txlist&address=${account}&startblock=${fromBlock}&endblock=${toBlock}`
|
||||
|
||||
try {
|
||||
const result = await fetch(url).then(res => res.json())
|
||||
|
||||
if (result.status === '0') {
|
||||
return []
|
||||
}
|
||||
|
||||
const toAddressLowerCase = to.toLowerCase()
|
||||
const transactions: APITransaction[] = result.result
|
||||
return transactions.filter(t => t.to.toLowerCase() === toAddressLowerCase)
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
export const fetchAccountTransactions = (api: string) => {
|
||||
return api.includes('blockscout') ? fetchAccountTransactionsFromBlockscout : fetchAccountTransactionsFromEtherscan
|
||||
}
|
||||
|
||||
export const fetchPendingTransactions = async ({
|
||||
account,
|
||||
api
|
||||
}: PendingTransactionsParams): Promise<APIPendingTransaction[]> => {
|
||||
const url = `${api}?module=account&action=pendingtxlist&address=${account}`
|
||||
|
||||
try {
|
||||
const result = await fetch(url).then(res => res.json())
|
||||
if (result.status === '0') {
|
||||
return []
|
||||
}
|
||||
|
||||
return result.result
|
||||
} catch (e) {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
export const getFailedTransactions = async (
|
||||
account: string,
|
||||
to: string,
|
||||
startTimestamp: number,
|
||||
endTimestamp: number,
|
||||
api: string,
|
||||
fetchAccountTransactions: (args: AccountTransactionsParams) => Promise<APITransaction[]>
|
||||
): Promise<APITransaction[]> => {
|
||||
const transactions = await fetchAccountTransactions({ account, to, startTimestamp, endTimestamp, api })
|
||||
|
||||
return transactions.filter(t => t.isError !== '0')
|
||||
}
|
||||
|
||||
export const getSuccessTransactions = async (
|
||||
account: string,
|
||||
to: string,
|
||||
startTimestamp: number,
|
||||
endTimestamp: number,
|
||||
api: string,
|
||||
fetchAccountTransactions: (args: AccountTransactionsParams) => Promise<APITransaction[]>
|
||||
): Promise<APITransaction[]> => {
|
||||
const transactions = await fetchAccountTransactions({ account, to, startTimestamp, endTimestamp, api })
|
||||
|
||||
return transactions.filter(t => t.isError === '0')
|
||||
}
|
||||
|
||||
export const filterValidatorSignatureTransaction = (
|
||||
transactions: APITransaction[],
|
||||
messageData: string
|
||||
): APITransaction[] => {
|
||||
const messageDataValue = messageData.replace('0x', '')
|
||||
return transactions.filter(
|
||||
t =>
|
||||
(t.input.includes(SUBMIT_SIGNATURE_HASH) || t.input.includes(EXECUTE_AFFIRMATION_HASH)) &&
|
||||
t.input.includes(messageDataValue)
|
||||
)
|
||||
}
|
||||
|
||||
export const getValidatorFailedTransactionsForMessage = async ({
|
||||
account,
|
||||
to,
|
||||
messageData,
|
||||
startTimestamp,
|
||||
endTimestamp
|
||||
}: GetFailedTransactionParams): Promise<APITransaction[]> => {
|
||||
const failedTransactions = await getFailedTransactions(
|
||||
account,
|
||||
to,
|
||||
startTimestamp,
|
||||
endTimestamp,
|
||||
HOME_EXPLORER_API,
|
||||
fetchAccountTransactionsFromBlockscout
|
||||
)
|
||||
|
||||
return filterValidatorSignatureTransaction(failedTransactions, messageData)
|
||||
}
|
||||
|
||||
export const getValidatorSuccessTransactionsForMessage = async ({
|
||||
account,
|
||||
to,
|
||||
messageData,
|
||||
startTimestamp,
|
||||
endTimestamp
|
||||
}: GetFailedTransactionParams): Promise<APITransaction[]> => {
|
||||
const transactions = await getSuccessTransactions(
|
||||
account,
|
||||
to,
|
||||
startTimestamp,
|
||||
endTimestamp,
|
||||
HOME_EXPLORER_API,
|
||||
fetchAccountTransactionsFromBlockscout
|
||||
)
|
||||
|
||||
return filterValidatorSignatureTransaction(transactions, messageData)
|
||||
}
|
||||
|
||||
export const getExecutionFailedTransactionForMessage = async ({
|
||||
account,
|
||||
to,
|
||||
messageData,
|
||||
startTimestamp,
|
||||
endTimestamp
|
||||
}: GetFailedTransactionParams): Promise<APITransaction[]> => {
|
||||
const failedTransactions = await getFailedTransactions(
|
||||
account,
|
||||
to,
|
||||
startTimestamp,
|
||||
endTimestamp,
|
||||
FOREIGN_EXPLORER_API,
|
||||
fetchAccountTransactions(FOREIGN_EXPLORER_API)
|
||||
)
|
||||
|
||||
const messageDataValue = messageData.replace('0x', '')
|
||||
return failedTransactions.filter(t => t.input.includes(EXECUTE_SIGNATURES_HASH) && t.input.includes(messageDataValue))
|
||||
}
|
||||
|
||||
export const getValidatorPendingTransactionsForMessage = async ({
|
||||
account,
|
||||
to,
|
||||
messageData
|
||||
}: GetPendingTransactionParams): Promise<APIPendingTransaction[]> => {
|
||||
const pendingTransactions = await fetchPendingTransactions({
|
||||
account,
|
||||
api: HOME_EXPLORER_API
|
||||
})
|
||||
|
||||
const toAddressLowerCase = to.toLowerCase()
|
||||
const messageDataValue = messageData.replace('0x', '')
|
||||
|
||||
return pendingTransactions.filter(
|
||||
t =>
|
||||
t.to.toLowerCase() === toAddressLowerCase &&
|
||||
(t.input.includes(SUBMIT_SIGNATURE_HASH) || t.input.includes(EXECUTE_AFFIRMATION_HASH)) &&
|
||||
t.input.includes(messageDataValue)
|
||||
)
|
||||
}
|
||||
|
||||
export const getExecutionPendingTransactionsForMessage = async ({
|
||||
account,
|
||||
to,
|
||||
messageData
|
||||
}: GetPendingTransactionParams): Promise<APIPendingTransaction[]> => {
|
||||
const pendingTransactions = await fetchPendingTransactions({
|
||||
account,
|
||||
api: FOREIGN_EXPLORER_API
|
||||
})
|
||||
|
||||
const toAddressLowerCase = to.toLowerCase()
|
||||
const messageDataValue = messageData.replace('0x', '')
|
||||
|
||||
return pendingTransactions.filter(
|
||||
t =>
|
||||
t.to.toLowerCase() === toAddressLowerCase &&
|
||||
t.input.includes(EXECUTE_SIGNATURES_HASH) &&
|
||||
t.input.includes(messageDataValue)
|
||||
)
|
||||
}
|
||||
53
alm/src/utils/getCollectedSignaturesEvent.ts
Normal file
53
alm/src/utils/getCollectedSignaturesEvent.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import Web3 from 'web3'
|
||||
import { Contract, EventData } from 'web3-eth-contract'
|
||||
import { homeBlockNumberProvider } from '../services/BlockNumberProvider'
|
||||
import { BLOCK_RANGE } from '../config/constants'
|
||||
|
||||
export const getCollectedSignaturesEvent = async (
|
||||
web3: Maybe<Web3>,
|
||||
contract: Maybe<Contract>,
|
||||
fromBlock: number,
|
||||
toBlock: number,
|
||||
messageHash: string,
|
||||
setCollectedSignaturesEvent: Function,
|
||||
subscriptions: number[]
|
||||
) => {
|
||||
if (!web3 || !contract) return
|
||||
const currentBlock = homeBlockNumberProvider.get()
|
||||
|
||||
let events: EventData[] = []
|
||||
let securedToBlock = toBlock
|
||||
if (currentBlock) {
|
||||
// prevent errors if the toBlock parameter is bigger than the latest
|
||||
securedToBlock = toBlock >= currentBlock ? currentBlock : toBlock
|
||||
events = await contract.getPastEvents('CollectedSignatures', {
|
||||
fromBlock,
|
||||
toBlock: securedToBlock
|
||||
})
|
||||
}
|
||||
|
||||
const filteredEvents = events.filter(e => e.returnValues.messageHash === messageHash)
|
||||
|
||||
if (filteredEvents.length) {
|
||||
const event = filteredEvents[0]
|
||||
setCollectedSignaturesEvent(event)
|
||||
homeBlockNumberProvider.stop()
|
||||
} else {
|
||||
const newFromBlock = currentBlock ? securedToBlock : fromBlock
|
||||
const newToBlock = currentBlock ? toBlock + BLOCK_RANGE : toBlock
|
||||
const timeoutId = setTimeout(
|
||||
() =>
|
||||
getCollectedSignaturesEvent(
|
||||
web3,
|
||||
contract,
|
||||
newFromBlock,
|
||||
newToBlock,
|
||||
messageHash,
|
||||
setCollectedSignaturesEvent,
|
||||
subscriptions
|
||||
),
|
||||
500
|
||||
)
|
||||
subscriptions.push(timeoutId)
|
||||
}
|
||||
}
|
||||
317
alm/src/utils/getConfirmationsForTx.ts
Normal file
317
alm/src/utils/getConfirmationsForTx.ts
Normal file
@@ -0,0 +1,317 @@
|
||||
import Web3 from 'web3'
|
||||
import { Contract } from 'web3-eth-contract'
|
||||
import validatorsCache from '../services/ValidatorsCache'
|
||||
import {
|
||||
CACHE_KEY_FAILED,
|
||||
CACHE_KEY_SUCCESS,
|
||||
HOME_RPC_POLLING_INTERVAL,
|
||||
ONE_DAY_TIMESTAMP,
|
||||
VALIDATOR_CONFIRMATION_STATUS
|
||||
} from '../config/constants'
|
||||
import {
|
||||
GetFailedTransactionParams,
|
||||
APITransaction,
|
||||
APIPendingTransaction,
|
||||
GetPendingTransactionParams
|
||||
} from './explorer'
|
||||
import { BasicConfirmationParam, ConfirmationParam } from '../hooks/useMessageConfirmations'
|
||||
|
||||
export const getValidatorConfirmation = (
|
||||
web3: Web3,
|
||||
hashMsg: string,
|
||||
bridgeContract: Contract,
|
||||
confirmationContractMethod: Function
|
||||
) => async (validator: string): Promise<BasicConfirmationParam> => {
|
||||
const hashSenderMsg = web3.utils.soliditySha3Raw(validator, hashMsg)
|
||||
|
||||
const signatureFromCache = validatorsCache.get(hashSenderMsg)
|
||||
if (signatureFromCache) {
|
||||
return {
|
||||
validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS
|
||||
}
|
||||
}
|
||||
|
||||
const confirmed = await confirmationContractMethod(bridgeContract, hashSenderMsg)
|
||||
const status = confirmed ? VALIDATOR_CONFIRMATION_STATUS.SUCCESS : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
|
||||
|
||||
// If validator confirmed signature, we cache the result to avoid doing future requests for a result that won't change
|
||||
if (confirmed) {
|
||||
validatorsCache.set(hashSenderMsg, confirmed)
|
||||
}
|
||||
|
||||
return {
|
||||
validator,
|
||||
status
|
||||
}
|
||||
}
|
||||
|
||||
export const getValidatorSuccessTransaction = (
|
||||
bridgeContract: Contract,
|
||||
messageData: string,
|
||||
timestamp: number,
|
||||
getSuccessTransactions: (args: GetFailedTransactionParams) => Promise<APITransaction[]>
|
||||
) => async (validatorData: BasicConfirmationParam): Promise<ConfirmationParam> => {
|
||||
const { validator } = validatorData
|
||||
const validatorCacheKey = `${CACHE_KEY_SUCCESS}${validatorData.validator}-${messageData}`
|
||||
const fromCache = validatorsCache.getData(validatorCacheKey)
|
||||
|
||||
if (fromCache && fromCache.txHash) {
|
||||
return fromCache
|
||||
}
|
||||
|
||||
const transactions = await getSuccessTransactions({
|
||||
account: validatorData.validator,
|
||||
to: bridgeContract.options.address,
|
||||
messageData,
|
||||
startTimestamp: timestamp,
|
||||
endTimestamp: timestamp + ONE_DAY_TIMESTAMP
|
||||
})
|
||||
|
||||
let txHashTimestamp = 0
|
||||
let txHash = ''
|
||||
const status = VALIDATOR_CONFIRMATION_STATUS.SUCCESS
|
||||
|
||||
if (transactions.length > 0) {
|
||||
const tx = transactions[0]
|
||||
txHashTimestamp = parseInt(tx.timeStamp)
|
||||
txHash = tx.hash
|
||||
|
||||
// cache the result
|
||||
validatorsCache.setData(validatorCacheKey, {
|
||||
validator,
|
||||
status,
|
||||
txHash,
|
||||
timestamp: txHashTimestamp
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
validator,
|
||||
status,
|
||||
txHash,
|
||||
timestamp: txHashTimestamp
|
||||
}
|
||||
}
|
||||
|
||||
export const getValidatorFailedTransaction = (
|
||||
bridgeContract: Contract,
|
||||
messageData: string,
|
||||
timestamp: number,
|
||||
getFailedTransactions: (args: GetFailedTransactionParams) => Promise<APITransaction[]>
|
||||
) => async (validatorData: BasicConfirmationParam): Promise<ConfirmationParam> => {
|
||||
const validatorCacheKey = `${CACHE_KEY_FAILED}${validatorData.validator}-${messageData}`
|
||||
const failedFromCache = validatorsCache.getData(validatorCacheKey)
|
||||
|
||||
if (failedFromCache && failedFromCache.txHash) {
|
||||
return failedFromCache
|
||||
}
|
||||
|
||||
const failedTransactions = await getFailedTransactions({
|
||||
account: validatorData.validator,
|
||||
to: bridgeContract.options.address,
|
||||
messageData,
|
||||
startTimestamp: timestamp,
|
||||
endTimestamp: timestamp + ONE_DAY_TIMESTAMP
|
||||
})
|
||||
const newStatus =
|
||||
failedTransactions.length > 0 ? VALIDATOR_CONFIRMATION_STATUS.FAILED : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
|
||||
|
||||
let txHashTimestamp = 0
|
||||
let txHash = ''
|
||||
// If validator signature failed, we cache the result to avoid doing future requests for a result that won't change
|
||||
if (failedTransactions.length > 0) {
|
||||
const failedTx = failedTransactions[0]
|
||||
txHashTimestamp = parseInt(failedTx.timeStamp)
|
||||
txHash = failedTx.hash
|
||||
|
||||
validatorsCache.setData(validatorCacheKey, {
|
||||
validator: validatorData.validator,
|
||||
status: newStatus,
|
||||
txHash,
|
||||
timestamp: txHashTimestamp
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
validator: validatorData.validator,
|
||||
status: newStatus,
|
||||
txHash,
|
||||
timestamp: txHashTimestamp
|
||||
}
|
||||
}
|
||||
|
||||
export const getValidatorPendingTransaction = (
|
||||
bridgeContract: Contract,
|
||||
messageData: string,
|
||||
getPendingTransactions: (args: GetPendingTransactionParams) => Promise<APIPendingTransaction[]>
|
||||
) => async (validatorData: BasicConfirmationParam): Promise<ConfirmationParam> => {
|
||||
const failedTransactions = await getPendingTransactions({
|
||||
account: validatorData.validator,
|
||||
to: bridgeContract.options.address,
|
||||
messageData
|
||||
})
|
||||
|
||||
const newStatus =
|
||||
failedTransactions.length > 0 ? VALIDATOR_CONFIRMATION_STATUS.PENDING : VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
|
||||
|
||||
let timestamp = 0
|
||||
let txHash = ''
|
||||
|
||||
if (failedTransactions.length > 0) {
|
||||
const failedTx = failedTransactions[0]
|
||||
timestamp = Math.floor(new Date().getTime() / 1000.0)
|
||||
txHash = failedTx.hash
|
||||
}
|
||||
|
||||
return {
|
||||
validator: validatorData.validator,
|
||||
status: newStatus,
|
||||
txHash,
|
||||
timestamp
|
||||
}
|
||||
}
|
||||
|
||||
export const getConfirmationsForTx = async (
|
||||
messageData: string,
|
||||
web3: Maybe<Web3>,
|
||||
validatorList: string[],
|
||||
bridgeContract: Maybe<Contract>,
|
||||
confirmationContractMethod: Function,
|
||||
setResult: Function,
|
||||
requiredSignatures: number,
|
||||
setSignatureCollected: Function,
|
||||
waitingBlocksResolved: boolean,
|
||||
subscriptions: number[],
|
||||
timestamp: number,
|
||||
getFailedTransactions: (args: GetFailedTransactionParams) => Promise<APITransaction[]>,
|
||||
setFailedConfirmations: Function,
|
||||
getPendingTransactions: (args: GetPendingTransactionParams) => Promise<APIPendingTransaction[]>,
|
||||
setPendingConfirmations: Function,
|
||||
getSuccessTransactions: (args: GetFailedTransactionParams) => Promise<APITransaction[]>
|
||||
) => {
|
||||
if (!web3 || !validatorList || !validatorList.length || !bridgeContract || !waitingBlocksResolved) return
|
||||
|
||||
// If all the information was not collected, then it should retry
|
||||
let shouldRetry = false
|
||||
const hashMsg = web3.utils.soliditySha3Raw(messageData)
|
||||
let validatorConfirmations = await Promise.all(
|
||||
validatorList.map(getValidatorConfirmation(web3, hashMsg, bridgeContract, confirmationContractMethod))
|
||||
)
|
||||
|
||||
const successConfirmations = validatorConfirmations.filter(c => c.status === VALIDATOR_CONFIRMATION_STATUS.SUCCESS)
|
||||
|
||||
const notSuccessConfirmations = validatorConfirmations.filter(c => c.status !== VALIDATOR_CONFIRMATION_STATUS.SUCCESS)
|
||||
|
||||
// If signatures not collected, it needs to retry in the next blocks
|
||||
if (successConfirmations.length !== requiredSignatures) {
|
||||
// Check if confirmation is pending
|
||||
const validatorPendingConfirmationsChecks = await Promise.all(
|
||||
notSuccessConfirmations.map(getValidatorPendingTransaction(bridgeContract, messageData, getPendingTransactions))
|
||||
)
|
||||
const validatorPendingConfirmations = validatorPendingConfirmationsChecks.filter(
|
||||
c => c.status === VALIDATOR_CONFIRMATION_STATUS.PENDING
|
||||
)
|
||||
|
||||
validatorPendingConfirmations.forEach(validatorData => {
|
||||
const index = validatorConfirmations.findIndex(e => e.validator === validatorData.validator)
|
||||
validatorConfirmations[index] = validatorData
|
||||
})
|
||||
|
||||
if (validatorPendingConfirmations.length > 0) {
|
||||
setPendingConfirmations(true)
|
||||
}
|
||||
|
||||
const undefinedConfirmations = validatorConfirmations.filter(
|
||||
c => c.status === VALIDATOR_CONFIRMATION_STATUS.UNDEFINED
|
||||
)
|
||||
// Check if confirmation failed
|
||||
const validatorFailedConfirmationsChecks = await Promise.all(
|
||||
undefinedConfirmations.map(
|
||||
getValidatorFailedTransaction(bridgeContract, messageData, timestamp, getFailedTransactions)
|
||||
)
|
||||
)
|
||||
const validatorFailedConfirmations = validatorFailedConfirmationsChecks.filter(
|
||||
c => c.status === VALIDATOR_CONFIRMATION_STATUS.FAILED
|
||||
)
|
||||
validatorFailedConfirmations.forEach(validatorData => {
|
||||
const index = validatorConfirmations.findIndex(e => e.validator === validatorData.validator)
|
||||
validatorConfirmations[index] = validatorData
|
||||
})
|
||||
const messageConfirmationsFailed = validatorFailedConfirmations.length > validatorList.length - requiredSignatures
|
||||
if (messageConfirmationsFailed) {
|
||||
setFailedConfirmations(true)
|
||||
}
|
||||
|
||||
const missingConfirmations = validatorConfirmations.filter(
|
||||
c => c.status === VALIDATOR_CONFIRMATION_STATUS.UNDEFINED || c.status === VALIDATOR_CONFIRMATION_STATUS.PENDING
|
||||
)
|
||||
|
||||
if (missingConfirmations.length > 0) {
|
||||
shouldRetry = true
|
||||
}
|
||||
} else {
|
||||
// If signatures collected, it should set other signatures as not required
|
||||
const notRequiredConfirmations = notSuccessConfirmations.map(c => ({
|
||||
validator: c.validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.NOT_REQUIRED
|
||||
}))
|
||||
|
||||
validatorConfirmations = [...successConfirmations, ...notRequiredConfirmations]
|
||||
setSignatureCollected(true)
|
||||
}
|
||||
|
||||
// Set confirmations to update UI and continue requesting the transactions for the signatures
|
||||
setResult(validatorConfirmations)
|
||||
|
||||
// get transactions from success signatures
|
||||
const successConfirmationWithData = await Promise.all(
|
||||
validatorConfirmations
|
||||
.filter(c => c.status === VALIDATOR_CONFIRMATION_STATUS.SUCCESS)
|
||||
.map(getValidatorSuccessTransaction(bridgeContract, messageData, timestamp, getSuccessTransactions))
|
||||
)
|
||||
|
||||
const successConfirmationWithTxFound = successConfirmationWithData.filter(v => v.txHash !== '')
|
||||
|
||||
const updatedValidatorConfirmations = [...validatorConfirmations]
|
||||
|
||||
if (successConfirmationWithTxFound.length > 0) {
|
||||
successConfirmationWithTxFound.forEach(validatorData => {
|
||||
const index = updatedValidatorConfirmations.findIndex(e => e.validator === validatorData.validator)
|
||||
updatedValidatorConfirmations[index] = validatorData
|
||||
})
|
||||
}
|
||||
|
||||
setResult(updatedValidatorConfirmations)
|
||||
|
||||
// Retry if not all transaction were found for validator confirmations
|
||||
if (successConfirmationWithTxFound.length < successConfirmationWithData.length) {
|
||||
shouldRetry = true
|
||||
}
|
||||
|
||||
if (shouldRetry) {
|
||||
const timeoutId = setTimeout(
|
||||
() =>
|
||||
getConfirmationsForTx(
|
||||
messageData,
|
||||
web3,
|
||||
validatorList,
|
||||
bridgeContract,
|
||||
confirmationContractMethod,
|
||||
setResult,
|
||||
requiredSignatures,
|
||||
setSignatureCollected,
|
||||
waitingBlocksResolved,
|
||||
subscriptions,
|
||||
timestamp,
|
||||
getFailedTransactions,
|
||||
setFailedConfirmations,
|
||||
getPendingTransactions,
|
||||
setPendingConfirmations,
|
||||
getSuccessTransactions
|
||||
),
|
||||
HOME_RPC_POLLING_INTERVAL
|
||||
)
|
||||
subscriptions.push(timeoutId)
|
||||
}
|
||||
}
|
||||
137
alm/src/utils/getFinalizationEvent.ts
Normal file
137
alm/src/utils/getFinalizationEvent.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
import { Contract, EventData } from 'web3-eth-contract'
|
||||
import Web3 from 'web3'
|
||||
import { CACHE_KEY_EXECUTION_FAILED, THREE_DAYS_TIMESTAMP, VALIDATOR_CONFIRMATION_STATUS } from '../config/constants'
|
||||
import { ExecutionData } from '../hooks/useMessageConfirmations'
|
||||
import {
|
||||
APIPendingTransaction,
|
||||
APITransaction,
|
||||
GetFailedTransactionParams,
|
||||
GetPendingTransactionParams
|
||||
} from './explorer'
|
||||
import { getBlock, MessageObject } from './web3'
|
||||
import validatorsCache from '../services/ValidatorsCache'
|
||||
|
||||
export const getFinalizationEvent = async (
|
||||
contract: Maybe<Contract>,
|
||||
eventName: string,
|
||||
web3: Maybe<Web3>,
|
||||
setResult: React.Dispatch<React.SetStateAction<ExecutionData>>,
|
||||
waitingBlocksResolved: boolean,
|
||||
message: MessageObject,
|
||||
interval: number,
|
||||
subscriptions: number[],
|
||||
timestamp: number,
|
||||
collectedSignaturesEvent: Maybe<EventData>,
|
||||
getFailedExecution: (args: GetFailedTransactionParams) => Promise<APITransaction[]>,
|
||||
setFailedExecution: Function,
|
||||
getPendingExecution: (args: GetPendingTransactionParams) => Promise<APIPendingTransaction[]>,
|
||||
setPendingExecution: Function
|
||||
) => {
|
||||
if (!contract || !web3 || !waitingBlocksResolved) return
|
||||
// Since it filters by the message id, only one event will be fetched
|
||||
// so there is no need to limit the range of the block to reduce the network traffic
|
||||
const events: EventData[] = await contract.getPastEvents(eventName, {
|
||||
fromBlock: 0,
|
||||
toBlock: 'latest',
|
||||
filter: {
|
||||
messageId: message.id
|
||||
}
|
||||
})
|
||||
if (events.length > 0) {
|
||||
const event = events[0]
|
||||
const [txReceipt, block] = await Promise.all([
|
||||
web3.eth.getTransactionReceipt(event.transactionHash),
|
||||
getBlock(web3, event.blockNumber)
|
||||
])
|
||||
|
||||
const blockTimestamp = typeof block.timestamp === 'string' ? parseInt(block.timestamp) : block.timestamp
|
||||
const validatorAddress = web3.utils.toChecksumAddress(txReceipt.from)
|
||||
|
||||
setResult({
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.SUCCESS,
|
||||
validator: validatorAddress,
|
||||
txHash: event.transactionHash,
|
||||
timestamp: blockTimestamp,
|
||||
executionResult: event.returnValues.status
|
||||
})
|
||||
} else {
|
||||
// If event is defined, it means it is a message from Home to Foreign
|
||||
if (collectedSignaturesEvent) {
|
||||
const validator = collectedSignaturesEvent.returnValues.authorityResponsibleForRelay
|
||||
|
||||
const pendingTransactions = await getPendingExecution({
|
||||
account: validator,
|
||||
messageData: message.data,
|
||||
to: contract.options.address
|
||||
})
|
||||
|
||||
// If the transaction is pending it sets the status and avoid making the request for failed transactions
|
||||
if (pendingTransactions.length > 0) {
|
||||
const pendingTx = pendingTransactions[0]
|
||||
|
||||
const nowTimestamp = Math.floor(new Date().getTime() / 1000.0)
|
||||
|
||||
setResult({
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.PENDING,
|
||||
validator: validator,
|
||||
txHash: pendingTx.hash,
|
||||
timestamp: nowTimestamp,
|
||||
executionResult: false
|
||||
})
|
||||
setPendingExecution(true)
|
||||
} else {
|
||||
const validatorExecutionCacheKey = `${CACHE_KEY_EXECUTION_FAILED}${validator}-${message.id}`
|
||||
const failedFromCache = validatorsCache.get(validatorExecutionCacheKey)
|
||||
|
||||
if (!failedFromCache) {
|
||||
const failedTransactions = await getFailedExecution({
|
||||
account: validator,
|
||||
to: contract.options.address,
|
||||
messageData: message.data,
|
||||
startTimestamp: timestamp,
|
||||
endTimestamp: timestamp + THREE_DAYS_TIMESTAMP
|
||||
})
|
||||
|
||||
if (failedTransactions.length > 0) {
|
||||
const failedTx = failedTransactions[0]
|
||||
|
||||
// If validator execution failed, we cache the result to avoid doing future requests for a result that won't change
|
||||
validatorsCache.set(validatorExecutionCacheKey, true)
|
||||
|
||||
const timestamp = parseInt(failedTx.timeStamp)
|
||||
setResult({
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.FAILED,
|
||||
validator: validator,
|
||||
txHash: failedTx.hash,
|
||||
timestamp,
|
||||
executionResult: false
|
||||
})
|
||||
setFailedExecution(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const timeoutId = setTimeout(
|
||||
() =>
|
||||
getFinalizationEvent(
|
||||
contract,
|
||||
eventName,
|
||||
web3,
|
||||
setResult,
|
||||
waitingBlocksResolved,
|
||||
message,
|
||||
interval,
|
||||
subscriptions,
|
||||
timestamp,
|
||||
collectedSignaturesEvent,
|
||||
getFailedExecution,
|
||||
setFailedExecution,
|
||||
getPendingExecution,
|
||||
setPendingExecution
|
||||
),
|
||||
interval
|
||||
)
|
||||
subscriptions.push(timeoutId)
|
||||
}
|
||||
}
|
||||
41
alm/src/utils/networks.ts
Normal file
41
alm/src/utils/networks.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { formatDistance } from 'date-fns'
|
||||
import { CONFIRMATIONS_STATUS_DESCRIPTION, TRANSACTION_STATUS_DESCRIPTION } from '../config/descriptions'
|
||||
import { FOREIGN_EXPLORER_TX_TEMPLATE, HOME_EXPLORER_TX_TEMPLATE } from '../config/constants'
|
||||
|
||||
export const validTxHash = (txHash: string) => /^0x[a-fA-F0-9]{64}$/.test(txHash)
|
||||
|
||||
export const formatTxHash = (txHash: string) => `${txHash.substring(0, 6)}...${txHash.substring(txHash.length - 4)}`
|
||||
|
||||
export const getExplorerTxUrl = (txHash: string, isHome: boolean) => {
|
||||
const template = isHome ? HOME_EXPLORER_TX_TEMPLATE : FOREIGN_EXPLORER_TX_TEMPLATE
|
||||
return template.replace('%s', txHash)
|
||||
}
|
||||
|
||||
export const formatTxHashExtended = (txHash: string) =>
|
||||
`${txHash.substring(0, 10)}...${txHash.substring(txHash.length - 8)}`
|
||||
|
||||
export const formatTimestamp = (timestamp: number): string => {
|
||||
const txDate = new Date(0).setUTCSeconds(timestamp)
|
||||
return formatDistance(txDate, new Date(), {
|
||||
addSuffix: true
|
||||
})
|
||||
}
|
||||
|
||||
export const getTransactionStatusDescription = (status: string, timestamp: Maybe<number> = null) => {
|
||||
let description = TRANSACTION_STATUS_DESCRIPTION[status]
|
||||
|
||||
if (timestamp) {
|
||||
description = description.replace('%t', formatTimestamp(timestamp))
|
||||
}
|
||||
|
||||
return description
|
||||
}
|
||||
|
||||
export const getConfirmationsStatusDescription = (status: string, home: string, foreign: string) => {
|
||||
let description = CONFIRMATIONS_STATUS_DESCRIPTION[status]
|
||||
|
||||
description = description.replace('%homeChain', home)
|
||||
description = description.replace('%foreignChain', foreign)
|
||||
|
||||
return description
|
||||
}
|
||||
50
alm/src/utils/signatureWaitingForBlocks.ts
Normal file
50
alm/src/utils/signatureWaitingForBlocks.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { VALIDATOR_CONFIRMATION_STATUS } from '../config/constants'
|
||||
import { BlockNumberProvider } from '../services/BlockNumberProvider'
|
||||
|
||||
export const checkSignaturesWaitingForBLocks = async (
|
||||
targetBlock: number,
|
||||
setWaitingStatus: Function,
|
||||
setWaitingBlocksResolved: Function,
|
||||
validatorList: string[],
|
||||
setConfirmations: Function,
|
||||
blockProvider: BlockNumberProvider,
|
||||
interval: number,
|
||||
subscriptions: number[]
|
||||
) => {
|
||||
const currentBlock = blockProvider.get()
|
||||
|
||||
if (currentBlock && currentBlock >= targetBlock) {
|
||||
setWaitingStatus(false)
|
||||
setWaitingBlocksResolved(true)
|
||||
blockProvider.stop()
|
||||
} else {
|
||||
let nextInterval = interval
|
||||
if (!currentBlock) {
|
||||
nextInterval = 500
|
||||
} else {
|
||||
const validatorsWaiting = validatorList.map(validator => {
|
||||
return {
|
||||
validator,
|
||||
status: VALIDATOR_CONFIRMATION_STATUS.WAITING
|
||||
}
|
||||
})
|
||||
setWaitingStatus(true)
|
||||
setConfirmations(validatorsWaiting)
|
||||
}
|
||||
const timeoutId = setTimeout(
|
||||
() =>
|
||||
checkSignaturesWaitingForBLocks(
|
||||
targetBlock,
|
||||
setWaitingStatus,
|
||||
setWaitingBlocksResolved,
|
||||
validatorList,
|
||||
setConfirmations,
|
||||
blockProvider,
|
||||
interval,
|
||||
subscriptions
|
||||
),
|
||||
nextInterval
|
||||
)
|
||||
subscriptions.push(timeoutId)
|
||||
}
|
||||
}
|
||||
63
alm/src/utils/web3.ts
Normal file
63
alm/src/utils/web3.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import Web3 from 'web3'
|
||||
import { BlockTransactionString } from 'web3-eth'
|
||||
import { TransactionReceipt } from 'web3-eth'
|
||||
import { AbiItem } from 'web3-utils'
|
||||
import memoize from 'fast-memoize'
|
||||
import promiseRetry from 'promise-retry'
|
||||
import { HOME_AMB_ABI, FOREIGN_AMB_ABI } from '../abis'
|
||||
|
||||
export interface MessageObject {
|
||||
id: string
|
||||
data: string
|
||||
}
|
||||
|
||||
const rawGetWeb3 = (url: string) => new Web3(new Web3.providers.HttpProvider(url))
|
||||
const memoized = memoize(rawGetWeb3)
|
||||
|
||||
export const getWeb3 = (url: string) => memoized(url)
|
||||
|
||||
export const filterEventsByAbi = (
|
||||
txReceipt: TransactionReceipt,
|
||||
web3: Web3,
|
||||
bridgeAddress: string,
|
||||
eventAbi: AbiItem
|
||||
): MessageObject[] => {
|
||||
const eventHash = web3.eth.abi.encodeEventSignature(eventAbi)
|
||||
const events = txReceipt.logs.filter(e => e.address === bridgeAddress && e.topics[0] === eventHash)
|
||||
|
||||
return events.map(e => {
|
||||
let decodedLogs: { [p: string]: string } = {
|
||||
messageId: '',
|
||||
encodedData: ''
|
||||
}
|
||||
if (eventAbi && eventAbi.inputs && eventAbi.inputs.length) {
|
||||
decodedLogs = web3.eth.abi.decodeLog(eventAbi.inputs, e.data, [e.topics[1]])
|
||||
}
|
||||
return { id: decodedLogs.messageId, data: decodedLogs.encodedData }
|
||||
})
|
||||
}
|
||||
|
||||
export const getHomeMessagesFromReceipt = (txReceipt: TransactionReceipt, web3: Web3, bridgeAddress: string) => {
|
||||
const UserRequestForSignatureAbi: AbiItem = HOME_AMB_ABI.filter(
|
||||
(e: AbiItem) => e.type === 'event' && e.name === 'UserRequestForSignature'
|
||||
)[0]
|
||||
return filterEventsByAbi(txReceipt, web3, bridgeAddress, UserRequestForSignatureAbi)
|
||||
}
|
||||
|
||||
export const getForeignMessagesFromReceipt = (txReceipt: TransactionReceipt, web3: Web3, bridgeAddress: string) => {
|
||||
const userRequestForAffirmationAbi: AbiItem = FOREIGN_AMB_ABI.filter(
|
||||
(e: AbiItem) => e.type === 'event' && e.name === 'UserRequestForAffirmation'
|
||||
)[0]
|
||||
return filterEventsByAbi(txReceipt, web3, bridgeAddress, userRequestForAffirmationAbi)
|
||||
}
|
||||
|
||||
// In some rare cases the block data is not available yet for the block of a new event detected
|
||||
// so this logic retry to get the block in case it fails
|
||||
export const getBlock = async (web3: Web3, blockNumber: number): Promise<BlockTransactionString> =>
|
||||
promiseRetry(async retry => {
|
||||
const result = await web3.eth.getBlock(blockNumber)
|
||||
if (!result) {
|
||||
return retry('Error getting block data')
|
||||
}
|
||||
return result
|
||||
})
|
||||
25
alm/tsconfig.json
Normal file
25
alm/tsconfig.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "preserve"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
}
|
||||
31
burner-wallet-plugin/.eslintrc.js
Normal file
31
burner-wallet-plugin/.eslintrc.js
Normal file
@@ -0,0 +1,31 @@
|
||||
module.exports = {
|
||||
parser: "@typescript-eslint/parser", // Specifies the ESLint parser
|
||||
extends: [
|
||||
"plugin:react/recommended",
|
||||
"plugin:@typescript-eslint/recommended", // Uses the recommended rules from @typescript-eslint/eslint-plugin
|
||||
"../.eslintrc"
|
||||
],
|
||||
parserOptions: {
|
||||
ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features
|
||||
sourceType: "module", // Allows for the use of imports
|
||||
ecmaFeatures: {
|
||||
jsx: true // Allows for the parsing of JSX
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off", // Reduce the use of 'any'
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
"@typescript-eslint/no-var-requires": "off",
|
||||
"react/prop-types": "off",
|
||||
"@typescript-eslint/ban-ts-ignore": "off",
|
||||
"@typescript-eslint/member-delimiter-style": "off",
|
||||
"@typescript-eslint/indent": "off",
|
||||
"@typescript-eslint/explicit-member-accessibility": "off"
|
||||
},
|
||||
settings: {
|
||||
react: {
|
||||
version: "detect",
|
||||
}
|
||||
}
|
||||
};
|
||||
30
burner-wallet-plugin/Dockerfile
Normal file
30
burner-wallet-plugin/Dockerfile
Normal file
@@ -0,0 +1,30 @@
|
||||
FROM node:12 as plugin-base
|
||||
|
||||
WORKDIR /mono
|
||||
COPY package.json .
|
||||
RUN mkdir -p contracts/node_modules
|
||||
|
||||
COPY burner-wallet-plugin/package.json ./burner-wallet-plugin/
|
||||
COPY burner-wallet-plugin/lerna.json ./burner-wallet-plugin/
|
||||
COPY burner-wallet-plugin/yarn.lock ./burner-wallet-plugin/
|
||||
COPY burner-wallet-plugin/tsconfig.json ./burner-wallet-plugin/
|
||||
COPY burner-wallet-plugin/tokenbridge-bw-exchange/package.json ./burner-wallet-plugin/tokenbridge-bw-exchange/
|
||||
COPY burner-wallet-plugin/staging/package.json ./burner-wallet-plugin/staging/
|
||||
COPY burner-wallet-plugin/testing/package.json ./burner-wallet-plugin/testing/
|
||||
COPY yarn.lock .
|
||||
RUN yarn install --production --frozen-lockfile
|
||||
|
||||
COPY ./burner-wallet-plugin/tokenbridge-bw-exchange ./burner-wallet-plugin/tokenbridge-bw-exchange
|
||||
RUN yarn build:plugin
|
||||
|
||||
|
||||
FROM plugin-base as testing
|
||||
COPY ./burner-wallet-plugin/testing ./burner-wallet-plugin/testing
|
||||
WORKDIR /mono/burner-wallet-plugin
|
||||
CMD ["yarn", "start-testing"]
|
||||
|
||||
|
||||
FROM plugin-base as staging
|
||||
COPY ./burner-wallet-plugin/staging ./burner-wallet-plugin/staging
|
||||
WORKDIR /mono/burner-wallet-plugin
|
||||
CMD ["yarn", "start-staging"]
|
||||
41
burner-wallet-plugin/README.md
Normal file
41
burner-wallet-plugin/README.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# TokenBridge Burner Wallet 2 Plugin
|
||||
|
||||
Please refer to the [Plugin README](./tokenrbdige-bw-exchange/README.md) for resources provided, instructions to install and use the plugin.
|
||||
|
||||
### Setup
|
||||
1. [Initialize](../README.md#initializing-the-monorepository) the monorepository.
|
||||
2. Run `yarn build` or from the monorepository root `yarn build:plugin`
|
||||
|
||||
### Run Burner Wallet with the plugin in Mainnet & Classic
|
||||
1. Create `.env` file in `staging` folder and set `REACT_APP_INFURA_KEY=<your key from infura.com>`
|
||||
2. Run `yarn start-staging` to start the wallet connected to Mainnet & Classic and interact with the ETH - WETC Bridge.
|
||||
|
||||
### Run Burner Wallet with the plugin in Sokol & Kovan
|
||||
1. Create `.env` file in `testing` folder and set `REACT_APP_INFURA_KEY=<your key from infura.com>`.
|
||||
Also, a private key can be set to start the wallet with the specified account `REACT_APP_PK=0x...`
|
||||
2. Run `yarn start-testing` to start the wallet connected to Sokol & Kovan and interact with a test bridge
|
||||
that works on top of the AMB bridge.
|
||||
|
||||
### Docker Setup
|
||||
Docker can be used to build the services and run the testing and staging wallets.
|
||||
|
||||
First you may want to create the `.env` files for testing and staging as mentioned before. This is optional before building the containers, variables can be passes later using `--env-file` or `--env` parameters in `docker run`.
|
||||
|
||||
Build the services with docker-compose:
|
||||
```bash
|
||||
docker-compose build
|
||||
```
|
||||
|
||||
### Run Burner Wallet with the plugin in Mainnet & Classic using Docker
|
||||
```bash
|
||||
docker run -ti -p 8080:8080 -e PORT=8080 --rm burner-wallet-plugin_staging
|
||||
```
|
||||
|
||||
### Run Burner Wallet with the plugin in Sokol & Kovan using Docker
|
||||
```bash
|
||||
docker run -ti -p 8080:8080 -e PORT=8080 --rm burner-wallet-plugin_testing
|
||||
```
|
||||
### Publish to npm
|
||||
In order to make this plugin accessible, it should be available as a npm package. Follow the [instructions](publish.md) to publish
|
||||
the package to npm registry.
|
||||
|
||||
17
burner-wallet-plugin/docker-compose.yml
Normal file
17
burner-wallet-plugin/docker-compose.yml
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
version: '2.4'
|
||||
services:
|
||||
staging:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: burner-wallet-plugin/Dockerfile
|
||||
target: staging
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
testing:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: burner-wallet-plugin/Dockerfile
|
||||
target: testing
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
10
burner-wallet-plugin/lerna.json
Normal file
10
burner-wallet-plugin/lerna.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"packages": [
|
||||
"basic-wallet",
|
||||
"local-wallet",
|
||||
"tokenbridge-bw-exchange"
|
||||
],
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"version": "independent"
|
||||
}
|
||||
28
burner-wallet-plugin/package.json
Normal file
28
burner-wallet-plugin/package.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "burner-wallet-plugin",
|
||||
"description": "Burner Wallet 2 plugin",
|
||||
"version": "1.0.0",
|
||||
"license": "GPL-3.0-only",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"install": "lerna bootstrap",
|
||||
"build": "lerna run --ignore testing --ignore staging build --stream",
|
||||
"lint": "eslint '*/**/*.{js,ts,tsx}' --ignore-path ../.eslintignore",
|
||||
"start-staging": "lerna run --scope staging start --stream",
|
||||
"start-testing": "lerna run --scope testing start --stream",
|
||||
"test": "lerna run --ignore testing --ignore staging test --stream"
|
||||
},
|
||||
"workspaces": [
|
||||
"staging",
|
||||
"testing",
|
||||
"tokenbridge-bw-exchange"
|
||||
],
|
||||
"dependencies": {
|
||||
"@types/color": "3.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "1.13.0",
|
||||
"@typescript-eslint/parser": "1.13.0",
|
||||
"eslint-plugin-react": "7.19.0",
|
||||
"lerna": "3.16.4",
|
||||
"typescript": "3.5.3"
|
||||
}
|
||||
}
|
||||
36
burner-wallet-plugin/publish.md
Normal file
36
burner-wallet-plugin/publish.md
Normal file
@@ -0,0 +1,36 @@
|
||||
## Plugin Package Information
|
||||
|
||||
The package to be published gets its configuration from `tokenbridge/burner-wallet-plugin/tokenbridge-bw-exchange/package.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "tokenbridge-bw-exchange",
|
||||
"version": "1.0.0",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"files": [
|
||||
"/dist"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
- `name` is the name of how package will be available in npm.
|
||||
- `main` is entry point for the package
|
||||
- `types` is the entry point for typescript types
|
||||
- `files` is the list of files included when publishing the package. So we have to run `yarn build` first to
|
||||
generate the `dist` folder.
|
||||
|
||||
## Steps to publish to npm
|
||||
|
||||
1. Create account in https://www.npmjs.com/
|
||||
|
||||
2. Go to `tokenbridge/burner-wallet-plugin/tokenbridge-bw-exchange/`
|
||||
|
||||
3. Run `yarn build`. Make sure it generates the `dist` folder
|
||||
|
||||
4. Update `version` in `tokenbridge/burner-wallet-plugin/tokenbridge-bw-exchange/package.json`
|
||||
5. Run `yarn login` and fill login information if required.
|
||||
6. Run `yarn publish --access public`.
|
||||
The prompt will ask for the new version, complete it with the version from `package.json`
|
||||
|
||||
More information in https://classic.yarnpkg.com/en/docs/publishing-a-package/
|
||||
1
burner-wallet-plugin/staging/.env.example
Normal file
1
burner-wallet-plugin/staging/.env.example
Normal file
@@ -0,0 +1 @@
|
||||
REACT_APP_INFURA_KEY=
|
||||
40
burner-wallet-plugin/staging/package.json
Normal file
40
burner-wallet-plugin/staging/package.json
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "staging",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@burner-wallet/assets": "^1.1.10",
|
||||
"@burner-wallet/core": "^1.1.0",
|
||||
"@burner-wallet/exchange": "^1.1.4",
|
||||
"@burner-wallet/metamask-plugin": "^1.0.0",
|
||||
"@burner-wallet/modern-ui": "^1.0.7",
|
||||
"@poanet/tokenbridge-bw-exchange": "^1.0.0",
|
||||
"@types/node": "12.0.4",
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "16.8.4",
|
||||
"@types/react-router-dom": "^4.3.3",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6",
|
||||
"react-scripts": "3.0.1",
|
||||
"typescript": "3.5.1"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
38
burner-wallet-plugin/staging/public/index.html
Normal file
38
burner-wallet-plugin/staging/public/index.html
Normal file
@@ -0,0 +1,38 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>React App</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
||||
15
burner-wallet-plugin/staging/public/manifest.json
Normal file
15
burner-wallet-plugin/staging/public/manifest.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
21
burner-wallet-plugin/staging/src/index.tsx
Normal file
21
burner-wallet-plugin/staging/src/index.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import BurnerCore from '@burner-wallet/core'
|
||||
import { InjectedSigner, LocalSigner } from '@burner-wallet/core/signers'
|
||||
import { InfuraGateway, InjectedGateway } from '@burner-wallet/core/gateways'
|
||||
import Exchange from '@burner-wallet/exchange'
|
||||
import ModernUI from '@burner-wallet/modern-ui'
|
||||
import { Etc, Wetc, TokenBridgeGateway, WETCBridge } from '@poanet/tokenbridge-bw-exchange'
|
||||
import MetamaskPlugin from '@burner-wallet/metamask-plugin'
|
||||
|
||||
const core = new BurnerCore({
|
||||
signers: [new InjectedSigner(), new LocalSigner()],
|
||||
gateways: [new InjectedGateway(), new InfuraGateway(process.env.REACT_APP_INFURA_KEY), new TokenBridgeGateway()],
|
||||
assets: [Wetc, Etc]
|
||||
})
|
||||
|
||||
const exchange = new Exchange([new WETCBridge()])
|
||||
|
||||
const BurnerWallet = () => <ModernUI title="Staging Wallet" core={core} plugins={[exchange, new MetamaskPlugin()]} />
|
||||
|
||||
ReactDOM.render(<BurnerWallet />, document.getElementById('root'))
|
||||
1
burner-wallet-plugin/staging/src/react-app-env.d.ts
vendored
Normal file
1
burner-wallet-plugin/staging/src/react-app-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="react-scripts" />
|
||||
25
burner-wallet-plugin/staging/tsconfig.json
Normal file
25
burner-wallet-plugin/staging/tsconfig.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "preserve"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
}
|
||||
14
burner-wallet-plugin/testing/.env.example
Normal file
14
burner-wallet-plugin/testing/.env.example
Normal file
@@ -0,0 +1,14 @@
|
||||
REACT_APP_INFURA_KEY=
|
||||
#REACT_APP_PK=0x
|
||||
|
||||
REACT_APP_MODE=AMB_NATIVE_TO_ERC677
|
||||
|
||||
REACT_APP_HOME_TOKEN_NAME=sPOA
|
||||
REACT_APP_HOME_NETWORK=77
|
||||
REACT_APP_HOME_MEDIATOR_ADDRESS=0x867949C3F2f66D827Ed40847FaA7B3a369370e13
|
||||
REACT_APP_HOME_TOKEN_ADDRESS=
|
||||
|
||||
REACT_APP_FOREIGN_TOKEN_NAME=ksPOA
|
||||
REACT_APP_FOREIGN_NETWORK=42
|
||||
REACT_APP_FOREIGN_MEDIATOR_ADDRESS=0x99FB1a25caeB9c3a5Bf132686E2fe5e27BC0e2dd
|
||||
REACT_APP_FOREIGN_TOKEN_ADDRESS=0xff94183659f549D6273349696d73686Ee1d2AC83
|
||||
42
burner-wallet-plugin/testing/package.json
Normal file
42
burner-wallet-plugin/testing/package.json
Normal file
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"name": "testing",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@burner-wallet/assets": "^1.1.10",
|
||||
"@burner-wallet/core": "^1.1.0",
|
||||
"@burner-wallet/exchange": "^1.1.4",
|
||||
"@burner-wallet/metamask-plugin": "^1.0.0",
|
||||
"@burner-wallet/modern-ui": "^1.0.7",
|
||||
"@poanet/tokenbridge-bw-exchange": "^1.0.0",
|
||||
"@types/node": "12.0.4",
|
||||
"@types/react": "16.8.19",
|
||||
"@types/react-dom": "16.8.4",
|
||||
"@types/react-router-dom": "^4.3.3",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6",
|
||||
"react-scripts": "3.0.1",
|
||||
"typescript": "3.5.1"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"axios": "^0.19.0"
|
||||
}
|
||||
}
|
||||
BIN
burner-wallet-plugin/testing/public/favicon.ico
Normal file
BIN
burner-wallet-plugin/testing/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
38
burner-wallet-plugin/testing/public/index.html
Normal file
38
burner-wallet-plugin/testing/public/index.html
Normal file
@@ -0,0 +1,38 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>React App</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
||||
15
burner-wallet-plugin/testing/public/manifest.json
Normal file
15
burner-wallet-plugin/testing/public/manifest.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"short_name": "Burner Wallet",
|
||||
"name": "Burner Wallet",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
53
burner-wallet-plugin/testing/src/LocalhostGateway.ts
Normal file
53
burner-wallet-plugin/testing/src/LocalhostGateway.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { Gateway } from '@burner-wallet/core/gateways'
|
||||
import Web3 from 'web3'
|
||||
|
||||
export default class LocalhostGateway extends Gateway {
|
||||
private readonly providers: object
|
||||
private readonly providerStrings: { [id: string]: string }
|
||||
constructor() {
|
||||
super()
|
||||
this.providerStrings = {
|
||||
'111': 'http://localhost:8545',
|
||||
'1337': 'http://localhost:8546'
|
||||
}
|
||||
this.providers = {}
|
||||
}
|
||||
|
||||
isAvailable() {
|
||||
return true
|
||||
}
|
||||
|
||||
getNetworks() {
|
||||
return ['111', '1337']
|
||||
}
|
||||
|
||||
_provider(network) {
|
||||
if (!this.providers[network]) {
|
||||
this._makeProvider(network)
|
||||
}
|
||||
return this.providers[network]
|
||||
}
|
||||
|
||||
_makeProvider(network) {
|
||||
if (!this.providerStrings[network]) {
|
||||
throw new Error(`Network ${network} not supported by LocalhostGateway`)
|
||||
}
|
||||
|
||||
this.providers[network] = new Web3.providers.HttpProvider(this.providerStrings[network])
|
||||
}
|
||||
|
||||
send(network, payload) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (this.getNetworks().indexOf(network) === -1) {
|
||||
return reject(new Error('LocalhostGateway does not support this network'))
|
||||
}
|
||||
|
||||
this._provider(network).send(payload, (err, response) => {
|
||||
if (err) {
|
||||
return reject(err)
|
||||
}
|
||||
return resolve(response.result)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
125
burner-wallet-plugin/testing/src/index.tsx
Normal file
125
burner-wallet-plugin/testing/src/index.tsx
Normal file
@@ -0,0 +1,125 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import { Asset } from '@burner-wallet/assets'
|
||||
import BurnerCore from '@burner-wallet/core'
|
||||
import { InjectedSigner, LocalSigner } from '@burner-wallet/core/signers'
|
||||
import { InfuraGateway, InjectedGateway } from '@burner-wallet/core/gateways'
|
||||
import ModernUI from '@burner-wallet/modern-ui'
|
||||
import Exchange from '@burner-wallet/exchange'
|
||||
import {
|
||||
Mediator,
|
||||
sPOA,
|
||||
ERC677Asset,
|
||||
TokenBridgeGateway,
|
||||
NativeMediatorAsset,
|
||||
MediatorErcToNative,
|
||||
BridgeableERC20Asset
|
||||
} from '@poanet/tokenbridge-bw-exchange'
|
||||
import MetamaskPlugin from '@burner-wallet/metamask-plugin'
|
||||
import LocalhostGateway from './LocalhostGateway'
|
||||
|
||||
let assetIdAtHome = 'assetAtHome'
|
||||
const assetIdAtForeign = 'assetAtForeign'
|
||||
let assetAtHome: Asset
|
||||
let assetAtForeign: Asset
|
||||
let testBridge: Mediator
|
||||
|
||||
if (process.env.REACT_APP_MODE === 'AMB_NATIVE_TO_ERC677') {
|
||||
sPOA.setMediatorAddress(process.env.REACT_APP_HOME_MEDIATOR_ADDRESS)
|
||||
assetAtHome = sPOA
|
||||
assetIdAtHome = sPOA.id
|
||||
|
||||
assetAtForeign = new ERC677Asset({
|
||||
id: 'assetAtForeign',
|
||||
// @ts-ignore
|
||||
name: process.env.REACT_APP_FOREIGN_TOKEN_NAME,
|
||||
// @ts-ignore
|
||||
network: process.env.REACT_APP_FOREIGN_NETWORK,
|
||||
// @ts-ignore
|
||||
address: process.env.REACT_APP_FOREIGN_TOKEN_ADDRESS
|
||||
})
|
||||
|
||||
testBridge = new Mediator({
|
||||
assetA: assetIdAtHome,
|
||||
// @ts-ignore
|
||||
assetABridge: process.env.REACT_APP_HOME_MEDIATOR_ADDRESS,
|
||||
assetB: assetIdAtForeign,
|
||||
// @ts-ignore
|
||||
assetBBridge: process.env.REACT_APP_FOREIGN_MEDIATOR_ADDRESS
|
||||
})
|
||||
} else if (process.env.REACT_APP_MODE === 'AMB_ERC677_TO_ERC677') {
|
||||
assetAtHome = new ERC677Asset({
|
||||
id: 'assetAtHome',
|
||||
// @ts-ignore
|
||||
name: process.env.REACT_APP_HOME_TOKEN_NAME,
|
||||
// @ts-ignore
|
||||
network: process.env.REACT_APP_HOME_NETWORK,
|
||||
// @ts-ignore
|
||||
address: process.env.REACT_APP_HOME_TOKEN_ADDRESS
|
||||
})
|
||||
|
||||
assetAtForeign = new ERC677Asset({
|
||||
id: 'assetAtForeign',
|
||||
// @ts-ignore
|
||||
name: process.env.REACT_APP_FOREIGN_TOKEN_NAME,
|
||||
// @ts-ignore
|
||||
network: process.env.REACT_APP_FOREIGN_NETWORK,
|
||||
// @ts-ignore
|
||||
address: process.env.REACT_APP_FOREIGN_TOKEN_ADDRESS
|
||||
})
|
||||
|
||||
testBridge = new Mediator({
|
||||
assetA: assetIdAtHome,
|
||||
// @ts-ignore
|
||||
assetABridge: process.env.REACT_APP_HOME_MEDIATOR_ADDRESS,
|
||||
assetB: assetIdAtForeign,
|
||||
// @ts-ignore
|
||||
assetBBridge: process.env.REACT_APP_FOREIGN_MEDIATOR_ADDRESS
|
||||
})
|
||||
} else {
|
||||
// process.env.REACT_APP_MODE === 'AMB_ERC20_TO_NATIVE'
|
||||
assetAtHome = new NativeMediatorAsset({
|
||||
id: assetIdAtHome,
|
||||
name: 'qDAI',
|
||||
network: process.env.REACT_APP_HOME_NETWORK as string,
|
||||
mediatorAddress: process.env.REACT_APP_HOME_MEDIATOR_ADDRESS
|
||||
})
|
||||
|
||||
assetAtForeign = new BridgeableERC20Asset({
|
||||
id: 'assetAtForeign',
|
||||
// @ts-ignore
|
||||
name: process.env.REACT_APP_FOREIGN_TOKEN_NAME,
|
||||
// @ts-ignore
|
||||
network: process.env.REACT_APP_FOREIGN_NETWORK,
|
||||
// @ts-ignore
|
||||
address: process.env.REACT_APP_FOREIGN_TOKEN_ADDRESS,
|
||||
// @ts-ignore
|
||||
bridgeAddress: process.env.REACT_APP_FOREIGN_MEDIATOR_ADDRESS
|
||||
})
|
||||
|
||||
testBridge = new MediatorErcToNative({
|
||||
assetA: assetIdAtHome,
|
||||
// @ts-ignore
|
||||
assetABridge: process.env.REACT_APP_HOME_MEDIATOR_ADDRESS,
|
||||
assetB: assetIdAtForeign,
|
||||
// @ts-ignore
|
||||
assetBBridge: process.env.REACT_APP_FOREIGN_MEDIATOR_ADDRESS
|
||||
})
|
||||
}
|
||||
|
||||
const core = new BurnerCore({
|
||||
signers: [new InjectedSigner(), new LocalSigner({ privateKey: process.env.REACT_APP_PK, saveKey: false })],
|
||||
gateways: [
|
||||
new InjectedGateway(),
|
||||
new LocalhostGateway(),
|
||||
new TokenBridgeGateway(),
|
||||
new InfuraGateway(process.env.REACT_APP_INFURA_KEY)
|
||||
],
|
||||
assets: [assetAtHome, assetAtForeign]
|
||||
})
|
||||
|
||||
const exchange = new Exchange([testBridge])
|
||||
|
||||
const BurnerWallet = () => <ModernUI title="Testing Wallet" core={core} plugins={[exchange, new MetamaskPlugin()]} />
|
||||
|
||||
ReactDOM.render(<BurnerWallet />, document.getElementById('root'))
|
||||
1
burner-wallet-plugin/testing/src/react-app-env.d.ts
vendored
Normal file
1
burner-wallet-plugin/testing/src/react-app-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="react-scripts" />
|
||||
26
burner-wallet-plugin/testing/tsconfig.json
Normal file
26
burner-wallet-plugin/testing/tsconfig.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"noImplicitAny": false,
|
||||
"jsx": "preserve"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
}
|
||||
37
burner-wallet-plugin/tokenbridge-bw-exchange/README.md
Normal file
37
burner-wallet-plugin/tokenbridge-bw-exchange/README.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# TokenBridge Burner Wallet 2 Plugin
|
||||
|
||||
This plugin defines a Bridge trading pair to be used in the Exchange Plugin.
|
||||
|
||||
Bridge trading pairs and assets supported:
|
||||
* ETC - WETC Bridge
|
||||
|
||||
It also provides some generic resources that can be used and extended:
|
||||
* **ERC677Asset** - A representation of an Erc677 token
|
||||
* **NativeMediatorAsset** - Represents a native token that interacts with a Mediator extension.
|
||||
* **Mediator Pair** - Represents an Exchange Pair that interacts with mediators extensions.
|
||||
* **TokenBridgeGateway** - A gateway to operate with ETC, POA Sokol and POA Core networks.
|
||||
|
||||
### Install package
|
||||
```
|
||||
yarn add @poanet/tokenbridge-bw-exchange
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```javascript
|
||||
import { Etc, Wetc, EtcGateway, WETCBridge } from '@poanet/tokenbridge-bw-exchange'
|
||||
|
||||
const core = new BurnerCore({
|
||||
...
|
||||
gateways: [new EtcGateway(), new InfuraGateway(process.env.REACT_APP_INFURA_KEY)],
|
||||
assets: [Etc, Wetc]
|
||||
})
|
||||
|
||||
const exchange = new Exchange({
|
||||
pairs: [new WETCBridge()]
|
||||
})
|
||||
```
|
||||
|
||||
This is how the exchange plugin will look like:
|
||||
|
||||

|
||||
40
burner-wallet-plugin/tokenbridge-bw-exchange/package.json
Normal file
40
burner-wallet-plugin/tokenbridge-bw-exchange/package.json
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "@poanet/tokenbridge-bw-exchange",
|
||||
"version": "1.0.0",
|
||||
"license": "GPL-3.0",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"files": [
|
||||
"/dist",
|
||||
"README.md"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"start-basic": "tsc -w",
|
||||
"start-local": "tsc -w",
|
||||
"test": "TS_NODE_PROJECT=\"tsconfig.testing.json\" mocha -r ts-node/register test/**/*.spec.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@burner-wallet/assets": "^1.1.10",
|
||||
"@burner-wallet/core": "^1.1.9",
|
||||
"@burner-wallet/exchange": "^1.1.4",
|
||||
"@burner-wallet/types": "^1.0.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mocha": "^7.0.2",
|
||||
"chai": "^4.2.0",
|
||||
"mocha": "^5.2.0",
|
||||
"ts-node": "^8.8.2",
|
||||
"typescript": "^3.5.2"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/poanetwork/tokenbridge.git",
|
||||
"directory": "burner-wallet-plugin/tokenbridge-bw-exchange"
|
||||
},
|
||||
"homepage": "https://tokenbridge.net/",
|
||||
"keywords": [
|
||||
"tokenbridge",
|
||||
"burner-wallet"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import { ERC20Asset } from '@burner-wallet/assets'
|
||||
import { MEDIATOR_ABI, constants } from '../../utils'
|
||||
import { toBN } from 'web3-utils'
|
||||
|
||||
interface BridgeableERC20Constructor {
|
||||
abi?: object
|
||||
address: string
|
||||
id: string
|
||||
name: string
|
||||
network: string
|
||||
bridgeAddress: string
|
||||
}
|
||||
|
||||
export default class BridgeableERC20Asset extends ERC20Asset {
|
||||
protected bridgeAddress: string
|
||||
private _bridge
|
||||
|
||||
constructor({ bridgeAddress, ...params }: BridgeableERC20Constructor) {
|
||||
super({ ...params })
|
||||
this.bridgeAddress = bridgeAddress.toLowerCase()
|
||||
}
|
||||
|
||||
getBridgeContract() {
|
||||
if (!this._bridge) {
|
||||
const Contract = this.getWeb3().eth.Contract
|
||||
this._bridge = new Contract(MEDIATOR_ABI, this.bridgeAddress)
|
||||
}
|
||||
return this._bridge
|
||||
}
|
||||
|
||||
async _send({ from, to, value }) {
|
||||
if (to.toLowerCase() === this.bridgeAddress) {
|
||||
const allowance = await this.allowance(from, to)
|
||||
if (toBN(allowance).lt(toBN(value))) {
|
||||
await this.approve(from, to, value)
|
||||
}
|
||||
const receipt = await this.getBridgeContract()
|
||||
.methods.relayTokens(from, value)
|
||||
.send({ from })
|
||||
const transferLog = Object.values(receipt.events as object).find(
|
||||
e => e.raw.topics[0] === constants.TRANSFER_TOPIC
|
||||
)
|
||||
return {
|
||||
...receipt,
|
||||
txHash: receipt.transactionHash,
|
||||
id: `${receipt.transactionHash}-${transferLog.logIndex}`
|
||||
}
|
||||
}
|
||||
return super._send({ from, to, value })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import { ERC20Asset } from '@burner-wallet/assets'
|
||||
import { ERC677_ABI, constants } from '../../utils'
|
||||
|
||||
const BLOCK_LOOKBACK = 250
|
||||
|
||||
interface ERC677Constructor {
|
||||
abi?: object
|
||||
address: string
|
||||
id: string
|
||||
name: string
|
||||
network: string
|
||||
}
|
||||
|
||||
export default class ERC677Asset extends ERC20Asset {
|
||||
constructor({ abi = ERC677_ABI, ...params }: ERC677Constructor) {
|
||||
super({ abi, type: 'erc677', ...params })
|
||||
}
|
||||
|
||||
async _send({ from, to, value }) {
|
||||
const receipt = await this.getContract()
|
||||
.methods.transferAndCall(to, value, '0x')
|
||||
.send({ from })
|
||||
return {
|
||||
...receipt,
|
||||
txHash: receipt.transactionHash,
|
||||
id: `${receipt.transactionHash}-${receipt.events.Transfer.logIndex}`
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides ERC20Asset `startWatchingAddress` to get the `Transfer` events by topic instead of
|
||||
* the event name because ERC677 abi has two events definitions named `Transfer` and
|
||||
* `getPastEvents` method does not provide a way to choose the correct one to use.
|
||||
* @param address
|
||||
*/
|
||||
startWatchingAddress(address) {
|
||||
let block = 0
|
||||
return this.poll(async () => {
|
||||
const currentBlock = await this.getWeb3().eth.getBlockNumber()
|
||||
if (block === 0) {
|
||||
block = Math.max(currentBlock - BLOCK_LOOKBACK, 0)
|
||||
}
|
||||
|
||||
const allTransferEvents = await this.getContract().getPastEvents('allEvents', {
|
||||
fromBlock: block,
|
||||
toBlock: currentBlock,
|
||||
topics: [constants.TRANSFER_TOPIC]
|
||||
})
|
||||
// Manually filter `to` parameter because `filter` option does not work with allEvents
|
||||
const events = allTransferEvents.filter(e => e.returnValues.to.toLowerCase() === address.toLowerCase())
|
||||
|
||||
await events.map(async event =>
|
||||
this.core.addHistoryEvent({
|
||||
id: `${event.transactionHash}-${event.logIndex}`,
|
||||
asset: this.id,
|
||||
type: 'send',
|
||||
value: event.returnValues.value.toString(),
|
||||
from: event.returnValues.from,
|
||||
to: event.returnValues.to,
|
||||
tx: event.transactionHash,
|
||||
timestamp: await this._getBlockTimestamp(event.blockNumber)
|
||||
})
|
||||
)
|
||||
|
||||
block = currentBlock
|
||||
}, this._pollInterval)
|
||||
}
|
||||
|
||||
async getTx(txHash) {
|
||||
const historyEvents = this.core.getHistoryEvents({ asset: this.id })
|
||||
const eventMatch = historyEvents.filter(e => e.tx === txHash)
|
||||
if (eventMatch.length > 0) {
|
||||
return eventMatch[0]
|
||||
} else {
|
||||
return super.getTx(txHash)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import NativeMediatorAsset from './NativeMediatorAsset'
|
||||
import { isBridgeContract, HOME_NATIVE_TO_ERC_ABI } from '../../utils'
|
||||
|
||||
class EtcNativeAsset extends NativeMediatorAsset {
|
||||
constructor(props) {
|
||||
super({ mediatorAddress: '0x073081832B4Ecdce79d4D6753565c85Ba4b3BeA9', ...props })
|
||||
}
|
||||
|
||||
async scanMediatorEvents(address, fromBlock, toBlock) {
|
||||
const web3 = this.getWeb3()
|
||||
const contract = new web3.eth.Contract(HOME_NATIVE_TO_ERC_ABI, this.mediatorAddress)
|
||||
const listenToBridgeEvent = await isBridgeContract(contract)
|
||||
if (listenToBridgeEvent && this.mediatorAddress != '') {
|
||||
const events = await contract.getPastEvents('AffirmationCompleted', {
|
||||
fromBlock,
|
||||
toBlock
|
||||
})
|
||||
const filteredEvents = events.filter(
|
||||
event => event.returnValues.recipient.toLowerCase() === address.toLowerCase()
|
||||
)
|
||||
|
||||
for (const event of filteredEvents) {
|
||||
this.core.addHistoryEvent({
|
||||
id: `${event.transactionHash}-${event.logIndex}`,
|
||||
asset: this.id,
|
||||
type: 'send',
|
||||
value: event.returnValues.value.toString(),
|
||||
from: this.mediatorAddress,
|
||||
to: event.returnValues.recipient,
|
||||
tx: event.transactionHash,
|
||||
timestamp: await this._getBlockTimestamp(event.blockNumber)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
await super.scanMediatorEvents(address, fromBlock, toBlock)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new EtcNativeAsset({
|
||||
id: 'etc',
|
||||
name: 'ETC',
|
||||
network: '61',
|
||||
icon: 'https://user-images.githubusercontent.com/4614574/77648741-666cf800-6f47-11ea-8cb4-01b9db00c264.png'
|
||||
})
|
||||
@@ -0,0 +1,65 @@
|
||||
import { NativeAsset } from '@burner-wallet/assets'
|
||||
import { Contract, EventData } from 'web3-eth-contract'
|
||||
import { MEDIATOR_ABI } from '../../utils'
|
||||
|
||||
interface NativeMediatorConstructor {
|
||||
mediatorAddress?: string
|
||||
id: string
|
||||
name: string
|
||||
network: string
|
||||
}
|
||||
|
||||
export default class NativeMediatorAsset extends NativeAsset {
|
||||
protected mediatorAddress: string
|
||||
|
||||
constructor({ mediatorAddress = '', ...params }: NativeMediatorConstructor) {
|
||||
super({ ...params })
|
||||
this.mediatorAddress = mediatorAddress
|
||||
}
|
||||
|
||||
async scanBlocks(address, fromBlock, toBlock) {
|
||||
await super.scanBlocks(address, fromBlock, toBlock)
|
||||
await this.scanMediatorEvents(address, fromBlock, toBlock)
|
||||
}
|
||||
|
||||
async getTx(txHash) {
|
||||
const historyEvents = this.core.getHistoryEvents({ asset: this.id, account: this.mediatorAddress })
|
||||
const eventMatch = historyEvents.filter(e => e.tx === txHash)
|
||||
if (eventMatch.length > 0) {
|
||||
return eventMatch[0]
|
||||
} else {
|
||||
return super.getTx(txHash)
|
||||
}
|
||||
}
|
||||
|
||||
async scanMediatorEvents(address, fromBlock, toBlock) {
|
||||
if (this.mediatorAddress != '') {
|
||||
const web3 = this.getWeb3()
|
||||
const contract: Contract = new web3.eth.Contract(MEDIATOR_ABI, this.mediatorAddress)
|
||||
const events: EventData[] = await contract.getPastEvents('TokensBridged', {
|
||||
fromBlock,
|
||||
toBlock,
|
||||
filter: {
|
||||
recipient: address
|
||||
}
|
||||
})
|
||||
|
||||
for (const event of events) {
|
||||
this.core.addHistoryEvent({
|
||||
id: `${event.transactionHash}-${event.logIndex}`,
|
||||
asset: this.id,
|
||||
type: 'send',
|
||||
value: event.returnValues.value.toString(),
|
||||
from: this.mediatorAddress,
|
||||
to: event.returnValues.recipient,
|
||||
tx: event.transactionHash,
|
||||
timestamp: await this._getBlockTimestamp(event.blockNumber)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setMediatorAddress(mediatorAddress) {
|
||||
this.mediatorAddress = mediatorAddress
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { default as ERC677Asset } from './ERC677Asset'
|
||||
|
||||
export default new ERC677Asset({
|
||||
id: 'wetc',
|
||||
name: 'WETC',
|
||||
network: '1',
|
||||
address: '0x86aabcc646f290b9fc9bd05ce17c3858d1511da1'
|
||||
})
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user