diff --git a/.env.example b/.env.example index 4fcaed68..ba988a59 100644 --- a/.env.example +++ b/.env.example @@ -2,4 +2,22 @@ REACT_APP_HOME_BRIDGE_ADDRESS=0xABb4C1399DcC28FBa3Beb76CAE2b50Be3e087353 REACT_APP_FOREIGN_BRIDGE_ADDRESS=0xE405F6872cE38a7a4Ff63DcF946236D458c2ca3a REACT_APP_FOREIGN_HTTP_PARITY_URL=https://kovan.infura.io/mew REACT_APP_HOME_HTTP_PARITY_URL=https://sokol.poa.network -REACT_APP_GAS_PRICE_SPEED_TYPE=fast +REACT_APP_HOME_NATIVE_NAME=POA + +REACT_APP_HOME_NETWORK_NAME="POA Sokol" +REACT_APP_FOREIGN_NETWORK_NAME=Kovan + +REACT_APP_HOME_EXPLORER_TX_TEMPLATE=https://blockscout.com/poa/sokol/tx/%s +REACT_APP_FOREIGN_EXPLORER_TX_TEMPLATE=https://blockscout.com/eth/kovan/tx/%s +REACT_APP_HOME_EXPLORER_ADDRESS_TEMPLATE=https://blockscout.com/poa/sokol/address/%s +REACT_APP_FOREIGN_EXPLORER_ADDRESS_TEMPLATE=https://blockscout.com/eth/kovan/address/%s + +REACT_APP_HOME_GAS_PRICE_ORACLE_URL=https://gasprice.poa.network/ +REACT_APP_HOME_GAS_PRICE_SPEED_TYPE=standard +REACT_APP_HOME_GAS_PRICE_FALLBACK=5000000000 +REACT_APP_HOME_GAS_PRICE_UPDATE_INTERVAL=15000 + +REACT_APP_FOREIGN_GAS_PRICE_ORACLE_URL=https://gasprice.poa.network/ +REACT_APP_FOREIGN_GAS_PRICE_SPEED_TYPE=standard +REACT_APP_FOREIGN_GAS_PRICE_FALLBACK=5000000000 +REACT_APP_FOREIGN_GAS_PRICE_UPDATE_INTERVAL=15000 diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000..3f62891e --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v8.12 diff --git a/README.md b/README.md index 4bdfaf45..f04f742a 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ [![Build Status](https://travis-ci.org/patitonar/bridge-ui.svg?branch=master)](https://travis-ci.org/patitonar/bridge-ui) [![Gitter](https://badges.gitter.im/poanetwork/poa-bridge.svg)](https://gitter.im/poanetwork/poa-bridge?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Coverage Status](https://coveralls.io/repos/github/patitonar/bridge-ui/badge.svg?branch=master)](https://coveralls.io/github/patitonar/bridge-ui?branch=master) +[![dependencies Status](https://david-dm.org/poanetwork/bridge-ui/status.svg)](https://david-dm.org/poanetwork/bridge-ui) Welcome to the POA Bridge! Following is an overview of the POA Bridge and Bridge UI Application, as well as [basic instructions for getting started](#getting-started). @@ -151,15 +153,39 @@ If successful, you will see bridge processes run when you issue a command. For e REACT_APP_FOREIGN_HTTP_PARITY_URL=https://kovan.infura.io/mew # public RPC node for Home network REACT_APP_HOME_HTTP_PARITY_URL=https://sokol.poa.network - # Gas price speed option (slow, standard, fast, instant) - REACT_APP_GAS_PRICE_SPEED_TYPE=fast ``` +Explanation: + +Name | Description +--------- | ------- +REACT_APP_HOME_BRIDGE_ADDRESS | address that you have deployed at step#3. Should also be recorded at `sokol-kovan-bridge/poa-bridge-contracts/deploy/bridgeDeploymentResults.json` +REACT_APP_FOREIGN_BRIDGE_ADDRESS | address that you have deployed at step#3. +REACT_APP_FOREIGN_HTTP_PARITY_URL | http public rpc node for Foreign Network +REACT_APP_HOME_HTTP_PARITY_URL | http public rpc node for Foreign Network +REACT_APP_HOME_NATIVE_NAME | name of the home native coin +REACT_APP_HOME_NETWORK_NAME | name to be displayed for home network +REACT_APP_FOREIGN_NETWORK_NAME | name to be displayed for foreign network +REACT_APP_HOME_EXPLORER_TX_TEMPLATE | template link to transaction on home explorer. `%s` will be replaced by transaction hash +REACT_APP_FOREIGN_EXPLORER_TX_TEMPLATE | template link to transaction on foreign explorer. `%s` will be replaced by transaction hash +REACT_APP_HOME_EXPLORER_ADDRESS_TEMPLATE | template link to address on home explorer. `%s` will be replaced by address +REACT_APP_FOREIGN_EXPLORER_ADDRESS_TEMPLATE | template link to address on foreign explorer. `%s` will be replaced by address +REACT_APP_HOME_GAS_PRICE_ORACLE_URL | The URL used to get a JSON response from the gas price prediction oracle for Home network. +REACT_APP_HOME_GAS_PRICE_SPEED_TYPE | Gas Price speed (slow, standard, fast, instant) +REACT_APP_HOME_GAS_PRICE_FALLBACK | The gas price (in Wei) that is used if both the oracle and the fall back gas price specified in the Home Bridge contract are not available. +REACT_APP_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. +REACT_APP_FOREIGN_GAS_PRICE_ORACLE_URL | The URL used to get a JSON response from the gas price prediction oracle for Foreign network. +REACT_APP_FOREIGN_GAS_PRICE_SPEED_TYPE | Gas Price speed (slow, standard, fast, instant) +REACT_APP_FOREIGN_GAS_PRICE_FALLBACK | The gas price (in Wei) that is used if both the oracle and the fall back gas price specified in the Foreign Bridge contract are not available. +REACT_APP_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. + * Run `npm run start` * Make sure your web3 wallet (Nifty Wallet or MetaMask) is funded and connected to the POA Sokol Network (see step 2) * Specify an amount and click `Transfer` to complete a cross-chain transaction from Sokol to Kovan ## Testing +To run tests + `npm run test` To run tests with coverage diff --git a/e2e-script/MetaMask.js b/e2e-script/MetaMask.js index bd273719..017bd4c7 100644 --- a/e2e-script/MetaMask.js +++ b/e2e-script/MetaMask.js @@ -137,8 +137,9 @@ class MetaMask extends Page { break; } } + const index = networks.length > 8 ? 8 : networks.length; await this.driver.executeScript("document.getElementsByClassName('dropdown-menu-item')[" + - (networks.length - 1) + "].click();"); + (index - 1) + "].click();"); return await super.fillWithWait(fieldNewRPCURL, url) && await super.clickWithWait(buttonSave) && await super.clickWithWait(arrowBackRPCURL); diff --git a/e2e-script/Utils.js b/e2e-script/Utils.js index e6fe213d..f75ea523 100644 --- a/e2e-script/Utils.js +++ b/e2e-script/Utils.js @@ -41,6 +41,15 @@ class Utils { } } + static async getErc20NativeStartURL() { + try { + let obj = JSON.parse(fs.readFileSync(configFile), "utf8"); + return obj.erc20NativeUrl; + } catch (err) { + return null; + } + } + static async startBrowserWithMetamask() { let source = './e2e-script/MetaMask.crx'; let options = new chrome.Options(); diff --git a/e2e-script/bridge/Dockerfile b/e2e-script/bridge/Dockerfile index 875f6d6e..b36986f1 100644 --- a/e2e-script/bridge/Dockerfile +++ b/e2e-script/bridge/Dockerfile @@ -9,5 +9,6 @@ RUN apt-get clean RUN git clone https://github.com/poanetwork/bridge-nodejs.git WORKDIR /bridge-nodejs -RUN cd submodules/poa-bridge-contracts && git submodule update --init --recursive && ls && git checkout refactor_v1 +RUN git fetch && git checkout support-erc20-native-#81 +RUN cd submodules/poa-bridge-contracts && git submodule update --init --recursive RUN npm install diff --git a/e2e-script/config.json b/e2e-script/config.json index 024eb3a9..31e1ba00 100644 --- a/e2e-script/config.json +++ b/e2e-script/config.json @@ -1,6 +1,7 @@ { "startUrl" : "http://10.1.0.101:3000", "erc20Url" : "http://10.1.0.104:3000", + "erc20NativeUrl" : "http://10.1.0.105:3000", "homeAccount": "./e2e-script/accounts/user77_7FC1.json", "foreignAccount": "./e2e-script/accounts/user42_7FC1.json" } diff --git a/e2e-script/contracts/Dockerfile b/e2e-script/contracts/Dockerfile index 4209b2af..6e7ed617 100644 --- a/e2e-script/contracts/Dockerfile +++ b/e2e-script/contracts/Dockerfile @@ -8,7 +8,8 @@ RUN git clone https://github.com/poanetwork/poa-bridge-contracts.git RUN mkdir submodules && \ mv poa-bridge-contracts submodules && \ cd submodules/poa-bridge-contracts && \ - git checkout refactor_v1 + git fetch && \ + git checkout develop RUN cd submodules/poa-bridge-contracts && \ npm install && \ @@ -18,5 +19,6 @@ RUN cd submodules/poa-bridge-contracts && \ COPY deploy.sh . COPY contracts.env submodules/poa-bridge-contracts/deploy/ COPY erc-contracts.env submodules/poa-bridge-contracts/deploy/ +COPY erc-native-contracts.env submodules/poa-bridge-contracts/deploy/ COPY deployERC20.js submodules/poa-bridge-contracts/deploy/ RUN cd submodules/poa-bridge-contracts/deploy && cp contracts.env .env diff --git a/e2e-script/contracts/contracts.env b/e2e-script/contracts/contracts.env index 3a5242d3..0a07499c 100644 --- a/e2e-script/contracts/contracts.env +++ b/e2e-script/contracts/contracts.env @@ -2,7 +2,8 @@ BRIDGE_MODE=NATIVE_TO_ERC DEPLOYMENT_ACCOUNT_ADDRESS=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b DEPLOYMENT_ACCOUNT_PRIVATE_KEY=8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9 DEPLOYMENT_GAS_LIMIT=4000000 -DEPLOYMENT_GAS_PRICE=10 +HOME_DEPLOYMENT_GAS_PRICE=10000000000 +FOREIGN_DEPLOYMENT_GAS_PRICE=10000000000 GET_RECEIPT_INTERVAL_IN_MILLISECONDS=50 BRIDGEABLE_TOKEN_NAME="Your New Bridged Token" @@ -17,7 +18,7 @@ HOME_DAILY_LIMIT=30000000000000000000000000 HOME_MAX_AMOUNT_PER_TX=1500000000000000000000000 HOME_MIN_AMOUNT_PER_TX=10000000000000000 HOME_REQUIRED_BLOCK_CONFIRMATIONS=1 -HOME_GAS_PRICE=1 +HOME_GAS_PRICE=1000000000 FOREIGN_RPC_URL=http://parity2:8545 FOREIGN_OWNER_MULTISIG=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b @@ -27,7 +28,7 @@ FOREIGN_DAILY_LIMIT=15000000000000000000000000 FOREIGN_MAX_AMOUNT_PER_TX=750000000000000000000000 FOREIGN_MIN_AMOUNT_PER_TX=10000000000000000 FOREIGN_REQUIRED_BLOCK_CONFIRMATIONS=1 -FOREIGN_GAS_PRICE=1 +FOREIGN_GAS_PRICE=10000000000 REQUIRED_NUMBER_OF_VALIDATORS=1 VALIDATORS="0xaaB52d66283F7A1D5978bcFcB55721ACB467384b" diff --git a/e2e-script/contracts/deploy.sh b/e2e-script/contracts/deploy.sh index f175910a..9331dd5d 100755 --- a/e2e-script/contracts/deploy.sh +++ b/e2e-script/contracts/deploy.sh @@ -7,3 +7,8 @@ node deployERC20.js cp erc-contracts.env .env echo "Deploying erc20-erc20 contracts" node deploy.js +cp erc-native-contracts.env .env +echo "Deploying Block Reward contract" +node src/utils/deployBlockReward.js +echo "Deploying erc20-native contracts" +node deploy.js diff --git a/e2e-script/contracts/erc-contracts.env b/e2e-script/contracts/erc-contracts.env index 5aa00e21..869261e5 100644 --- a/e2e-script/contracts/erc-contracts.env +++ b/e2e-script/contracts/erc-contracts.env @@ -2,7 +2,8 @@ BRIDGE_MODE=ERC_TO_ERC DEPLOYMENT_ACCOUNT_ADDRESS=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b DEPLOYMENT_ACCOUNT_PRIVATE_KEY=8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9 DEPLOYMENT_GAS_LIMIT=4000000 -DEPLOYMENT_GAS_PRICE=10 +HOME_DEPLOYMENT_GAS_PRICE=10000000000 +FOREIGN_DEPLOYMENT_GAS_PRICE=10000000000 GET_RECEIPT_INTERVAL_IN_MILLISECONDS=50 BRIDGEABLE_TOKEN_NAME="Your New Bridged Token" @@ -17,7 +18,7 @@ HOME_DAILY_LIMIT=30000000000000000000000000 HOME_MAX_AMOUNT_PER_TX=1500000000000000000000000 HOME_MIN_AMOUNT_PER_TX=10000000000000000 HOME_REQUIRED_BLOCK_CONFIRMATIONS=1 -HOME_GAS_PRICE=10 +HOME_GAS_PRICE=1000000000 FOREIGN_RPC_URL=http://parity2:8545 FOREIGN_OWNER_MULTISIG=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b @@ -27,8 +28,8 @@ FOREIGN_DAILY_LIMIT=15000000000000000000000000 FOREIGN_MAX_AMOUNT_PER_TX=750000000000000000000000 FOREIGN_MIN_AMOUNT_PER_TX=10000000000000000 FOREIGN_REQUIRED_BLOCK_CONFIRMATIONS=1 -FOREIGN_GAS_PRICE=10 -ERC20_TOKEN_ADDRESS=0x7777D2BF48993088dC1ceD832863b80427Ff5Ec9 +FOREIGN_GAS_PRICE=10000000000 +ERC20_TOKEN_ADDRESS=0x3C665A31199694Bf723fD08844AD290207B5797f REQUIRED_NUMBER_OF_VALIDATORS=1 VALIDATORS="0xaaB52d66283F7A1D5978bcFcB55721ACB467384b" diff --git a/e2e-script/contracts/erc-native-contracts.env b/e2e-script/contracts/erc-native-contracts.env new file mode 100644 index 00000000..fd86652c --- /dev/null +++ b/e2e-script/contracts/erc-native-contracts.env @@ -0,0 +1,36 @@ +BRIDGE_MODE=ERC_TO_NATIVE +DEPLOYMENT_ACCOUNT_PRIVATE_KEY=8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9 +DEPLOYMENT_GAS_LIMIT=4000000 +HOME_DEPLOYMENT_GAS_PRICE=10000000000 +FOREIGN_DEPLOYMENT_GAS_PRICE=10000000000 +GET_RECEIPT_INTERVAL_IN_MILLISECONDS=50 + +BRIDGEABLE_TOKEN_NAME="Your New Bridged Token" +BRIDGEABLE_TOKEN_SYMBOL="TEST" +BRIDGEABLE_TOKEN_DECIMALS="18" + +HOME_RPC_URL=http://parity1:8545 +HOME_OWNER_MULTISIG=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b +HOME_UPGRADEABLE_ADMIN_VALIDATORS=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b +HOME_UPGRADEABLE_ADMIN_BRIDGE=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b +HOME_DAILY_LIMIT=30000000000000000000000000 +HOME_MAX_AMOUNT_PER_TX=1500000000000000000000000 +HOME_MIN_AMOUNT_PER_TX=10000000000000000 +HOME_REQUIRED_BLOCK_CONFIRMATIONS=1 +HOME_GAS_PRICE=1000000000 + +FOREIGN_RPC_URL=http://parity2:8545 +FOREIGN_OWNER_MULTISIG=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b +FOREIGN_UPGRADEABLE_ADMIN_VALIDATORS=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b +FOREIGN_UPGRADEABLE_ADMIN_BRIDGE=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b +FOREIGN_DAILY_LIMIT=15000000000000000000000000 +FOREIGN_MAX_AMOUNT_PER_TX=750000000000000000000000 +FOREIGN_MIN_AMOUNT_PER_TX=10000000000000000 +FOREIGN_REQUIRED_BLOCK_CONFIRMATIONS=1 +FOREIGN_GAS_PRICE=10000000000 + +BLOCK_REWARD_ADDRESS=0xF9698Eb93702dfdd0e2d802088d4c21822a8A977 +ERC20_TOKEN_ADDRESS=0x3C665A31199694Bf723fD08844AD290207B5797f + +REQUIRED_NUMBER_OF_VALIDATORS=1 +VALIDATORS="0xaaB52d66283F7A1D5978bcFcB55721ACB467384b" diff --git a/e2e-script/docker-compose.yml b/e2e-script/docker-compose.yml index 8094d706..ced60577 100644 --- a/e2e-script/docker-compose.yml +++ b/e2e-script/docker-compose.yml @@ -35,6 +35,7 @@ services: build: bridge environment: - NODE_ENV=production + - BRIDGE_MODE=NATIVE_TO_ERC - QUEUE_URL=amqp://rabbit - REDIS_URL=redis://redis - HOME_RPC_URL=http://parity1:8545 @@ -69,8 +70,38 @@ services: - HOME_RPC_URL=http://parity1:8545 - FOREIGN_RPC_URL=http://parity2:8545 - HOME_BRIDGE_ADDRESS=0x1feB40aD9420b186F019A717c37f5546165d411E - - FOREIGN_BRIDGE_ADDRESS=0xD0B9745831dDA9cbb47D0dEa904972cDcecc52e8 - - ERC20_TOKEN_ADDRESS=0x7777D2BF48993088dC1ceD832863b80427Ff5Ec9 + - FOREIGN_BRIDGE_ADDRESS=0x4a58D6d8D416a5fBCAcf3dC52eb8bE8948E25127 + - ERC20_TOKEN_ADDRESS=0x3C665A31199694Bf723fD08844AD290207B5797f + - BRIDGEABLE_TOKEN_ADDRESS=0x792455a6bCb62Ed4C4362D323E0590654CA4765c + - VALIDATOR_ADDRESS=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b + - VALIDATOR_ADDRESS_PRIVATE_KEY=8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9 + - REDIS_LOCK_TTL=1000 + - HOME_GAS_PRICE_ORACLE_URL=https://gasprice.poa.network/ + - HOME_GAS_PRICE_SPEED_TYPE=standard + - HOME_GAS_PRICE_FALLBACK=1 + - HOME_GAS_PRICE_UPDATE_INTERVAL=600000 + - FOREIGN_GAS_PRICE_ORACLE_URL=https://gasprice.poa.network/ + - FOREIGN_GAS_PRICE_SPEED_TYPE=standard + - FOREIGN_GAS_PRICE_FALLBACK=1 + - FOREIGN_GAS_PRICE_UPDATE_INTERVAL=600000 + - HOME_POLLING_INTERVAL=500 + - FOREIGN_POLLING_INTERVAL=500 + - ALLOW_HTTP=yes + networks: + - testnet + command: "true" + bridge-erc20-native: + build: bridge + environment: + - NODE_ENV=production + - BRIDGE_MODE=ERC_TO_NATIVE + - QUEUE_URL=amqp://rabbit + - REDIS_URL=redis://redis + - HOME_RPC_URL=http://parity1:8545 + - FOREIGN_RPC_URL=http://parity2:8545 + - HOME_BRIDGE_ADDRESS=0x488Af810997eD1730cB3a3918cD83b3216E6eAda + - FOREIGN_BRIDGE_ADDRESS=0x488Af810997eD1730cB3a3918cD83b3216E6eAda + - ERC20_TOKEN_ADDRESS=0x3C665A31199694Bf723fD08844AD290207B5797f - BRIDGEABLE_TOKEN_ADDRESS=0x792455a6bCb62Ed4C4362D323E0590654CA4765c - VALIDATOR_ADDRESS=0xaaB52d66283F7A1D5978bcFcB55721ACB467384b - VALIDATOR_ADDRESS_PRIVATE_KEY=8e829f695aed89a154550f30262f1529582cc49dc30eff74a6b491359e0230f9 @@ -96,7 +127,21 @@ services: - REACT_APP_FOREIGN_BRIDGE_ADDRESS=0x2B6871b9B02F73fa24F4864322CdC78604207769 - REACT_APP_FOREIGN_HTTP_PARITY_URL=http://10.1.0.103:8545 - REACT_APP_HOME_HTTP_PARITY_URL=http://10.1.0.102:8545 - - REACT_APP_GAS_PRICE_SPEED_TYPE=fast + - REACT_APP_HOME_NATIVE_NAME=POA + - REACT_APP_HOME_NETWORK_NAME=Sokol + - REACT_APP_FOREIGN_NETWORK_NAME=Kovan + - REACT_APP_HOME_EXPLORER_TX_TEMPLATE=https://blockscout.com/poa/sokol/tx//%s + - REACT_APP_FOREIGN_EXPLORER_TX_TEMPLATE=https://blockscout.com/eth/kovan/tx/%s + - REACT_APP_HOME_EXPLORER_ADDRESS_TEMPLATE=https://blockscout.com/poa/sokol/address/%s + - REACT_APP_FOREIGN_EXPLORER_ADDRESS_TEMPLATE=https://blockscout.com/eth/kovan/address/%s + - REACT_APP_HOME_GAS_PRICE_ORACLE_URL=https://gasprice.poa.network/ + - REACT_APP_HOME_GAS_PRICE_SPEED_TYPE=standard + - REACT_APP_HOME_GAS_PRICE_FALLBACK=5000000000 + - REACT_APP_HOME_GAS_PRICE_UPDATE_INTERVAL=15000 + - REACT_APP_FOREIGN_GAS_PRICE_ORACLE_URL=https://gasprice.poa.network/ + - REACT_APP_FOREIGN_GAS_PRICE_SPEED_TYPE=standard + - REACT_APP_FOREIGN_GAS_PRICE_FALLBACK=5000000000 + - REACT_APP_FOREIGN_GAS_PRICE_UPDATE_INTERVAL=15000 ports: - "3000:3000" networks: @@ -107,16 +152,58 @@ services: build: .. environment: - REACT_APP_HOME_BRIDGE_ADDRESS=0x1feB40aD9420b186F019A717c37f5546165d411E - - REACT_APP_FOREIGN_BRIDGE_ADDRESS=0xD0B9745831dDA9cbb47D0dEa904972cDcecc52e8 + - REACT_APP_FOREIGN_BRIDGE_ADDRESS=0x4a58D6d8D416a5fBCAcf3dC52eb8bE8948E25127 - REACT_APP_FOREIGN_HTTP_PARITY_URL=http://10.1.0.103:8545 - REACT_APP_HOME_HTTP_PARITY_URL=http://10.1.0.102:8545 - - REACT_APP_GAS_PRICE_SPEED_TYPE=fast + - REACT_APP_HOME_NATIVE_NAME=POA + - REACT_APP_HOME_NETWORK_NAME=Sokol + - REACT_APP_FOREIGN_NETWORK_NAME=Kovan + - REACT_APP_HOME_EXPLORER_TX_TEMPLATE=https://blockscout.com/poa/sokol/tx//%s + - REACT_APP_FOREIGN_EXPLORER_TX_TEMPLATE=https://blockscout.com/eth/kovan/tx/%s + - REACT_APP_HOME_EXPLORER_ADDRESS_TEMPLATE=https://blockscout.com/poa/sokol/address/%s + - REACT_APP_FOREIGN_EXPLORER_ADDRESS_TEMPLATE=https://blockscout.com/eth/kovan/address/%s + - REACT_APP_HOME_GAS_PRICE_ORACLE_URL=https://gasprice.poa.network/ + - REACT_APP_HOME_GAS_PRICE_SPEED_TYPE=standard + - REACT_APP_HOME_GAS_PRICE_FALLBACK=5000000000 + - REACT_APP_HOME_GAS_PRICE_UPDATE_INTERVAL=15000 + - REACT_APP_FOREIGN_GAS_PRICE_ORACLE_URL=https://gasprice.poa.network/ + - REACT_APP_FOREIGN_GAS_PRICE_SPEED_TYPE=standard + - REACT_APP_FOREIGN_GAS_PRICE_FALLBACK=5000000000 + - REACT_APP_FOREIGN_GAS_PRICE_UPDATE_INTERVAL=15000 ports: - "3001:3000" networks: testnet: ipv4_address: 10.1.0.104 command: "true" + ui-erc20-native: + build: .. + environment: + - REACT_APP_HOME_BRIDGE_ADDRESS=0x488Af810997eD1730cB3a3918cD83b3216E6eAda + - REACT_APP_FOREIGN_BRIDGE_ADDRESS=0x488Af810997eD1730cB3a3918cD83b3216E6eAda + - REACT_APP_FOREIGN_HTTP_PARITY_URL=http://10.1.0.103:8545 + - REACT_APP_HOME_HTTP_PARITY_URL=http://10.1.0.102:8545 + - REACT_APP_HOME_NATIVE_NAME=POA + - REACT_APP_HOME_NETWORK_NAME=Sokol + - REACT_APP_FOREIGN_NETWORK_NAME=Kovan + - REACT_APP_HOME_EXPLORER_TX_TEMPLATE=https://blockscout.com/poa/sokol/tx//%s + - REACT_APP_FOREIGN_EXPLORER_TX_TEMPLATE=https://blockscout.com/eth/kovan/tx/%s + - REACT_APP_HOME_EXPLORER_ADDRESS_TEMPLATE=https://blockscout.com/poa/sokol/address/%s + - REACT_APP_FOREIGN_EXPLORER_ADDRESS_TEMPLATE=https://blockscout.com/eth/kovan/address/%s + - REACT_APP_HOME_GAS_PRICE_ORACLE_URL=https://gasprice.poa.network/ + - REACT_APP_HOME_GAS_PRICE_SPEED_TYPE=standard + - REACT_APP_HOME_GAS_PRICE_FALLBACK=5000000000 + - REACT_APP_HOME_GAS_PRICE_UPDATE_INTERVAL=15000 + - REACT_APP_FOREIGN_GAS_PRICE_ORACLE_URL=https://gasprice.poa.network/ + - REACT_APP_FOREIGN_GAS_PRICE_SPEED_TYPE=standard + - REACT_APP_FOREIGN_GAS_PRICE_FALLBACK=5000000000 + - REACT_APP_FOREIGN_GAS_PRICE_UPDATE_INTERVAL=15000 + ports: + - "3002:3000" + networks: + testnet: + ipv4_address: 10.1.0.105 + command: "true" networks: testnet: driver: bridge diff --git a/e2e-script/run-tests.sh b/e2e-script/run-tests.sh index 89d7758e..e3741b43 100755 --- a/e2e-script/run-tests.sh +++ b/e2e-script/run-tests.sh @@ -10,12 +10,14 @@ docker-compose run -d bridge npm run watcher:affirmation-request docker-compose run -d bridge-erc20 npm run watcher:signature-request docker-compose run -d bridge-erc20 npm run watcher:collected-signatures docker-compose run -d bridge-erc20 npm run watcher:affirmation-request +docker-compose run -d bridge-erc20-native npm run watcher:signature-request +docker-compose run -d bridge-erc20-native npm run watcher:collected-signatures +docker-compose run -d bridge-erc20-native npm run watcher:affirmation-request docker-compose run -d bridge npm run sender:home docker-compose run -d bridge npm run sender:foreign docker-compose run -d ui npm start docker-compose run -d ui-erc20 npm start -echo "Waiting for blocks being generated" -sleep 5m +docker-compose run -d ui-erc20-native npm start cd .. npm run startE2e rc=$? diff --git a/e2e-script/test.js b/e2e-script/test.js index caad04b6..8ace5807 100644 --- a/e2e-script/test.js +++ b/e2e-script/test.js @@ -216,4 +216,94 @@ test.describe('e2e-test for bridge.poa, version 1.5.0', async function () { let result = (Math.abs(shouldBe - newForeignBalance)) < (maxAmountPerTransactionLimit / 100); return await assert.equal(result, true, "Test FAILED. Foreign POA balance is not correct after transaction"); }); + + test.it('ERC20-Native - User is able to open main page of bridge-ui ', + async function () { + startURL = await Utils.getErc20NativeStartURL(); + let result = await mainPage.open(startURL); + console.log("Test URL: " + startURL); + return await assert.equal(result, true, "Test FAILED. Build failed."); + }); + + test.it('ERC20-Native - Home page: disclaimer is displayed ', + async function () { + let result = await mainPage.confirmDisclaimer(); + return await assert.equal(result, true, "Test FAILED. Disclaimer is not displayed"); + }); + + test.it('ERC20-Native - Main page: foreign erc20 balance is displayed ', + async function () { + await foreignAccount.setMetaMaskNetwork(); + foreignBalanceBefore = await mainPage.getForeignPOABalance(); + console.log("foreignBalanceBefore = "+foreignBalanceBefore); + let result = foreignBalanceBefore !== 0; + return await assert.equal(result, true, "Test FAILED. Foreign erc20 balance is zero"); + }); + + test.it('ERC20-Native - Main page: home erc20 balance is displayed ', + async function () { + homeBalanceBefore = await mainPage.getHomePOABalance(); + console.log("homeBalanceBefore = "+homeBalanceBefore); + let result = homeBalanceBefore !== 0; + return await assert.equal(result, true, "Test FAILED. Home erc20 balance is zero or not displayed "); + }); + + test.it('ERC20-Native - User is able to send tokens from Foreign account to Home account ', + async function () { + homeBalanceBefore = await mainPage.getForeignPOABalance(); + foreignBalanceBefore = await mainPage.getHomePOABalance(); + let result = await foreignAccount.transferTokens(maxAmountPerTransactionLimit); + return await assert.equal(result, true, "Test FAILED. User is able send tokens from Foreign account to Home account"); + }); + + test.it('ERC20-Native - Foreign POA balance has correctly changed after transaction', + async function () { + let newForeignBalance = await mainPage.getHomePOABalance(); + let shouldBe = foreignBalanceBefore - maxAmountPerTransactionLimit; + console.log("newForeignBalance = " + newForeignBalance); + console.log("shouldBe = " + shouldBe); + let result = (Math.abs(shouldBe - newForeignBalance)) < (maxAmountPerTransactionLimit / 100); + return await assert.equal(result, true, "Test FAILED.Foreign POA balance is not correct after transaction"); + }); + + test.it('ERC20-Native - Home account has received correct amount of tokens after transaction ', + async function () { + let newHomeBalance = await mainPage.getForeignPOABalance(); + let shouldBe = homeBalanceBefore + maxAmountPerTransactionLimit; + console.log("newHomeBalance = " + newHomeBalance); + console.log("shouldBe = " + shouldBe); + let result = (Math.abs(shouldBe - newHomeBalance)) < (maxAmountPerTransactionLimit / 100); + return await assert.equal(result, true, "Test FAILED.Home POA balance is not correct after transaction"); + }); + test.it('ERC20-Native - User is able to send tokens from Home account to Foreign account ', + async function () { + await homeAccount.setMetaMaskNetwork(); + homeBalanceBefore = await mainPage.getHomePOABalance(); + foreignBalanceBefore = await mainPage.getForeignPOABalance(); + let result = await homeAccount.transferTokens(maxAmountPerTransactionLimit); + return await assert.equal(result, true, "Test FAILED. User is able send tokens from Home account to Foreign account"); + }); + + test.it('ERC20-Native - Home POA balance has correctly changed after transaction', + async function () { + let newHomeBalance = await mainPage.getHomePOABalance(); + let shouldBe = homeBalanceBefore - maxAmountPerTransactionLimit; + console.log("newHomeBalance = " + newHomeBalance); + console.log("shouldBe = " + shouldBe); + let result = (Math.abs(shouldBe - newHomeBalance)) < (maxAmountPerTransactionLimit / 100); + homeBalanceBefore = newHomeBalance; + return await assert.equal(result, true, "Test FAILED.Home POA balance is not correct after transaction"); + }); + + test.it('ERC20-Native - Foreign account has received correct amount of tokens after transaction ', + async function () { + let newForeignBalance = await mainPage.getForeignPOABalance(); + + let shouldBe = foreignBalanceBefore + maxAmountPerTransactionLimit; + console.log("newForeignBalance = " + newForeignBalance); + console.log("shouldBe = " + shouldBe); + + let result = (Math.abs(shouldBe - newForeignBalance)) < (maxAmountPerTransactionLimit / 100); + return await assert.equal(result, true, "Test FAILED. Foreign POA balance is not correct after transaction"); + }); }); diff --git a/src/assets/images/logos/logo-loader.svg b/src/assets/images/logos/logo-loader.svg index 175b6b03..cd27a3f8 100644 --- a/src/assets/images/logos/logo-loader.svg +++ b/src/assets/images/logos/logo-loader.svg @@ -1,6 +1,27 @@ - - - Loading - - + + + + + Bridge Loading + diff --git a/src/components/Bridge.js b/src/components/Bridge.js index 6b0f08b1..6d568e6c 100644 --- a/src/components/Bridge.js +++ b/src/components/Bridge.js @@ -9,12 +9,11 @@ import { BridgeNetwork } from './index' import { ModalContainer } from './ModalContainer' import { NetworkDetails } from './NetworkDetails' import { TransferAlert } from './TransferAlert' -import homeLogo from '../assets/images/logos/logo-poa-sokol@2x.png' -import foreignLogo from '../assets/images/logos/logo-poa-20@2x.png' import homeLogoPurple from '../assets/images/logos/logo-poa-sokol-purple@2x.png' import foreignLogoPurple from '../assets/images/logos/logo-poa-20-purple@2x.png' import leftImage from '../assets/images/pattern-1.png' import rightImage from '../assets/images/pattern-2.png' +import { BRIDGE_MODES } from '../stores/utils/bridgeMode' @inject("RootStore") @observer @@ -62,10 +61,11 @@ export class Bridge extends React.Component { } async _sendToHome(amount){ - const { web3Store, homeStore, alertStore, txStore, isErcToErcMode } = this.props.RootStore + const { web3Store, homeStore, alertStore, txStore, bridgeMode } = this.props.RootStore + const isErcToErcMode = bridgeMode === BRIDGE_MODES.ERC_TO_ERC const { isLessThan, isGreaterThan } = this if(web3Store.metamaskNet.id.toString() !== web3Store.homeNet.id.toString()){ - swal("Error", `Please switch metamask to ${web3Store.homeNet.name} network`, "error") + swal("Error", `Please switch wallet to ${web3Store.homeNet.name} network`, "error") return } if(isLessThan(amount, homeStore.minPerTx)){ @@ -108,21 +108,22 @@ export class Bridge extends React.Component { } async _sendToForeign(amount){ - const { web3Store, foreignStore, alertStore, txStore, isErcToErcMode } = this.props.RootStore + const { web3Store, foreignStore, alertStore, txStore, bridgeMode } = this.props.RootStore + const isExternalErc20 = bridgeMode === BRIDGE_MODES.ERC_TO_ERC || bridgeMode === BRIDGE_MODES.ERC_TO_NATIVE const { isLessThan, isGreaterThan } = this if(web3Store.metamaskNet.id.toString() !== web3Store.foreignNet.id.toString()){ - swal("Error", `Please switch metamask to ${web3Store.foreignNet.name} network`, "error") + swal("Error", `Please switch wallet to ${web3Store.foreignNet.name} network`, "error") return } - if(!isErcToErcMode && isLessThan(amount, foreignStore.minPerTx)){ + if(!isExternalErc20 && isLessThan(amount, foreignStore.minPerTx)){ alertStore.pushError(`The amount is less than minimum amount per transaction.\nThe min per transaction is: ${foreignStore.minPerTx} ${foreignStore.symbol}`) return } - if(!isErcToErcMode && isGreaterThan(amount, foreignStore.maxPerTx)){ + if(!isExternalErc20 && isGreaterThan(amount, foreignStore.maxPerTx)){ alertStore.pushError(`The amount is above maximum amount per transaction.\nThe max per transaction is: ${foreignStore.maxPerTx} ${foreignStore.symbol}`) return } - if(!isErcToErcMode && isGreaterThan(amount, foreignStore.maxCurrentDeposit)){ + if(!isExternalErc20 && isGreaterThan(amount, foreignStore.maxCurrentDeposit)){ alertStore.pushError(`The amount is above current daily limit.\nThe max withdrawal today: ${foreignStore.maxCurrentDeposit} ${foreignStore.symbol}`) return } @@ -131,7 +132,7 @@ export class Bridge extends React.Component { } else { try { alertStore.setLoading(true) - if (isErcToErcMode) { + if (isExternalErc20) { return await txStore.erc20transfer({ to: foreignStore.FOREIGN_BRIDGE_ADDRESS, from: web3Store.defaultAccount.address, @@ -174,8 +175,8 @@ export class Bridge extends React.Component { } const { reverse } = this.state - const homeDisplayName = 'POA ' + web3Store.homeNet.name - const foreignDisplayName = 'ETH ' + web3Store.foreignNet.name + const homeDisplayName = homeStore.networkName + const foreignDisplayName = foreignStore.networkName const confirmationData = { from: reverse ? foreignDisplayName : homeDisplayName, @@ -214,7 +215,9 @@ export class Bridge extends React.Component { } loadHomeDetails = () => { - const { web3Store, homeStore, isErcToErcMode } = this.props.RootStore + const { web3Store, homeStore, bridgeMode } = this.props.RootStore + const isErcToErcMode = bridgeMode === BRIDGE_MODES.ERC_TO_ERC + const isExternalErc20 = bridgeMode === BRIDGE_MODES.ERC_TO_ERC || bridgeMode === BRIDGE_MODES.ERC_TO_NATIVE const modalData = { isHome: true, @@ -230,14 +233,18 @@ export class Bridge extends React.Component { balance: homeStore.getDisplayedBalance(), displayTokenAddress: isErcToErcMode, tokenAddress: homeStore.tokenAddress, - displayBridgeLimits: true + tokenName: homeStore.tokenName, + displayBridgeLimits: true, + nativeSupplyTitle: !isExternalErc20, + getExplorerAddressUrl: address => homeStore.getExplorerAddressUrl(address) } this.setState({ modalData, showModal: true }) } loadForeignDetails = () => { - const { web3Store, foreignStore, isErcToErcMode } = this.props.RootStore + const { web3Store, foreignStore, bridgeMode } = this.props.RootStore + const isExternalErc20 = bridgeMode === BRIDGE_MODES.ERC_TO_ERC || bridgeMode === BRIDGE_MODES.ERC_TO_NATIVE const foreignURL = new URL(web3Store.FOREIGN_HTTP_PARITY_URL) const foreignDisplayUrl = `${foreignURL.protocol}//${foreignURL.hostname}` @@ -252,10 +259,12 @@ export class Bridge extends React.Component { maxPerTx: foreignStore.maxPerTx, minPerTx: foreignStore.minPerTx, tokenAddress: foreignStore.tokenAddress, + tokenName: foreignStore.tokenName, totalSupply: foreignStore.totalSupply, balance: foreignStore.balance, displayTokenAddress: true, - displayBridgeLimits: !isErcToErcMode + displayBridgeLimits: !isExternalErc20, + getExplorerAddressUrl: address => foreignStore.getExplorerAddressUrl(address) } this.setState({ modalData, showModal: true }) @@ -274,13 +283,16 @@ export class Bridge extends React.Component { } } + const homeNetworkName = homeStore.networkName + const foreignNetworkName = foreignStore.networkName + return(
+ labelName={reverse ? foreignStore.symbol : homeStore.symbol} />
@@ -289,9 +301,8 @@ export class Bridge extends React.Component {
@@ -316,7 +326,7 @@ export class Bridge extends React.Component { + labelName={reverse ? homeStore.symbol : foreignStore.symbol} /> {this.setState({showModal: false})}} showModal={showModal} diff --git a/src/components/BridgeAddress.js b/src/components/BridgeAddress.js index 3c1f07ad..b6d73ec4 100644 --- a/src/components/BridgeAddress.js +++ b/src/components/BridgeAddress.js @@ -1,6 +1,6 @@ import React from 'react' -export const BridgeAddress = ({ isHome, reverse, logo}) => { +export const BridgeAddress = ({ isHome, reverse, labelName}) => { const getAddress = () => isHome ? (
) : @@ -10,7 +10,11 @@ export const BridgeAddress = ({ isHome, reverse, logo}) => { (
- home logo +
+ {labelName} +
{getAddress()} @@ -20,7 +24,11 @@ export const BridgeAddress = ({ isHome, reverse, logo}) => { {getAddress()}
- foreign logo +
+ {labelName} +
) diff --git a/src/components/BridgeNetwork.js b/src/components/BridgeNetwork.js index 94bc218a..68090203 100644 --- a/src/components/BridgeNetwork.js +++ b/src/components/BridgeNetwork.js @@ -5,7 +5,6 @@ import infoIcon from '../assets/images/icons/icon-info.svg' export const BridgeNetwork = ({ isHome, networkTitle, - networkData, currency, balance, showModal @@ -30,7 +29,6 @@ export const BridgeNetwork = ({

{networkTitle} - {networkData.name}

Balance: diff --git a/src/components/BridgeStatistics.js b/src/components/BridgeStatistics.js index 5c71a6a2..9ab8b64d 100644 --- a/src/components/BridgeStatistics.js +++ b/src/components/BridgeStatistics.js @@ -2,7 +2,7 @@ import React from 'react' import numeral from 'numeral' import { DataBlock } from './DataBlock' -export const BridgeStatistics = ({ gasValue, users, totalBridged, homeBalance, foreignSupply, homeSymbol, foreignSymbol }) => ( +export const BridgeStatistics = ({ users, totalBridged, homeBalance, homeNativeSupplyTitle, foreignSupply, homeSymbol, foreignSymbol }) => (

diff --git a/src/components/Configuration.js b/src/components/Configuration.js index 7549d114..ba99bffe 100644 --- a/src/components/Configuration.js +++ b/src/components/Configuration.js @@ -15,17 +15,21 @@ export const Configuration = ({ requiredSignatures, authorities, symbol, maxSing value={numeral(authorities).format('0')} type='' /> -
- -
- + {maxSingleDeposit && maxSingleDeposit !== '0' + &&
+ && + } + {maxSingleDeposit && maxSingleDeposit !== '0' + &&
+ && + }
); diff --git a/src/components/DailyQuotaModal.js b/src/components/DailyQuotaModal.js index d3de9202..2310b30c 100644 --- a/src/components/DailyQuotaModal.js +++ b/src/components/DailyQuotaModal.js @@ -33,22 +33,22 @@ export class DailyQuotaModal extends React.Component { const isHome = web3Store.metamaskNet.id.toString() === web3Store.homeNet.id.toString() const value = isHome ? homeStore.maxCurrentDeposit : foreignStore.maxCurrentDeposit + const limit = isHome ? homeStore.maxPerTx : foreignStore.maxPerTx const from = isHome ? homeStore.symbol : foreignStore.symbol const to = isHome ? foreignStore.symbol : homeStore.symbol - const networkFrom = isHome ? 'POA' : 'ETH' - const networkTo = isHome ? 'ETH' : 'POA' - const networkNameFrom = isHome ? web3Store.homeNet.name : web3Store.foreignNet.name - const networkNameTo = isHome ? web3Store.foreignNet.name : web3Store.homeNet.name - + const networkNameFrom = isHome ? homeStore.networkName : foreignStore.networkName + const networkNameTo = isHome ? foreignStore.networkName : homeStore.networkName + const description = limit && limit !== '0' ? `${numeral(value).format('0,0.0', Math.floor)} ${from} on ${networkNameFrom + ' '} + remaining for transfer to ${to + ' '} + on ${networkNameTo}` + : `No limit configured` return (
Daily Quota - {numeral(value).format('0,0.0', Math.floor)} {from} on {networkFrom} {networkNameFrom + ' '} - remaining for transfer to {to + ' '} - on {networkTo} {networkNameTo} + {description}
diff --git a/src/components/Footer.js b/src/components/Footer.js index 5827702c..6e35175b 100644 --- a/src/components/Footer.js +++ b/src/components/Footer.js @@ -8,7 +8,7 @@ export const Footer = () => (
- +
diff --git a/src/components/NetworkDetails.js b/src/components/NetworkDetails.js index 286beb92..6f63363b 100644 --- a/src/components/NetworkDetails.js +++ b/src/components/NetworkDetails.js @@ -2,7 +2,6 @@ import React from 'react' import copyIcon from '../assets/images/icons/copy.svg' import { CopyToClipboard } from 'react-copy-to-clipboard' import numeral from 'numeral' -import { getAddressUrl } from '../stores/utils/web3' export const NetworkDetails = ({ isHome, @@ -19,21 +18,29 @@ export const NetworkDetails = ({ totalBalance, balance, displayTokenAddress, - displayBridgeLimits + displayBridgeLimits, + nativeSupplyTitle, + tokenName, + getExplorerAddressUrl }) => { const networkTitle = isHome ? 'Bridge Home' : 'Bridge Foreign' const logoClass = isHome ? 'home-logo' : 'foreign-logo' - const totalTitle = isHome ? `Locked ${currency} in Bridge Contract` : `${currency} Tokens Amount` + const totalTitle = isHome + ? nativeSupplyTitle ? `Native Coins Amount` : `Totally minted by the bridge` + : `${currency} Tokens Amount` const totalAmount = isHome ? totalBalance : totalSupply - const explorerPath = getAddressUrl(networkData.id) const formattedBalance = isNaN(numeral(balance).format('0.00', Math.floor)) ? numeral(0).format('0,0.00', Math.floor) - : numeral(balance).format('0,0.00', Math.floor) + : numeral(balance).format('0,0.000', Math.floor) return (
- home logo +
+ {currency} +

@@ -43,7 +50,7 @@ export const NetworkDetails = ({

{networkTitle} Address - + {address.slice(0,27).concat('...')} @@ -61,13 +68,13 @@ export const NetworkDetails = ({

} {displayBridgeLimits &&

Minimum Amount Per Transaction - {numeral(minPerTx).format('0,0.0', Math.floor)} {currency} + {numeral(minPerTx).format('0,0.000', Math.floor)} {currency}

} {displayTokenAddress && (

Token Address - + {tokenAddress.slice(0,27).concat('...')} @@ -76,9 +83,15 @@ export const NetworkDetails = ({

)} + {displayTokenAddress && ( +

+ Token Name + {tokenName || 'No token name'} +

+ )}

{totalTitle} - {numeral(totalAmount).format('0,0.00', Math.floor)} {currency} + {numeral(totalAmount).format('0,0.000', Math.floor)} {currency}

Your {currency} Balance diff --git a/src/components/RelayEvents.js b/src/components/RelayEvents.js index 10e97e44..129fa771 100644 --- a/src/components/RelayEvents.js +++ b/src/components/RelayEvents.js @@ -2,7 +2,6 @@ import React from 'react'; import { inject, observer } from "mobx-react"; import { EventsListHeader } from './index' import { Event } from './index' -import { getExplorerUrl } from '../stores/utils/web3' const WAIT_INTERVAL = 700; @@ -133,7 +132,7 @@ export class RelayEvents extends React.Component { } render(){ - const { homeStore, foreignStore, web3Store } = this.props.RootStore + const { homeStore, foreignStore } = this.props.RootStore const { selectedList } = this.state const home = this.getHomeEvents(homeStore, foreignStore) const foreign = this.getForeignEvents(foreignStore, homeStore) @@ -146,22 +145,22 @@ export class RelayEvents extends React.Component { handleKeyDown={selectedList === this.homeValue ? this.handleKeyDownHome : this.handleKeyDownForeign} onChangeList={this.onChangeList} selected={selectedList} - homeName={'POA ' + web3Store.homeNet.name} + homeName={homeStore.networkName} homeValue={this.homeValue} - foreignName={'ETH ' + web3Store.foreignNet.name} + foreignName={foreignStore.networkName} foreignValue={this.foreingValue} /> {selectedList === this.homeValue && home.map(event => )} {selectedList === this.foreingValue && foreign.map(event => )}

diff --git a/src/components/StatisticsPage.js b/src/components/StatisticsPage.js index 6cdcc516..c9075c51 100644 --- a/src/components/StatisticsPage.js +++ b/src/components/StatisticsPage.js @@ -3,13 +3,17 @@ import { inject, observer } from "mobx-react" import pattern from '../assets/images/pattern.svg' import { BridgeStatistics } from './index' import { TransactionsStatistics } from './TransactionsStatistics' +import { BRIDGE_MODES } from '../stores/utils/bridgeMode' @inject("RootStore") @observer export class StatisticsPage extends React.Component { render(){ - const { homeStore, foreignStore } = this.props.RootStore + const { homeStore, foreignStore, bridgeMode } = this.props.RootStore + const isNativeToErc = bridgeMode === BRIDGE_MODES.NATIVE_TO_ERC + const leftTitle = isNativeToErc ? 'Deposits' : 'Withdraws' + const rightTitle = isNativeToErc ? 'Withdraws' : 'Deposits' return(
@@ -17,27 +21,27 @@ export class StatisticsPage extends React.Component {
Bridge Statistics
- Network Deposits + Tokens {leftTitle}
- Network Withdraws + Tokens {rightTitle}
diff --git a/src/components/StatusPage.js b/src/components/StatusPage.js index 69a07081..52d42d84 100644 --- a/src/components/StatusPage.js +++ b/src/components/StatusPage.js @@ -10,7 +10,14 @@ import pattern from '../assets/images/pattern.svg' export class StatusPage extends React.Component { render() { - const { homeStore } = this.props.RootStore + const { homeStore, foreignStore, web3Store } = this.props.RootStore + const isHome = web3Store.metamaskNet.id.toString() === web3Store.homeNet.id.toString() + const requiredSignatures = isHome ? homeStore.requiredSignatures : foreignStore.requiredSignatures + const authorities = isHome ? homeStore.validators.length : foreignStore.validators.length + const symbol = isHome ? homeStore.symbol : foreignStore.symbol + const maxSingleDeposit = isHome ? homeStore.maxPerTx : foreignStore.maxPerTx + const maxTotalBalance = isHome ? homeStore.maxCurrentDeposit : foreignStore.maxCurrentDeposit + const validatorsList = isHome ? homeStore.validators : foreignStore.validators return (
@@ -18,16 +25,16 @@ export class StatusPage extends React.Component {
Configuration + requiredSignatures={requiredSignatures} + authorities={authorities} + symbol={symbol} + maxSingleDeposit={maxSingleDeposit} + maxTotalBalance={maxTotalBalance} />
Authorities
- {homeStore.validators.map((validator,i) => ( + {validatorsList.map((validator,i) => ( ))}
diff --git a/src/components/TransferAlert.js b/src/components/TransferAlert.js index 1191cbed..b31a12ec 100644 --- a/src/components/TransferAlert.js +++ b/src/components/TransferAlert.js @@ -1,8 +1,6 @@ import React from 'react' import arrowsIcon from '../assets/images/icon-arrows@2x.png' import arrowIconRight from '../assets/images/icons/icon-arrow-right.svg' -import logoHomeSmall from '../assets/images/logos/logo-poa-main-net_small.svg' -import logoForeignSmall from '../assets/images/logos/logo-poa-20_small.svg' import numeral from 'numeral' @@ -28,13 +26,21 @@ export const TransferAlert = ({
- arrow right +
+ {fromCurrency} +
{numeral(amount).format('0,0[.][000000000000000000]', Math.floor)} {fromCurrency}
arrow right
{numeral(amount).format('0,0[.][000000000000000000]', Math.floor)} {toCurrency}
- arrow right +
+ {toCurrency} +

Please confirm that you would like to send {numeral(amount).format('0,0[.][000000000000000000]', Math.floor)} {fromCurrency} from {from} to receive {numeral(amount).format('0,0[.][000000000000000000]', Math.floor)} {toCurrency} on {to}.

diff --git a/src/components/Wallet.js b/src/components/Wallet.js index b11906ec..6c4fad9c 100644 --- a/src/components/Wallet.js +++ b/src/components/Wallet.js @@ -1,8 +1,6 @@ import React from 'react'; import { inject, observer } from "mobx-react"; import walletIcon from '../assets/images/icons/icon-wallet.svg' -import { getAddressUrl } from '../stores/utils/web3' - @inject("RootStore") @observer @@ -10,18 +8,19 @@ export class Wallet extends React.Component { render() { const { web3Store, homeStore, foreignStore, alertStore } = this.props.RootStore const isHome = web3Store.metamaskNet.id.toString() === web3Store.homeNet.id.toString() - const explorerPath = getAddressUrl(web3Store.metamaskNet.id) + const address = web3Store.defaultAccount.address + const explorerAddressUrl = isHome ? homeStore.getExplorerAddressUrl(address) : foreignStore.getExplorerAddressUrl(address) const completed = isHome ? homeStore.getDailyQuotaCompleted() : foreignStore.getDailyQuotaCompleted() const width = `${completed}%` const wallet = web3Store.defaultAccount.address !== '' && web3Store.defaultAccount.address !== undefined ? ( {web3Store.defaultAccount.address.slice(0,17).concat('...')} ) - : (Login with Metamask) + : (Login with wallet) return ( - + {recipient ? Recipient : ''} {recipient ? recipient.slice(0,27).concat('...') : ''} diff --git a/src/stores/ForeignStore.js b/src/stores/ForeignStore.js index 9f8a0abd..f97b36a5 100644 --- a/src/stores/ForeignStore.js +++ b/src/stores/ForeignStore.js @@ -1,8 +1,6 @@ import { action, observable } from 'mobx'; -import { abi as FOREIGN_NATIVE_ABI } from '../contracts/ForeignBridgeNativeToErc.json'; -import { abi as FOREIGN_ERC_ABI } from '../contracts/ForeignBridgeErcToErc'; import { abi as ERC677_ABI } from '../contracts/ERC677BridgeToken.json'; -import { getBlockNumber, getExplorerUrl } from './utils/web3' +import { getBlockNumber } from './utils/web3' import { getMaxPerTxLimit, getMinPerTxLimit, @@ -12,29 +10,41 @@ import { getBalanceOf, getErc677TokenAddress, getSymbol, - getErc20TokenAddress + getErc20TokenAddress, + getBridgeValidators, + getName } from './utils/contract' import { balanceLoaded, removePendingTransaction } from './utils/testUtils' +import { getBridgeABIs, getUnit, BRIDGE_MODES } from './utils/bridgeMode' +import { abi as BRIDGE_VALIDATORS_ABI } from '../contracts/BridgeValidators' +import ERC20Bytes32Abi from './utils/ERC20Bytes32.abi' class ForeignStore { @observable state = null; @observable loading = true; @observable events = []; @observable totalSupply = ''; - @observable symbol = ''; + @observable symbol = 'NOSYM'; + @observable tokenName = ''; @observable balance = ''; @observable filter = false; @observable maxCurrentDeposit = ''; @observable maxPerTx = ''; @observable minPerTx = ''; @observable latestBlockNumber = 0; + @observable validators = [] + @observable foreignBridgeValidators = '' + @observable requiredSignatures = 0 @observable dailyLimit = 0 @observable totalSpentPerDay = 0 @observable tokenAddress = ''; + networkName = process.env.REACT_APP_FOREIGN_NETWORK_NAME || 'Unknown' filteredBlockNumber = 0; foreignBridge = {}; tokenContract = {} FOREIGN_BRIDGE_ADDRESS = process.env.REACT_APP_FOREIGN_BRIDGE_ADDRESS; + explorerTxTemplate = process.env.REACT_APP_FOREIGN_EXPLORER_TX_TEMPLATE || '' + explorerAddressTemplate = process.env.REACT_APP_FOREIGN_EXPLORER_ADDRESS_TEMPLATE || '' constructor (rootStore) { this.web3Store = rootStore.web3Store; @@ -51,7 +61,7 @@ class ForeignStore { setTimeout(() => this.setForeign(), 200) return } - const FOREIGN_ABI = this.rootStore.isErcToErcMode ? FOREIGN_ERC_ABI : FOREIGN_NATIVE_ABI + const { FOREIGN_ABI } = getBridgeABIs(this.rootStore.bridgeMode) this.foreignBridge = new this.foreignWeb3.eth.Contract(FOREIGN_ABI, this.FOREIGN_BRIDGE_ADDRESS); await this.getBlockNumber() await this.getTokenInfo() @@ -60,6 +70,7 @@ class ForeignStore { this.getEvents() this.getTokenBalance() this.getCurrentLimit() + this.getValidators() setInterval(() => { this.getBlockNumber() this.getEvents() @@ -98,9 +109,21 @@ class ForeignStore { @action async getTokenInfo(){ try { - this.tokenAddress = this.rootStore.isErcToErcMode ? await getErc20TokenAddress(this.foreignBridge) : await getErc677TokenAddress(this.foreignBridge) + this.tokenAddress = this.rootStore.bridgeMode === BRIDGE_MODES.ERC_TO_ERC || this.rootStore.bridgeMode === BRIDGE_MODES.ERC_TO_NATIVE + ? await getErc20TokenAddress(this.foreignBridge) + : await getErc677TokenAddress(this.foreignBridge) this.tokenContract = new this.foreignWeb3.eth.Contract(ERC677_ABI, this.tokenAddress); - this.symbol = await getSymbol(this.tokenContract) + const alternativeContract = new this.foreignWeb3.eth.Contract(ERC20Bytes32Abi, this.tokenAddress); + try { + this.symbol =await getSymbol(this.tokenContract) + } catch(e) { + this.symbol = this.foreignWeb3.utils.hexToAscii(await getSymbol(alternativeContract)).replace(/\u0000*$/, '') + } + try { + this.tokenName = await getName(this.tokenContract) + } catch(e) { + this.tokenName = this.foreignWeb3.utils.hexToAscii(await getName(alternativeContract)).replace(/\u0000*$/, '') + } } catch(e) { console.error(e) } @@ -124,6 +147,11 @@ class ForeignStore { try { fromBlock = fromBlock || this.filteredBlockNumber || this.latestBlockNumber - 50 toBlock = toBlock || this.filteredBlockNumber || "latest" + + if(fromBlock < 0) { + fromBlock = 0 + } + let foreignEvents = await getPastEvents(this.foreignBridge, fromBlock, toBlock) if(!this.filter){ @@ -136,9 +164,10 @@ class ForeignStore { const TxReceipt = await this.getTxReceipt(event.transactionHash) if(TxReceipt && TxReceipt.logs && TxReceipt.logs.length > 1 && this.waitingForConfirmation.size) { this.alertStore.setLoadingStepIndex(3) - const urlExplorer = getExplorerUrl(this.web3Store.foreignNet.id) + 'tx/' + event.transactionHash + const urlExplorer = this.getExplorerTxUrl(event.transactionHash) + const unitReceived = getUnit(this.rootStore.bridgeMode).unitForeign setTimeout(() => { - this.alertStore.pushSuccess(`Tokens received on Ethereum ${this.web3Store.foreignNet.name} on Tx + this.alertStore.pushSuccess(`${unitReceived} received on ${this.networkName} on Tx ${event.transactionHash}`, this.alertStore.FOREIGN_TRANSFER_SUCCESS)} , 2000) @@ -220,6 +249,26 @@ class ForeignStore { return this.dailyLimit ? this.totalSpentPerDay / this.dailyLimit * 100 : 0 } + getExplorerTxUrl(txHash) { + return this.explorerTxTemplate.replace('%s', txHash) + } + + getExplorerAddressUrl(address) { + return this.explorerAddressTemplate.replace('%s', address) + } + + @action + async getValidators(){ + try { + const foreignValidatorsAddress = await this.foreignBridge.methods.validatorContract().call() + this.foreignBridgeValidators = new this.foreignWeb3.eth.Contract(BRIDGE_VALIDATORS_ABI, foreignValidatorsAddress); + this.validators = await getBridgeValidators(this.foreignBridgeValidators) + this.requiredSignatures = await this.foreignBridgeValidators.methods.requiredSignatures().call() + } catch(e){ + console.error(e) + } + } + } export default ForeignStore; diff --git a/src/stores/GasPriceStore.js b/src/stores/GasPriceStore.js index 741ec383..d13d411f 100644 --- a/src/stores/GasPriceStore.js +++ b/src/stores/GasPriceStore.js @@ -1,29 +1,60 @@ import { observable, computed } from "mobx"; import Web3Utils from 'web3-utils'; -import { getGasPrices } from './utils/web3' +import { fetchGasPrice, fetchGasPriceFromOracle } from './utils/gas' -const GAS_PRICE_SPEED_TYPE = process.env.REACT_APP_GAS_PRICE_SPEED_TYPE || 'fast' +const HOME_GAS_PRICE_FALLBACK = process.env.REACT_APP_HOME_GAS_PRICE_FALLBACK +const HOME_GAS_PRICE_ORACLE_URL = process.env.REACT_APP_HOME_GAS_PRICE_ORACLE_URL +const HOME_GAS_PRICE_SPEED_TYPE = process.env.REACT_APP_HOME_GAS_PRICE_SPEED_TYPE +const HOME_GAS_PRICE_UPDATE_INTERVAL = process.env.REACT_APP_HOME_GAS_PRICE_UPDATE_INTERVAL +const FOREIGN_GAS_PRICE_FALLBACK = process.env.REACT_APP_FOREIGN_GAS_PRICE_FALLBACK +const FOREIGN_GAS_PRICE_ORACLE_URL = process.env.REACT_APP_FOREIGN_GAS_PRICE_ORACLE_URL +const FOREIGN_GAS_PRICE_SPEED_TYPE = process.env.REACT_APP_FOREIGN_GAS_PRICE_SPEED_TYPE +const FOREIGN_GAS_PRICE_UPDATE_INTERVAL = process.env.REACT_APP_FOREIGN_GAS_PRICE_UPDATE_INTERVAL class GasPriceStore { - @observable gasPrices = {}; - gasPricePromise = null; + @observable gasPrice = null + oracleUrl = null + speedType = null + updateInterval = null + bridgeContract = null + constructor(rootStore) { - this.getGasPrices() - this.alertStore = rootStore.alertStore; + this.alertStore = rootStore.alertStore + this.homeStore = rootStore.homeStore + this.foreignStore = rootStore.foreignStore + this.web3Store = rootStore.web3Store + + this.updateGasPrice() } - async getGasPrices(){ - this.gasPricePromise = getGasPrices().then((data) => { - console.log(data) - this.gasPrices = data; - }).catch((e) => { - this.alertStore.pushError(e) + async updateGasPrice() { + await this.web3Store.setHomeWeb3Promise + + if (await this.web3Store.onHomeSide()) { + this.gasPrice = HOME_GAS_PRICE_FALLBACK + this.oracleUrl = HOME_GAS_PRICE_ORACLE_URL + this.speedType = HOME_GAS_PRICE_SPEED_TYPE + this.updateInterval = HOME_GAS_PRICE_UPDATE_INTERVAL || 900000 + this.bridgeContract = this.homeStore.homeBridge + } else { + this.gasPrice = FOREIGN_GAS_PRICE_FALLBACK + this.oracleUrl = FOREIGN_GAS_PRICE_ORACLE_URL + this.speedType = FOREIGN_GAS_PRICE_SPEED_TYPE + this.updateInterval = FOREIGN_GAS_PRICE_UPDATE_INTERVAL || 900000 + this.bridgeContract = this.foreignStore.foreignBridge + } + + const newGasPrice = await fetchGasPrice({ + bridgeContract: this.bridgeContract, + oracleFn: () => fetchGasPriceFromOracle(this.oracleUrl, this.speedType) }) + + this.gasPrice = newGasPrice || this.gasPrice + setTimeout(() => this.updateGasPrice(), this.updateInterval) } @computed get gasPriceInHex() { - const toWei = Web3Utils.toWei(this.gasPrices[GAS_PRICE_SPEED_TYPE].toString(), 'gwei') - return Web3Utils.toHex(toWei) + return Web3Utils.toHex(this.gasPrice) } } diff --git a/src/stores/HomeStore.js b/src/stores/HomeStore.js index 1472df09..5eb980e8 100644 --- a/src/stores/HomeStore.js +++ b/src/stores/HomeStore.js @@ -1,9 +1,8 @@ import { action, observable } from 'mobx'; -import { abi as HOME_NATIVE_ABI } from '../contracts/HomeBridgeNativeToErc.json'; -import { abi as HOME_ERC_ABI } from '../contracts/HomeBridgeErcToErc'; import { abi as BRIDGE_VALIDATORS_ABI } from '../contracts/BridgeValidators.json' import { abi as ERC677_ABI } from '../contracts/ERC677BridgeToken.json' -import { getBlockNumber, getBalance, getExplorerUrl } from './utils/web3' +import { abi as BLOCK_REWARD_ABI } from '../contracts/IBlockReward' +import { getBlockNumber, getBalance } from './utils/web3' import { getMaxPerTxLimit, getMinPerTxLimit, @@ -13,11 +12,17 @@ import { getErc677TokenAddress, getSymbol, getTotalSupply, - getBalanceOf + getBalanceOf, + mintedTotally, + totalBurntCoins, + getBridgeValidators, + getName } from './utils/contract' import { balanceLoaded, removePendingTransaction } from './utils/testUtils' import Web3Utils from 'web3-utils' import BN from 'bignumber.js' +import { getBridgeABIs, getUnit, BRIDGE_MODES } from './utils/bridgeMode' +import ERC20Bytes32Abi from './utils/ERC20Bytes32.abi' async function asyncForEach(array, callback) { for (let index = 0; index < array.length; index++) { @@ -41,7 +46,8 @@ class HomeStore { @observable dailyLimit = 0 @observable totalSpentPerDay = 0 @observable tokenAddress = ''; - @observable symbol = 'POA'; + @observable symbol = process.env.REACT_APP_HOME_NATIVE_NAME || 'NONAME'; + @observable tokenName = ''; @observable userBalance = 0 @observable statistics = { deposits: 0, @@ -52,10 +58,14 @@ class HomeStore { users: new Set(), finished: false } + networkName = process.env.REACT_APP_HOME_NETWORK_NAME || 'Unknown' filteredBlockNumber = 0 homeBridge = {}; HOME_BRIDGE_ADDRESS = process.env.REACT_APP_HOME_BRIDGE_ADDRESS; + explorerTxTemplate = process.env.REACT_APP_HOME_EXPLORER_TX_TEMPLATE || '' + explorerAddressTemplate = process.env.REACT_APP_HOME_EXPLORER_ADDRESS_TEMPLATE || '' tokenContract = {} + blockRewardContract = {} constructor (rootStore) { this.homeWeb3 = rootStore.web3Store.homeWeb3 @@ -71,10 +81,12 @@ class HomeStore { setTimeout(() => this.setHome(), 200) return } - const HOME_ABI = this.rootStore.isErcToErcMode ? HOME_ERC_ABI : HOME_NATIVE_ABI + const { HOME_ABI } = getBridgeABIs(this.rootStore.bridgeMode) this.homeBridge = new this.homeWeb3.eth.Contract(HOME_ABI, this.HOME_BRIDGE_ADDRESS); - if (this.rootStore.isErcToErcMode) { - this.getTokenInfo() + if (this.rootStore.bridgeMode === BRIDGE_MODES.ERC_TO_ERC) { + await this.getTokenInfo() + } else if(this.rootStore.bridgeMode === BRIDGE_MODES.ERC_TO_NATIVE) { + await this.getBlockRewardContract() } await this.getBlockNumber() this.getMinPerTxLimit() @@ -100,6 +112,18 @@ class HomeStore { this.tokenAddress = await getErc677TokenAddress(this.homeBridge) this.tokenContract = new this.homeWeb3.eth.Contract(ERC677_ABI, this.tokenAddress); this.symbol = await getSymbol(this.tokenContract) + this.tokenName = await getName(this.tokenContract) + const alternativeContract = new this.foreignWeb3.eth.Contract(ERC20Bytes32Abi, this.tokenAddress); + try { + this.symbol =await getSymbol(this.tokenContract) + } catch(e) { + this.symbol = this.homeWeb3.utils.hexToAscii(await getSymbol(alternativeContract)).replace(/\u0000*$/, '') + } + try { + this.tokenName = await getName(this.tokenContract) + } catch(e) { + this.tokenName = this.homeWeb3.utils.hexToAscii(await getName(alternativeContract)).replace(/\u0000*$/, '') + } } catch(e) { console.error(e) } @@ -135,12 +159,16 @@ class HomeStore { @action async getBalance() { try { - if (this.rootStore.isErcToErcMode) { + if (this.rootStore.bridgeMode === BRIDGE_MODES.ERC_TO_ERC) { this.balance = await getTotalSupply(this.tokenContract) this.web3Store.getWeb3Promise.then(async () => { this.userBalance = await getBalanceOf(this.tokenContract, this.web3Store.defaultAccount.address) balanceLoaded() }) + } else if (this.rootStore.bridgeMode === BRIDGE_MODES.ERC_TO_NATIVE) { + const mintedCoins = await mintedTotally(this.blockRewardContract) + const burntCoins = await totalBurntCoins(this.homeBridge) + this.balance = Web3Utils.fromWei(mintedCoins.minus(burntCoins).toString(10)) } else { this.balance = await getBalance(this.homeWeb3, this.HOME_BRIDGE_ADDRESS) } @@ -155,6 +183,11 @@ class HomeStore { try { fromBlock = fromBlock || this.filteredBlockNumber || this.latestBlockNumber - 50 toBlock = toBlock || this.filteredBlockNumber || "latest" + + if(fromBlock < 0) { + fromBlock = 0 + } + let events = await getPastEvents(this.homeBridge, fromBlock, toBlock) let homeEvents = [] @@ -173,9 +206,10 @@ class HomeStore { const confirmationEvents = homeEvents.filter((event) => event.event === "AffirmationCompleted" && this.waitingForConfirmation.has(event.returnValues.transactionHash)) confirmationEvents.forEach(event => { this.alertStore.setLoadingStepIndex(3) - const urlExplorer = getExplorerUrl(this.web3Store.homeNet.id) + 'tx/' + event.transactionHash + const urlExplorer = this.getExplorerTxUrl(event.transactionHash) + const unitReceived = getUnit(this.rootStore.bridgeMode).unitHome setTimeout(() => { - this.alertStore.pushSuccess(`Tokens received on POA ${this.web3Store.homeNet.name} on Tx + this.alertStore.pushSuccess(`${unitReceived} received on ${this.networkName} on Tx ${event.transactionHash}`, this.alertStore.HOME_TRANSFER_SUCCESS)} , 2000) @@ -203,6 +237,14 @@ class HomeStore { } } + getExplorerTxUrl(txHash) { + return this.explorerTxTemplate.replace('%s', txHash) + } + + getExplorerAddressUrl(address) { + return this.explorerAddressTemplate.replace('%s', address) + } + @action async filterByTxHashInReturnValues(transactionHash) { const events = await this.getEvents(1,"latest"); @@ -251,16 +293,7 @@ class HomeStore { try { const homeValidatorsAddress = await this.homeBridge.methods.validatorContract().call() this.homeBridgeValidators = new this.homeWeb3.eth.Contract(BRIDGE_VALIDATORS_ABI, homeValidatorsAddress); - - let ValidatorAdded = await this.homeBridgeValidators.getPastEvents('ValidatorAdded', {fromBlock: 0}); - let ValidatorRemoved = await this.homeBridgeValidators.getPastEvents('ValidatorRemoved', {fromBlock: 0}); - let homeAddedValidators = ValidatorAdded.map(val => { - return val.returnValues.validator - }) - const homeRemovedValidators = ValidatorRemoved.map(val => { - return val.returnValues.validator - }) - this.validators = homeAddedValidators.filter(val => !homeRemovedValidators.includes(val)); + this.validators = await getBridgeValidators(this.homeBridgeValidators) this.requiredSignatures = await this.homeBridgeValidators.methods.requiredSignatures().call() } catch(e){ console.error(e) @@ -318,7 +351,12 @@ class HomeStore { } getDisplayedBalance() { - return this.rootStore.isErcToErcMode ? this.userBalance : this.web3Store.defaultAccount.homeBalance + return this.rootStore.bridgeMode === BRIDGE_MODES.ERC_TO_ERC ? this.userBalance : this.web3Store.defaultAccount.homeBalance + } + + async getBlockRewardContract () { + const blockRewardAddress = await this.homeBridge.methods.blockRewardContract().call() + this.blockRewardContract = new this.homeWeb3.eth.Contract(BLOCK_REWARD_ABI, blockRewardAddress) } } diff --git a/src/stores/RootStore.js b/src/stores/RootStore.js index 0b086cd8..e6cbb6c6 100644 --- a/src/stores/RootStore.js +++ b/src/stores/RootStore.js @@ -5,9 +5,9 @@ import ForeignStore from './ForeignStore' import AlertStore from './AlertStore' import GasPriceStore from './GasPriceStore' import TxStore from './TxStore' -import { abi as HOME_ERC_ABI } from '../contracts/HomeBridgeErcToErc'; +import { abi as HOME_ERC_ABI } from '../contracts/HomeBridgeErcToErc' +import { decodeBridgeMode } from './utils/bridgeMode' import { getWeb3Instance } from './utils/web3' -import { getErc677TokenAddress } from './utils/contract' class RootStore { constructor() { @@ -25,12 +25,8 @@ class RootStore { async setBridgeMode() { const homeWeb3 = getWeb3Instance(process.env.REACT_APP_HOME_HTTP_PARITY_URL) const homeBridge = new homeWeb3.eth.Contract(HOME_ERC_ABI, process.env.REACT_APP_HOME_BRIDGE_ADDRESS) - try { - await getErc677TokenAddress(homeBridge) - this.isErcToErcMode = true - } catch (e) { - this.isErcToErcMode = false - } + const bridgeModeHash = await homeBridge.methods.getBridgeMode().call() + this.bridgeMode = decodeBridgeMode(bridgeModeHash) this.bridgeModeInitialized = true } } diff --git a/src/stores/TxStore.js b/src/stores/TxStore.js index f60df514..c6a2b59f 100644 --- a/src/stores/TxStore.js +++ b/src/stores/TxStore.js @@ -1,10 +1,8 @@ -import { action, observable } from "mobx"; +import { action } from "mobx"; import { estimateGas } from './utils/web3' import { addPendingTransaction } from './utils/testUtils' class TxStore { - @observable txs = [] - txHashToIndex = {} constructor(rootStore) { this.web3Store = rootStore.web3Store this.gasPriceStore = rootStore.gasPriceStore @@ -15,10 +13,9 @@ class TxStore { @action async doSend({to, from, value, data}){ - const index = this.txs.length; return this.web3Store.getWeb3Promise.then(async ()=> { if(!this.web3Store.defaultAccount){ - this.alertStore.pushError("Please unlock metamask") + this.alertStore.pushError("Please unlock wallet") return } try { @@ -33,15 +30,13 @@ class TxStore { data }).on('transactionHash', (hash) => { console.log('txHash', hash) - this.txHashToIndex[hash] = index; - this.txs[index] = {status: 'pending', name: `Sending ${to} ${value}`, hash} this.alertStore.setLoadingStepIndex(1) addPendingTransaction() this.getTxReceipt(hash) }).on('error', (e) => { if(!e.message.includes('not mined within 50 blocks') && !e.message.includes('Failed to subscribe to new newBlockHeaders')){ this.alertStore.setLoading(false) - this.alertStore.pushError('Transaction rejected on Metamask'); + this.alertStore.pushError('Transaction rejected on wallet'); } }) } catch(e) { @@ -60,7 +55,7 @@ class TxStore { ).encodeABI() return this.doSend({to: tokenAddress, from, value: '0x00', data}) } else { - this.alertStore.pushError('Please unlock metamask'); + this.alertStore.pushError('Please unlock wallet'); } }) } catch(e) { @@ -78,7 +73,7 @@ class TxStore { ).encodeABI({ from: this.web3Store.defaultAccount.address }) return this.doSend({to: this.foreignStore.tokenAddress, from, value: '0x', data}) } else { - this.alertStore.pushError('Please unlock metamask'); + this.alertStore.pushError('Please unlock wallet'); } }) } catch(e) { @@ -102,11 +97,10 @@ class TxStore { async getTxStatus(hash) { const web3 = this.web3Store.injectedWeb3; + const { toBN } = web3.utils web3.eth.getTransactionReceipt(hash, (error, res) => { if(res && res.blockNumber){ - if(res.status === '0x1'){ - const index = this.txHashToIndex[hash] - this.txs[index].status = `mined` + if(toBN(res.status).eq(toBN(1))){ if(this.web3Store.metamaskNet.id === this.web3Store.homeNet.id.toString()) { const blockConfirmations = this.homeStore.latestBlockNumber - res.blockNumber if(blockConfirmations >= 8) { @@ -135,9 +129,6 @@ class TxStore { } } else { this.alertStore.setLoading(false) - const index = this.txHashToIndex[hash] - this.txs[index].status = `error` - this.txs[index].name = `Mined but with errors. Perhaps out of gas` this.alertStore.pushError(`${hash} Mined but with errors. Perhaps out of gas`) } } else { diff --git a/src/stores/Web3Store.js b/src/stores/Web3Store.js index 66910452..af991b88 100644 --- a/src/stores/Web3Store.js +++ b/src/stores/Web3Store.js @@ -2,6 +2,7 @@ import { action, observable } from "mobx"; import getWeb3, { getBalance, getWeb3Instance, getNetwork } from './utils/web3'; import { balanceLoaded } from './utils/testUtils' import swal from 'sweetalert' +import { BRIDGE_MODES } from './utils/bridgeMode' class Web3Store { @observable injectedWeb3 = {}; @@ -14,6 +15,7 @@ class Web3Store { @observable errors = []; @observable getWeb3Promise = null; + @observable setHomeWeb3Promise = null; @observable metamaskNotSetted = false; @observable homeNet = {id: '', name: ''}; @@ -56,7 +58,9 @@ class Web3Store { @action async setWeb3Home() { this.homeWeb3 = getWeb3Instance(this.HOME_HTTP_PARITY_URL) - this.homeNet = await getNetwork(this.homeWeb3) + this.setHomeWeb3Promise = getNetwork(this.homeWeb3).then(homeNet => { + this.homeNet = homeNet + }) } @action @@ -84,7 +88,7 @@ class Web3Store { await this.rootStore.homeStore.getBalance() this.alertStore.setLoading(false) } - if (this.rootStore.bridgeModeInitialized && !this.rootStore.isErcToErcMode) { + if (this.rootStore.bridgeModeInitialized && this.rootStore.bridgeMode !== BRIDGE_MODES.ERC_TO_ERC) { balanceLoaded() } } catch(e){ @@ -101,14 +105,14 @@ class Web3Store { } if(this.metamaskNet.name !== this.homeNet.name && this.metamaskNet.name !== this.foreignNet.name) { this.metamaskNotSetted = true - this.alertStore.pushError(`You are on an unknown network on metamask. Please select POA ${this.homeNet.name} or ETH ${this.foreignNet.name} in order to communicate with the bridge.`) + this.alertStore.pushError(`You are on an unknown network on your wallet. Please select ${this.homeNet.name} or ${this.foreignNet.name} in order to communicate with the bridge.`) } } } showInstallMetamaskAlert() { const errorNode = document.createElement("div") - errorNode.innerHTML = "You need to install metamask and select an account. Please follow the instructions on the POA Network wiki and reload the page." + errorNode.innerHTML = "You need to install a wallet and select an account. Please follow the instructions on the POA Network wiki and reload the page." swal({ title: "Error", content: errorNode, @@ -117,6 +121,11 @@ class Web3Store { }); } + async onHomeSide() { + await this.getWeb3Promise + await this.setHomeWeb3Promise + return this.metamaskNet.id === this.homeNet.id.toString() + } } export default Web3Store; diff --git a/src/stores/utils/ERC20Bytes32.abi.json b/src/stores/utils/ERC20Bytes32.abi.json new file mode 100644 index 00000000..3c6ccd2d --- /dev/null +++ b/src/stores/utils/ERC20Bytes32.abi.json @@ -0,0 +1,30 @@ +[ + { + "constant": true, + "inputs": [], + "name": "name", + "outputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "symbol", + "outputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + } +] diff --git a/src/stores/utils/bridgeMode.js b/src/stores/utils/bridgeMode.js new file mode 100644 index 00000000..92ec98b1 --- /dev/null +++ b/src/stores/utils/bridgeMode.js @@ -0,0 +1,63 @@ +import { abi as HOME_NATIVE_TO_ERC_ABI } from '../../contracts/HomeBridgeNativeToErc.json' +import { abi as FOREIGN_NATIVE_TO_ERC_ABI } from '../../contracts/ForeignBridgeNativeToErc.json' +import { abi as HOME_ERC_TO_ERC_ABI } from '../../contracts/HomeBridgeErcToErc.json' +import { abi as FOREIGN_ERC_TO_ERC_ABI } from '../../contracts/ForeignBridgeErcToErc.json' +import { abi as HOME_ERC_TO_NATIVE_ABI } from '../../contracts/HomeBridgeErcToNative.json' +import { abi as FOREIGN_ERC_TO_NATIVE_ABI } from '../../contracts/ForeignBridgeErcToNative.json' + +export const BRIDGE_MODES = { + NATIVE_TO_ERC: 'NATIVE_TO_ERC', + ERC_TO_ERC: 'ERC_TO_ERC', + ERC_TO_NATIVE: 'ERC_TO_NATIVE' +} + +export const getBridgeABIs = (bridgeMode) => { + let HOME_ABI = null + let FOREIGN_ABI = null + if (bridgeMode === BRIDGE_MODES.NATIVE_TO_ERC) { + HOME_ABI = HOME_NATIVE_TO_ERC_ABI + FOREIGN_ABI = FOREIGN_NATIVE_TO_ERC_ABI + } else if (bridgeMode === BRIDGE_MODES.ERC_TO_ERC) { + HOME_ABI = HOME_ERC_TO_ERC_ABI + FOREIGN_ABI = FOREIGN_ERC_TO_ERC_ABI + } else if (bridgeMode === BRIDGE_MODES.ERC_TO_NATIVE) { + HOME_ABI = HOME_ERC_TO_NATIVE_ABI + FOREIGN_ABI = FOREIGN_ERC_TO_NATIVE_ABI + } else { + throw new Error(`Unrecognized bridge mode: ${bridgeMode}`) + } + + return { HOME_ABI, FOREIGN_ABI } +} + +export const decodeBridgeMode = (bridgeModeHash) => { + switch (bridgeModeHash) { + case '0x92a8d7fe': + return BRIDGE_MODES.NATIVE_TO_ERC + case '0xba4690f5': + return BRIDGE_MODES.ERC_TO_ERC + case '0x18762d46': + return BRIDGE_MODES.ERC_TO_NATIVE + default: + throw new Error(`Unrecognized bridge mode hash: '${bridgeModeHash}'`) + } +} + +export const getUnit = (bridgeMode) => { + let unitHome = null + let unitForeign = null + if (bridgeMode === BRIDGE_MODES.NATIVE_TO_ERC) { + unitHome = 'Native coins' + unitForeign = 'Tokens' + } else if (bridgeMode === BRIDGE_MODES.ERC_TO_ERC) { + unitHome = 'Tokens' + unitForeign = 'Tokens' + } else if (bridgeMode === BRIDGE_MODES.ERC_TO_NATIVE) { + unitHome = 'Tokens' + unitForeign = 'Native coins' + } else { + throw new Error(`Unrecognized bridge mode: ${bridgeMode}`) + } + + return { unitHome, unitForeign } +} diff --git a/src/stores/utils/contract.js b/src/stores/utils/contract.js index 90e922c6..2182d4cf 100644 --- a/src/stores/utils/contract.js +++ b/src/stores/utils/contract.js @@ -42,3 +42,27 @@ export const getBalanceOf = async (contract, address) => { const balance = await contract.methods.balanceOf(address).call() return Web3Utils.fromWei(balance) } + +export const mintedTotally = async (contract) => { + const mintedCoins = await contract.methods.mintedTotally().call() + return new BN(mintedCoins) +} + +export const totalBurntCoins = async (contract) => { + const burntCoins = await contract.methods.totalBurntCoins().call() + return new BN(burntCoins) +} + +export const getBridgeValidators = async (bridgeValidatorContract) => { + let ValidatorAdded = await bridgeValidatorContract.getPastEvents('ValidatorAdded', {fromBlock: 0}); + let ValidatorRemoved = await bridgeValidatorContract.getPastEvents('ValidatorRemoved', {fromBlock: 0}); + let addedValidators = ValidatorAdded.map(val => { + return val.returnValues.validator + }) + const removedValidators = ValidatorRemoved.map(val => { + return val.returnValues.validator + }) + return addedValidators.filter(val => !removedValidators.includes(val)); +} + +export const getName = (contract) => contract.methods.name().call() diff --git a/src/stores/utils/gas.js b/src/stores/utils/gas.js new file mode 100644 index 00000000..44275ced --- /dev/null +++ b/src/stores/utils/gas.js @@ -0,0 +1,26 @@ +const { toWei } = require('web3').utils + +export async function fetchGasPrice({ bridgeContract, oracleFn }) { + let gasPrice = null + try { + gasPrice = await oracleFn() + } catch (e) { + if (!e.message.includes('Gas Price Oracle url not defined')) { + console.error(`Gas Price API is not available. ${e.message}`) + } + } + return gasPrice +} + +export async function fetchGasPriceFromOracle(oracleUrl, speedType) { + if(!oracleUrl) { + throw new Error(`Gas Price Oracle url not defined`) + } + const response = await fetch(oracleUrl) + const json = await response.json() + const gasPrice = json[speedType] + if (!gasPrice) { + throw new Error(`Response from Oracle didn't include gas price for ${speedType} type.`) + } + return toWei(gasPrice.toString(), 'gwei') +} diff --git a/src/stores/utils/web3.js b/src/stores/utils/web3.js index ed030e45..f073bdea 100644 --- a/src/stores/utils/web3.js +++ b/src/stores/utils/web3.js @@ -24,11 +24,11 @@ const getWeb3 = () => { processWeb3(web3, resolve, reject) } else { // Fallback to localhost if no web3 injection. - const errorMsg = `Metamask is not installed. Please go to - Metamask website and return to this page after you installed it` + const errorMsg = `A wallet is not installed. Please go to + Nifty Wallet and return to this page after you installed it` reject({message: errorMsg}) console.log('No web3 instance injected, using Local web3.'); - console.error('Metamask not found'); + console.error('wallet not found'); } }) }) @@ -37,27 +37,15 @@ const getWeb3 = () => { export default getWeb3 const networks = { - 1: 'Network', + 1: 'ETH Mainnet', 3: 'Ropsten', 4: 'Rinkeby', - 42:'Kovan', - 77:'Sokol', - 99:'Network' + 42: 'Kovan', + 77: 'Sokol', + 99: 'POA Network', + 100: 'Dai Chain' } -const explorers = { - 1: 'https://etherscan.io/', - 3: 'https://ropsten.etherscan.io/', - 4: 'https://rinkeby.etherscan.io/', - 42:'https://kovan.etherscan.io/', - 77:'https://sokol-explorer.poa.network/', - 99:'https://poaexplorer.com/' -} - -export const getExplorerUrl = (id) => explorers[id] - -export const getAddressUrl = (id) => getExplorerUrl(id) + (id.toString() === '77' ? 'account/' : 'address/') - export const getNetworkName = (id) => networks[id] || 'Unknown' export const getBalance = async (web3, address) => { @@ -86,8 +74,6 @@ export const estimateGas = async (web3, to, gasPrice, from, value, data) =>{ return Web3Utils.toHex(gas.toString()) } -export const getGasPrices = () => fetch('https://gasprice.poa.network/').then(response => response.json()) - const processWeb3 = (web3, resolve, reject) => { web3.version.getNetwork((err, netId) => { const netIdName = getNetworkName(netId) diff --git a/submodules/poa-bridge-contracts b/submodules/poa-bridge-contracts index 4e869c0c..5892b379 160000 --- a/submodules/poa-bridge-contracts +++ b/submodules/poa-bridge-contracts @@ -1 +1 @@ -Subproject commit 4e869c0c06dcff4d1d38ca65e9a62e973f5c3e97 +Subproject commit 5892b3792959e5dfc0379a7fb71fa383971c7388