From 0ff2aac3fcd4b4ad1953760f3f54a551b5005a88 Mon Sep 17 00:00:00 2001 From: Michael Toutonghi Date: Mon, 19 Oct 2020 18:11:45 +0200 Subject: [PATCH 1/7] Switch to ropsten, add RFOX and RFOX claim api/button, fix VerusPay address only bug, fix bug with sending eth/erc20 amounts in scientific notation --- env/main.json | 2 +- ios/assets/env/main.json | 2 +- package.json | 1 + src/components/RFoxClaim.js | 134 ++++++++++++++++++ src/containers/Coin/SendCoin/SendCoin.js | 101 ++++++++++++- src/images/cryptologo/default/web3/index.js | 4 +- src/images/cryptologo/default/web3/rfox.svg | 13 ++ src/reducers/gui/coinMenus.js | 17 ++- src/utils/CoinData/CoinData.js | 1 + src/utils/CoinData/CoinsList.js | 39 +++-- src/utils/api/channels/erc20/callCreator.js | 3 +- .../api/channels/erc20/requests/preflight.js | 5 +- src/utils/api/channels/erc20/requests/send.js | 5 +- .../channels/erc20/requests/specific/index.js | 5 + .../specific/rfox/claimAccountBalance.js | 54 +++++++ .../specific/rfox/getTotalAccountBalance.js | 25 ++++ .../erc20/requests/specific/rfox/index.js | 8 ++ .../api/channels/eth/requests/preflight.js | 3 +- src/utils/api/channels/eth/requests/send.js | 3 +- src/utils/constants/storeType.js | 1 + src/utils/constants/web3Constants.js | 5 +- src/utils/math.js | 29 +++- yarn.lock | 10 ++ 23 files changed, 439 insertions(+), 31 deletions(-) create mode 100644 src/components/RFoxClaim.js create mode 100644 src/images/cryptologo/default/web3/rfox.svg create mode 100644 src/utils/api/channels/erc20/requests/specific/index.js create mode 100644 src/utils/api/channels/erc20/requests/specific/rfox/claimAccountBalance.js create mode 100644 src/utils/api/channels/erc20/requests/specific/rfox/getTotalAccountBalance.js create mode 100644 src/utils/api/channels/erc20/requests/specific/rfox/index.js diff --git a/env/main.json b/env/main.json index 3a78a1f4..a3f0a1a6 100644 --- a/env/main.json +++ b/env/main.json @@ -20,7 +20,7 @@ "ENABLE_ELECTRUM": true, "ENABLE_GENERAL": true, - "ETH_NETWORK": "homestead", + "ETH_NETWORK": "ropsten", "BIOMETRIC_SECURITY_THRESHOLD": "SECURE_HARDWARE", diff --git a/ios/assets/env/main.json b/ios/assets/env/main.json index 3a78a1f4..a3f0a1a6 100644 --- a/ios/assets/env/main.json +++ b/ios/assets/env/main.json @@ -20,7 +20,7 @@ "ENABLE_ELECTRUM": true, "ENABLE_GENERAL": true, - "ETH_NETWORK": "homestead", + "ETH_NETWORK": "ropsten", "BIOMETRIC_SECURITY_THRESHOLD": "SECURE_HARDWARE", diff --git a/package.json b/package.json index 862bc76e..cec8508c 100755 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "dependencies": { "@babel/core": "^7.6.2", "@babel/runtime": "^7.6.2", + "@ethersproject/signing-key": "^5.0.5", "@react-native-community/art": "^1.2.0", "@react-native-community/async-storage": "^1.5.1", "@react-native-community/cameraroll": "^4.0.1", diff --git a/src/components/RFoxClaim.js b/src/components/RFoxClaim.js new file mode 100644 index 00000000..4bf41928 --- /dev/null +++ b/src/components/RFoxClaim.js @@ -0,0 +1,134 @@ +/* + This component creates a modal to claim RFox + migrated funds +*/ + +import { ethers } from "ethers"; +import React, { Component } from "react" +import { + View, + Modal, + Text, + ScrollView, + Alert, +} from "react-native" +import AlertAsync from "react-native-alert-async"; +import StandardButton from "../components/StandardButton" +import Colors from '../globals/colors'; +import Styles from '../styles/index' +import { claimRFoxAccountBalances, estimateGasClaimRFoxAccountBalances } from "../utils/api/channels/erc20/requests/specific/rfox/claimAccountBalance"; +import { ETHERS } from "../utils/constants/web3Constants"; + +class RFoxClaim extends Component { + constructor(props) { + super(props) + this.state = { + loading: false, + rewards: ethers.utils.formatUnits(props.rewards) + } + } + + cancelHandler = () => { + if (this.props.cancel) { + this.props.cancel() + } + } + + canClaim = (feeString) => { + return AlertAsync( + 'Claim RFOX?', + `Would you like to claim ${this.state.rewards} RFOX? This action will cost you an estimated ${feeString} ETH in fees.`, + [ + { + text: 'No', + onPress: () => Promise.resolve(false), + style: 'cancel', + }, + {text: 'Yes', onPress: () => Promise.resolve(true)}, + ], + { + cancelable: false, + }, + ) + } + + claim = () => { + this.setState({ loading: true }, async () => { + try { + const gasFee = ethers.utils.formatUnits( + await estimateGasClaimRFoxAccountBalances( + this.props.pubKey, + this.props.fromAddress + ), + ETHERS + ); + + if (await this.canClaim(gasFee.toString())) { + try { + await claimRFoxAccountBalances(this.props.privKey, this.props.pubKey) + + this.props.onSuccess() + } catch(e) { + console.error(e) + Alert.alert("Error", "Error claiming RFOX funds, try adding ETH to your wallet to cover fees, and claim again.") + this.setState({ + loading: false + }) + } + } + } catch(e) { + console.error(e) + Alert.alert("Error", "Error estimating gas for claim tx.") + } + + this.setState({ loading: false }) + }) + } + + render() { + return ( + + + + + {"Claim RFox Funds"} + + + + + {'You have the following funds available to claim.'} + + {this.state.rewards + " RFOX"} + + {"Press the claim button to add them to your wallet!"} + + + + + + + + + + + ); + } +} + +export default RFoxClaim; diff --git a/src/containers/Coin/SendCoin/SendCoin.js b/src/containers/Coin/SendCoin/SendCoin.js index 3384a81b..5bde4633 100644 --- a/src/containers/Coin/SendCoin/SendCoin.js +++ b/src/containers/Coin/SendCoin/SendCoin.js @@ -32,6 +32,12 @@ import { API_GET_FIATPRICE, API_GET_BALANCES } from "../../../utils/constants/in import BigNumber from "bignumber.js" import { VerusPayLogo } from "../../../images/customIcons" import Colors from "../../../globals/colors" +import { UtilityContracts } from "../../../utils/api/channels/erc20/callCreator" +import { ethers } from "ethers" +import { stubFalse } from "lodash" +import RFoxClaim from "../../../components/RFoxClaim" +import { Portal } from "react-native-paper" +import { DISABLE_CLAIM_BUTTON } from "../../../utils/constants/storeType" class SendCoin extends Component { constructor(props) { @@ -51,11 +57,15 @@ class SendCoin extends Component { formErrors: { toAddress: null, amount: null }, spendableBalance: 0, addressCheckEnabled: true, - addressCheckSecretCounter: 0 + addressCheckSecretCounter: 0, + rewards: ethers.BigNumber.from(0), + rewardModalOpen: false, + pubKey: null }; this.ADDR_CHECK_SECRET_COUNTER_TRIGGER = 10 this._unsubscribeFocus = null; + this.ZERO = ethers.BigNumber.from(0) } componentDidMount() { @@ -70,13 +80,24 @@ class SendCoin extends Component { this._unsubscribeFocus(); } + componentDidUpdate(lastProps, lastState) { + if (lastState.rewardModalOpen !== this.state.rewardModalOpen && this.state.rewardModalOpen === false) { + this.initializeState() + } + } + initializeState = () => { this.setState( { coin: this.props.activeCoin, account: this.props.activeAccount, activeCoinsForUser: this.props.activeCoinsForUser, - toAddress: this.props.data ? this.props.data.address : null, + toAddress: + this.state.toAddress && this.state.toAddress.length > 0 + ? this.state.toAddress + : this.props.data + ? this.props.data.address + : null, }, () => { const activeUser = this.state.account; @@ -87,6 +108,15 @@ class SendCoin extends Component { ); }; + checkRfoxClaims = async (pubKey) => { + const balances = await UtilityContracts.rfox.getRfoxAccountBalances(pubKey) + + this.setState({ + rewards: balances.available, + pubKey + }) + } + handleState = async (activeUser, coinObj) => { const { channel } = this.props; @@ -127,6 +157,14 @@ class SendCoin extends Component { await this.updateBtcFees(); } + if ( + coinObj.id === "RFOX" && + activeUser.keys[coinObj.id] != null && + activeUser.keys[coinObj.id][channel] != null + ) { + await this.checkRfoxClaims(activeUser.keys[coinObj.id][channel].pubKey); + } + this.setState({ loading: false }); } ); @@ -224,6 +262,14 @@ class SendCoin extends Component { }) } + getPrivKey = () => { + const { activeAccount, activeCoin, channel } = this.props + + if (activeAccount != null && activeAccount.keys[activeCoin.id] != null && activeAccount.keys[activeCoin.id][channel] != null) { + return activeAccount.keys[activeCoin.id][channel].privKey + } else return null + } + //TODO: Add fee to Bitcoin object in CoinData validateFormData = () => { @@ -294,8 +340,22 @@ class SendCoin extends Component { ); }; + claimSuccess() { + this.props.dispatch({ + type: DISABLE_CLAIM_BUTTON + }) + + this.setState({ + rewardModalOpen: false + }, () => { + Alert.alert("Success!", "RFOX claimed, it may take a few minutes to show in your wallet.") + }) + } + render() { - const { balances } = this.props; + const { balances, claimDisabled } = this.props; + const hasRewards = this.state.rewards.gt(this.ZERO) + const privKey = this.getPrivKey() return ( + + {this.state.rewardModalOpen && this.claimSuccess()} + cancel={() => { + this.setState({ rewardModalOpen: false }); + }} + />} + ) : ( - + + {hasRewards ? ( + this.setState({ + rewardModalOpen: true + })} + title="CLAIM" + color={Colors.successButtonColor} + disabled={claimDisabled} + /> + ) : null} { errors: state.errors[API_GET_BALANCES][channel][chainTicker], }, activeAccount: state.authentication.activeAccount, + claimDisabled: state.coinMenus.claimDisabled } }; diff --git a/src/images/cryptologo/default/web3/index.js b/src/images/cryptologo/default/web3/index.js index ddbda0dc..1f6c67d1 100644 --- a/src/images/cryptologo/default/web3/index.js +++ b/src/images/cryptologo/default/web3/index.js @@ -11,6 +11,7 @@ import UNI from './uni.svg' import VEN from './ven.svg' import YFI from './yfi.svg' import ZRX from './zrx.svg' +import RFOX from './rfox.svg' export default { ETH, @@ -25,5 +26,6 @@ export default { UNI, VEN, YFI, - ZRX + ZRX, + RFOX } \ No newline at end of file diff --git a/src/images/cryptologo/default/web3/rfox.svg b/src/images/cryptologo/default/web3/rfox.svg new file mode 100644 index 00000000..f1f725c0 --- /dev/null +++ b/src/images/cryptologo/default/web3/rfox.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/reducers/gui/coinMenus.js b/src/reducers/gui/coinMenus.js index 5912a1df..73351ccc 100644 --- a/src/reducers/gui/coinMenus.js +++ b/src/reducers/gui/coinMenus.js @@ -9,15 +9,18 @@ */ import { + DISABLE_CLAIM_BUTTON, INIT_COIN_SUB_WALLETS, SET_COIN_SUB_WALLET, - SET_USER_COINS + SET_USER_COINS, + SIGN_OUT } from '../../utils/constants/storeType' import { getDefaultSubWallets } from '../../utils/defaultSubWallets'; export const coinMenus = (state = { activeSubWallets: {}, - allSubWallets: {} + allSubWallets: {}, + claimDisabled: false }, action) => { switch (action.type) { case SET_COIN_SUB_WALLET: @@ -40,6 +43,16 @@ export const coinMenus = (state = { ...state, allSubWallets: subWallets }; + case DISABLE_CLAIM_BUTTON: + return { + ...state, + claimDisabled: true + }; + case SIGN_OUT: + return { + ...state, + claimDisabled: false + }; default: return state; } diff --git a/src/utils/CoinData/CoinData.js b/src/utils/CoinData/CoinData.js index 6991346e..0451e10f 100644 --- a/src/utils/CoinData/CoinData.js +++ b/src/utils/CoinData/CoinData.js @@ -107,6 +107,7 @@ export const CoinLogos = { ven: CoinLogoSvgs.web3.VEN, yfi: CoinLogoSvgs.web3.YFI, zrx: CoinLogoSvgs.web3.ZRX, + rfox: CoinLogoSvgs.web3.RFOX }; //To make flatlist render faster diff --git a/src/utils/CoinData/CoinsList.js b/src/utils/CoinData/CoinsList.js index 31a3f832..b5752345 100644 --- a/src/utils/CoinData/CoinsList.js +++ b/src/utils/CoinData/CoinsList.js @@ -78,19 +78,19 @@ export const coinsList = { tags: [], proto: 'erc20' }, - // tst: { - // id: "TST", - // currency_id: '0x722dd3F80BAC40c951b51BdD28Dd19d435762180', - // system_id: '.eth', - // display_ticker: 'TST', - // display_name: "ERC20 Test Token", - // description: "A test token for testing the ERC20 protocol.", - // compatible_channels: [ERC20, GENERAL], - // dominant_channel: ERC20, - // decimals: ETHERS, - // tags: [], - // proto: 'erc20' - // }, + tst: { + id: "TST", + currency_id: '0x722dd3F80BAC40c951b51BdD28Dd19d435762180', + system_id: '.eth', + display_ticker: 'TST', + display_name: "ERC20 Test Token", + description: "A test token for testing the ERC20 protocol.", + compatible_channels: [ERC20, GENERAL], + dominant_channel: ERC20, + decimals: ETHERS, + tags: [], + proto: 'erc20' + }, yfi: { id: "YFI", currency_id: '0x0bc529c00C6401aEF6D220BE8C6Ea1667F6Ad93e', @@ -156,6 +156,19 @@ export const coinsList = { tags: [], proto: 'erc20' }, + rfox: { + id: "RFOX", + currency_id: '0x4847179E54AC60F21a995A3cD0c172CE51a88C45', + system_id: '.eth', + display_ticker: 'RFOX', + display_name: "RedFOX Labs", + description: "", + compatible_channels: [ERC20, GENERAL], + dominant_channel: ERC20, + decimals: ETHERS, + tags: [], + proto: 'erc20' + }, bal: { id: "BAL", currency_id: '0xba100000625a3754423978a60c9317c58a424e3d', diff --git a/src/utils/api/channels/erc20/callCreator.js b/src/utils/api/channels/erc20/callCreator.js index 19ddcb26..26d7d71a 100644 --- a/src/utils/api/channels/erc20/callCreator.js +++ b/src/utils/api/channels/erc20/callCreator.js @@ -1,4 +1,5 @@ export * from './requests/getErc20Balance' export * from './requests/getErc20Transactions' export * from './requests/preflight' -export * from './requests/send' \ No newline at end of file +export * from './requests/send' +export * from './requests/specific/index' \ No newline at end of file diff --git a/src/utils/api/channels/erc20/requests/preflight.js b/src/utils/api/channels/erc20/requests/preflight.js index 7daacec1..1ea719f1 100644 --- a/src/utils/api/channels/erc20/requests/preflight.js +++ b/src/utils/api/channels/erc20/requests/preflight.js @@ -1,16 +1,17 @@ import { ethers } from "ethers" import Web3Provider from '../../../../web3/provider' import { ERC20, ETH } from "../../../../constants/intervalConstants" +import { scientificToDecimal } from "../../../../math" // TODO: Add balance recalculation with eth gas export const txPreflight = async (coinObj, activeUser, address, amount, params) => { try { const fromAddress = activeUser.keys[coinObj.id][ERC20].addresses[0] - const contract = Web3Provider.getContract(coinObj.currency_id) const signer = new ethers.VoidSigner(fromAddress, Web3Provider.DefaultProvider) + const contract = Web3Provider.getContract(coinObj.currency_id).connect(signer) const balance = await contract.balanceOf(signer.getAddress()) const gasPrice = await Web3Provider.DefaultProvider.getGasPrice() - const amountBn = ethers.utils.parseUnits(amount.toString(), coinObj.decimals) + const amountBn = ethers.utils.parseUnits(scientificToDecimal(amount.toString()), coinObj.decimals) const gasEst = await contract.estimateGas.transfer(address, amountBn) const transaction = await contract.callStatic.transfer( address, diff --git a/src/utils/api/channels/erc20/requests/send.js b/src/utils/api/channels/erc20/requests/send.js index 7e33bb1f..45b21338 100644 --- a/src/utils/api/channels/erc20/requests/send.js +++ b/src/utils/api/channels/erc20/requests/send.js @@ -1,17 +1,18 @@ import { ethers } from "ethers" import Web3Provider from '../../../../web3/provider' import { ERC20, ETH } from "../../../../constants/intervalConstants" +import { scientificToDecimal } from "../../../../math" export const send = async (coinObj, activeUser, address, amount, params) => { try { const { privKey } = activeUser.keys[coinObj.id][ERC20] const contract = Web3Provider.getContract(coinObj.currency_id) const gasPrice = await Web3Provider.DefaultProvider.getGasPrice() - const amountBn = ethers.utils.parseUnits(amount.toString(), coinObj.decimals) - const gasEst = await contract.estimateGas.transfer(address, amountBn) + const amountBn = ethers.utils.parseUnits(scientificToDecimal(amount.toString()), coinObj.decimals) const signableContract = contract.connect( new ethers.Wallet(ethers.utils.hexlify(privKey), Web3Provider.DefaultProvider) ); + const gasEst = await signableContract.estimateGas.transfer(address, amountBn) const response = await signableContract.transfer( address, amountBn diff --git a/src/utils/api/channels/erc20/requests/specific/index.js b/src/utils/api/channels/erc20/requests/specific/index.js new file mode 100644 index 00000000..4cf574d6 --- /dev/null +++ b/src/utils/api/channels/erc20/requests/specific/index.js @@ -0,0 +1,5 @@ +import rfox from './rfox' + +export const UtilityContracts = { + rfox +} \ No newline at end of file diff --git a/src/utils/api/channels/erc20/requests/specific/rfox/claimAccountBalance.js b/src/utils/api/channels/erc20/requests/specific/rfox/claimAccountBalance.js new file mode 100644 index 00000000..ed91916f --- /dev/null +++ b/src/utils/api/channels/erc20/requests/specific/rfox/claimAccountBalance.js @@ -0,0 +1,54 @@ +import { RFOX_UTILITY_CONTRACT } from "../../../../../../constants/web3Constants" +import Web3Provider from "../../../../../../web3/provider" +import { computePublicKey } from '@ethersproject/signing-key' +import { ethers } from "ethers" + +/** + * Estimates the gas required to claim the account balance of an RFOX account + * @param {String} pubKey The public key that is stored in the accountObj of the user + */ +export const estimateGasClaimRFoxAccountBalances = async (pubKey, fromAddress) => { + let contract = null + const signer = new ethers.VoidSigner(fromAddress, Web3Provider.DefaultProvider) + + try { + contract = Web3Provider.getContract(RFOX_UTILITY_CONTRACT) + } catch(e) { + await Web3Provider.initContract(RFOX_UTILITY_CONTRACT) + contract = Web3Provider.getContract(RFOX_UTILITY_CONTRACT) + } + + const uncompressedPubKey = computePublicKey(Buffer.from(pubKey, 'hex')) + + const x = Buffer.from(uncompressedPubKey.slice(4, 68), 'hex') + const y = Buffer.from(uncompressedPubKey.slice(68), 'hex') + + return (await contract.connect(signer).estimateGas.withdrawBalance(x, y)).mul(await Web3Provider.DefaultProvider.getGasPrice()) +} + +/** + * Claims claimable account balance of a RedFOX account + * @param {String} privKey The private key that is stored in the accountObj of the user + * @param {String} pubKey The public key that is stored in the accountObj of the user + */ +export const claimRFoxAccountBalances = async (privKey, pubKey) => { + let contract = null + + try { + contract = Web3Provider.getContract(RFOX_UTILITY_CONTRACT) + } catch(e) { + await Web3Provider.initContract(RFOX_UTILITY_CONTRACT) + contract = Web3Provider.getContract(RFOX_UTILITY_CONTRACT) + } + + const signableContract = contract.connect( + new ethers.Wallet(ethers.utils.hexlify(privKey), Web3Provider.DefaultProvider) + ); + + const uncompressedPubKey = computePublicKey(Buffer.from(pubKey, 'hex')) + + const x = Buffer.from(uncompressedPubKey.slice(4, 68), 'hex') + const y = Buffer.from(uncompressedPubKey.slice(68), 'hex') + + return await signableContract.withdrawBalance(x, y) +} \ No newline at end of file diff --git a/src/utils/api/channels/erc20/requests/specific/rfox/getTotalAccountBalance.js b/src/utils/api/channels/erc20/requests/specific/rfox/getTotalAccountBalance.js new file mode 100644 index 00000000..439f74d6 --- /dev/null +++ b/src/utils/api/channels/erc20/requests/specific/rfox/getTotalAccountBalance.js @@ -0,0 +1,25 @@ +import { RFOX_UTILITY_CONTRACT } from "../../../../../../constants/web3Constants" +import Web3Provider from "../../../../../../web3/provider" +import { computePublicKey } from '@ethersproject/signing-key' + +/** + * Gets total account balance of a RedFOX account + * @param {String} pubKey The public key that is stored in the accountObj of the user + */ +export const getRfoxAccountBalances = async (pubKey) => { + let contract = null + + try { + contract = Web3Provider.getContract(RFOX_UTILITY_CONTRACT) + } catch(e) { + await Web3Provider.initContract(RFOX_UTILITY_CONTRACT) + contract = Web3Provider.getContract(RFOX_UTILITY_CONTRACT) + } + + const uncompressedPubKey = computePublicKey(Buffer.from(pubKey, 'hex')) + + const x = Buffer.from(uncompressedPubKey.slice(4, 68), 'hex') + const y = Buffer.from(uncompressedPubKey.slice(68), 'hex') + + return await contract.totalAccountBalance(x, y) +} \ No newline at end of file diff --git a/src/utils/api/channels/erc20/requests/specific/rfox/index.js b/src/utils/api/channels/erc20/requests/specific/rfox/index.js new file mode 100644 index 00000000..3c2c7a5e --- /dev/null +++ b/src/utils/api/channels/erc20/requests/specific/rfox/index.js @@ -0,0 +1,8 @@ +import { getRfoxAccountBalances } from './getTotalAccountBalance' +import { claimRFoxAccountBalances } from './claimAccountBalance' + + +export default { + getRfoxAccountBalances, + claimRFoxAccountBalances +} \ No newline at end of file diff --git a/src/utils/api/channels/eth/requests/preflight.js b/src/utils/api/channels/eth/requests/preflight.js index a56718c7..c8df1c48 100644 --- a/src/utils/api/channels/eth/requests/preflight.js +++ b/src/utils/api/channels/eth/requests/preflight.js @@ -4,13 +4,14 @@ import { ETH } from "../../../../constants/intervalConstants" import { ETH_NETWORK_IDS } from "../../../../constants/constants" import { ETH_NETWORK } from '../../../../../../env/main.json' import BigNumber from "bignumber.js" +import { scientificToDecimal } from "../../../../math" export const txPreflight = async (coinObj, activeUser, address, amount, params) => { try { const fromAddress = activeUser.keys[coinObj.id][ETH].addresses[0] const signer = new ethers.VoidSigner(fromAddress, Web3Provider.DefaultProvider) const balance = await signer.getBalance() - const value = ethers.utils.parseUnits(amount.toString()) + const value = ethers.utils.parseUnits(scientificToDecimal(amount.toString())) const transaction = await signer.populateTransaction({ to: address, diff --git a/src/utils/api/channels/eth/requests/send.js b/src/utils/api/channels/eth/requests/send.js index 0cdf59c7..2349ee79 100644 --- a/src/utils/api/channels/eth/requests/send.js +++ b/src/utils/api/channels/eth/requests/send.js @@ -4,6 +4,7 @@ import { ETH } from "../../../../constants/intervalConstants" import { ETH_NETWORK_IDS } from "../../../../constants/constants" import { ETH_NETWORK } from '../../../../../../env/main.json' import { etherKeys } from "agama-wallet-lib/src/keys" +import { scientificToDecimal } from "../../../../math" export const send = async (coinObj, activeUser, address, amount, params) => { try { @@ -13,7 +14,7 @@ export const send = async (coinObj, activeUser, address, amount, params) => { let transaction = await voidSigner.populateTransaction({ to: address, - value: ethers.utils.parseUnits(amount.toString()), + value: ethers.utils.parseUnits(scientificToDecimal(amount.toString())), chainId: ETH_NETWORK_IDS[ETH_NETWORK] }) diff --git a/src/utils/constants/storeType.js b/src/utils/constants/storeType.js index 76100a7d..8f46e3fe 100644 --- a/src/utils/constants/storeType.js +++ b/src/utils/constants/storeType.js @@ -32,6 +32,7 @@ export const SET_ACTIVE_SECTION = 'SET_ACTIVE_SECTION'; export const SET_COINMENU_FOCUS = 'SET_COINMENU_FOCUS'; export const SET_USER_COINS = 'SET_USER_COINS'; export const SET_COIN_STATUS = 'SET_COIN_STATUS'; +export const DISABLE_CLAIM_BUTTON = 'DISABLE_CLAIM_BUTTON' // Custom Coins export const SET_ACTIVE_SECTION_CUSTOM_COIN = 'SET_ACTIVE_SECTION_CUSTOM_COIN'; diff --git a/src/utils/constants/web3Constants.js b/src/utils/constants/web3Constants.js index c7451568..17e3190c 100644 --- a/src/utils/constants/web3Constants.js +++ b/src/utils/constants/web3Constants.js @@ -1 +1,4 @@ -export const ETHERS = 18 \ No newline at end of file +export const ETHERS = 18 + +// Non-ERC20 utility contracts +export const RFOX_UTILITY_CONTRACT = "0x5087884aC1740ee1FdF76A78B93057D7304ca392" \ No newline at end of file diff --git a/src/utils/math.js b/src/utils/math.js index fc140bb4..c256ace5 100644 --- a/src/utils/math.js +++ b/src/utils/math.js @@ -95,4 +95,31 @@ export class MathableNumber { display() { return ethers.utils.formatUnits(this.num, this.maxDecimals) } -} \ No newline at end of file +} + +export const scientificToDecimal = function(number) { + let numberHasSign = number.startsWith("-") || number.startsWith("+"); + let sign = numberHasSign ? number[0] : ""; + number = numberHasSign ? number.replace(sign, "") : number; + + + if (/\d+\.?\d*e[\+\-]*\d+/i.test(number)) { + let zero = '0'; + let parts = String(number).toLowerCase().split('e'); + let e = parts.pop(); + let l = Math.abs(e); + let sign = e / l; + let coeff_array = parts[0].split('.'); + + if (sign === -1) { + coeff_array[0] = Math.abs(coeff_array[0]); + number = zero + '.' + new Array(l).join(zero) + coeff_array.join(''); + } else { + let dec = coeff_array[1]; + if (dec) l = l - dec.length; + number = coeff_array.join('') + new Array(l + 1).join(zero); + } + } + + return `${sign}${number}`; +}; \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 01a8874a..9be17bdb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1113,6 +1113,16 @@ "@ethersproject/properties" "^5.0.3" elliptic "6.5.3" +"@ethersproject/signing-key@^5.0.5": + version "5.0.5" + resolved "https://registry.yarnpkg.com/@ethersproject/signing-key/-/signing-key-5.0.5.tgz#acfd06fc05a14180df7e027688bbd23fc4baf782" + integrity sha512-Z1wY7JC1HVO4CvQWY2TyTTuAr8xK3bJijZw1a9G92JEmKdv1j255R/0YLBBcFTl2J65LUjtXynNJ2GbArPGi5g== + dependencies: + "@ethersproject/bytes" "^5.0.4" + "@ethersproject/logger" "^5.0.5" + "@ethersproject/properties" "^5.0.3" + elliptic "6.5.3" + "@ethersproject/solidity@^5.0.0": version "5.0.3" resolved "https://registry.yarnpkg.com/@ethersproject/solidity/-/solidity-5.0.3.tgz#178197cb2f19d2986dadd515928c5dba3cb27e55" From 7a0055f807c2913ec49d58258dbee6eda6a0f4cc Mon Sep 17 00:00:00 2001 From: Michael Toutonghi Date: Mon, 19 Oct 2020 19:30:02 +0200 Subject: [PATCH 2/7] Update RFOX test ERC20 and test utility contracts --- src/utils/CoinData/CoinsList.js | 2 +- src/utils/constants/web3Constants.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/CoinData/CoinsList.js b/src/utils/CoinData/CoinsList.js index b5752345..91385468 100644 --- a/src/utils/CoinData/CoinsList.js +++ b/src/utils/CoinData/CoinsList.js @@ -158,7 +158,7 @@ export const coinsList = { }, rfox: { id: "RFOX", - currency_id: '0x4847179E54AC60F21a995A3cD0c172CE51a88C45', + currency_id: '0xdb558DE7A385db8190eEE2A3f0886A8EB30CE7ca', system_id: '.eth', display_ticker: 'RFOX', display_name: "RedFOX Labs", diff --git a/src/utils/constants/web3Constants.js b/src/utils/constants/web3Constants.js index 17e3190c..79f5c702 100644 --- a/src/utils/constants/web3Constants.js +++ b/src/utils/constants/web3Constants.js @@ -1,4 +1,4 @@ export const ETHERS = 18 // Non-ERC20 utility contracts -export const RFOX_UTILITY_CONTRACT = "0x5087884aC1740ee1FdF76A78B93057D7304ca392" \ No newline at end of file +export const RFOX_UTILITY_CONTRACT = "0x35Ce83A906bB8e6283E94489d3FBfC5e68982598" \ No newline at end of file From 9ed0d7291b297b5734f06cdf02db6490d1230ede Mon Sep 17 00:00:00 2001 From: Michael Toutonghi Date: Tue, 20 Oct 2020 13:35:46 +0200 Subject: [PATCH 3/7] Fix VerusPay handling of partial URLs --- src/containers/VerusPay/VerusPay.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/containers/VerusPay/VerusPay.js b/src/containers/VerusPay/VerusPay.js index 51ea329d..70a6734c 100644 --- a/src/containers/VerusPay/VerusPay.js +++ b/src/containers/VerusPay/VerusPay.js @@ -148,7 +148,7 @@ class VerusPay extends Component { if (coinURLParsed) { this.handleVerusQR(coinURLParsed); - } else if (result.length >= 34 && result.length <= 35) { + } else if (result.length >= 33 && result.length <= 42) { this.addressOnly(result); } else { this.errorHandler(FORMAT_UNKNOWN); @@ -200,7 +200,7 @@ class VerusPay extends Component { let fullURL = /^\w{1,30}:\w{33,36}\?amount\=\d*\.{1}\d*/; //:
?amount= - let partialURL = /^\w{1,30}:\w{33,36}$/; + let partialURL = /\w{1,30}:\w{33,36}/; //:
try { From 50b3bd55a61e38eccffc7967c2c629a6c9d17855 Mon Sep 17 00:00:00 2001 From: Michael Toutonghi Date: Wed, 21 Oct 2020 20:35:09 +0200 Subject: [PATCH 4/7] Add new RFOX migration contract and ERC20 --- env/main.json | 2 +- ios/assets/env/main.json | 2 +- src/containers/ConfirmSend/ConfirmSend.js | 2 +- .../ProfileSettings/ProfileSettings.js | 2 +- src/utils/CoinData/CoinData.js | 62 ++++++++++++++++--- src/utils/CoinData/CoinsList.js | 2 +- src/utils/biometry/biometry.js | 6 +- src/utils/constants/web3Constants.js | 2 +- 8 files changed, 65 insertions(+), 15 deletions(-) diff --git a/env/main.json b/env/main.json index a3f0a1a6..3a78a1f4 100644 --- a/env/main.json +++ b/env/main.json @@ -20,7 +20,7 @@ "ENABLE_ELECTRUM": true, "ENABLE_GENERAL": true, - "ETH_NETWORK": "ropsten", + "ETH_NETWORK": "homestead", "BIOMETRIC_SECURITY_THRESHOLD": "SECURE_HARDWARE", diff --git a/ios/assets/env/main.json b/ios/assets/env/main.json index a3f0a1a6..3a78a1f4 100644 --- a/ios/assets/env/main.json +++ b/ios/assets/env/main.json @@ -20,7 +20,7 @@ "ENABLE_ELECTRUM": true, "ENABLE_GENERAL": true, - "ETH_NETWORK": "ropsten", + "ETH_NETWORK": "homestead", "BIOMETRIC_SECURITY_THRESHOLD": "SECURE_HARDWARE", diff --git a/src/containers/ConfirmSend/ConfirmSend.js b/src/containers/ConfirmSend/ConfirmSend.js index 42723b2b..f6fdd849 100644 --- a/src/containers/ConfirmSend/ConfirmSend.js +++ b/src/containers/ConfirmSend/ConfirmSend.js @@ -101,7 +101,7 @@ class ConfirmSend extends Component { if(res.err || !res) { this.setState({ loading: false, - err: res.result + err: res ? res.result : "Unknown error" }); clearInterval(this.loadingInterval); } else { diff --git a/src/containers/Settings/ProfileSettings/ProfileSettings.js b/src/containers/Settings/ProfileSettings/ProfileSettings.js index f08a742a..02939dba 100644 --- a/src/containers/Settings/ProfileSettings/ProfileSettings.js +++ b/src/containers/Settings/ProfileSettings/ProfileSettings.js @@ -149,7 +149,7 @@ class ProfileSettings extends Component { chevron /> - {this.state.supportedBiometryType.biometry && ( + {(this.props.activeAccount.biometry || this.state.supportedBiometryType.biometry) && ( { if (this.props.activeAccount.biometry) { this.openPasswordCheck() diff --git a/src/utils/CoinData/CoinData.js b/src/utils/CoinData/CoinData.js index 0451e10f..e66b67d3 100644 --- a/src/utils/CoinData/CoinData.js +++ b/src/utils/CoinData/CoinData.js @@ -2,11 +2,12 @@ import { electrumServers } from 'agama-wallet-lib/src/electrum-servers'; import { MAX_VERIFICATION } from '../constants/constants' import Colors from '../../globals/colors' import { coinsList } from './CoinsList' -import { DLIGHT, ELECTRUM, GENERAL } from '../constants/intervalConstants'; +import { DLIGHT, ELECTRUM, ERC20, GENERAL } from '../constants/intervalConstants'; import { ENABLE_VERUS_IDENTITIES } from '../../../env/main.json' import CoinLogoSvgs from '../../images/cryptologo/index' +import { ETHERS } from '../constants/web3Constants'; const getDefaultApps = (coinName) => { return ({ @@ -72,6 +73,19 @@ export const explorers = { OOT: 'https://explorer.utrum.io', VRSC: 'https://explorer.veruscoin.io', ETH: 'https://etherscan.io', + RFOX: 'https://etherscan.io', + BAT: 'https://etherscan.io', + DAI: 'https://etherscan.io', + BAL: 'https://etherscan.io', + BUSD: 'https://etherscan.io', + BNT: 'https://etherscan.io', + HOT: 'https://etherscan.io', + LINK: 'https://etherscan.io', + NEXO: 'https://etherscan.io', + UNI: 'https://etherscan.io', + VEN: 'https://etherscan.io', + YFI: 'https://etherscan.io', + ZRX: 'https://etherscan.io', TST: 'https://ropsten.etherscan.io', BTC: 'https://www.blockchain.com/btc' } @@ -147,6 +161,46 @@ export const findCoinObj = (id, userName) => { return coinObj; } +export const getCoinObj = (coinList, coinId) => { + return coinList.find(coinObj => { + return coinObj.id === coinId + }) +} + +export const getCoinLogo = (id) => { + const idLc = id.toLowerCase() + + if (coinsList[idLc]) return CoinLogos[idLc] + else return null +} + +export const createErc20CoinObj = (contractAddress, displayName, displayTicker, description, userName) => { + if (coinsList[displayTicker.toLowerCase()]) throw new Error(`Coin with ticker ${displayTicker} already exists in coin list`) + let coinObj = { + id: displayTicker, + currency_id: contractAddress, + system_id: '.eth', + display_ticker: displayTicker, + display_name: displayName, + description: description, + compatible_channels: [ERC20, GENERAL], + dominant_channel: ERC20, + decimals: ETHERS, + tags: [], + proto: 'erc20' + } + + coinObj.users = userName != null ? [userName] : []; + + const DEFAULT_APPS = getDefaultApps(coinObj.display_name) + + coinObj.apps = DEFAULT_APPS.apps; + coinObj.defaultApp = DEFAULT_APPS.defaultApp + + return coinObj; +} + +// DEPRECATED /** * @param {String} id The coin's identifier to be used in code * @param {String} name The coin's full name for display @@ -190,9 +244,3 @@ export const createCoinObj = (id, name, description, defaultFee, serverList, use return coinObj; } -export const getCoinObj = (coinList, coinId) => { - return coinList.find(coinObj => { - return coinObj.id === coinId - }) -} - diff --git a/src/utils/CoinData/CoinsList.js b/src/utils/CoinData/CoinsList.js index 91385468..1c407e01 100644 --- a/src/utils/CoinData/CoinsList.js +++ b/src/utils/CoinData/CoinsList.js @@ -158,7 +158,7 @@ export const coinsList = { }, rfox: { id: "RFOX", - currency_id: '0xdb558DE7A385db8190eEE2A3f0886A8EB30CE7ca', + currency_id: '0xa1d6Df714F91DeBF4e0802A542E13067f31b8262', system_id: '.eth', display_ticker: 'RFOX', display_name: "RedFOX Labs", diff --git a/src/utils/biometry/biometry.js b/src/utils/biometry/biometry.js index 5390656e..14076aa6 100644 --- a/src/utils/biometry/biometry.js +++ b/src/utils/biometry/biometry.js @@ -93,8 +93,10 @@ export const getSupportedBiometryType = async () => { export const passesSecurityThreshold = async () => { if (Platform.OS == 'ios') return true + else return false - const securityLevel = await Keychain.getSecurityLevel({ accessControl: Keychain.ACCESS_CONTROL.BIOMETRY_ANY }) + //TODO: RE-Enable Biometry on Android when it can be forced to prompt every login + // const securityLevel = await Keychain.getSecurityLevel({ accessControl: Keychain.ACCESS_CONTROL.BIOMETRY_ANY }) - return securityLevel == Keychain.SECURITY_LEVEL[BIOMETRIC_SECURITY_THRESHOLD] + // return securityLevel == Keychain.SECURITY_LEVEL[BIOMETRIC_SECURITY_THRESHOLD] } \ No newline at end of file diff --git a/src/utils/constants/web3Constants.js b/src/utils/constants/web3Constants.js index 79f5c702..6056b8e1 100644 --- a/src/utils/constants/web3Constants.js +++ b/src/utils/constants/web3Constants.js @@ -1,4 +1,4 @@ export const ETHERS = 18 // Non-ERC20 utility contracts -export const RFOX_UTILITY_CONTRACT = "0x35Ce83A906bB8e6283E94489d3FBfC5e68982598" \ No newline at end of file +export const RFOX_UTILITY_CONTRACT = "0xD82F7e3956d3FF391C927Cd7d0A7A57C360DF5b9" \ No newline at end of file From 01d970595d641818a22b4f64c4f235b4aa6b6080 Mon Sep 17 00:00:00 2001 From: Michael Toutonghi Date: Wed, 21 Oct 2020 21:38:25 +0200 Subject: [PATCH 5/7] Remove invalid "confirmations" label for non-ETH coins --- .../TxDetailsModal/TxDetailsModal.js | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/components/TxDetailsModal/TxDetailsModal.js b/src/components/TxDetailsModal/TxDetailsModal.js index 841bae75..bb1286d3 100644 --- a/src/components/TxDetailsModal/TxDetailsModal.js +++ b/src/components/TxDetailsModal/TxDetailsModal.js @@ -116,7 +116,12 @@ class TxDetailsModal extends Component { Type: - + {txData.type || "??"} @@ -142,16 +147,19 @@ class TxDetailsModal extends Component { )} - - - Confirmations: - - - {txData.confirmations != null - ? txData.confirmations - : "??"} - - + {explorers[activeCoinID] && + explorers[activeCoinID].includes("etherscan") ? ( + + + {"Confirmations:"} + + + {txData.confirmations != null + ? txData.confirmations + : "??"} + + + ) : null} Address: Date: Thu, 22 Oct 2020 01:01:51 -0700 Subject: [PATCH 6/7] Bump version --- .gitlab-ci.yml | 2 +- android/app/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index deafa919..4264da49 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2,7 +2,7 @@ stages: - build variables: - VERSION: 0.2.0-beta + VERSION: 0.2.0-beta-1 POST_MESSAGE: "Pipeline Trigger: ${CI_PIPELINE_SOURCE}\n Branch: ${CI_COMMIT_REF_NAME}\n Commit: ${CI_COMMIT_SHA}\n diff --git a/android/app/build.gradle b/android/app/build.gradle index 8d017800..c41772f5 100755 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -5,7 +5,7 @@ import com.android.build.OutputFile def versionMajor = 0 def versionMinor = 2 def versionRevision = 0 -def versionBuild = 0 +def versionBuild = 1 def keystorePropertiesFile = rootProject.file("keystore.properties"); From a8ec20c4646df01bebad42c170957926efb4b1a6 Mon Sep 17 00:00:00 2001 From: Asher Dawes Date: Thu, 22 Oct 2020 01:33:37 -0700 Subject: [PATCH 7/7] bump version --- .gitlab-ci.yml | 2 +- ios/verusMobile.xcodeproj/project.pbxproj | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4264da49..b397690f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -40,7 +40,7 @@ Android:release: - yarn install - cd android - echo ${KEYSTORE} | xxd -r -p - > app/${MYAPP_RELEASE_STORE_FILE} - - ./gradlew assembleRelease; + - ./gradlew assembleRelease - rm app/${MYAPP_RELEASE_STORE_FILE} after_script: - curl -F file=@"android/app/build/outputs/apk/release/VerusMobile-${VERSION}.apk" diff --git a/ios/verusMobile.xcodeproj/project.pbxproj b/ios/verusMobile.xcodeproj/project.pbxproj index c6006b89..a5507957 100644 --- a/ios/verusMobile.xcodeproj/project.pbxproj +++ b/ios/verusMobile.xcodeproj/project.pbxproj @@ -911,7 +911,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0.05; + CURRENT_PROJECT_VERSION = 0.06; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = J8Y97B5NMQ; ENABLE_BITCODE = NO; @@ -981,7 +981,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0.05; + CURRENT_PROJECT_VERSION = 0.06; DEVELOPMENT_TEAM = J8Y97B5NMQ; ENABLE_BITCODE = NO; INFOPLIST_FILE = verusmobile/Info.plist;