From 6c621d1d72c424e64a604a51e9891dfc48fcd577 Mon Sep 17 00:00:00 2001 From: michaeltout Date: Fri, 4 Aug 2023 17:56:24 +0200 Subject: [PATCH 01/42] Add ERC20 modal, add ability to connect to goerli ETH on testnet account, fix bug with adding pbaas currency when account is logged out and then logged in --- env/main.android.json | 2 + env/main.ios.json | 2 + ios/assets/env/main.json | 2 + src/VerusMobile.js | 10 +- src/actions/actions/UserData.js | 39 +++++- .../dispatchers/Erc20WalletReduxManager.js | 4 +- .../sendModal/dispatchers/sendModal.js | 19 ++- .../wallet/dispatchers/erc20/updates.js | 2 + .../actions/wallet/dispatchers/eth/updates.js | 3 +- .../AddErc20TokenConfirm.js | 97 ++++++++++++++ .../AddErc20TokenConfirm.render.js | 118 ++++++++++++++++++ .../AddErc20TokenForm/AddErc20TokenForm.js | 79 ++++++++++++ .../AddErc20TokenForm.render.js | 41 ++++++ .../AddErc20TokenResult.js | 18 +++ .../AddErc20TokenResult.render.js | 71 +++++++++++ .../AddPbaasCurrencyConfirm.js | 9 -- .../AddPbaasCurrencyForm.js | 13 +- src/components/SendModal/SendModal.js | 2 + src/components/SendModal/SendModal.render.js | 13 +- src/containers/AddCoin/AddCoin.js | 11 +- src/containers/CoinDetails/CoinDetails.js | 2 +- src/containers/Home/Home.js | 10 +- src/containers/Home/Home.render.js | 1 + src/containers/Home/HomeFAB/HomeFAB.js | 20 ++- .../Home/HomeWidgets/CurrencyWidget.js | 110 +++++++++------- src/containers/SideMenu/SideMenu.render.js | 2 +- src/reducers/channelStores/erc20.js | 26 +--- src/sagas/channels/erc20.js | 8 +- src/sagas/channels/verusid.js | 4 + src/sagas/channels/vrpc.js | 6 +- src/utils/CoinData/CoinData.js | 5 +- src/utils/CoinData/CoinDirectory.js | 88 ++++++++++++- src/utils/CoinData/CoinsList.js | 17 +++ src/utils/CoinData/Graphics.js | 3 +- .../erc20/requests/getErc20Balance.js | 8 +- .../erc20/requests/getErc20Transactions.js | 12 +- .../api/channels/erc20/requests/preflight.js | 4 +- src/utils/api/channels/erc20/requests/send.js | 4 +- .../specific/rfox/claimAccountBalance.js | 6 +- .../specific/rfox/getTotalAccountBalance.js | 4 +- .../channels/eth/requests/getEthBalance.js | 10 +- .../eth/requests/getEthTransactions.js | 12 +- .../api/channels/eth/requests/getTxReceipt.js | 6 +- .../api/channels/eth/requests/preflight.js | 4 +- src/utils/api/channels/eth/requests/send.js | 4 +- .../asyncStore/contractDefinitionStorage.js | 70 +++++++++++ src/utils/constants/constants.js | 3 +- src/utils/constants/sendModal.js | 2 + src/utils/constants/storeType.js | 3 - src/utils/vrpc/vrpcInterface.js | 8 +- src/utils/web3/provider.js | 32 ++++- src/utils/web3/web3Interface.js | 51 ++++---- 52 files changed, 908 insertions(+), 192 deletions(-) create mode 100644 src/components/SendModal/AddErc20Token/AddErc20TokenConfirm/AddErc20TokenConfirm.js create mode 100644 src/components/SendModal/AddErc20Token/AddErc20TokenConfirm/AddErc20TokenConfirm.render.js create mode 100644 src/components/SendModal/AddErc20Token/AddErc20TokenForm/AddErc20TokenForm.js create mode 100644 src/components/SendModal/AddErc20Token/AddErc20TokenForm/AddErc20TokenForm.render.js create mode 100644 src/components/SendModal/AddErc20Token/AddErc20TokenResult/AddErc20TokenResult.js create mode 100644 src/components/SendModal/AddErc20Token/AddErc20TokenResult/AddErc20TokenResult.render.js create mode 100644 src/utils/asyncStore/contractDefinitionStorage.js diff --git a/env/main.android.json b/env/main.android.json index 624ba1e6..d4edde30 100644 --- a/env/main.android.json +++ b/env/main.android.json @@ -28,6 +28,7 @@ }, "ETH_HOMESTEAD": "homestead", + "ETH_GOERLI": "goerli", "VERUSID_NETWORK_DEFAULT": "VRSC", "BIOMETRIC_SECURITY_THRESHOLD": "SECURE_HARDWARE", @@ -46,6 +47,7 @@ "NOTIFICATIONS_STORAGE_INTERNAL_KEY": "notifications", "WIDGET_STORAGE_INTERNAL_KEY": "widgets", "CURRENCY_DEFINITION_STORAGE_INTERNAL_KEY": "currencyDefinitions", + "CONTRACT_DEFINITION_STORAGE_INTERNAL_KEY": "ethereumContractDefinitions", "WYRE_ACCESSIBLE": true } diff --git a/env/main.ios.json b/env/main.ios.json index e3c8b3de..c9139c9d 100644 --- a/env/main.ios.json +++ b/env/main.ios.json @@ -28,6 +28,7 @@ }, "ETH_HOMESTEAD": "homestead", + "ETH_GOERLI": "goerli", "VERUSID_NETWORK_DEFAULT": "VRSC", "BIOMETRIC_SECURITY_THRESHOLD": "SECURE_HARDWARE", @@ -46,6 +47,7 @@ "NOTIFICATIONS_STORAGE_INTERNAL_KEY": "notifications", "WIDGET_STORAGE_INTERNAL_KEY": "widgets", "CURRENCY_DEFINITION_STORAGE_INTERNAL_KEY": "currencyDefinitions", + "CONTRACT_DEFINITION_STORAGE_INTERNAL_KEY": "ethereumContractDefinitions", "WYRE_ACCESSIBLE": true } diff --git a/ios/assets/env/main.json b/ios/assets/env/main.json index e3c8b3de..c9139c9d 100644 --- a/ios/assets/env/main.json +++ b/ios/assets/env/main.json @@ -28,6 +28,7 @@ }, "ETH_HOMESTEAD": "homestead", + "ETH_GOERLI": "goerli", "VERUSID_NETWORK_DEFAULT": "VRSC", "BIOMETRIC_SECURITY_THRESHOLD": "SECURE_HARDWARE", @@ -46,6 +47,7 @@ "NOTIFICATIONS_STORAGE_INTERNAL_KEY": "notifications", "WIDGET_STORAGE_INTERNAL_KEY": "widgets", "CURRENCY_DEFINITION_STORAGE_INTERNAL_KEY": "currencyDefinitions", + "CONTRACT_DEFINITION_STORAGE_INTERNAL_KEY": "ethereumContractDefinitions", "WYRE_ACCESSIBLE": true } diff --git a/src/VerusMobile.js b/src/VerusMobile.js index ee22df82..aa75a429 100644 --- a/src/VerusMobile.js +++ b/src/VerusMobile.js @@ -39,6 +39,7 @@ import { NavigationContainer } from "@react-navigation/native"; import LoadingModal from "./components/LoadingModal/LoadingModal"; import { CoinDirectory } from "./utils/CoinData/CoinDirectory"; import { removeInactiveCurrencyDefinitions } from "./utils/asyncStore/currencyDefinitionStorage"; +import { removeInactiveContractDefinitions } from "./utils/asyncStore/contractDefinitionStorage"; class VerusMobile extends React.Component { constructor(props) { @@ -107,8 +108,13 @@ class VerusMobile extends React.Component { clearCachedVersions() .then(async () => { await clearCachedVrpcResponses() - await removeInactiveCurrencyDefinitions(await purgeUnusedCoins()) - await CoinDirectory.populateCurrencyDefinitionsFromStorage() + + const usedCoins = await purgeUnusedCoins() + await removeInactiveCurrencyDefinitions(usedCoins) + await removeInactiveContractDefinitions(usedCoins) + + await CoinDirectory.populatePbaasCurrencyDefinitionsFromStorage() + await CoinDirectory.populateEthereumContractDefinitionsFromStorage() return initCache() }) diff --git a/src/actions/actions/UserData.js b/src/actions/actions/UserData.js index 81ce2fe8..37ba4816 100644 --- a/src/actions/actions/UserData.js +++ b/src/actions/actions/UserData.js @@ -159,7 +159,7 @@ export const setTestnetOverrides = async (userID, testnetOverrides) => { }) .catch(err => reject(err)); }); -} +}; export const deleteProfile = async (account, dispatch) => { // Clear existing account lifecycles @@ -205,7 +205,37 @@ export const fetchUsers = () => { : user.keyDerivationVersion, disabledServices: user.disabledServices == null ? {} : user.disabledServices, - testnetOverrides: user.testnetOverrides == null ? {} : user.testnetOverrides, + testnetOverrides: + user.testnetOverrides == null ? {} : user.testnetOverrides, + }; + } else { + return user; + } + }); + + await setUsers(users); + } + + // Update testnet overrides to include ETH + if ( + users.some( + value => + value.testnetOverrides != null && + value.testnetOverrides.hasOwnProperty('VRSC') && + !value.testnetOverrides.hasOwnProperty('ETH'), + ) + ) { + console.warn('Updating testnet profile to account for goerli ETH'); + + users = users.map(user => { + if ( + user.testnetOverrides != null && + user.testnetOverrides.hasOwnProperty('VRSC') && + !user.testnetOverrides.hasOwnProperty('ETH') + ) { + return { + ...user, + testnetOverrides: {...user.testnetOverrides, ETH: 'GETH'}, }; } else { return user; @@ -300,7 +330,10 @@ export const authenticateAccount = async (account, password) => { ? {} : {[WYRE_SERVICE_ID]: true} : account.disabledServices, - testnetOverrides: account.testnetOverrides == null ? {} : account.testnetOverrides, + testnetOverrides: + account.testnetOverrides == null + ? {} + : account.testnetOverrides, }, await initSession(password), ), diff --git a/src/actions/actions/channels/erc20/dispatchers/Erc20WalletReduxManager.js b/src/actions/actions/channels/erc20/dispatchers/Erc20WalletReduxManager.js index 47786832..0cab5e67 100644 --- a/src/actions/actions/channels/erc20/dispatchers/Erc20WalletReduxManager.js +++ b/src/actions/actions/channels/erc20/dispatchers/Erc20WalletReduxManager.js @@ -7,7 +7,7 @@ import { export const initErc20Wallet = async (coinObj) => { Store.dispatch({ type: INIT_ERC20_CHANNEL_START, - payload: { chainTicker: coinObj.id, contractAddress: coinObj.currency_id } + payload: { chainTicker: coinObj.id, contractAddress: coinObj.currency_id, network: coinObj.network } }) return @@ -16,7 +16,7 @@ export const initErc20Wallet = async (coinObj) => { export const closeErc20Wallet = async (coinObj) => { Store.dispatch({ type: CLOSE_ERC20_CHANNEL, - payload: { chainTicker: coinObj.id, contractAddress: coinObj.currency_id } + payload: { chainTicker: coinObj.id, contractAddress: coinObj.currency_id, network: coinObj.network } }) return diff --git a/src/actions/actions/sendModal/dispatchers/sendModal.js b/src/actions/actions/sendModal/dispatchers/sendModal.js index 8128dcc2..86741e59 100644 --- a/src/actions/actions/sendModal/dispatchers/sendModal.js +++ b/src/actions/actions/sendModal/dispatchers/sendModal.js @@ -30,7 +30,9 @@ import { SEND_MODAL_SHOW_EXPORTTO_FIELD, SEND_MODAL_SHOW_VIA_FIELD, SEND_MODAL_PRICE_ESTIMATE, - SEND_MODAL_ADVANCED_FORM + SEND_MODAL_ADVANCED_FORM, + SEND_MODAL_CONTRACT_ADDRESS_FIELD, + ADD_ERC20_TOKEN_MODAL } from '../../../../utils/constants/sendModal'; import { CLOSE_SEND_COIN_MODAL, @@ -135,6 +137,21 @@ export const openAddPbaasCurrencyModal = (coinObj, data) => { ); }; +export const openAddErc20TokenModal = (coinObj, data) => { + openSendModal( + `Add ERC20 Token`, + coinObj, + null, + data == null + ? { + [SEND_MODAL_CONTRACT_ADDRESS_FIELD]: '', + } + : data, + ADD_ERC20_TOKEN_MODAL, + 'To add an ERC20 token to your wallet, enter its contract address here and press continue.', + ); +}; + export const openProvisionIdentityModal = (coinObj, req) => { openSendModal( `Request VerusID`, diff --git a/src/actions/actions/wallet/dispatchers/erc20/updates.js b/src/actions/actions/wallet/dispatchers/erc20/updates.js index 7e3e42fd..37233988 100644 --- a/src/actions/actions/wallet/dispatchers/erc20/updates.js +++ b/src/actions/actions/wallet/dispatchers/erc20/updates.js @@ -12,6 +12,7 @@ export const updateErc20Balances = async (activeUser, coinObj) => { activeUser.keys[coinObj.id][ERC20].addresses[0], coinObj.currency_id, coinObj.decimals, + coinObj.network ) ).toString(); @@ -50,6 +51,7 @@ export const updateErc20Transactions = async (activeUser, coinObj) => { activeUser.keys[coinObj.id][ERC20].addresses[0], coinObj.currency_id, coinObj.decimals, + coinObj.network ), }; } else { diff --git a/src/actions/actions/wallet/dispatchers/eth/updates.js b/src/actions/actions/wallet/dispatchers/eth/updates.js index 6e66cfff..6be4ff2b 100644 --- a/src/actions/actions/wallet/dispatchers/eth/updates.js +++ b/src/actions/actions/wallet/dispatchers/eth/updates.js @@ -8,7 +8,7 @@ export const updateEthBalances = async (activeUser, coinObj) => { activeUser.keys[coinObj.id][ETH].addresses.length > 0 ) { const balance = ( - await getStandardEthBalance(activeUser.keys[coinObj.id][ETH].addresses[0]) + await getStandardEthBalance(activeUser.keys[coinObj.id][ETH].addresses[0], coinObj.network) ).toString(); return { @@ -44,6 +44,7 @@ export const updateEthTransactions = async (activeUser, coinObj) => { header: {}, body: await getStandardEthTransactions( activeUser.keys[coinObj.id][ETH].addresses[0], + coinObj.network ), }; } else { diff --git a/src/components/SendModal/AddErc20Token/AddErc20TokenConfirm/AddErc20TokenConfirm.js b/src/components/SendModal/AddErc20Token/AddErc20TokenConfirm/AddErc20TokenConfirm.js new file mode 100644 index 00000000..fe75464a --- /dev/null +++ b/src/components/SendModal/AddErc20Token/AddErc20TokenConfirm/AddErc20TokenConfirm.js @@ -0,0 +1,97 @@ +import React, {useState, useCallback} from 'react'; +import {useDispatch, useSelector} from 'react-redux'; +import {Alert} from 'react-native'; +import {addCoin, addKeypairs, setUserCoins} from '../../../../actions/actionCreators'; +import { + refreshActiveChainLifecycles +} from '../../../../actions/actions/intervals/dispatchers/lifecycleManager'; +import { + SEND_MODAL_FORM_STEP_FORM, + SEND_MODAL_FORM_STEP_RESULT, +} from '../../../../utils/constants/sendModal'; +import {AddErc20TokenConfirmRender} from './AddErc20TokenConfirm.render'; +import { CoinDirectory } from '../../../../utils/CoinData/CoinDirectory'; +import { coinsList } from '../../../../utils/CoinData/CoinsList'; + +const AddErc20TokenConfirm = props => { + const [contract, setCurrency] = useState(props.route.params.contract); + + const dispatch = useDispatch(); + const sendModal = useSelector(state => state.sendModal); + const activeAccount = useSelector( + state => state.authentication.activeAccount, + ); + const activeCoinList = useSelector(state => state.coins.activeCoinList); + + const goBack = useCallback(() => { + props.setModalHeight(); + props.navigation.navigate(SEND_MODAL_FORM_STEP_FORM); + }, [props]); + + const submitData = useCallback(async () => { + await props.setLoading(true); + await props.setPreventExit(true); + + try { + const {coinObj} = sendModal; + + await CoinDirectory.addErc20Token(contract, coinObj.network); + const fullCoinData = CoinDirectory.findCoinObj(contract.address) + + dispatch( + await addKeypairs( + fullCoinData, + activeAccount.keys, + activeAccount.keyDerivationVersion == null + ? 0 + : activeAccount.keyDerivationVersion, + ), + ); + + const addCoinAction = await addCoin( + fullCoinData, + activeCoinList, + activeAccount.id, + fullCoinData.compatible_channels, + ); + + if (addCoinAction) { + dispatch(addCoinAction); + + const setUserCoinsAction = setUserCoins( + activeCoinList, + activeAccount.id, + ); + dispatch(setUserCoinsAction); + + refreshActiveChainLifecycles(setUserCoinsAction.payload.activeCoinsForUser); + } else { + throw new Error('Error adding coin'); + } + + props.navigation.navigate(SEND_MODAL_FORM_STEP_RESULT, { + contract + }); + } catch (e) { + Alert.alert('Could not add currency', e.message); + } + + props.setPreventExit(false); + props.setLoading(false); + }, [ + contract, + sendModal, + activeAccount, + activeCoinList, + dispatch, + props, + ]); + + return AddErc20TokenConfirmRender({ + contract, + goBack, + submitData + }); +}; + +export default AddErc20TokenConfirm; diff --git a/src/components/SendModal/AddErc20Token/AddErc20TokenConfirm/AddErc20TokenConfirm.render.js b/src/components/SendModal/AddErc20Token/AddErc20TokenConfirm/AddErc20TokenConfirm.render.js new file mode 100644 index 00000000..28278906 --- /dev/null +++ b/src/components/SendModal/AddErc20Token/AddErc20TokenConfirm/AddErc20TokenConfirm.render.js @@ -0,0 +1,118 @@ +import React from 'react'; +import { View, SafeAreaView, FlatList, TouchableOpacity } from 'react-native'; +import { List, Text, Divider, Button } from 'react-native-paper'; +import Colors from '../../../../globals/colors'; +import Styles from '../../../../styles'; +import { copyToClipboard } from '../../../../utils/clipboard/clipboard'; + +export const AddErc20TokenConfirmRender = ({ + contract, + goBack, + submitData +}) => { + return ( + + + { + if (item.condition == null || item.condition === true) { + return ( + + item.onPress()}> + + item.right ? ( + + {item.right} + + ) : null + } + /> + + + + ); + } else { + return null; + } + }} + data={[ + { + key: 'Contract Address', + data: contract.address, + numLines: 100, + onPress: () => copyToClipboard( + contract.address, {title: 'Address copied', message: `${contract.address} copied to clipboard.`} + ), + }, + { + key: 'Token Symbol', + data: contract.symbol, + numLines: 100, + onPress: () => copyToClipboard( + contract.symbol, {title: 'Symbol copied', message: `${contract.symbol} copied to clipboard.`} + ), + }, + { + key: 'Token Name', + data: contract.name, + numLines: 100, + onPress: () => copyToClipboard( + contract.name, {title: 'Name copied', message: `${contract.name} copied to clipboard.`} + ), + }, + { + key: 'Token Decimals', + data: contract.decimals, + numLines: 100, + onPress: () => copyToClipboard( + contract.decimals, {title: 'Decimals copied', message: `${contract.decimals} copied to clipboard.`} + ), + } + ]} + /> + + + + + + + ); +}; diff --git a/src/components/SendModal/AddErc20Token/AddErc20TokenForm/AddErc20TokenForm.js b/src/components/SendModal/AddErc20Token/AddErc20TokenForm/AddErc20TokenForm.js new file mode 100644 index 00000000..e29a2a4d --- /dev/null +++ b/src/components/SendModal/AddErc20Token/AddErc20TokenForm/AddErc20TokenForm.js @@ -0,0 +1,79 @@ +import {useCallback} from 'react'; +import {useSelector, useDispatch} from 'react-redux'; +import {Alert} from 'react-native'; +import {createAlert} from '../../../../actions/actions/alert/dispatchers/alert'; +import { + SEND_MODAL_FORM_STEP_CONFIRM, + SEND_MODAL_CONTRACT_ADDRESS_FIELD, +} from '../../../../utils/constants/sendModal'; +import {getWeb3ProviderForNetwork} from '../../../../utils/web3/provider'; +import {AddErc20TokenFormRender} from './AddErc20TokenForm.render'; + +const AddErc20TokenForm = props => { + const dispatch = useDispatch(); + const sendModal = useSelector(state => state.sendModal); + + const formHasError = useCallback(() => { + const {data} = sendModal; + + const tokenAddr = + data[SEND_MODAL_CONTRACT_ADDRESS_FIELD] != null + ? data[SEND_MODAL_CONTRACT_ADDRESS_FIELD].trim() + : ''; + + if (!tokenAddr || tokenAddr.length < 1) { + createAlert('Required Field', 'Address is a required field.'); + return true; + } + + return false; + }, [sendModal, dispatch]); + + const submitData = useCallback(async () => { + if (formHasError()) { + return; + } + + props.setLoading(true); + + const {coinObj, data} = sendModal; + + const contractAddress = data[SEND_MODAL_CONTRACT_ADDRESS_FIELD]; + + try { + const contract = getWeb3ProviderForNetwork( + coinObj.network, + ).getUnitializedContractInstance(contractAddress); + + const name = await contract.name(); + const symbol = await contract.symbol(); + const decimals = await contract.decimals(); + + Alert.alert( + 'Warning', + 'Data about this ERC20 token was retrieved from the token contract itself. Verify that the contract address you entered is correct before trusting the displayed symbol, name, and decimals.', + ); + + props.navigation.navigate(SEND_MODAL_FORM_STEP_CONFIRM, { + contract: { + address: contractAddress, + symbol, + decimals, + name, + }, + }); + } catch (e) { + Alert.alert('Error', e.message); + } + + props.setLoading(false); + }, [formHasError, sendModal, dispatch, props]); + + return AddErc20TokenFormRender({ + submitData, + updateSendFormData: props.updateSendFormData, + formDataValue: sendModal.data[SEND_MODAL_CONTRACT_ADDRESS_FIELD], + }); +}; + +export default AddErc20TokenForm; diff --git a/src/components/SendModal/AddErc20Token/AddErc20TokenForm/AddErc20TokenForm.render.js b/src/components/SendModal/AddErc20Token/AddErc20TokenForm/AddErc20TokenForm.render.js new file mode 100644 index 00000000..8d0a0ff9 --- /dev/null +++ b/src/components/SendModal/AddErc20Token/AddErc20TokenForm/AddErc20TokenForm.render.js @@ -0,0 +1,41 @@ +import React from "react"; +import { ScrollView, View, TouchableWithoutFeedback, Keyboard } from "react-native"; +import { TextInput, Button } from "react-native-paper"; +import Styles from "../../../../styles"; +import { SEND_MODAL_CONTRACT_ADDRESS_FIELD } from "../../../../utils/constants/sendModal"; + +export const AddErc20TokenFormRender = ({submitData, updateSendFormData, formDataValue}) => { + return ( + + + + + updateSendFormData(SEND_MODAL_CONTRACT_ADDRESS_FIELD, text) + } + autoCapitalize={"none"} + autoCorrect={false} + /> + + + + + + + ); +}; \ No newline at end of file diff --git a/src/components/SendModal/AddErc20Token/AddErc20TokenResult/AddErc20TokenResult.js b/src/components/SendModal/AddErc20Token/AddErc20TokenResult/AddErc20TokenResult.js new file mode 100644 index 00000000..c28615a2 --- /dev/null +++ b/src/components/SendModal/AddErc20Token/AddErc20TokenResult/AddErc20TokenResult.js @@ -0,0 +1,18 @@ +import {useState} from 'react'; +import {closeSendModal} from '../../../../actions/actions/sendModal/dispatchers/sendModal'; +import {AddErc20TokenResultRender} from './AddErc20TokenResult.render'; + +const AddErc20TokenResult = (props) => { + const [contract, setContract] = useState(props.route.params == null ? {} : props.route.params.contract); + + const finishSend = () => { + closeSendModal() + }; + + return AddErc20TokenResultRender({ + contract, + finishSend + }); +}; + +export default AddErc20TokenResult; diff --git a/src/components/SendModal/AddErc20Token/AddErc20TokenResult/AddErc20TokenResult.render.js b/src/components/SendModal/AddErc20Token/AddErc20TokenResult/AddErc20TokenResult.render.js new file mode 100644 index 00000000..acc29a46 --- /dev/null +++ b/src/components/SendModal/AddErc20Token/AddErc20TokenResult/AddErc20TokenResult.render.js @@ -0,0 +1,71 @@ +import React from 'react'; +import {ScrollView, View, TouchableOpacity} from 'react-native'; +import {Button, Text} from 'react-native-paper'; +import Colors from '../../../../globals/colors'; +import Styles from '../../../../styles'; +import {copyToClipboard} from '../../../../utils/clipboard/clipboard'; +import AnimatedSuccessCheckmark from '../../../AnimatedSuccessCheckmark'; + +export const AddErc20TokenResultRender = ({contract, finishSend}) => { + return ( + + + copyToClipboard(contract.symbol, { + title: 'Token copied', + message: `${contract.symbol} copied to clipboard.`, + }) + } + style={{ + width: '75%', + marginTop: 16, + }}> + + {`${contract.symbol} added`} + + + + + + + + {`${contract.symbol} will now show on your wallet home page.`} + + + + + + + ); +}; diff --git a/src/components/SendModal/AddPbaasCurrency/AddPbaasCurrencyConfirm/AddPbaasCurrencyConfirm.js b/src/components/SendModal/AddPbaasCurrency/AddPbaasCurrencyConfirm/AddPbaasCurrencyConfirm.js index a66ec4c8..60aa6f06 100644 --- a/src/components/SendModal/AddPbaasCurrency/AddPbaasCurrencyConfirm/AddPbaasCurrencyConfirm.js +++ b/src/components/SendModal/AddPbaasCurrency/AddPbaasCurrencyConfirm/AddPbaasCurrencyConfirm.js @@ -11,7 +11,6 @@ import { } from '../../../../utils/constants/sendModal'; import {AddPbaasCurrencyConfirmRender} from './AddPbaasCurrencyConfirm.render'; import { CoinDirectory } from '../../../../utils/CoinData/CoinDirectory'; -import { IS_TOKEN_FLAG } from '../../../../utils/constants/currencies'; const AddPbaasCurrencyConfirm = props => { const [currency, setCurrency] = useState(props.route.params.currency); @@ -39,14 +38,6 @@ const AddPbaasCurrencyConfirm = props => { await props.setPreventExit(true); try { - // const isToken = (currency.options & IS_TOKEN_FLAG) == IS_TOKEN_FLAG; - - // if (!isToken) { - // throw new Error(`Currently, only adding on-chain currencies is supported in Verus Mobile. ${ - // currency.fullyqualifiedname - // } is an independant blockchain and can be accessed in native mode through Verus Desktop or the Verus CLI.`); - // } - await CoinDirectory.addPbaasCurrency(currency, Object.keys(activeAccount.testnetOverrides).length > 0, true) const fullCoinData = CoinDirectory.findCoinObj(currency.currencyid) diff --git a/src/components/SendModal/AddPbaasCurrency/AddPbaasCurrencyForm/AddPbaasCurrencyForm.js b/src/components/SendModal/AddPbaasCurrency/AddPbaasCurrencyForm/AddPbaasCurrencyForm.js index c2c64937..8006fc9f 100644 --- a/src/components/SendModal/AddPbaasCurrency/AddPbaasCurrencyForm/AddPbaasCurrencyForm.js +++ b/src/components/SendModal/AddPbaasCurrency/AddPbaasCurrencyForm/AddPbaasCurrencyForm.js @@ -36,17 +36,6 @@ const AddPbaasCurrencyForm = (props) => { return false; }, [sendModal, dispatch]); - const getPotentialPrimaryAddresses = useCallback(async (coinObj, channel) => { - const seeds = await requestSeeds(); - - const seed = seeds[channel]; - - const keyObj = await deriveKeyPair(seed, coinObj, channel); - const {addresses} = keyObj; - - return addresses; - }, []); - const submitData = useCallback(async () => { if (formHasError()) { return; @@ -88,7 +77,7 @@ const AddPbaasCurrencyForm = (props) => { } props.setLoading(false) - }, [formHasError, getPotentialPrimaryAddresses, sendModal, dispatch, props]); + }, [formHasError, sendModal, dispatch, props]); return AddPbaasCurrencyFormRender({ submitData, diff --git a/src/components/SendModal/SendModal.js b/src/components/SendModal/SendModal.js index 8f9506f4..543f40ef 100644 --- a/src/components/SendModal/SendModal.js +++ b/src/components/SendModal/SendModal.js @@ -17,6 +17,7 @@ import { PROVISION_IDENTITY_SEND_MODAL, ADD_PBAAS_CURRENCY_MODAL, CONVERT_OR_CROSS_CHAIN_SEND_MODAL, + ADD_ERC20_TOKEN_MODAL, } from "../../utils/constants/sendModal"; import { SendModalRender } from "./SendModal.render" @@ -33,6 +34,7 @@ class SendModal extends Component { [PROVISION_IDENTITY_SEND_MODAL]: 442, [AUTHENTICATE_USER_SEND_MODAL]: 442, [ADD_PBAAS_CURRENCY_MODAL]: 442, + [ADD_ERC20_TOKEN_MODAL]: 442, [CONVERT_OR_CROSS_CHAIN_SEND_MODAL]: 696 }; diff --git a/src/components/SendModal/SendModal.render.js b/src/components/SendModal/SendModal.render.js index 4ade29c0..e63bef45 100644 --- a/src/components/SendModal/SendModal.render.js +++ b/src/components/SendModal/SendModal.render.js @@ -3,6 +3,7 @@ import { Platform, SafeAreaView } from "react-native"; import { Text, Portal, Button } from "react-native-paper"; import { Colors } from "react-native/Libraries/NewAppScreen"; import { + ADD_ERC20_TOKEN_MODAL, ADD_PBAAS_CURRENCY_MODAL, AUTHENTICATE_USER_SEND_MODAL, CONVERSION_SEND_MODAL, @@ -48,6 +49,9 @@ import AddPbaasCurrencyResult from "./AddPbaasCurrency/AddPbaasCurrencyResult/Ad import ConvertOrCrossChainSendForm from "./ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm"; import ConvertOrCrossChainSendConfirm from "./ConvertOrCrossChainSend/ConvertOrCrossChainSendConfirm/ConvertOrCrossChainSendConfirm"; import ConvertOrCrossChainSendResult from "./ConvertOrCrossChainSend/ConvertOrCrossChainSendResult/ConvertOrCrossChainSendResult"; +import AddErc20TokenForm from "./AddErc20Token/AddErc20TokenForm/AddErc20TokenForm"; +import AddErc20TokenConfirm from "./AddErc20Token/AddErc20TokenConfirm/AddErc20TokenConfirm"; +import AddErc20TokenResult from "./AddErc20Token/AddErc20TokenResult/AddErc20TokenResult"; const TopTabs = createMaterialTopTabNavigator(); const Root = createStackNavigator(); @@ -61,7 +65,8 @@ const SEND_FORMS = { [PROVISION_IDENTITY_SEND_MODAL]: ProvisionIdentityForm, [AUTHENTICATE_USER_SEND_MODAL]: AuthenticateUserForm, [ADD_PBAAS_CURRENCY_MODAL]: AddPbaasCurrencyForm, - [CONVERT_OR_CROSS_CHAIN_SEND_MODAL]: ConvertOrCrossChainSendForm + [CONVERT_OR_CROSS_CHAIN_SEND_MODAL]: ConvertOrCrossChainSendForm, + [ADD_ERC20_TOKEN_MODAL]: AddErc20TokenForm }; const SEND_CONFIRMATION = { @@ -73,7 +78,8 @@ const SEND_CONFIRMATION = { [PROVISION_IDENTITY_SEND_MODAL]: ProvisionIdentityConfirm, [AUTHENTICATE_USER_SEND_MODAL]: AuthenticateUserPassword, [ADD_PBAAS_CURRENCY_MODAL]: AddPbaasCurrencyConfirm, - [CONVERT_OR_CROSS_CHAIN_SEND_MODAL]: ConvertOrCrossChainSendConfirm + [CONVERT_OR_CROSS_CHAIN_SEND_MODAL]: ConvertOrCrossChainSendConfirm, + [ADD_ERC20_TOKEN_MODAL]: AddErc20TokenConfirm }; const SEND_RESULTS = { @@ -85,7 +91,8 @@ const SEND_RESULTS = { [PROVISION_IDENTITY_SEND_MODAL]: ProvisionIdentityResult, [AUTHENTICATE_USER_SEND_MODAL]: AuthenticateUserResult, [ADD_PBAAS_CURRENCY_MODAL]: AddPbaasCurrencyResult, - [CONVERT_OR_CROSS_CHAIN_SEND_MODAL]: ConvertOrCrossChainSendResult + [CONVERT_OR_CROSS_CHAIN_SEND_MODAL]: ConvertOrCrossChainSendResult, + [ADD_ERC20_TOKEN_MODAL]: AddErc20TokenResult }; export const SendModalRender = function () { diff --git a/src/containers/AddCoin/AddCoin.js b/src/containers/AddCoin/AddCoin.js index 8e34e922..e4ed24c6 100644 --- a/src/containers/AddCoin/AddCoin.js +++ b/src/containers/AddCoin/AddCoin.js @@ -25,8 +25,7 @@ class AddCoin extends Component { error: null, query: '', coinList: [], - fullCoinDetails: null, - activeCoinIds: [] + fullCoinDetails: null }; } @@ -36,9 +35,7 @@ class AddCoin extends Component { componentDidUpdate(lastProps, lastState) { if (lastState.query !== this.state.query || this.props.activeCoinsForUser !== lastProps.activeCoinsForUser) { - this.setState({coinList: this.getCoinList(), activeCoinIds: this.props.activeCoinsForUser.map( - coinObj => coinObj.id, - )}); + this.setState({coinList: this.getCoinList()}); } } @@ -121,7 +118,9 @@ class AddCoin extends Component { }; render() { - const activeCoinIds = this.state.activeCoinIds + const activeCoinIds = this.props.activeCoinsForUser.map( + coinObj => coinObj.id, + ); return ( diff --git a/src/containers/CoinDetails/CoinDetails.js b/src/containers/CoinDetails/CoinDetails.js index 9dfa6ba1..616dba41 100644 --- a/src/containers/CoinDetails/CoinDetails.js +++ b/src/containers/CoinDetails/CoinDetails.js @@ -108,7 +108,7 @@ class CoinDetails extends Component { } render() { - const Logo = getCoinLogo(this.state.fullCoinData.id, 'dark') + const Logo = getCoinLogo(this.state.fullCoinData.id, this.state.fullCoinData.proto, 'dark') return ( diff --git a/src/containers/Home/Home.js b/src/containers/Home/Home.js index 8b60d569..ee859df8 100644 --- a/src/containers/Home/Home.js +++ b/src/containers/Home/Home.js @@ -53,7 +53,7 @@ import {createAlert} from '../../actions/actions/alert/dispatchers/alert'; import {VERUSID_SERVICE_ID} from '../../utils/constants/services'; import { dragDetectionEnabled } from '../../utils/dragDetection'; import { CoinDirectory } from '../../utils/CoinData/CoinDirectory'; -import { openAddPbaasCurrencyModal } from '../../actions/actions/sendModal/dispatchers/sendModal'; +import { openAddErc20TokenModal, openAddPbaasCurrencyModal } from '../../actions/actions/sendModal/dispatchers/sendModal'; class Home extends Component { constructor(props) { @@ -510,6 +510,14 @@ class Home extends Component { ); }; + _addErc20Token = () => { + openAddErc20TokenModal( + CoinDirectory.findCoinObj( + this.props.testnetOverrides.ETH ? this.props.testnetOverrides.ETH : 'ETH', + ), + ); + }; + render() { return HomeRender.call(this); } diff --git a/src/containers/Home/Home.render.js b/src/containers/Home/Home.render.js index 5076a4af..4cf45757 100644 --- a/src/containers/Home/Home.render.js +++ b/src/containers/Home/Home.render.js @@ -54,6 +54,7 @@ export const HomeRender = function () { handleVerusPay={() => this._verusPay()} handleEditCards={() => this.setEditingCards(!this.state.editingCards)} handleAddPbaasCurrency={() => this._addPbaasCurrency()} + handleAddErc20Token={() => this._addErc20Token()} showConfigureHomeCards={!dragDetection} /> { - const { handleAddCoin, handleVerusPay, handleEditCards, showConfigureHomeCards, handleAddPbaasCurrency } = props + const { + handleAddCoin, + handleVerusPay, + handleEditCards, + showConfigureHomeCards, + handleAddPbaasCurrency, + handleAddErc20Token, + } = props; + const [state, setState] = React.useState({ open: false }); const onStateChange = ({ open }) => setState({ open }); @@ -23,6 +31,11 @@ const HomeFAB = (props) => { label: 'Manage Coins', onPress: handleAddCoin, }, + { + icon: 'rocket-launch', + label: 'Add ERC20 Token', + onPress: handleAddErc20Token, + }, { icon: 'rocket-launch', label: 'Add PBaaS Currency', @@ -45,6 +58,11 @@ const HomeFAB = (props) => { label: 'Manage Coins', onPress: handleAddCoin, }, + { + icon: 'ethereum', + label: 'Add ERC20 Token', + onPress: handleAddErc20Token, + }, { icon: 'rocket-launch', label: 'Add PBaaS Currency', diff --git a/src/containers/Home/HomeWidgets/CurrencyWidget.js b/src/containers/Home/HomeWidgets/CurrencyWidget.js index 85a98fba..a1c41eac 100644 --- a/src/containers/Home/HomeWidgets/CurrencyWidget.js +++ b/src/containers/Home/HomeWidgets/CurrencyWidget.js @@ -1,25 +1,24 @@ import BigNumber from 'bignumber.js'; -import React, { useState, useEffect } from 'react'; -import { View, Dimensions } from 'react-native'; -import { Avatar, Card, Paragraph } from 'react-native-paper'; -import { useSelector } from 'react-redux'; -import { CoinLogos, getCoinLogo } from '../../../utils/CoinData/CoinData'; -import { USD } from '../../../utils/constants/currencies'; -import { GENERAL } from '../../../utils/constants/intervalConstants'; -import { truncateDecimal } from '../../../utils/math'; -import { formatCurrency } from "react-native-format-currency"; -import SubWalletsLogo from '../../../images/customIcons/SubWallets.svg' -import { extractDisplaySubWallets } from '../../../utils/subwallet/extractSubWallets'; -import { normalizeNum } from '../../../utils/normalizeNum'; +import React, {useState, useEffect} from 'react'; +import {View, Dimensions} from 'react-native'; +import {Avatar, Card, Paragraph} from 'react-native-paper'; +import {useSelector} from 'react-redux'; +import {getCoinLogo} from '../../../utils/CoinData/CoinData'; +import {USD} from '../../../utils/constants/currencies'; +import {GENERAL} from '../../../utils/constants/intervalConstants'; +import {formatCurrency} from 'react-native-format-currency'; +import SubWalletsLogo from '../../../images/customIcons/SubWallets.svg'; +import {extractDisplaySubWallets} from '../../../utils/subwallet/extractSubWallets'; +import {normalizeNum} from '../../../utils/normalizeNum'; const CurrencyWidget = props => { - const { currencyBalance, coinObj } = props; - const { width } = Dimensions.get('window'); + const {currencyBalance, coinObj} = props; + const {width} = Dimensions.get('window'); - const Logo = getCoinLogo(coinObj.id) - const themeColor = coinObj.theme_color ? coinObj.theme_color : '#1C1C1C' - - const allSubwallets = useSelector(state => extractDisplaySubWallets(state)) + const Logo = getCoinLogo(coinObj.id, coinObj.proto); + const themeColor = coinObj.theme_color ? coinObj.theme_color : '#1C1C1C'; + + const allSubwallets = useSelector(state => extractDisplaySubWallets(state)); const displayCurrency = useSelector(state => state.settings.generalWalletSettings.displayCurrency ? state.settings.generalWalletSettings.displayCurrency @@ -32,23 +31,29 @@ const CurrencyWidget = props => { : null, ); - const [uniValueDisplay, setUniValueDisplay] = useState("-"); - + const [uniValueDisplay, setUniValueDisplay] = useState('-'); + // Recalculate fiat value useEffect(() => { if (uniRate != null && currencyBalance != null && displayCurrency != null) { const price = BigNumber(uniRate); - const displayValueRaw = normalizeNum(Number((BigNumber(currencyBalance).multipliedBy(price))), 2)[3] + const displayValueRaw = normalizeNum( + Number(BigNumber(currencyBalance).multipliedBy(price)), + 2, + )[3]; const [valueFormattedWithSymbol, valueFormattedWithoutSymbol, symbol] = formatCurrency({amount: displayValueRaw, code: displayCurrency}); - setUniValueDisplay(valueFormattedWithSymbol) + setUniValueDisplay(valueFormattedWithSymbol); } }, [currencyBalance, uniRate, displayCurrency]); - const displayedName = coinObj.display_name.length > 8 ? coinObj.display_ticker : coinObj.display_name; + const displayedName = + coinObj.display_name.length > 8 + ? coinObj.display_ticker + : coinObj.display_name; return ( { style={{ display: 'flex', flexDirection: 'row', - alignItems: "flex-start", - justifyContent: "space-between", + alignItems: 'flex-start', + justifyContent: 'space-between', }}> { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', - overflow: "visible" + overflow: 'visible', }}> {Logo == null ? ( { /> )} {displayedName} @@ -104,33 +114,45 @@ const CurrencyWidget = props => { alignItems: 'center', justifyContent: 'center', marginRight: -10, - marginTop: -12 + marginTop: -12, }}> - {allSubwallets[coinObj.id] - ? allSubwallets[coinObj.id].length - : 1} + {allSubwallets[coinObj.id] ? allSubwallets[coinObj.id].length : 1} - - {coinObj.testnet || uniValueDisplay === '-' ? `${currencyBalance == null ? '-' : normalizeNum(Number(currencyBalance), 8)[3]} ${ - coinObj.display_ticker - }` : uniValueDisplay} + + {coinObj.testnet || uniValueDisplay === '-' + ? `${ + currencyBalance == null + ? '-' + : normalizeNum(Number(currencyBalance), 8)[3] + } ${coinObj.display_ticker}` + : uniValueDisplay} - - {coinObj.testnet ? "Testnet Currency" : coinObj.pbaas_options ? "PBaaS Currency" : - uniValueDisplay === '-' ? uniValueDisplay : `${currencyBalance == null ? '-' : normalizeNum(Number(currencyBalance), 8)[3]} ${ - coinObj.display_ticker - }` - } + + {coinObj.testnet && coinObj.proto === 'erc20' + ? 'Testnet ERC20 Token' + : coinObj.testnet + ? 'Testnet Currency' + : coinObj.pbaas_options + ? 'PBaaS Currency' + : uniValueDisplay === '-' + ? coinObj.proto === 'erc20' + ? 'ERC20 Token' + : uniValueDisplay + : `${ + currencyBalance == null + ? '-' + : normalizeNum(Number(currencyBalance), 8)[3] + } ${coinObj.display_ticker}`} ); }; - - -export default CurrencyWidget; \ No newline at end of file +export default CurrencyWidget; diff --git a/src/containers/SideMenu/SideMenu.render.js b/src/containers/SideMenu/SideMenu.render.js index 4063c2ed..d89ff71d 100644 --- a/src/containers/SideMenu/SideMenu.render.js +++ b/src/containers/SideMenu/SideMenu.render.js @@ -136,7 +136,7 @@ export const renderMainDrawerComponents = function() { data={this.props.activeCoinsForUser} style={Styles.underflow} renderItem={({ item, index }) => { - const Logo = getCoinLogo(item.id, 'dark'); + const Logo = getCoinLogo(item.id, item.proto, 'dark'); return ( { + VrpcProvider.addDefaultEndpoints(); + }) } function* handleFinishVerusidInit(action) { diff --git a/src/sagas/channels/vrpc.js b/src/sagas/channels/vrpc.js index 4f0e94bb..07b74a67 100644 --- a/src/sagas/channels/vrpc.js +++ b/src/sagas/channels/vrpc.js @@ -29,7 +29,11 @@ function* handleVrpcChannelClose(action) { } function * handleSignOut() { - VrpcProvider.deleteAllEndpoints() + VrpcProvider.deleteAllEndpoints(); + + setImmediate(() => { + VrpcProvider.addDefaultEndpoints(); + }) } function * handleFinishVrpcInit(action) { diff --git a/src/utils/CoinData/CoinData.js b/src/utils/CoinData/CoinData.js index aa0f125b..816b4aae 100644 --- a/src/utils/CoinData/CoinData.js +++ b/src/utils/CoinData/CoinData.js @@ -107,6 +107,7 @@ export const explorers = { VRSC: 'https://insight.verus.io/', VRSCTEST: 'https://testex.verus.io', ETH: 'https://etherscan.io', + GETH: 'https://goerli.etherscan.io/', RFOX: 'https://etherscan.io', BAT: 'https://etherscan.io', DAI: 'https://etherscan.io', @@ -149,6 +150,7 @@ export const CoinLogos = { DAI: CoinLogoIcons.web3.DAI, DAIWYRE: CoinLogoIcons.web3.DAI, ETH: CoinLogoIcons.web3.ETH, + GETH: CoinLogoIcons.web3.ETH, BAL: CoinLogoIcons.web3.BAL, BNT: CoinLogoIcons.web3.BNT, HOT: CoinLogoIcons.web3.HOT, @@ -283,8 +285,9 @@ export const getCoinObj = (coinList, coinId) => { }) } -export const getCoinLogo = (id, theme = 'light') => { +export const getCoinLogo = (id, proto, theme = 'light') => { if (CoinLogos[id]) return CoinLogos[id][theme] + else if (proto === 'erc20') return CoinLogos.ETH[theme] else return CoinLogoIcons.pbaas.RenderPbaasCurrencyLogo(id)[theme] } diff --git a/src/utils/CoinData/CoinDirectory.js b/src/utils/CoinData/CoinDirectory.js index 6986e3eb..ee2cee1c 100644 --- a/src/utils/CoinData/CoinDirectory.js +++ b/src/utils/CoinData/CoinDirectory.js @@ -1,4 +1,4 @@ -import { DLIGHT_PRIVATE, ELECTRUM, IS_PBAAS, IS_VERUS, IS_ZCASH, VERUSID, VRPC, WYRE_SERVICE } from "../constants/intervalConstants"; +import { DLIGHT_PRIVATE, ELECTRUM, ERC20, GENERAL, IS_PBAAS, IS_VERUS, IS_ZCASH, VERUSID, VRPC, WYRE_SERVICE } from "../constants/intervalConstants"; import { getDefaultApps, getSystemNameFromSystemId } from "./CoinData"; import { electrumServers } from './electrum/servers'; import { ENABLE_VERUS_IDENTITIES } from '../../../env/index' @@ -7,6 +7,7 @@ import { VERUS_APPS, coinsList } from "./CoinsList"; import { DEFAULT_DECIMALS } from "../constants/web3Constants"; import { getStoredCurrencyDefinitions, storeCurrencyDefinitionForCurrencyId } from "../asyncStore/currencyDefinitionStorage"; import { timeout } from "../promises"; +import { getStoredContractDefinitions, storeContractDefinitionForNetwork } from "../asyncStore/contractDefinitionStorage"; class _CoinDirectory { fullCoinList = []; @@ -116,7 +117,7 @@ class _CoinDirectory { } } - // Function to add PBaaS currency. This function is not implemented. + // Function to add PBaaS currency async addPbaasCurrency(currencyDefinition, isTestnet = false, checkEndpoint = true, trySystemFallback = true, storeResults = true) { const id = currencyDefinition.currencyid const system = currencyDefinition.systemid @@ -227,7 +228,58 @@ class _CoinDirectory { this.updateCoinLists(); } - async populateCurrencyDefinitionsFromStorage() { + /** + * Adds a custom ERC20 token + * @param {{ address: string, symbol: string, name: string, decimals: number }} contractDefinition + * @param {string} network + * @param {boolean} storeResults + * @returns + */ + async addErc20Token(contractDefinition, network = 'homestead', storeResults = true) { + const id = contractDefinition.address; + + if (this.coinExistsInDirectory(id)) { + if (this.coins[id].network === network) { + return; + } else { + throw new Error( + 'Currency already exists in directory on ' + + + this.coins[id].network + + ' network', + ); + } + } + + const tokenCoinObj = { + id: contractDefinition.address, + currency_id: contractDefinition.address, + system_id: '.eth', + display_ticker: contractDefinition.symbol, + display_name: contractDefinition.name, + alt_names: [], + theme_color: '#141C30', + compatible_channels: [ERC20], + dominant_channel: ERC20, + decimals: contractDefinition.decimals, + tags: [], + proto: 'erc20', + network, + testnet: network !== 'homestead' + } + + if (storeResults) { + await storeContractDefinitionForNetwork( + network, + contractDefinition.address, + contractDefinition, + ); + } + + Object.assign(this.coins, { [tokenCoinObj.id]: tokenCoinObj }); + this.updateCoinLists(); + } + + async populatePbaasCurrencyDefinitionsFromStorage() { const storedDefinitions = await getStoredCurrencyDefinitions() const mainnetCurrencies = storedDefinitions[coinsList.VRSC.currency_id] ? storedDefinitions[coinsList.VRSC.currency_id] @@ -260,6 +312,36 @@ class _CoinDirectory { ) } } + + async populateEthereumContractDefinitionsFromStorage() { + const storedDefinitions = await getStoredContractDefinitions() + const mainnetTokens = storedDefinitions[coinsList.ETH.network] + ? storedDefinitions[coinsList.ETH.network] + : {}; + const testnetTokens = storedDefinitions[coinsList.GETH.network] + ? storedDefinitions[coinsList.GETH.network] + : {}; + + for (const key in mainnetTokens) { + if (this.coinExistsInDirectory(key)) continue; + + await this.addErc20Token( + mainnetTokens[key], + coinsList.ETH.network, + false + ) + } + + for (const key in testnetTokens) { + if (this.coinExistsInDirectory(key)) continue; + + await this.addErc20Token( + testnetTokens[key], + coinsList.GETH.network, + false + ) + } + } } export const CoinDirectory = new _CoinDirectory(JSON.parse(JSON.stringify(coinsList))); \ No newline at end of file diff --git a/src/utils/CoinData/CoinsList.js b/src/utils/CoinData/CoinsList.js index 17b9faac..3e12975c 100644 --- a/src/utils/CoinData/CoinsList.js +++ b/src/utils/CoinData/CoinsList.js @@ -148,6 +148,23 @@ export const coinsList = { decimals: ETHERS, network: "homestead" }, + GETH: { + id: 'GETH', + currency_id: '', + system_id: '.eth', + display_ticker: 'gETH', + display_name: 'Goerli Ethereum', + alt_names: [], + theme_color: '#141C30', + website: 'https://ethereum.org/en/', + compatible_channels: [ETH], + dominant_channel: ETH, + tags: [], + proto: 'eth', + decimals: ETHERS, + network: "goerli", + testnet: true + }, BAT: { id: 'BAT', currency_id: '0x0d8775f648430679a709e98d2b0cb6250d2887ef', diff --git a/src/utils/CoinData/Graphics.js b/src/utils/CoinData/Graphics.js index 103f3de6..85da4ee3 100644 --- a/src/utils/CoinData/Graphics.js +++ b/src/utils/CoinData/Graphics.js @@ -26,9 +26,10 @@ export const RenderSquareLogo = (LogoComponent, color, width = 40, height = 40) }; export const RenderSquareCoinLogo = (chainTicker, style = {}, width = 40, height = 40) => { - const Logo = getCoinLogo(chainTicker); const coinObj = CoinDirectory.findCoinObj(chainTicker) + const Logo = getCoinLogo(chainTicker, coinObj.proto); + return RenderSquareLogo( { @@ -14,12 +14,12 @@ export const getErc20Balance = async (address, contract) => { } else throw new Error(`ERC20 contract ${contract.address} does not support a known balance function.`) } -export const getStandardErc20Balance = async (address, contractAddress, decimals = ETHERS) => { +export const getStandardErc20Balance = async (address, contractAddress, decimals = ETHERS, network = 'homestead') => { return BigNumber( ethers.utils.formatUnits( await getErc20Balance( address, - Web3Provider.getContract(contractAddress, [ + getWeb3ProviderForNetwork(network).getContract(contractAddress, [ { "constant":true, "inputs":[{"name":"_owner","type":"address"}], diff --git a/src/utils/api/channels/erc20/requests/getErc20Transactions.js b/src/utils/api/channels/erc20/requests/getErc20Transactions.js index 9db2ca18..e41082be 100644 --- a/src/utils/api/channels/erc20/requests/getErc20Transactions.js +++ b/src/utils/api/channels/erc20/requests/getErc20Transactions.js @@ -1,4 +1,4 @@ -import Web3Provider from '../../../../web3/provider' +import { getWeb3ProviderForNetwork } from '../../../../web3/provider' import { ETHERS } from '../../../../constants/web3Constants' import { ethers } from 'ethers' import { ETH } from '../../../../constants/intervalConstants' @@ -7,13 +7,13 @@ import BigNumber from 'bignumber.js' import { standardizeEthTxObj } from '../../../../standardization/standardizeTxObj' // Gets an ERC20 token transaction list for an address -export const getErc20Transactions = async (address, contractAddress) => { - return await Web3Provider.EtherscanProvider.getHistory(address, null, null, contractAddress) +export const getErc20Transactions = async (address, contractAddress, network = 'homestead') => { + return await getWeb3ProviderForNetwork(network).EtherscanProvider.getHistory(address, null, null, contractAddress) } -export const getStandardErc20Transactions = async(address, contractAddress, decimals = ETHERS) => { +export const getStandardErc20Transactions = async(address, contractAddress, decimals = ETHERS, network) => { let processedTxs = standardizeEthTxObj( - await getErc20Transactions(address, contractAddress), + await getErc20Transactions(address, contractAddress, network), address, decimals ); @@ -22,7 +22,7 @@ export const getStandardErc20Transactions = async(address, contractAddress, deci let tx = processedTxs[i] if (tx.type === 'self') { - const txReceipt = await getTxReceipt(tx.txid) + const txReceipt = await getTxReceipt(tx.txid, network) const fee = ethers.utils.formatEther(txReceipt.gasUsed.mul(ethers.utils.parseEther(tx.gasPrice))).toString(); processedTxs[i] = { ...tx, ...txReceipt, amount: "0", fee, feeCurr: ETH.toUpperCase() } diff --git a/src/utils/api/channels/erc20/requests/preflight.js b/src/utils/api/channels/erc20/requests/preflight.js index f57f5862..53cea0e7 100644 --- a/src/utils/api/channels/erc20/requests/preflight.js +++ b/src/utils/api/channels/erc20/requests/preflight.js @@ -1,5 +1,5 @@ import { ethers } from "ethers" -import Web3Provider from '../../../../web3/provider' +import { getWeb3ProviderForNetwork } from '../../../../web3/provider' import { ERC20, ETH } from "../../../../constants/intervalConstants" import { scientificToDecimal } from "../../../../math" import { ETHERS } from "../../../../constants/web3Constants" @@ -7,6 +7,8 @@ import { ETHERS } from "../../../../constants/web3Constants" // TODO: Add balance recalculation with eth gas export const txPreflight = async (coinObj, activeUser, address, amount, params) => { try { + const Web3Provider = getWeb3ProviderForNetwork(coinObj.network); + const fromAddress = activeUser.keys[coinObj.id][ERC20].addresses[0] const signer = new ethers.VoidSigner(fromAddress, Web3Provider.DefaultProvider) const contract = Web3Provider.getContract(coinObj.currency_id).connect(signer) diff --git a/src/utils/api/channels/erc20/requests/send.js b/src/utils/api/channels/erc20/requests/send.js index a6e9a8e7..aa9626e1 100644 --- a/src/utils/api/channels/erc20/requests/send.js +++ b/src/utils/api/channels/erc20/requests/send.js @@ -1,5 +1,5 @@ import { ethers } from "ethers" -import Web3Provider from '../../../../web3/provider' +import { getWeb3ProviderForNetwork } from '../../../../web3/provider' import { ERC20, ETH } from "../../../../constants/intervalConstants" import { scientificToDecimal } from "../../../../math" import { requestPrivKey } from "../../../../auth/authBox" @@ -7,6 +7,8 @@ import { ETHERS } from "../../../../constants/web3Constants" export const send = async (coinObj, activeUser, address, amount, params) => { try { + const Web3Provider = getWeb3ProviderForNetwork(coinObj.network) + const privKey = await requestPrivKey(coinObj.id, ERC20) const contract = Web3Provider.getContract(coinObj.currency_id) const gasPrice = await Web3Provider.DefaultProvider.getGasPrice() diff --git a/src/utils/api/channels/erc20/requests/specific/rfox/claimAccountBalance.js b/src/utils/api/channels/erc20/requests/specific/rfox/claimAccountBalance.js index ed91916f..59fc49de 100644 --- a/src/utils/api/channels/erc20/requests/specific/rfox/claimAccountBalance.js +++ b/src/utils/api/channels/erc20/requests/specific/rfox/claimAccountBalance.js @@ -1,5 +1,5 @@ import { RFOX_UTILITY_CONTRACT } from "../../../../../../constants/web3Constants" -import Web3Provider from "../../../../../../web3/provider" +import { getWeb3ProviderForNetwork } from "../../../../../../web3/provider" import { computePublicKey } from '@ethersproject/signing-key' import { ethers } from "ethers" @@ -8,6 +8,8 @@ import { ethers } from "ethers" * @param {String} pubKey The public key that is stored in the accountObj of the user */ export const estimateGasClaimRFoxAccountBalances = async (pubKey, fromAddress) => { + const Web3Provider = getWeb3ProviderForNetwork('homestead') + let contract = null const signer = new ethers.VoidSigner(fromAddress, Web3Provider.DefaultProvider) @@ -32,6 +34,8 @@ export const estimateGasClaimRFoxAccountBalances = async (pubKey, fromAddress) = * @param {String} pubKey The public key that is stored in the accountObj of the user */ export const claimRFoxAccountBalances = async (privKey, pubKey) => { + const Web3Provider = getWeb3ProviderForNetwork('homestead') + let contract = null try { diff --git a/src/utils/api/channels/erc20/requests/specific/rfox/getTotalAccountBalance.js b/src/utils/api/channels/erc20/requests/specific/rfox/getTotalAccountBalance.js index 439f74d6..e2798a99 100644 --- a/src/utils/api/channels/erc20/requests/specific/rfox/getTotalAccountBalance.js +++ b/src/utils/api/channels/erc20/requests/specific/rfox/getTotalAccountBalance.js @@ -1,5 +1,5 @@ import { RFOX_UTILITY_CONTRACT } from "../../../../../../constants/web3Constants" -import Web3Provider from "../../../../../../web3/provider" +import { getWeb3ProviderForNetwork } from "../../../../../../web3/provider" import { computePublicKey } from '@ethersproject/signing-key' /** @@ -7,6 +7,8 @@ import { computePublicKey } from '@ethersproject/signing-key' * @param {String} pubKey The public key that is stored in the accountObj of the user */ export const getRfoxAccountBalances = async (pubKey) => { + const Web3Provider = getWeb3ProviderForNetwork('homestead') + let contract = null try { diff --git a/src/utils/api/channels/eth/requests/getEthBalance.js b/src/utils/api/channels/eth/requests/getEthBalance.js index c883dea8..799f7de0 100644 --- a/src/utils/api/channels/eth/requests/getEthBalance.js +++ b/src/utils/api/channels/eth/requests/getEthBalance.js @@ -1,13 +1,13 @@ -import Web3Provider from '../../../../web3/provider' +import { getWeb3ProviderForNetwork } from '../../../../web3/provider' import ethers from 'ethers'; import BigNumber from "bignumber.js"; // Gets the Ethereum balance of an address or name as a big number -export const getEthBalance = async (address) => { - return await Web3Provider.DefaultProvider.getBalance(address) +export const getEthBalance = async (address, network = 'homestead') => { + return await getWeb3ProviderForNetwork(network).DefaultProvider.getBalance(address) } //TODO: Handle BigNumbers -export const getStandardEthBalance = async (address) => { - return BigNumber(ethers.utils.formatUnits(await getEthBalance(address))) +export const getStandardEthBalance = async (address, network) => { + return BigNumber(ethers.utils.formatUnits(await getEthBalance(address, network))) } \ No newline at end of file diff --git a/src/utils/api/channels/eth/requests/getEthTransactions.js b/src/utils/api/channels/eth/requests/getEthTransactions.js index 029b8cd2..0c870be8 100644 --- a/src/utils/api/channels/eth/requests/getEthTransactions.js +++ b/src/utils/api/channels/eth/requests/getEthTransactions.js @@ -1,17 +1,17 @@ -import Web3Provider from '../../../../web3/provider' +import { getWeb3ProviderForNetwork } from '../../../../web3/provider' import { ethers } from 'ethers' import { getTxReceipt } from './getTxReceipt' import BigNumber from 'bignumber.js' import { standardizeEthTxObj } from '../../../../standardization/standardizeTxObj' // Gets the Ethereum transaction history of an address or name -export const getEthTransactions = async (address) => { - return await Web3Provider.EtherscanProvider.getHistory(address) +export const getEthTransactions = async (address, network = 'homestead') => { + return await getWeb3ProviderForNetwork(network).EtherscanProvider.getHistory(address) } -export const getStandardEthTransactions = async (address) => { +export const getStandardEthTransactions = async (address, network) => { let processedTxs = standardizeEthTxObj( - await getEthTransactions(address), + await getEthTransactions(address, network), address ); @@ -19,7 +19,7 @@ export const getStandardEthTransactions = async (address) => { let tx = processedTxs[i] if (tx.type === 'self') { - const txReceipt = await getTxReceipt(tx.txid) + const txReceipt = await getTxReceipt(tx.txid, network) const fee = ethers.utils.formatEther(txReceipt.gasUsed.mul(ethers.utils.parseEther(tx.gasPrice))).toString() processedTxs[i] = { ...tx, ...txReceipt, amount: fee, fee } diff --git a/src/utils/api/channels/eth/requests/getTxReceipt.js b/src/utils/api/channels/eth/requests/getTxReceipt.js index 7a435411..9ba0dd0b 100644 --- a/src/utils/api/channels/eth/requests/getTxReceipt.js +++ b/src/utils/api/channels/eth/requests/getTxReceipt.js @@ -1,9 +1,9 @@ import store from '../../../../../store/index'; import { saveEthTxReceipt } from '../../../../../actions/actionCreators'; import { MIN_ETH_TX_CONFS } from '../../../../../../env/index' -import Web3Provider from '../../../../web3/provider' +import { getWeb3ProviderForNetwork } from '../../../../web3/provider' -export const getTxReceipt = async (txid) => { +export const getTxReceipt = async (txid, network) => { let cachedReceipts = store.getState().ethtxreceipts.txReceipts; //If already loaded into redux store (from cache), get data from there and avoid call @@ -11,7 +11,7 @@ export const getTxReceipt = async (txid) => { return cachedReceipts[txid] } - const txReceipt = await Web3Provider.DefaultProvider.getTransactionReceipt(txid) + const txReceipt = await getWeb3ProviderForNetwork(network).DefaultProvider.getTransactionReceipt(txid) if (txReceipt.confirmations >= MIN_ETH_TX_CONFS) await saveEthTxReceipt(txReceipt, txid, store) diff --git a/src/utils/api/channels/eth/requests/preflight.js b/src/utils/api/channels/eth/requests/preflight.js index acfad04a..dcc09b4e 100644 --- a/src/utils/api/channels/eth/requests/preflight.js +++ b/src/utils/api/channels/eth/requests/preflight.js @@ -1,5 +1,5 @@ import { ethers } from "ethers" -import Web3Provider from '../../../../web3/provider' +import { getWeb3ProviderForNetwork } from '../../../../web3/provider' import { ETH } from "../../../../constants/intervalConstants" import { ETH_NETWORK_IDS } from "../../../../constants/constants" import { ETH_HOMESTEAD } from '../../../../../../env/index' @@ -8,6 +8,8 @@ import { scientificToDecimal } from "../../../../math" export const txPreflight = async (coinObj, activeUser, address, amount, params) => { try { + const Web3Provider = getWeb3ProviderForNetwork(coinObj.network) + const fromAddress = activeUser.keys[coinObj.id][ETH].addresses[0] const signer = new ethers.VoidSigner(fromAddress, Web3Provider.DefaultProvider) const balance = await signer.getBalance() diff --git a/src/utils/api/channels/eth/requests/send.js b/src/utils/api/channels/eth/requests/send.js index 69980790..6ac1d49c 100644 --- a/src/utils/api/channels/eth/requests/send.js +++ b/src/utils/api/channels/eth/requests/send.js @@ -1,5 +1,5 @@ import { ethers } from "ethers" -import Web3Provider from '../../../../web3/provider' +import { getWeb3ProviderForNetwork } from '../../../../web3/provider' import { ETH } from "../../../../constants/intervalConstants" import { ETH_NETWORK_IDS } from "../../../../constants/constants" import { ETH_HOMESTEAD } from '../../../../../../env/index' @@ -8,6 +8,8 @@ import { requestPrivKey } from "../../../../auth/authBox" export const send = async (coinObj, activeUser, address, amount, params) => { try { + const Web3Provider = getWeb3ProviderForNetwork(coinObj.network) + const fromAddress = activeUser.keys[coinObj.id][ETH].addresses[0] const privKey = await requestPrivKey(coinObj.id, ETH) const voidSigner = new ethers.VoidSigner(fromAddress, Web3Provider.DefaultProvider) diff --git a/src/utils/asyncStore/contractDefinitionStorage.js b/src/utils/asyncStore/contractDefinitionStorage.js new file mode 100644 index 00000000..4c423429 --- /dev/null +++ b/src/utils/asyncStore/contractDefinitionStorage.js @@ -0,0 +1,70 @@ +import AsyncStorage from '@react-native-async-storage/async-storage'; +import { CONTRACT_DEFINITION_STORAGE_INTERNAL_KEY } from '../../../env/index' + +export const storeContractDefinitions = async (contractDefinitions) => { + return await AsyncStorage.setItem(CONTRACT_DEFINITION_STORAGE_INTERNAL_KEY, JSON.stringify(contractDefinitions)) +}; + +export const getStoredContractDefinitions = async () => { + const res = await AsyncStorage.getItem(CONTRACT_DEFINITION_STORAGE_INTERNAL_KEY); + + if (!res) return {}; + else return JSON.parse(res); +}; + +// Gets contract definitions, sets contractDefinitions[ethNetwork] to data, and stores the new definitions +export const storeContractDefinitionsForNetwork = async (ethNetwork, definitions) => { + const contractDefinitions = await getStoredContractDefinitions(); + contractDefinitions[ethNetwork] = definitions; + await storeContractDefinitions(contractDefinitions); +} + +// Gets contract definitions, sets contractDefinitions[ethNetwork][contractAddress] to definition, and stores the new definitions +export const storeContractDefinitionForNetwork = async (ethNetwork, contractAddress, definition) => { + const contractDefinitions = await getStoredContractDefinitions(); + + if (!contractDefinitions[ethNetwork]) contractDefinitions[ethNetwork] = {} + + contractDefinitions[ethNetwork][contractAddress] = definition; + await storeContractDefinitions(contractDefinitions); +} + +// Gets contract definitions for ethNetwork +export const getStoredContractDefinitionsForNetwork = async (ethNetwork) => { + const contractDefinitions = await getStoredContractDefinitions(); + return contractDefinitions[ethNetwork] ? contractDefinitions[ethNetwork] : {}; +} + +export const removeInactiveContractDefinitions = async (activeCoinList) => { + let storedDefinitions = await getStoredContractDefinitions(); + + const activeCoinIds = {} + activeCoinList.map(coin => { + activeCoinIds[coin.id] = true + }) + + for (const network in storedDefinitions) { + const tokens = Object.keys(storedDefinitions[network]) + + for (const contractAddress of tokens) { + if (!activeCoinIds[contractAddress]) { + delete storedDefinitions[network][contractAddress] + } + } + } + + await storeContractDefinitions(storedDefinitions) +} + +// Gets contract definition given ethNetwork and contractAddress +export const getStoredContractDefinitionForNetwork = async ( + ethNetwork, + contractAddress, +) => { + const contractDefinitions = await getStoredContractDefinitions(); + return contractDefinitions[ethNetwork] + ? contractDefinitions[ethNetwork][contractAddress] + ? contractDefinitions[ethNetwork][contractAddress] + : null + : {}; +}; diff --git a/src/utils/constants/constants.js b/src/utils/constants/constants.js index 921de800..d4d54b84 100644 --- a/src/utils/constants/constants.js +++ b/src/utils/constants/constants.js @@ -163,7 +163,8 @@ export const DEVICE_WINDOW_HEIGHT = Dimensions.get('window').height; // Init export const START_COINS = ["VRSC", "BTC", "ETH"] export const TEST_PROFILE_OVERRIDES = { - ['VRSC']: 'VRSCTEST' + ['VRSC']: 'VRSCTEST', + ['ETH']: 'GETH' }; // Account data initialization steps diff --git a/src/utils/constants/sendModal.js b/src/utils/constants/sendModal.js index 8da45a70..3fbc37a3 100644 --- a/src/utils/constants/sendModal.js +++ b/src/utils/constants/sendModal.js @@ -22,6 +22,7 @@ export const SEND_MODAL_SHOW_CONVERTTO_FIELD = 'SEND_MODAL_SHOW_CONVERTTO_FIELD' export const SEND_MODAL_SHOW_VIA_FIELD = 'SEND_MODAL_SHOW_VIA_FIELD' export const SEND_MODAL_SHOW_EXPORTTO_FIELD = 'SEND_MODAL_SHOW_EXPORTTO_FIELD' export const SEND_MODAL_ADVANCED_FORM = 'SEND_MODAL_ADVANCED_FORM' +export const SEND_MODAL_CONTRACT_ADDRESS_FIELD = 'SEND_MODAL_CONTRACT_ADDRESS_FIELD' // Send modal types export const TRADITIONAL_CRYPTO_SEND_MODAL = 'TRADITIONAL_CRYPTO_SEND_MODAL' @@ -33,6 +34,7 @@ export const PROVISION_IDENTITY_SEND_MODAL = 'PROVISION_IDENTITY_SEND_MODAL' export const AUTHENTICATE_USER_SEND_MODAL = 'AUTHENTICATE_USER_SEND_MODAL' export const ADD_PBAAS_CURRENCY_MODAL = 'ADD_PBAAS_CURRENCY_MODAL' export const CONVERT_OR_CROSS_CHAIN_SEND_MODAL = 'CONVERT_OR_CROSS_CHAIN_SEND_MODAL' +export const ADD_ERC20_TOKEN_MODAL = 'ADD_ERC20_TOKEN_MODAL' // Form steps export const SEND_MODAL_FORM_STEP_FORM = "SEND_MODAL_FORM_STEP_FORM" diff --git a/src/utils/constants/storeType.js b/src/utils/constants/storeType.js index 9fb3a942..987f52b6 100644 --- a/src/utils/constants/storeType.js +++ b/src/utils/constants/storeType.js @@ -211,9 +211,6 @@ export const CLOSE_ETH_CHANNEL = 'CLOSE_ETH_CHANNEL'; export const INIT_ERC20_CHANNEL_START = 'INIT_ERC20_CHANNEL_START'; export const INIT_ERC20_CHANNEL_FINISH = 'INIT_ERC20_CHANNEL_FINISH' export const CLOSE_ERC20_CHANNEL = 'CLOSE_ERC20_CHANNEL'; -export const ADD_WEB3_CONTRACT = 'ADD_WEB3_CONTRACT' -export const REMOVE_WEB3_CONTRACT = 'REMOVE_WEB3_CONTRACT' -export const CLEAR_WEB3_CONTRACTS = 'CLEAR_WEB3_CONTRACTS' // Verusd Rpc export const INIT_VRPC_CHANNEL_START = 'INIT_VRPC_CHANNEL_START'; diff --git a/src/utils/vrpc/vrpcInterface.js b/src/utils/vrpc/vrpcInterface.js index 509c732b..f279651e 100644 --- a/src/utils/vrpc/vrpcInterface.js +++ b/src/utils/vrpc/vrpcInterface.js @@ -299,6 +299,11 @@ class VrpcInterface { ); return new VerusIdInterface(systemId, endpoint) } + + addDefaultEndpoints = () => { + this.initEndpoint(coinsList.VRSC.system_id, coinsList.VRSC.vrpc_endpoints[0]); + this.initEndpoint(coinsList.VRSCTEST.system_id, coinsList.VRSCTEST.vrpc_endpoints[0]); + } } const VerusMobileVrpcInterface = new VrpcInterface(); @@ -307,8 +312,7 @@ setImmediate(() => { try { // Initialize VRSC and VRSCTEST endpoints to support App functions // that make calls to vrpc even if VRSC/VRSCTEST isn't added as a coin - VerusMobileVrpcInterface.initEndpoint(coinsList.VRSC.system_id, coinsList.VRSC.vrpc_endpoints[0]); - VerusMobileVrpcInterface.initEndpoint(coinsList.VRSCTEST.system_id, coinsList.VRSCTEST.vrpc_endpoints[0]); + VerusMobileVrpcInterface.addDefaultEndpoints() } catch(e) { Alert.alert("Error initializing Verus lite mode", e.message) } diff --git a/src/utils/web3/provider.js b/src/utils/web3/provider.js index 37f22157..4212c3d6 100644 --- a/src/utils/web3/provider.js +++ b/src/utils/web3/provider.js @@ -1,12 +1,34 @@ import Web3Interface from './web3Interface' import { ETH_HOMESTEAD, + ETH_GOERLI, ETHERSCAN_API_KEY, INFURA_PROJECT_ID, } from "../../../env/index"; -// Change the provider here to change ETH provider -export default new Web3Interface(ETH_HOMESTEAD, { - etherscan: ETHERSCAN_API_KEY, - infura: INFURA_PROJECT_ID -}) \ No newline at end of file +const Web3Providers = { + [ETH_HOMESTEAD]: new Web3Interface(ETH_HOMESTEAD, { + etherscan: ETHERSCAN_API_KEY, + infura: INFURA_PROJECT_ID + }), + [ETH_GOERLI]: new Web3Interface(ETH_GOERLI, { + etherscan: ETHERSCAN_API_KEY, + infura: INFURA_PROJECT_ID + }), +} + +Object.freeze(Web3Providers); + +export const getWeb3ProviderForNetwork = (network = ETH_HOMESTEAD) => { + if (Web3Providers.hasOwnProperty(network)) { + return Web3Providers[network]; + } else { + throw new Error(`No web3 provider for network ${network}`); + } +}; + +export const deleteAllWeb3Contracts = () => { + for (const network in Web3Providers) { + Web3Providers[network].deleteAllContracts() + } +} \ No newline at end of file diff --git a/src/utils/web3/web3Interface.js b/src/utils/web3/web3Interface.js index 3bdf8ee7..795aa931 100644 --- a/src/utils/web3/web3Interface.js +++ b/src/utils/web3/web3Interface.js @@ -1,16 +1,12 @@ import ethers from 'ethers'; -import Store from '../../store'; import { DEFAULT_ERC20_ABI } from '../constants/abi'; -import { - ADD_WEB3_CONTRACT, - CLEAR_WEB3_CONTRACTS, - REMOVE_WEB3_CONTRACT, -} from "../constants/storeType"; class Web3Interface { constructor(network, apiKeys) { this.network = network; - this.keys = apiKeys + this.keys = apiKeys; + + this.web3Contracts = {}; // { [key: contractAddress]: Array[contractAddress, contractAbi]} this.DefaultProvider = new ethers.getDefaultProvider(this.network, apiKeys); @@ -25,23 +21,16 @@ class Web3Interface { ); } - initContract = async (contractAddress) => { - const web3Contracts = Store.getState().channelStore_erc20.web3Contracts - + initContract = async (contractAddress) => { try { - if (web3Contracts[contractAddress]) + if (this.web3Contracts[contractAddress]) throw new Error( "Cannot initialize existing contract " + contractAddress ); const abi = DEFAULT_ERC20_ABI - Store.dispatch({ - type: ADD_WEB3_CONTRACT, - payload: { - contract: [contractAddress, abi], - } - }) + this.web3Contracts[contractAddress] = [contractAddress, abi]; } catch (e) { console.error(e); throw e; @@ -49,31 +38,25 @@ class Web3Interface { }; deleteContract = (contractAddress) => { - const web3Contracts = Store.getState().channelStore_erc20.web3Contracts - try { - if (!web3Contracts[contractAddress]) + if (!this.web3Contracts[contractAddress]) throw new Error( "Cannot delete uninitialized contract " + contractAddress ); - Store.dispatch({ - type: REMOVE_WEB3_CONTRACT, - payload: { - contractAddress, - } - }) + delete this.web3Contracts[contractAddress]; } catch (e) { console.error(e); throw e; } }; - deleteAllContracts = () => Store.dispatch({ type: CLEAR_WEB3_CONTRACTS }) + deleteAllContracts = () => { + this.web3Contracts = {}; + } getContract = (contractAddress, customAbiFragment) => { - const web3Contracts = Store.getState().channelStore_erc20.web3Contracts - const params = web3Contracts[contractAddress] + const params = this.web3Contracts[contractAddress] if (!params) throw new Error(`ERC20 contract ${contractAddress} not initialized`); @@ -83,6 +66,16 @@ class Web3Interface { this.DefaultProvider ); }; + + getUnitializedContractInstance = (contractAddress) => { + const abi = DEFAULT_ERC20_ABI + + return new ethers.Contract( + contractAddress, + abi, + this.DefaultProvider + ); + }; } export default Web3Interface \ No newline at end of file From 31cd548d88342de89fba34e77a89405e5e4348fd Mon Sep 17 00:00:00 2001 From: michaeltout Date: Fri, 4 Aug 2023 19:31:31 +0200 Subject: [PATCH 02/42] Estimate conversion through own method if estimateconversion call fails --- .../ConvertOrCrossChainSendConfirm.js | 276 +++++++++++------- 1 file changed, 164 insertions(+), 112 deletions(-) diff --git a/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendConfirm/ConvertOrCrossChainSendConfirm.js b/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendConfirm/ConvertOrCrossChainSendConfirm.js index 6b604bf5..4a576cb7 100644 --- a/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendConfirm/ConvertOrCrossChainSendConfirm.js +++ b/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendConfirm/ConvertOrCrossChainSendConfirm.js @@ -1,29 +1,50 @@ -import React, { useEffect, useRef, useState } from "react"; -import { ScrollView, View, TouchableOpacity, Alert } from "react-native"; -import { Button, List, Divider, Text } from "react-native-paper"; -import { useDispatch, useSelector } from 'react-redux' -import { expireCoinData } from "../../../../actions/actionCreators"; -import { traditionalCryptoSend } from "../../../../actions/actionDispatchers"; -import { copyToClipboard } from "../../../../utils/clipboard/clipboard"; -import { USD } from "../../../../utils/constants/currencies"; -import { API_GET_BALANCES, API_GET_FIATPRICE, API_GET_TRANSACTIONS, API_SEND } from "../../../../utils/constants/intervalConstants"; -import { SEND_MODAL_FORM_STEP_FORM, SEND_MODAL_FORM_STEP_RESULT } from "../../../../utils/constants/sendModal"; -import { coinsToSats, satsToCoins, truncateDecimal } from "../../../../utils/math"; -import Colors from "../../../../globals/colors"; -import Styles from "../../../../styles"; -import BigNumber from "bignumber.js"; -import { TransferDestination } from "verus-typescript-primitives"; -import { sendCurrencyTransfer } from "../../../../utils/api/channels/vrpc/callCreators"; -import { CoinDirectory } from "../../../../utils/CoinData/CoinDirectory"; - -function ConvertOrCrossChainSendConfirm({ navigation, route, setLoading, setModalHeight, setPreventExit }) { +import React, {useEffect, useRef, useState} from 'react'; +import {ScrollView, View, TouchableOpacity, Alert} from 'react-native'; +import {Button, List, Divider, Text} from 'react-native-paper'; +import {useDispatch, useSelector} from 'react-redux'; +import {expireCoinData} from '../../../../actions/actionCreators'; +import {traditionalCryptoSend} from '../../../../actions/actionDispatchers'; +import {copyToClipboard} from '../../../../utils/clipboard/clipboard'; +import {USD} from '../../../../utils/constants/currencies'; +import { + API_GET_BALANCES, + API_GET_FIATPRICE, + API_GET_TRANSACTIONS, + API_SEND, +} from '../../../../utils/constants/intervalConstants'; +import { + SEND_MODAL_FORM_STEP_FORM, + SEND_MODAL_FORM_STEP_RESULT, + SEND_MODAL_PRICE_ESTIMATE, +} from '../../../../utils/constants/sendModal'; +import { + coinsToSats, + satsToCoins, + truncateDecimal, +} from '../../../../utils/math'; +import Colors from '../../../../globals/colors'; +import Styles from '../../../../styles'; +import BigNumber from 'bignumber.js'; +import {TransferDestination} from 'verus-typescript-primitives'; +import {sendCurrencyTransfer} from '../../../../utils/api/channels/vrpc/callCreators'; +import {CoinDirectory} from '../../../../utils/CoinData/CoinDirectory'; + +function ConvertOrCrossChainSendConfirm({ + navigation, + route, + setLoading, + setModalHeight, + setPreventExit, +}) { const sendModal = useSelector(state => state.sendModal); const networkName = useSelector(state => { try { const subwallet = state.sendModal.subWallet; - return subwallet.network ? CoinDirectory.getBasicCoinObj(subwallet.network).display_ticker : null; - } catch(e) { + return subwallet.network + ? CoinDirectory.getBasicCoinObj(subwallet.network).display_ticker + : null; + } catch (e) { console.error(e); return null; } @@ -50,7 +71,7 @@ function ConvertOrCrossChainSendConfirm({ navigation, route, setLoading, setModa inputs, converterdef, submittedsats, - estimate + estimate, } = params; /** @@ -68,11 +89,7 @@ function ConvertOrCrossChainSendConfirm({ navigation, route, setLoading, setModa */ const txHex = hex; - const { - change, - fees, - sent - } = validation; + const {change, fees, sent} = validation; // const validation = { // change: {iJhCezBExJHvtyH3fGhNnt2NhU4Ztkf2yq: '399690000'}, @@ -104,36 +121,49 @@ function ConvertOrCrossChainSendConfirm({ navigation, route, setLoading, setModa */ const destination = address; + const primaryCurrencyAmountSats = sent[currency] ? sent[currency] : 0; + const primaryCurrencyAmount = satsToCoins(BigNumber(primaryCurrencyAmountSats)).toNumber(); + const remainingBalances = {}; deltaMap.forEach((value, key) => { if (balances.hasOwnProperty(key) && !value.isEqualTo(BigNumber(0))) { const satBalance = coinsToSats(BigNumber(balances[key])); - remainingBalances[key] = (satBalance.plus(value)).toString(); + remainingBalances[key] = satBalance.plus(value).toString(); } }); let conversionFeeMultiplier = BigNumber(0); - + if (convertto != null && via != null) { conversionFeeMultiplier = BigNumber(0.05); } else if (convertto != null) { conversionFeeMultiplier = BigNumber(0.025); } - const conversionFee = satsToCoins(BigNumber(satoshis)).multipliedBy(conversionFeeMultiplier); + const conversionFee = satsToCoins(BigNumber(satoshis)).multipliedBy( + conversionFeeMultiplier, + ); const destAddrString = destination.getAddressString(); - const toAddress = nameMap.has(destAddrString) ? `${nameMap.get(destAddrString)}@ (${destAddrString})` : destAddrString; - - const createAccordion = (label, description, left, currencies, showZeroValues = false) => { - const fields = [] + const toAddress = nameMap.has(destAddrString) + ? `${nameMap.get(destAddrString)}@ (${destAddrString})` + : destAddrString; + + const createAccordion = ( + label, + description, + left, + currencies, + showZeroValues = false, + ) => { + const fields = []; for (const key in currencies) { - const value = currencies[key] + const value = currencies[key]; const currencyName = nameMap.has(key) ? nameMap.get(key) : key; - const valueBn = satsToCoins(BigNumber(value)) + const valueBn = satsToCoins(BigNumber(value)); if (showZeroValues || !valueBn.isEqualTo(BigNumber(0))) { const readableValue = valueBn.toString(); @@ -155,13 +185,13 @@ function ConvertOrCrossChainSendConfirm({ navigation, route, setLoading, setModa label, description, left, - fields - } - } + fields, + }; + }; - const tryRenderFriendlyName = (address) => { - return nameMap.has(address) ? nameMap.get(address) : address - } + const tryRenderFriendlyName = address => { + return nameMap.has(address) ? nameMap.get(address) : address; + }; setConfirmationFields([ { @@ -172,7 +202,7 @@ function ConvertOrCrossChainSendConfirm({ navigation, route, setLoading, setModa copyToClipboard(source, { title: 'Address copied', message: `${source} copied to clipboard.`, - }) + }), }, { key: 'Destination', @@ -186,39 +216,58 @@ function ConvertOrCrossChainSendConfirm({ navigation, route, setLoading, setModa }, { key: 'Converting To', - data: via != null && via.length > 0 ? - `${tryRenderFriendlyName(convertto)} via ${tryRenderFriendlyName(via)}` - : - tryRenderFriendlyName(convertto), + data: + via != null && via.length > 0 + ? `${tryRenderFriendlyName(convertto)} via ${tryRenderFriendlyName( + via, + )}` + : tryRenderFriendlyName(convertto), numLines: 100, onPress: () => copyToClipboard(tryRenderFriendlyName(convertto), { title: 'Currency copied', message: `${tryRenderFriendlyName(convertto)} copied to clipboard.`, }), - condition: convertto != null && convertto.length > 0 + condition: convertto != null && convertto.length > 0, }, { key: 'Estimated To Receive', - data: estimate != null ? `${estimate.estimatedcurrencyout} ${tryRenderFriendlyName(convertto)}` : "", + data: + estimate != null + ? `${estimate.estimatedcurrencyout} ${tryRenderFriendlyName( + convertto, + )}` + : !!(sendModal.data[SEND_MODAL_PRICE_ESTIMATE]) ? `${Number( + ( + primaryCurrencyAmount * sendModal.data[SEND_MODAL_PRICE_ESTIMATE] + ).toFixed(8), + )} ${tryRenderFriendlyName(convertto)}` : "", numLines: 100, onPress: () => copyToClipboard(tryRenderFriendlyName(convertto), { title: 'Amount copied', message: `${estimate.estimatedcurrencyout} copied to clipboard.`, }), - condition: estimate != null && convertto != null && convertto.length > 0 + condition: + (estimate != null || !!(sendModal.data[SEND_MODAL_PRICE_ESTIMATE])) && convertto != null && convertto.length > 0, }, { key: 'Estimated Time Until Arrival', - data: exportto != null ? "20-30 minutes" : convertto != null ? "2-10 minutes" : "1-5 minutes", + data: + exportto != null + ? '20-30 minutes' + : convertto != null + ? '2-10 minutes' + : '1-5 minutes', numLines: 100, }, { key: 'Preconvert', - data: tryRenderFriendlyName(convertto) + " hasn't launched yet. You will receive your converted funds or have your transaction refunded once the currency launches or fails to launch.", + data: + tryRenderFriendlyName(convertto) + + " hasn't launched yet. You will receive your converted funds or have your transaction refunded once the currency launches or fails to launch.", numLines: 100, - condition: preconvert != null && preconvert + condition: preconvert != null && preconvert, }, { key: 'From Network', @@ -229,7 +278,8 @@ function ConvertOrCrossChainSendConfirm({ navigation, route, setLoading, setModa title: 'Currency copied', message: `${networkName} copied to clipboard.`, }), - condition: networkName != null && exportto != null && exportto.length > 0 + condition: + networkName != null && exportto != null && exportto.length > 0, }, { key: 'To Network', @@ -240,19 +290,19 @@ function ConvertOrCrossChainSendConfirm({ navigation, route, setLoading, setModa title: 'Currency copied', message: `${tryRenderFriendlyName(exportto)} copied to clipboard.`, }), - condition: exportto != null && exportto.length > 0 + condition: exportto != null && exportto.length > 0, }, createAccordion( 'Currency to send', 'The currencies that are being sent as part of this transaction', props => , - sent + sent, ), createAccordion( 'Transaction Fees', 'Fees deducted from your wallet to pay for this transaction', props => , - fees + fees, ), { key: 'Conversion Fee (taken from send amount)', @@ -263,89 +313,94 @@ function ConvertOrCrossChainSendConfirm({ navigation, route, setLoading, setModa title: 'Fee copied', message: `${conversionFee.toString()} copied to clipboard.`, }), - condition: !conversionFee.isEqualTo(BigNumber(0)) + condition: !conversionFee.isEqualTo(BigNumber(0)), }, createAccordion( 'Remaining Balances', - 'Your currency remaining in the address you\'re sending from after subtracting currency sent and fees (only affected balances shown)', + "Your currency remaining in the address you're sending from after subtracting currency sent and fees (only affected balances shown)", props => , remainingBalances, - true + true, ), ]); setLoading(false); setTimeout(() => { scrollRef.current.flashScrollIndicators(); - }, 500) + }, 500); }, []); - const toggleAccordion = (key) => { + const toggleAccordion = key => { setClosedAccordions({ ...closedAccordions, - [key]: !(closedAccordions[key]) - }) - } + [key]: !closedAccordions[key], + }); + }; const goBack = () => { - setModalHeight() - navigation.navigate(SEND_MODAL_FORM_STEP_FORM) - } + setModalHeight(); + navigation.navigate(SEND_MODAL_FORM_STEP_FORM); + }; const submitData = async () => { - await setLoading(true) - await setPreventExit(true) + await setLoading(true); + await setPreventExit(true); - const { - output, - validation, - hex, - names, - deltas, - source, - inputs - } = params; + const {output, validation, hex, names, deltas, source, inputs} = params; try { const destAddrString = output.address.getAddressString(); - const toAddress = names.hasOwnProperty(destAddrString) ? names[destAddrString] : destAddrString; + const toAddress = names.hasOwnProperty(destAddrString) + ? names[destAddrString] + : destAddrString; - const res = await sendCurrencyTransfer(sendModal.coinObj, sendModal.subWallet.api_channels[API_SEND], hex, inputs); + const res = await sendCurrencyTransfer( + sendModal.coinObj, + sendModal.subWallet.api_channels[API_SEND], + hex, + inputs, + ); if (res.err) throw new Error(res.result); - else navigation.navigate(SEND_MODAL_FORM_STEP_RESULT, { ...res.result, output, destination: toAddress }); - } catch(e) { - Alert.alert("Error", e.message) + else + navigation.navigate(SEND_MODAL_FORM_STEP_RESULT, { + ...res.result, + output, + destination: toAddress, + }); + } catch (e) { + Alert.alert('Error', e.message); } dispatch(expireCoinData(sendModal.coinObj.id, API_GET_FIATPRICE)); dispatch(expireCoinData(sendModal.coinObj.id, API_GET_TRANSACTIONS)); dispatch(expireCoinData(sendModal.coinObj.id, API_GET_BALANCES)); - setPreventExit(false) - setLoading(false) + setPreventExit(false); + setLoading(false); }; const renderItem = (item, index, divide = true) => { return ( - item.onPress()}> + item.onPress()}> + right={props => item.right ? ( + }}> {item.right} ) : null @@ -355,18 +410,20 @@ function ConvertOrCrossChainSendConfirm({ navigation, route, setLoading, setModa ); - } + }; return ( - + ref={scrollRef}> {confirmationFields.map((item, index) => { - if ((item.accordion || item.data != null) && (item.condition == null || item.condition === true)) + if ( + (item.accordion || item.data != null) && + (item.condition == null || item.condition === true) + ) if (item.accordion) { const key = index.toString(); @@ -384,9 +441,7 @@ function ConvertOrCrossChainSendConfirm({ navigation, route, setLoading, setModa ); } else { - return ( - renderItem(item, index) - ); + return renderItem(item, index); } else return null; })} @@ -395,25 +450,22 @@ function ConvertOrCrossChainSendConfirm({ navigation, route, setLoading, setModa style={{ ...Styles.fullWidthBlock, paddingHorizontal: 16, - flexDirection: "row", - justifyContent: "space-between" - }} - > + flexDirection: 'row', + justifyContent: 'space-between', + }}> From b542611811662948eb2185ffa5a93930a41a06d4 Mon Sep 17 00:00:00 2001 From: michaeltout Date: Fri, 4 Aug 2023 23:18:53 +0200 Subject: [PATCH 03/42] Do not show failed to estimate conversion result if fallback estimate is available --- .../ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js b/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js index 5e085d8e..09a48a06 100644 --- a/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js +++ b/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js @@ -580,7 +580,7 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor estimate } = res.result; - if (output.convertto != null && estimate == null) { + if (output.convertto != null && estimate == null && sendModal.data[SEND_MODAL_PRICE_ESTIMATE] == null) { Alert.alert("Could not estimate conversion result", 'Failed to calculate an estimated result for this conversion.') } From 6e8ffed060c715c372b6e2edefed2681f34c3b31 Mon Sep 17 00:00:00 2001 From: michaeltout Date: Mon, 7 Aug 2023 18:19:15 +0200 Subject: [PATCH 04/42] Modularize form components to be reused in ConvertOrCrossChainModal --- .../FormModules/ConvertFormModule.js | 120 +++++++ .../FormModules/CoreSendFormModule.js | 95 ++++++ .../FormModules/ExportFormModule.js | 76 +++++ .../ConvertOrCrossChainSendConfirm.js | 5 +- .../ConvertOrCrossChainSendForm.js | 321 ++++-------------- 5 files changed, 355 insertions(+), 262 deletions(-) create mode 100644 src/components/FormModules/ConvertFormModule.js create mode 100644 src/components/FormModules/CoreSendFormModule.js create mode 100644 src/components/FormModules/ExportFormModule.js diff --git a/src/components/FormModules/ConvertFormModule.js b/src/components/FormModules/ConvertFormModule.js new file mode 100644 index 00000000..f1d8ec51 --- /dev/null +++ b/src/components/FormModules/ConvertFormModule.js @@ -0,0 +1,120 @@ +import React from 'react'; +import { View, TouchableOpacity } from 'react-native'; +import { IconButton, Divider, TextInput, Text } from 'react-native-paper'; // Assuming you're using react-native-paper +import Colors from '../../globals/colors'; +import Styles from '../../styles'; + +const ConvertFormModule = ({ + isConversion, + isPreconvert, + advancedForm, + convertToField, + viaField, + handleFieldFocusConvertTo, + handleFieldFocusVia, + onConvertToChange, + onViaChange, + isVia, + showConversionField, + showViaField +}) => { + return ( + + + + + { + showConversionField ? ( + + { + (isPreconvert || advancedForm) ? ( + onConvertToChange(text)} + autoCapitalize={'none'} + autoCorrect={false} + style={{ flex: 1 }} + /> + ) : ( + handleFieldFocusConvertTo()} + style={{ + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + paddingHorizontal: 16, + paddingVertical: 10, + borderWidth: 1, + borderColor: Colors.verusDarkGray, + borderRadius: 4, + }}> + + {isConversion + ? `Convert to: ${convertToField}` + : 'Select currency to convert to'} + + + + ) + } + + ) : null + } + { + showViaField ? ( + + { + (isPreconvert || advancedForm) ? ( + onViaChange(text)} + autoCapitalize={'none'} + autoCorrect={false} + style={{ flex: 1 }} + /> + ) : ( + handleFieldFocusVia()} + style={{ + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + paddingHorizontal: 16, + paddingVertical: 10, + borderWidth: 1, + borderColor: Colors.verusDarkGray, + borderRadius: 4, + }}> + + {isVia ? `Convert via: ${viaField}` : 'Select currency to convert via'} + + + + ) + } + + ) : null + } + + ); +}; + +export default ConvertFormModule; \ No newline at end of file diff --git a/src/components/FormModules/CoreSendFormModule.js b/src/components/FormModules/CoreSendFormModule.js new file mode 100644 index 00000000..f8cbefe1 --- /dev/null +++ b/src/components/FormModules/CoreSendFormModule.js @@ -0,0 +1,95 @@ +import React from 'react'; +import { View } from 'react-native'; +import { Button, TextInput , Text} from 'react-native-paper'; +import Colors from '../../globals/colors'; +import Styles from '../../styles'; + +const CoreSendFormModule = ({ + sendingFromLabel = "Sending from", + sendingFromValue, + recipientAddressLabel = "Recipient address", + recipientAddressValue, + onRecipientAddressChange, + onSelfPress, + amountLabel = "Amount", + amountValue, + onAmountChange, + onMaxPress, + maxButtonDisabled = false, + estimatedResultSubtitle = null, + networkName +}) => { + return ( + + + + { + networkName != null ? ( + + {`on ${networkName} network`} + + ) : null + } + + + + + + + + + + + + + { + estimatedResultSubtitle != null ? ( + + {estimatedResultSubtitle} + + ) : null + } + + + ); +}; + +export default CoreSendFormModule; \ No newline at end of file diff --git a/src/components/FormModules/ExportFormModule.js b/src/components/FormModules/ExportFormModule.js new file mode 100644 index 00000000..3fcfec28 --- /dev/null +++ b/src/components/FormModules/ExportFormModule.js @@ -0,0 +1,76 @@ +import React from 'react'; +import { View, TouchableOpacity } from 'react-native'; +import { IconButton, Divider, Text, TextInput } from 'react-native-paper'; +import Colors from '../../globals/colors'; +import Styles from '../../styles'; + +const ExportFormModule = ({ + isExport, + isConversion, + isPreconvert, + exportToField, + handleNetworkFieldFocus, + onSystemChange, + localNetworkDefinition, + advancedForm +}) => { + return ( + + + + + + { + (isPreconvert || advancedForm) ? ( + onSystemChange(text)} + autoCapitalize={'none'} + autoCorrect={false} + style={{ flex: 1 }} + /> + ) : ( + handleNetworkFieldFocus()} + style={{ + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + paddingHorizontal: 16, + paddingVertical: 10, + borderWidth: 1, + borderColor: Colors.verusDarkGray, + borderRadius: 4, + }}> + + {isExport + ? `To network: ${exportToField}` + : isConversion + ? `On ${ + localNetworkDefinition + ? localNetworkDefinition.fullyqualifiedname + : 'current' + } network` + : 'Select network to send to'} + + + + ) + } + + + ); +}; + +export default ExportFormModule; \ No newline at end of file diff --git a/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendConfirm/ConvertOrCrossChainSendConfirm.js b/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendConfirm/ConvertOrCrossChainSendConfirm.js index 4a576cb7..9a259cc2 100644 --- a/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendConfirm/ConvertOrCrossChainSendConfirm.js +++ b/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendConfirm/ConvertOrCrossChainSendConfirm.js @@ -3,9 +3,7 @@ import {ScrollView, View, TouchableOpacity, Alert} from 'react-native'; import {Button, List, Divider, Text} from 'react-native-paper'; import {useDispatch, useSelector} from 'react-redux'; import {expireCoinData} from '../../../../actions/actionCreators'; -import {traditionalCryptoSend} from '../../../../actions/actionDispatchers'; import {copyToClipboard} from '../../../../utils/clipboard/clipboard'; -import {USD} from '../../../../utils/constants/currencies'; import { API_GET_BALANCES, API_GET_FIATPRICE, @@ -19,8 +17,7 @@ import { } from '../../../../utils/constants/sendModal'; import { coinsToSats, - satsToCoins, - truncateDecimal, + satsToCoins } from '../../../../utils/math'; import Colors from '../../../../globals/colors'; import Styles from '../../../../styles'; diff --git a/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js b/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js index 09a48a06..744b94f5 100644 --- a/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js +++ b/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js @@ -34,6 +34,9 @@ import { getAddressBalances, preflightCurrencyTransfer } from "../../../../utils import { DEST_ETH, DEST_ID, DEST_PKH, TransferDestination, fromBase58Check } from "verus-typescript-primitives"; import { CoinDirectory } from "../../../../utils/CoinData/CoinDirectory"; import { ethers } from "ethers"; +import CoreSendFormModule from "../../../FormModules/CoreSendFormModule"; +import ConvertFormModule from "../../../FormModules/ConvertFormModule"; +import ExportFormModule from "../../../FormModules/ExportFormModule"; const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFormData, navigation }) => { const sendModal = useSelector(state => state.sendModal); @@ -71,16 +74,16 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor const [conversionPaths, setConversionPaths] = useState(null); const [isConversion, setIsConversion] = useState( - sendModal.data[SEND_MODAL_CONVERTTO_FIELD] && - sendModal.data[SEND_MODAL_CONVERTTO_FIELD].length, + !!(sendModal.data[SEND_MODAL_CONVERTTO_FIELD] != null && + sendModal.data[SEND_MODAL_CONVERTTO_FIELD].length > 0) ); const [isVia, setIsVia] = useState( - sendModal.data[SEND_MODAL_VIA_FIELD] && - sendModal.data[SEND_MODAL_VIA_FIELD].length, + !!(sendModal.data[SEND_MODAL_VIA_FIELD] != null && + sendModal.data[SEND_MODAL_VIA_FIELD].length > 0) ); const [isExport, setIsExport] = useState( - sendModal.data[SEND_MODAL_EXPORTTO_FIELD] && - sendModal.data[SEND_MODAL_EXPORTTO_FIELD].length, + !!(sendModal.data[SEND_MODAL_EXPORTTO_FIELD] != null && + sendModal.data[SEND_MODAL_EXPORTTO_FIELD].length > 0) ); const [showConversionField, setShowConversionField] = useState( @@ -414,18 +417,18 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor }, [sendModal.subWallet.api_channels[API_SEND]]) useEffect(() => { - setIsConversion(sendModal.data[SEND_MODAL_CONVERTTO_FIELD] && - sendModal.data[SEND_MODAL_CONVERTTO_FIELD].length) + setIsConversion(!!(sendModal.data[SEND_MODAL_CONVERTTO_FIELD] != null && + sendModal.data[SEND_MODAL_CONVERTTO_FIELD].length > 0)) }, [sendModal.data[SEND_MODAL_CONVERTTO_FIELD]]) useEffect(() => { - setIsVia(sendModal.data[SEND_MODAL_VIA_FIELD] && - sendModal.data[SEND_MODAL_VIA_FIELD].length) + setIsVia(!!(sendModal.data[SEND_MODAL_VIA_FIELD] != null && + sendModal.data[SEND_MODAL_VIA_FIELD].length > 0)) }, [sendModal.data[SEND_MODAL_VIA_FIELD]]) useEffect(() => { - setIsExport(sendModal.data[SEND_MODAL_EXPORTTO_FIELD] && - sendModal.data[SEND_MODAL_EXPORTTO_FIELD].length) + setIsExport(!!(sendModal.data[SEND_MODAL_EXPORTTO_FIELD] != null && + sendModal.data[SEND_MODAL_EXPORTTO_FIELD].length > 0)) }, [sendModal.data[SEND_MODAL_EXPORTTO_FIELD]]) const fillAmount = (amount) => { @@ -741,258 +744,60 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor enableOnAndroid={true} extraScrollHeight={150} keyboardShouldPersistTaps="handled"> - - + updateSendFormData(SEND_MODAL_TO_ADDRESS_FIELD, text) + } + onSelfPress={() => setAddressSelf()} + amountValue={sendModal.data[SEND_MODAL_AMOUNT_FIELD]} + onAmountChange={text => updateSendFormData(SEND_MODAL_AMOUNT_FIELD, text)} + onMaxPress={() => maxAmount()} + maxButtonDisabled={localBalances == null} + networkName={networkName} + estimatedResultSubtitle={ + isConversion && + sendModal.data[SEND_MODAL_PRICE_ESTIMATE] != null && + processedAmount != null + ? `≈ ${Number( + (processedAmount * sendModal.data[SEND_MODAL_PRICE_ESTIMATE]).toFixed( + 8, + ), + )} ${sendModal.data[SEND_MODAL_CONVERTTO_FIELD]}` + : null + } /> - { - networkName != null ? ( - - {`on ${networkName} network`} - - ) : null - } - - - - - updateSendFormData(SEND_MODAL_TO_ADDRESS_FIELD, text) - } - autoCapitalize={'none'} - autoCorrect={false} - style={{ - flex: 1, - }} - /> - - - - - - - updateSendFormData(SEND_MODAL_AMOUNT_FIELD, text) - } - style={{ - flex: 1, - }} - /> - - - { - isConversion && sendModal.data[SEND_MODAL_PRICE_ESTIMATE] != null && processedAmount != null ? ( - - {`≈ ${Number((processedAmount*sendModal.data[SEND_MODAL_PRICE_ESTIMATE]).toFixed(8))} ${sendModal.data[SEND_MODAL_CONVERTTO_FIELD]}`} - - ) : null - } - { - showConversionField || showViaField ? ( - - - - - { - showConversionField && ( - - { - (sendModal.data[SEND_MODAL_IS_PRECONVERT] || sendModal.data[SEND_MODAL_ADVANCED_FORM]) ? ( - - updateSendFormData(SEND_MODAL_CONVERTTO_FIELD, text) - } - autoCapitalize={'none'} - autoCorrect={false} - style={{ - flex: 1, - }} - /> - ) : ( - handleFieldFocus(SEND_MODAL_CONVERTTO_FIELD)} - style={{ - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'space-between', - paddingHorizontal: 16, - paddingVertical: 10, - borderWidth: 1, - borderColor: Colors.verusDarkGray, - borderRadius: 4, - }}> - - {isConversion - ? `Convert to: ${sendModal.data[SEND_MODAL_CONVERTTO_FIELD]}` - : 'Select currency to convert to'} - - - - ) - } - - ) - } - { - showViaField && ( - - { - (sendModal.data[SEND_MODAL_IS_PRECONVERT] || sendModal.data[SEND_MODAL_ADVANCED_FORM]) ? ( - updateSendFormData(SEND_MODAL_VIA_FIELD, text)} - autoCapitalize={'none'} - autoCorrect={false} - style={{ - flex: 1, - }} - /> - ) : ( - handleFieldFocus(SEND_MODAL_VIA_FIELD)} - style={{ - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'space-between', - paddingHorizontal: 16, - paddingVertical: 10, - borderWidth: 1, - borderColor: Colors.verusDarkGray, - borderRadius: 4, - }}> - - {isVia - ? `Convert via: ${sendModal.data[SEND_MODAL_VIA_FIELD]}` - : isConversion - ? 'Direct conversion' - : 'Select currency to convert via'} - - - - ) - } - - ) - } - + (showConversionField || showViaField) ? ( + handleFieldFocus(SEND_MODAL_VIA_FIELD)} + handleFieldFocusConvertTo={() => handleFieldFocus(SEND_MODAL_CONVERTTO_FIELD)} + onViaChange={(text) => updateSendFormData(SEND_MODAL_VIA_FIELD, text)} + onConvertToChange={(text) => updateSendFormData(SEND_MODAL_CONVERTTO_FIELD, text)} + isVia={isVia} + showConversionField={showConversionField} + showViaField={showViaField} + /> ) : null } { showExportField && ( - - - - - - { - (sendModal.data[SEND_MODAL_IS_PRECONVERT] || sendModal.data[SEND_MODAL_ADVANCED_FORM]) ? ( - updateSendFormData(SEND_MODAL_EXPORTTO_FIELD, text)} - autoCapitalize={'none'} - autoCorrect={false} - style={{ - flex: 1, - }} - /> - ) : ( - handleFieldFocus(SEND_MODAL_EXPORTTO_FIELD)} - style={{ - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'space-between', - paddingHorizontal: 16, - paddingVertical: 10, - borderWidth: 1, - borderColor: Colors.verusDarkGray, - borderRadius: 4, - }}> - - {isExport - ? `To network: ${sendModal.data[SEND_MODAL_EXPORTTO_FIELD]}` - : isConversion - ? `On ${ - localNetworkDefinition - ? localNetworkDefinition.fullyqualifiedname - : 'current' - } network` - : 'Select network to send to'} - - - - ) - } - - + handleFieldFocus(SEND_MODAL_EXPORTTO_FIELD)} + onSystemChange={(text) => updateSendFormData(SEND_MODAL_EXPORTTO_FIELD, text)} + localNetworkDefinition={localNetworkDefinition} + advancedForm={sendModal.data[SEND_MODAL_ADVANCED_FORM]} + isPreconvert={sendModal.data[SEND_MODAL_IS_PRECONVERT]} + /> ) } { From a067651c9c11ef59e430a039abf3e919e9f280b0 Mon Sep 17 00:00:00 2001 From: michaeltout Date: Mon, 21 Aug 2023 14:23:58 +0200 Subject: [PATCH 05/42] Checkpoint for ETH bridge support: write conversion path algorithm for ETH -> VRSC transactions and begin implementing modal support --- .../sendModal/dispatchers/sendModal.js | 4 +- .../FormModules/ExportFormModule.js | 51 +- .../ConvertOrCrossChainSendForm.js | 351 +++-- src/containers/Coin/SendCoin/SendCoin.js | 15 +- src/utils/CoinData/CoinsList.js | 84 +- .../vrpc/requests/getCurrenciesMappedToEth.js | 93 ++ .../requests/getCurrencyConversionPaths.js | 235 +++- .../channels/vrpc/requests/listCurrencies.js | 5 + src/utils/api/routers/getAddressBalance.js | 54 + src/utils/api/routers/getConversionPaths.js | 20 +- .../constants/abis/verusBridgeDelegatorAbi.js | 1235 +++++++++++++++++ src/utils/constants/sendModal.js | 2 + src/utils/constants/web3Constants.js | 7 +- src/utils/web3/web3Interface.js | 25 + 14 files changed, 2031 insertions(+), 150 deletions(-) create mode 100644 src/utils/api/channels/vrpc/requests/getCurrenciesMappedToEth.js create mode 100644 src/utils/api/channels/vrpc/requests/listCurrencies.js create mode 100644 src/utils/api/routers/getAddressBalance.js create mode 100644 src/utils/constants/abis/verusBridgeDelegatorAbi.js diff --git a/src/actions/actions/sendModal/dispatchers/sendModal.js b/src/actions/actions/sendModal/dispatchers/sendModal.js index 86741e59..4b209158 100644 --- a/src/actions/actions/sendModal/dispatchers/sendModal.js +++ b/src/actions/actions/sendModal/dispatchers/sendModal.js @@ -32,7 +32,8 @@ import { SEND_MODAL_PRICE_ESTIMATE, SEND_MODAL_ADVANCED_FORM, SEND_MODAL_CONTRACT_ADDRESS_FIELD, - ADD_ERC20_TOKEN_MODAL + ADD_ERC20_TOKEN_MODAL, + SEND_MODAL_SHOW_MAPPING_FIELD } from '../../../../utils/constants/sendModal'; import { CLOSE_SEND_COIN_MODAL, @@ -99,6 +100,7 @@ export const openConvertOrCrossChainSendModal = (coinObj, subWallet, data) => { [SEND_MODAL_SHOW_CONVERTTO_FIELD]: true, [SEND_MODAL_SHOW_EXPORTTO_FIELD]: true, [SEND_MODAL_SHOW_VIA_FIELD]: true, + [SEND_MODAL_SHOW_MAPPING_FIELD]: (coinObj.proto === 'erc20' || coinObj.proto === 'eth'), [SEND_MODAL_ADVANCED_FORM]: false } : data, diff --git a/src/components/FormModules/ExportFormModule.js b/src/components/FormModules/ExportFormModule.js index 3fcfec28..6b69eaaa 100644 --- a/src/components/FormModules/ExportFormModule.js +++ b/src/components/FormModules/ExportFormModule.js @@ -10,9 +10,13 @@ const ExportFormModule = ({ isPreconvert, exportToField, handleNetworkFieldFocus, + handleMappingFieldFocus, onSystemChange, + onMappingChange, localNetworkDefinition, - advancedForm + advancedForm, + showMappingField, + mappingField }) => { return ( @@ -69,6 +73,51 @@ const ExportFormModule = ({ ) } + { + showMappingField && ( + + {isPreconvert || advancedForm ? ( + onMappingChange(text)} + autoCapitalize={'none'} + autoCorrect={false} + style={{flex: 1}} + /> + ) : ( + handleMappingFieldFocus()} + style={{ + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + paddingHorizontal: 16, + paddingVertical: 10, + borderWidth: 1, + borderColor: Colors.verusDarkGray, + borderRadius: 4, + }}> + 0 + ? Colors.quaternaryColor + : Colors.verusDarkGray, + }}> + {mappingField != null && mappingField.length > 0 + ? `Receive as: ${mappingField}` + : 'Select currency to receive as'} + + + + )} + + ) + } ); }; diff --git a/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js b/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js index 744b94f5..9ff57513 100644 --- a/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js +++ b/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js @@ -5,7 +5,7 @@ import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view import { TextInput, Button, Divider, Checkbox, List, Text, IconButton } from "react-native-paper"; import { useSelector } from 'react-redux'; import { createAlert } from "../../../../actions/actions/alert/dispatchers/alert"; -import { API_SEND, DLIGHT_PRIVATE } from "../../../../utils/constants/intervalConstants"; +import { API_SEND, DLIGHT_PRIVATE, ETH } from "../../../../utils/constants/intervalConstants"; import { SEND_MODAL_ADVANCED_FORM, SEND_MODAL_AMOUNT_FIELD, @@ -13,9 +13,11 @@ import { SEND_MODAL_EXPORTTO_FIELD, SEND_MODAL_FORM_STEP_CONFIRM, SEND_MODAL_IS_PRECONVERT, + SEND_MODAL_MAPPING_FIELD, SEND_MODAL_PRICE_ESTIMATE, SEND_MODAL_SHOW_CONVERTTO_FIELD, SEND_MODAL_SHOW_EXPORTTO_FIELD, + SEND_MODAL_SHOW_MAPPING_FIELD, SEND_MODAL_SHOW_VIA_FIELD, SEND_MODAL_TO_ADDRESS_FIELD, SEND_MODAL_VIA_FIELD, @@ -30,16 +32,20 @@ import { getCoinLogo } from "../../../../utils/CoinData/CoinData"; import { getCurrency, getIdentity } from "../../../../utils/api/channels/verusid/callCreators"; import selectAddresses from "../../../../selectors/address"; import MissingInfoRedirect from "../../../MissingInfoRedirect/MissingInfoRedirect"; -import { getAddressBalances, preflightCurrencyTransfer } from "../../../../utils/api/channels/vrpc/callCreators"; +import { preflightCurrencyTransfer } from "../../../../utils/api/channels/vrpc/callCreators"; import { DEST_ETH, DEST_ID, DEST_PKH, TransferDestination, fromBase58Check } from "verus-typescript-primitives"; import { CoinDirectory } from "../../../../utils/CoinData/CoinDirectory"; import { ethers } from "ethers"; import CoreSendFormModule from "../../../FormModules/CoreSendFormModule"; import ConvertFormModule from "../../../FormModules/ConvertFormModule"; import ExportFormModule from "../../../FormModules/ExportFormModule"; +import { getAddressBalances } from "../../../../utils/api/routers/getAddressBalance"; +import { coinsList } from "../../../../utils/CoinData/CoinsList"; +import { ETH_CONTRACT_ADDRESS } from "../../../../utils/constants/web3Constants"; const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFormData, navigation }) => { const sendModal = useSelector(state => state.sendModal); + const activeUser = useSelector(state => state.authentication.activeAccount); const addresses = useSelector(state => selectAddresses(state)); const activeAccount = useSelector(state => state.authentication.activeAccount); const networkName = useSelector(state => { @@ -64,7 +70,7 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor const [processedAmount, setProcessedAmount] = useState(null); - const [localNetworkDefinition, setLocalNetworkDefinition] = useState(null); + const [localNetworkName, setLocalNetworkName] = useState(null); const [localBalances, setLocalBalances] = useState(null); const [suggestions, setSuggestions] = useState([]); @@ -178,6 +184,185 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor else return "" } + const processConverttoSuggestionPaths = async (flatPaths, coinObj) => { + switch (coinObj.proto) { + case 'vrsc': + return flatPaths.map((path, index) => { + const priceFixed = Number(path.price.toFixed(2)) + + return { + title: path.destination.fullyqualifiedname, + logoid: path.destination.currencyid, + key: index.toString(), + description: path.via + ? `${ + path.exportto + ? path.gateway + ? `off-system to ${path.exportto.fullyqualifiedname} network ` + : `off-chain to ${path.exportto.fullyqualifiedname} network ` + : '' + }via ${path.via.fullyqualifiedname}` + : path.exportto + ? path.gateway && path.exportto + ? `off-system to ${path.exportto.fullyqualifiedname} network` + : `off-chain to ${path.exportto.fullyqualifiedname} network` + : 'direct', + values: { + [SEND_MODAL_VIA_FIELD]: path.via ? path.via.fullyqualifiedname : '', + [SEND_MODAL_CONVERTTO_FIELD]: path.destination.fullyqualifiedname, + [SEND_MODAL_EXPORTTO_FIELD]: path.exportto + ? path.exportto.fullyqualifiedname + : '', + [SEND_MODAL_PRICE_ESTIMATE]: path.price + }, + right: `${ + priceFixed === 0 + ? '<0.01' + : priceFixed === path.price + ? priceFixed + : `~${priceFixed}` + }`, + keywords: [path.destination.currencyid, path.destination.fullyqualifiedname] + }; + }); + case 'eth': + case 'erc20': + return flatPaths.filter(x => { + return !x.mapping + }).map((path, index) => { + const priceFixed = Number(path.price.toFixed(2)) + const addr = path.ethdest ? path.destination.address : null; + + const name = path.ethdest + ? path.destination.address === ETH_CONTRACT_ADDRESS ? path.destination.name : `${path.destination.name} (${ + addr.substring(0, 8) + '...' + addr.substring(addr.length - 8) + })` + : path.destination.fullyqualifiedname; + + return { + title: name, + logoid: path.ethdest ? path.destination.address : path.destination.currencyid, + logoproto: path.ethdest ? "erc20" : null, + key: index.toString(), + description: path.via + ? `${ + path.exportto + ? path.gateway + ? `off-system to ${path.exportto.fullyqualifiedname} network ` + : `off-chain to ${path.exportto.fullyqualifiedname} network ` + : '' + }via ${path.via.fullyqualifiedname}` + : path.exportto + ? path.gateway && path.exportto + ? `off-system to ${path.exportto.fullyqualifiedname} network` + : `off-chain to ${path.exportto.fullyqualifiedname} network` + : 'direct', + values: { + [SEND_MODAL_VIA_FIELD]: path.via ? path.via.fullyqualifiedname : '', + [SEND_MODAL_CONVERTTO_FIELD]: path.ethdest ? path.destination.address : path.destination.fullyqualifiedname, + [SEND_MODAL_EXPORTTO_FIELD]: path.exportto + ? path.exportto.fullyqualifiedname + : '', + [SEND_MODAL_PRICE_ESTIMATE]: path.price + }, + right: `${ + priceFixed === 0 + ? '<0.01' + : priceFixed === path.price + ? priceFixed + : `~${priceFixed}` + }`, + keywords: path.ethdest + ? [path.destination.address, path.destination.symbol, path.destination.name] + : [path.destination.currencyid, path.destination.fullyqualifiedname] + }; + }); + default: + return [] + } + } + + const processViaSuggestionPaths = async (flatPaths, coinObj) => { + switch (coinObj.proto) { + case 'vrsc': + return flatPaths.filter(x => { + if (isConversion) { + const destinationCurrency = sendModal.data[SEND_MODAL_CONVERTTO_FIELD]; + return ( + x.via != null && + (x.destination.currencyid === destinationCurrency || + destinationCurrency.toLowerCase() === + x.destination.fullyqualifiedname.toLowerCase()) + ); + } else return x.via != null; + }).map((path, index) => { + const priceFixed = Number(path.price.toFixed(2)) + + return { + title: path.via.fullyqualifiedname, + logoid: path.via.currencyid, + key: index.toString(), + description: `${ + path.exportto + ? path.gateway + ? `off-system to ${path.exportto.fullyqualifiedname} network ` + : `off-chain to ${path.exportto.fullyqualifiedname} network ` + : '' + }to ${path.destination.fullyqualifiedname}`, + values: { + [SEND_MODAL_VIA_FIELD]: path.via.fullyqualifiedname, + [SEND_MODAL_CONVERTTO_FIELD]: path.destination.fullyqualifiedname, + [SEND_MODAL_EXPORTTO_FIELD]: path.exportto + ? path.exportto.fullyqualifiedname + : '', + [SEND_MODAL_PRICE_ESTIMATE]: path.price + }, + right: `${ + priceFixed === 0 + ? '<0.01' + : priceFixed === path.price + ? priceFixed + : `~${priceFixed}` + }`, + keywords: [path.via.currencyid, path.via.fullyqualifiedname] + }; + }) + default: + return [] + } + } + + const processExporttoSuggestionPaths = async (flatPaths, coinObj) => { + switch (coinObj.proto) { + case 'vrsc': + const seenSystems = {} + return flatPaths.filter(x => { + if (x.exportto == null) return false; + + const seen = seenSystems[x.exportto.currencyid] != null; + + if (!seen) { + seenSystems[x.exportto.currencyid] = true; + return true + } else return false; + }).map((path, index) => { + return { + title: path.exportto.fullyqualifiedname, + logoid: path.exportto.currencyid, + key: index.toString(), + description: path.exportto.currencyid, + values: { + [SEND_MODAL_EXPORTTO_FIELD]: path.exportto.fullyqualifiedname, + }, + right: "", + keywords: [path.exportto.currencyid, path.exportto.fullyqualifiedname] + }; + }); + default: + return [] + } + } + const fetchSuggestionsBase = async (field) => { if (loadingSuggestions) return; let newSuggestionsBase = [] @@ -188,11 +373,13 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor // {[destinationid: string]: Array<{ // via?: CurrencyDefinition; // destination: CurrencyDefinition; - // exportto?: CurrencyDefinition; + // exportto?: string; // price: number; // viapriceinroot?: number; // destpriceinvia?: number; // gateway: boolean; + // mapping: boolean; + // bounceback: boolean; // }>} const paths = conversionPaths @@ -215,118 +402,15 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor switch (field) { case SEND_MODAL_CONVERTTO_FIELD: - newSuggestionsBase = flatPaths.map((path, index) => { - const priceFixed = Number(path.price.toFixed(2)) - - return { - title: path.destination.fullyqualifiedname, - logoid: path.destination.currencyid, - key: index.toString(), - description: path.via - ? `${ - path.exportto - ? path.gateway - ? `off-system to ${path.exportto.fullyqualifiedname} network ` - : `off-chain to ${path.exportto.fullyqualifiedname} network ` - : '' - }via ${path.via.fullyqualifiedname}` - : path.exportto - ? path.gateway && path.exportto - ? `off-system to ${path.exportto.fullyqualifiedname} network` - : `off-chain to ${path.exportto.fullyqualifiedname} network` - : 'direct', - values: { - [SEND_MODAL_VIA_FIELD]: path.via ? path.via.fullyqualifiedname : '', - [SEND_MODAL_CONVERTTO_FIELD]: path.destination.fullyqualifiedname, - [SEND_MODAL_EXPORTTO_FIELD]: path.exportto - ? path.exportto.fullyqualifiedname - : '', - [SEND_MODAL_PRICE_ESTIMATE]: path.price - }, - right: `${ - priceFixed === 0 - ? '<0.01' - : priceFixed === path.price - ? priceFixed - : `~${priceFixed}` - }`, - keywords: [path.destination.currencyid, path.destination.fullyqualifiedname] - }; - }); - + newSuggestionsBase = await processConverttoSuggestionPaths(flatPaths, sendModal.coinObj); setSuggestionBase(newSuggestionsBase); break; case SEND_MODAL_VIA_FIELD: - newSuggestionsBase = flatPaths.filter(x => { - if (isConversion) { - const destinationCurrency = sendModal.data[SEND_MODAL_CONVERTTO_FIELD]; - return ( - x.via != null && - (x.destination.currencyid === destinationCurrency || - destinationCurrency.toLowerCase() === - x.destination.fullyqualifiedname.toLowerCase()) - ); - } else return x.via != null; - }).map((path, index) => { - const priceFixed = Number(path.price.toFixed(2)) - - return { - title: path.via.fullyqualifiedname, - logoid: path.via.currencyid, - key: index.toString(), - description: `${ - path.exportto - ? path.gateway - ? `off-system to ${path.exportto.fullyqualifiedname} network ` - : `off-chain to ${path.exportto.fullyqualifiedname} network ` - : '' - }to ${path.destination.fullyqualifiedname}`, - values: { - [SEND_MODAL_VIA_FIELD]: path.via.fullyqualifiedname, - [SEND_MODAL_CONVERTTO_FIELD]: path.destination.fullyqualifiedname, - [SEND_MODAL_EXPORTTO_FIELD]: path.exportto - ? path.exportto.fullyqualifiedname - : '', - [SEND_MODAL_PRICE_ESTIMATE]: path.price - }, - right: `${ - priceFixed === 0 - ? '<0.01' - : priceFixed === path.price - ? priceFixed - : `~${priceFixed}` - }`, - keywords: [path.via.currencyid, path.via.fullyqualifiedname] - }; - }) - + newSuggestionsBase = await processViaSuggestionPaths(flatPaths, sendModal.coinObj); setSuggestionBase(newSuggestionsBase); break; case SEND_MODAL_EXPORTTO_FIELD: - const seenSystems = {} - newSuggestionsBase = flatPaths.filter(x => { - if (x.exportto == null) return false; - - const seen = seenSystems[x.exportto.currencyid] != null; - - if (!seen) { - seenSystems[x.exportto.currencyid] = true; - return true - } else return false; - }).map((path, index) => { - return { - title: path.exportto.fullyqualifiedname, - logoid: path.exportto.currencyid, - key: index.toString(), - description: path.exportto.currencyid, - values: { - [SEND_MODAL_EXPORTTO_FIELD]: path.exportto.fullyqualifiedname, - }, - right: "", - keywords: [path.exportto.currencyid, path.exportto.fullyqualifiedname] - }; - }); - + newSuggestionsBase = await processExporttoSuggestionPaths(flatPaths, sendModal.coinObj) setSuggestionBase(newSuggestionsBase); break; default: @@ -377,22 +461,37 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor leaveSearchMode(); }; - const fetchLocalNetworkDefinition = async () => { - const [channelName, address, systemId] = sendModal.subWallet.api_channels[API_SEND].split('.'); + const fetchLocalNetworkInfo = async () => { + let address; - const response = await getCurrency(sendModal.coinObj.system_id, systemId); + if (sendModal.coinObj.proto === 'erc20' || sendModal.coinObj.proto === 'eth') { + address = activeUser.keys[sendModal.coinObj.id][ETH].addresses[0]; - if (response.error) createAlert("Error", "Error fetching current network."); - else { - setLocalNetworkDefinition(response.result); - - const balanceRes = await getAddressBalances(response.result.currencyid, [address]); + setLocalNetworkName(coinsList.ETH.display_ticker); + } else { + const [channelName, channelAddress, systemId] = sendModal.subWallet.api_channels[API_SEND].split('.'); - if (balanceRes.error) createAlert("Error", "Error fetching balances."); + address = channelAddress; - setLocalBalances( - balanceRes.result.currencybalance ? balanceRes.result.currencybalance : {}, + const response = await getCurrency(sendModal.coinObj.system_id, systemId); + + if (response.error) createAlert("Error", "Error fetching current network."); + else { + setLocalNetworkName(response.result.fullyqualifiedname); + } + } + + try { + const balances = await getAddressBalances( + sendModal.coinObj, + sendModal.subWallet.api_channels[API_SEND], + {address}, ); + + setLocalBalances(balances); + } catch(e) { + console.error(e) + Alert.alert("Error fetching balances", e.message); } } @@ -413,7 +512,7 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor }, [sendModal.data[SEND_MODAL_AMOUNT_FIELD]]) useEffect(() => { - fetchLocalNetworkDefinition() + fetchLocalNetworkInfo() }, [sendModal.subWallet.api_channels[API_SEND]]) useEffect(() => { @@ -706,7 +805,7 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor {item.right} )} left={props => { - const Logo = getCoinLogo(item.logoid); + const Logo = getCoinLogo(item.logoid, item.logoproto); return ( @@ -794,9 +893,13 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor exportToField={sendModal.data[SEND_MODAL_EXPORTTO_FIELD]} handleNetworkFieldFocus={() => handleFieldFocus(SEND_MODAL_EXPORTTO_FIELD)} onSystemChange={(text) => updateSendFormData(SEND_MODAL_EXPORTTO_FIELD, text)} - localNetworkDefinition={localNetworkDefinition} + localNetworkName={localNetworkName} advancedForm={sendModal.data[SEND_MODAL_ADVANCED_FORM]} isPreconvert={sendModal.data[SEND_MODAL_IS_PRECONVERT]} + showMappingField={sendModal.data[SEND_MODAL_SHOW_MAPPING_FIELD]} + mappingField={sendModal.data[SEND_MODAL_MAPPING_FIELD]} + handleMappingFieldFocus={() => handleFieldFocus(SEND_MODAL_MAPPING_FIELD)} + onMappingChange={(text) => updateSendFormData(SEND_MODAL_MAPPING_FIELD, text)} /> ) } diff --git a/src/containers/Coin/SendCoin/SendCoin.js b/src/containers/Coin/SendCoin/SendCoin.js index 7f3689f5..2e5f578c 100644 --- a/src/containers/Coin/SendCoin/SendCoin.js +++ b/src/containers/Coin/SendCoin/SendCoin.js @@ -14,10 +14,12 @@ import { SEND_MODAL_CONVERTTO_FIELD, SEND_MODAL_EXPORTTO_FIELD, SEND_MODAL_IS_PRECONVERT, + SEND_MODAL_MAPPING_FIELD, SEND_MODAL_MEMO_FIELD, SEND_MODAL_PRICE_ESTIMATE, SEND_MODAL_SHOW_CONVERTTO_FIELD, SEND_MODAL_SHOW_EXPORTTO_FIELD, + SEND_MODAL_SHOW_MAPPING_FIELD, SEND_MODAL_SHOW_VIA_FIELD, SEND_MODAL_TO_ADDRESS_FIELD, SEND_MODAL_VIA_FIELD, @@ -49,10 +51,12 @@ const SendCoin = ({ navigation }) => { [SEND_MODAL_CONVERTTO_FIELD]: '', [SEND_MODAL_EXPORTTO_FIELD]: '', [SEND_MODAL_VIA_FIELD]: '', + [SEND_MODAL_MAPPING_FIELD]: '', [SEND_MODAL_PRICE_ESTIMATE]: null, [SEND_MODAL_IS_PRECONVERT]: false, [SEND_MODAL_SHOW_CONVERTTO_FIELD]: true, [SEND_MODAL_SHOW_EXPORTTO_FIELD]: true, + [SEND_MODAL_SHOW_MAPPING_FIELD]: false, [SEND_MODAL_SHOW_VIA_FIELD]: true, [SEND_MODAL_ADVANCED_FORM]: false }, @@ -68,10 +72,12 @@ const SendCoin = ({ navigation }) => { [SEND_MODAL_CONVERTTO_FIELD]: '', [SEND_MODAL_EXPORTTO_FIELD]: '', [SEND_MODAL_VIA_FIELD]: '', + [SEND_MODAL_MAPPING_FIELD]: '', [SEND_MODAL_PRICE_ESTIMATE]: null, [SEND_MODAL_IS_PRECONVERT]: false, [SEND_MODAL_SHOW_CONVERTTO_FIELD]: false, [SEND_MODAL_SHOW_EXPORTTO_FIELD]: true, + [SEND_MODAL_SHOW_MAPPING_FIELD]: (activeCoin.proto === 'erc20' || activeCoin.proto === 'eth'), [SEND_MODAL_SHOW_VIA_FIELD]: false, [SEND_MODAL_ADVANCED_FORM]: false }, @@ -87,10 +93,12 @@ const SendCoin = ({ navigation }) => { [SEND_MODAL_CONVERTTO_FIELD]: '', [SEND_MODAL_EXPORTTO_FIELD]: '', [SEND_MODAL_VIA_FIELD]: '', + [SEND_MODAL_MAPPING_FIELD]: '', [SEND_MODAL_PRICE_ESTIMATE]: null, [SEND_MODAL_IS_PRECONVERT]: true, [SEND_MODAL_SHOW_CONVERTTO_FIELD]: true, [SEND_MODAL_SHOW_EXPORTTO_FIELD]: true, + [SEND_MODAL_SHOW_MAPPING_FIELD]: false, [SEND_MODAL_SHOW_VIA_FIELD]: true, [SEND_MODAL_ADVANCED_FORM]: false }, @@ -106,10 +114,12 @@ const SendCoin = ({ navigation }) => { [SEND_MODAL_CONVERTTO_FIELD]: '', [SEND_MODAL_EXPORTTO_FIELD]: '', [SEND_MODAL_VIA_FIELD]: '', + [SEND_MODAL_MAPPING_FIELD]: '', [SEND_MODAL_PRICE_ESTIMATE]: null, [SEND_MODAL_IS_PRECONVERT]: false, [SEND_MODAL_SHOW_CONVERTTO_FIELD]: true, [SEND_MODAL_SHOW_EXPORTTO_FIELD]: true, + [SEND_MODAL_SHOW_MAPPING_FIELD]: (activeCoin.proto === 'erc20' || activeCoin.proto === 'eth'), [SEND_MODAL_SHOW_VIA_FIELD]: true, [SEND_MODAL_ADVANCED_FORM]: true }, @@ -122,7 +132,10 @@ const SendCoin = ({ navigation }) => { ] = useState(false); const channel = subWallet.channel.split(".")[0]; - const allowConvertOrOffchain = activeCoin.tags.includes(IS_PBAAS) && channel === VRPC; + const allowConvertOrOffchain = + (activeCoin.tags.includes(IS_PBAAS) && channel === VRPC) || + activeCoin.proto === 'erc20' || + activeCoin.proto === 'eth'; const selectConvertOrCrossChainOption = (option) => { setConvertOrCrossChainOptionsModalOpen(false) diff --git a/src/utils/CoinData/CoinsList.js b/src/utils/CoinData/CoinsList.js index 3e12975c..d867f8d5 100644 --- a/src/utils/CoinData/CoinsList.js +++ b/src/utils/CoinData/CoinsList.js @@ -101,6 +101,86 @@ export const coinsList = { default_app: 'wallet', apps: VERUS_APPS }, + iSojYsotVzXz4wh2eJriASGo6UidJDDhL2: { + testnet: true, + pbaas_options: 545, + id: "iSojYsotVzXz4wh2eJriASGo6UidJDDhL2", + currency_id: "iSojYsotVzXz4wh2eJriASGo6UidJDDhL2", + system_id: "iJhCezBExJHvtyH3fGhNnt2NhU4Ztkf2yq", + bitgojs_network_key: 'verustest', + display_ticker: 'Bridge.vETH', + display_name: 'Bridge.vETH', + alt_names: [], + theme_color: '#232323', + compatible_channels: [VERUSID, VRPC], + tags: [IS_VERUS, IS_ZCASH, IS_PBAAS], + proto: 'vrsc', + vrpc_endpoints: ['https://api.verustest.net'], + decimals: DEFAULT_DECIMALS, + seconds_per_block: 60, + default_app: 'wallet', + apps: VERUS_APPS + }, + iCtawpxUiCc2sEupt7Z4u8SDAncGZpgSKm: { + testnet: true, + pbaas_options: 136, + id: "iCtawpxUiCc2sEupt7Z4u8SDAncGZpgSKm", + currency_id: "iCtawpxUiCc2sEupt7Z4u8SDAncGZpgSKm", + system_id: "iJhCezBExJHvtyH3fGhNnt2NhU4Ztkf2yq", + bitgojs_network_key: 'verustest', + display_ticker: 'vETH', + display_name: 'vETH', + alt_names: [], + theme_color: '#232323', + compatible_channels: [VERUSID, VRPC], + tags: [IS_VERUS, IS_ZCASH, IS_PBAAS], + proto: 'vrsc', + vrpc_endpoints: ['https://api.verustest.net'], + decimals: DEFAULT_DECIMALS, + seconds_per_block: 60, + default_app: 'wallet', + apps: VERUS_APPS + }, + iCtawpxUiCc2sEupt7Z4u8SDAncGZpgSKm: { + testnet: true, + pbaas_options: 136, + id: "iCtawpxUiCc2sEupt7Z4u8SDAncGZpgSKm", + currency_id: "iCtawpxUiCc2sEupt7Z4u8SDAncGZpgSKm", + system_id: "iJhCezBExJHvtyH3fGhNnt2NhU4Ztkf2yq", + bitgojs_network_key: 'verustest', + display_ticker: 'vETH', + display_name: 'vETH', + alt_names: [], + theme_color: '#232323', + compatible_channels: [VERUSID, VRPC], + tags: [IS_VERUS, IS_ZCASH, IS_PBAAS], + proto: 'vrsc', + vrpc_endpoints: ['https://api.verustest.net'], + decimals: DEFAULT_DECIMALS, + seconds_per_block: 60, + default_app: 'wallet', + apps: VERUS_APPS + }, + iN9vbHXexEh6GTZ45fRoJGKTQThfbgUwMh: { + testnet: true, + pbaas_options: 32, + id: "iN9vbHXexEh6GTZ45fRoJGKTQThfbgUwMh", + currency_id: "iN9vbHXexEh6GTZ45fRoJGKTQThfbgUwMh", + system_id: "iCtawpxUiCc2sEupt7Z4u8SDAncGZpgSKm", + bitgojs_network_key: 'verustest', + display_ticker: 'DAI.vETH', + display_name: 'DAI.vETH', + alt_names: [], + theme_color: '#232323', + compatible_channels: [VERUSID, VRPC], + tags: [IS_VERUS, IS_ZCASH, IS_PBAAS], + proto: 'vrsc', + vrpc_endpoints: ['https://api.verustest.net'], + decimals: DEFAULT_DECIMALS, + seconds_per_block: 60, + default_app: 'wallet', + apps: VERUS_APPS + }, KMD: { id: 'KMD', display_name: 'Komodo', @@ -134,7 +214,7 @@ export const coinsList = { }, ETH: { id: 'ETH', - currency_id: '', + currency_id: '0x0000000000000000000000000000000000000000', system_id: '.eth', display_ticker: 'ETH', display_name: 'Ethereum', @@ -153,7 +233,7 @@ export const coinsList = { currency_id: '', system_id: '.eth', display_ticker: 'gETH', - display_name: 'Goerli Ethereum', + display_name: 'Testnet Ethereum', alt_names: [], theme_color: '#141C30', website: 'https://ethereum.org/en/', diff --git a/src/utils/api/channels/vrpc/requests/getCurrenciesMappedToEth.js b/src/utils/api/channels/vrpc/requests/getCurrenciesMappedToEth.js new file mode 100644 index 00000000..2dab6b7e --- /dev/null +++ b/src/utils/api/channels/vrpc/requests/getCurrenciesMappedToEth.js @@ -0,0 +1,93 @@ +import { DEST_ETH, toBase58Check } from "verus-typescript-primitives"; +import { listCurrencies } from "./listCurrencies"; +import { getWeb3ProviderForNetwork } from "../../../../web3/provider"; + +/** + * Returns a map of key: contract address and values: array of currencydefinitions + * @param {string} systemId + * @param {string} ethNetwork + * @returns {Promise<{result?: Map, error?: string}>} + */ +export const getCurrenciesMappedToEth = async (systemId, ethNetwork) => { + // TODO: Switch to using subset getter when support is added + const allImportedCurrencies = await listCurrencies(systemId, "imported"); + const allLocalCurrencies = await listCurrencies(systemId, "local"); + const allPbaasCurrencies = await listCurrencies(systemId, "pbaas"); + + if (allImportedCurrencies.error) return allImportedCurrencies; + if (allLocalCurrencies.error) return allImportedCurrencies; + if (allPbaasCurrencies.error) return allImportedCurrencies; + + const allCurrencyMap = new Map(); + const allCurrencies = [...allImportedCurrencies.result, ...allLocalCurrencies.result, ...allPbaasCurrencies.result]; + + for (const currency of allCurrencies) { + if (!allCurrencyMap.has(currency.currencydefinition.currencyid)) { + allCurrencyMap.set(currency.currencydefinition.currencyid, currency) + } + } + + /** + * @type {Map>} + */ + const mapped = new Map(); + + /** + * @type {Map>} + */ + const inversemap = new Map(); + + try { + for (const currency of allImportedCurrencies.result) { + if ( + currency.currencydefinition != null && + currency.currencydefinition.nativecurrencyid != null && + currency.currencydefinition.nativecurrencyid.type === DEST_ETH.toNumber() + ) { + + const contractAddr = currency.currencydefinition.nativecurrencyid.address + + if (mapped.has(contractAddr)) { + mapped.get(contractAddr).set(currency.currencydefinition.currencyid, currency) + } else { + mapped.set(contractAddr, new Map([[currency.currencydefinition.currencyid, currency]])) + } + } + } + + try { + const tokenList = await getWeb3ProviderForNetwork(ethNetwork) + .getVerusBridgeDelegatorContract() + .callStatic.getTokenList(0, 0); + + tokenList.forEach(tokenInfo => { + const [iAddrBytesHex, contractAddr, nftTokenId, flags] = tokenInfo; + + const iAddrBytes = Buffer.from(iAddrBytesHex.substring(2), 'hex'); + const iAddr = toBase58Check(iAddrBytes, 102); + + if (allCurrencyMap.has(iAddr)) { + const currency = allCurrencyMap.get(iAddr); + + if (mapped.has(contractAddr)) { + mapped.get(contractAddr).set(currency.currencydefinition.currencyid, currency); + } else { + mapped.set(contractAddr, new Map([[currency.currencydefinition.currencyid, currency]])); + } + + if (inversemap.has(iAddr)) { + inversemap.get(iAddr).add(contractAddr); + } else { + inversemap.set(iAddr, new Set([contractAddr])) + } + } + }) + } catch(e) { + console.error(e) + } + + return { result: {contractAddressToCurrencyDefinitionMap: mapped, currencyIdToContractAddressMap: inversemap} } + } catch(e) { + return { error: { code: -1, message: e.message } } + } +} \ No newline at end of file diff --git a/src/utils/api/channels/vrpc/requests/getCurrencyConversionPaths.js b/src/utils/api/channels/vrpc/requests/getCurrencyConversionPaths.js index 10291c3b..01839041 100644 --- a/src/utils/api/channels/vrpc/requests/getCurrencyConversionPaths.js +++ b/src/utils/api/channels/vrpc/requests/getCurrencyConversionPaths.js @@ -1,32 +1,229 @@ +import { ethers } from "ethers"; import VrpcProvider from "../../../../vrpc/vrpcInterface" +import { getCurrenciesMappedToEth } from "./getCurrenciesMappedToEth"; +import { ETH_BRIDGE_NAME, ETH_CONTRACT_ADDRESS } from "../../../../constants/web3Constants"; +import { DEST_ETH } from "verus-typescript-primitives"; +import { getWeb3ProviderForNetwork } from "../../../../web3/provider"; -export const getCurrencyConversionPaths = async (systemId, src, dest) => { - let sourceDefinition, destDefinition; +export const getCurrencyConversionPaths = async (systemId, src, ethNetwork) => { + const ethSrc = src === ETH_CONTRACT_ADDRESS || ethers.utils.isAddress(src); const endpoint = VrpcProvider.getEndpoint(systemId); - const sourceResponse = await endpoint.getCurrency(src); + if (ethSrc) { + const isEth = src === ETH_CONTRACT_ADDRESS; + const paths = {}; + const mappedCurrenciesResponse = await getCurrenciesMappedToEth(systemId, ethNetwork); - if (sourceResponse.error) throw new Error(sourceResponse.error.message) + if (mappedCurrenciesResponse.error) throw new Error(sourceResponse.error.message); - sourceDefinition = sourceResponse.result + // Determine which currencies can be converted + const bridgeCurrencyResponse = await endpoint.getCurrency(ETH_BRIDGE_NAME); + if (bridgeCurrencyResponse.error) throw new Error(bridgeCurrencyResponse.error.message); + const bridgeCurrencyDefinition = bridgeCurrencyResponse.result; + const { currencies: convertableCurrencies } = bridgeCurrencyDefinition; - if (dest != null) { - const destResponse = await endpoint.getCurrency(dest); + const {contractAddressToCurrencyDefinitionMap, currencyIdToContractAddressMap} = mappedCurrenciesResponse.result; - if (destResponse.error) throw new Error(destResponse.error.message) + if (contractAddressToCurrencyDefinitionMap.has(src)) { + const mappedToSource = contractAddressToCurrencyDefinitionMap.get(src); - destDefinition = destResponse.result - } + const systemCurrencyResponse = await endpoint.getCurrency(systemId); + + if (systemCurrencyResponse.error) throw new Error(systemCurrencyResponse.error.message); + + const systemDefinition = systemCurrencyResponse.result; - const paths = await endpoint.getCurrencyConversionPaths(sourceDefinition, destDefinition); + // Establish which currencies the ETH currency can be mapped to + for (const [key, currency] of mappedToSource) { + const { currencydefinition } = currency; + const { nativecurrencyid, currencyid } = currencydefinition; + const { bestcurrencystate } = bridgeCurrencyDefinition; + const { currencies: convertableCurrencyStates } = bestcurrencystate; + const isBridge = currencyid === bridgeCurrencyDefinition.currencyid; - for (const destinationid in paths) { - paths[destinationid] = paths[destinationid].filter(x => { - const offSystem = (x.destination.systemid != systemId) || (x.via != null && x.via.systemid != systemId) - - return !(offSystem && x.exportto == null) - }); - } + // If mapped to a currency that can be converted through the bridge or is the bridge + if ( + isEth || + (convertableCurrencies.includes(currencyid) || isBridge) + ) { + // You can always convert to the bridge if you're convertable, unless you're the bridge + if (!isBridge) { + const priceData = convertableCurrencyStates[currencyid]; + + paths[bridgeCurrencyDefinition.currencyid] = [{ + destination: bridgeCurrencyDefinition, + exportto: systemDefinition, + price: 1 / priceData.lastconversionprice, + gateway: true + }]; + } + + for (const convertableCurrencyId of convertableCurrencies) { + paths[convertableCurrencyId] = []; + + const convertableCurrencyResponse = await endpoint.getCurrency(convertableCurrencyId); + if (convertableCurrencyResponse.error) throw new Error(convertableCurrencyResponse.error.message); + const convertableCurrencyDefinition = convertableCurrencyResponse.result; + + const erc20sMappedToCurrency = currencyIdToContractAddressMap.get(convertableCurrencyDefinition.currencyid); + + if (isBridge) { + // If you are the bridge, you can convert to all of the bridge reserve currencies + const priceData = convertableCurrencyStates[convertableCurrencyId]; + + // You can convert to VRSC network + paths[convertableCurrencyId].push({ + destination: convertableCurrencyDefinition, + exportto: systemDefinition, + price: priceData.lastconversionprice, + gateway: true + }); - return paths; + // Or bounce back to ETH through any currency mapped to your destination + for (const contractAddress of erc20sMappedToCurrency) { + try { + if (contractAddress !== ETH_CONTRACT_ADDRESS) { + const contract = getWeb3ProviderForNetwork(ethNetwork).getUnitializedContractInstance(contractAddress); + const name = await contract.name(); + const symbol = await contract.symbol(); + const decimals = await contract.decimals(); + + paths[convertableCurrencyId].push({ + via: convertableCurrencyDefinition, + destination: { + address: contractAddress, + symbol, + decimals, + name, + }, + price: priceData.lastconversionprice, + gateway: true, + bounceback: true, + ethdest: true + }); + } else { + paths[convertableCurrencyId].push({ + via: convertableCurrencyDefinition, + destination: { + address: ETH_CONTRACT_ADDRESS, + symbol: "ETH", + decimals: 18, + name: "Ethereum", + }, + price: priceData.lastconversionprice, + gateway: true, + bounceback: true, + ethdest: true + }); + } + } catch(e) { + console.log("Error fetching information for contract " + contractAddress) + console.warn(e) + } + } + } else if (convertableCurrencyId !== currencyid) { + // If you're convertable and not the bridge, you can convert to either the bridge (above) + // or any of the bridge reserve currencies except for yourself via the bridge + const priceData = convertableCurrencyStates[convertableCurrencyId]; + + const viapriceinroot = 1 / priceData.lastconversionprice; + const destpriceinvia = priceData.lastconversionprice; + const price = 1 / (viapriceinroot/destpriceinvia); + + paths[convertableCurrencyId].push({ + destination: convertableCurrencyDefinition, + exportto: systemDefinition, + via: bridgeCurrencyDefinition, + price, + viapriceinroot, + destpriceinvia, + gateway: true + }); + + // Or bounce back to ETH through any currency mapped to your destination + for (const contractAddress of erc20sMappedToCurrency) { + try { + if (contractAddress !== ETH_CONTRACT_ADDRESS) { + const contract = getWeb3ProviderForNetwork(ethNetwork).getUnitializedContractInstance(contractAddress); + const name = await contract.name(); + const symbol = await contract.symbol(); + const decimals = await contract.decimals(); + + paths[convertableCurrencyId].push({ + via: bridgeCurrencyDefinition, + destination: { + address: contractAddress, + symbol, + decimals, + name, + }, + price, + gateway: true, + bounceback: true, + ethdest: true + }); + } else { + paths[convertableCurrencyId].push({ + via: bridgeCurrencyDefinition, + destination: { + address: ETH_CONTRACT_ADDRESS, + symbol: "ETH", + decimals: 18, + name: "Ethereum", + }, + price, + gateway: true, + bounceback: true, + ethdest: true + }); + } + } catch(e) { + console.log("Error fetching information for contract " + contractAddress) + console.warn(e) + } + } + } + } + } + + paths[currencyid] = [{ + destination: currencydefinition, + exportto: systemDefinition, + price: 1, + gateway: true, + mapping: true + }]; + } + } + + return paths; + } else { + let sourceDefinition, destDefinition; + + const sourceResponse = await endpoint.getCurrency(src); + + if (sourceResponse.error) throw new Error(sourceResponse.error.message); + + sourceDefinition = sourceResponse.result; + + // if (dest != null) { + // const destResponse = await endpoint.getCurrency(dest); + + // if (destResponse.error) throw new Error(destResponse.error.message) + + // destDefinition = destResponse.result + // } + + const paths = await endpoint.getCurrencyConversionPaths(sourceDefinition, destDefinition); + + for (const destinationid in paths) { + paths[destinationid] = paths[destinationid].filter(x => { + const offSystem = (x.destination.systemid != systemId) || (x.via != null && x.via.systemid != systemId); + + return !(offSystem && x.exportto == null); + }); + } + + return paths; + } } \ No newline at end of file diff --git a/src/utils/api/channels/vrpc/requests/listCurrencies.js b/src/utils/api/channels/vrpc/requests/listCurrencies.js new file mode 100644 index 00000000..8b2fe538 --- /dev/null +++ b/src/utils/api/channels/vrpc/requests/listCurrencies.js @@ -0,0 +1,5 @@ +import VrpcProvider from "../../../../vrpc/vrpcInterface" + +export const listCurrencies = (systemId, systemType = "local") => { + return VrpcProvider.getEndpoint(systemId).listCurrencies({ systemtype: systemType }); +} \ No newline at end of file diff --git a/src/utils/api/routers/getAddressBalance.js b/src/utils/api/routers/getAddressBalance.js new file mode 100644 index 00000000..7d843f50 --- /dev/null +++ b/src/utils/api/routers/getAddressBalance.js @@ -0,0 +1,54 @@ +import { + ERC20, + ETH, + VRPC +} from "../../constants/intervalConstants"; +import * as vrpc from "../channels/vrpc/callCreators"; +import * as eth from "../channels/eth/callCreator"; +import * as erc20 from "../channels/erc20/callCreator"; + +const ADDRESS_BALANCE_FUNCTION_MAP = { + [VRPC]: async (coinObj, channel, params) => { + const [channelName, iAddress, systemId] = channel.split('.'); + + const balanceRes = await vrpc.getAddressBalances(systemId, [params.address]); + + if (balanceRes.error) throw new Error(balanceRes.error.message); + else { + return balanceRes.result.currencybalance ? balanceRes.result.currencybalance : {}; + } + }, + [ETH]: async (coinObj, channel, params) => { + const balanceRes = await eth.getStandardEthBalance(params.address, coinObj.network); + + return { + [coinObj.currency_id]: balanceRes + } + }, + [ERC20]: async (coinObj, channel, params) => { + const balanceRes = await erc20.getStandardErc20Balance( + params.address, + coinObj.currency_id, + coinObj.decimals, + coinObj.network, + ); + + return { + [coinObj.currency_id]: balanceRes + } + } +}; + +/** + * Returns the address balance + * @param {Object} coinObj The coin object + * @param {String} channel The channel to search on + * @param {Object} params Any other parameters specific to the send channel's search function + */ +export const getAddressBalances = async (coinObj, channel, params) => { + const channelId = channel.split('.')[0] + + if (ADDRESS_BALANCE_FUNCTION_MAP[channelId] == null) + throw new Error(`No address balance function available for channel ${channelId}`); + else return await ADDRESS_BALANCE_FUNCTION_MAP[channelId](coinObj, channel, params); +}; \ No newline at end of file diff --git a/src/utils/api/routers/getConversionPaths.js b/src/utils/api/routers/getConversionPaths.js index eeb988b1..2a1e2311 100644 --- a/src/utils/api/routers/getConversionPaths.js +++ b/src/utils/api/routers/getConversionPaths.js @@ -1,16 +1,34 @@ import { + ERC20, + ETH, VRPC, WYRE_SERVICE, } from "../../constants/intervalConstants"; import * as wyre from "../channels/wyre/callCreators"; import * as vrpc from "../channels/vrpc/callCreators"; +import { getWeb3ProviderForNetwork } from "../../web3/provider"; +import { ETH_CONTRACT_ADDRESS } from "../../constants/web3Constants"; const CONVERSION_PATH_FUNCTION_MAP = { [WYRE_SERVICE]: wyre.getCurrencyConversionPaths, [VRPC]: (coinObj, channel, params) => { const [channelName, iAddress, systemId] = channel.split('.') - return vrpc.getCurrencyConversionPaths(systemId, params.src, params.dest) + return vrpc.getCurrencyConversionPaths(systemId, params.src) + }, + [ERC20]: (coinObj, channel, params) => { + return vrpc.getCurrencyConversionPaths( + getWeb3ProviderForNetwork(coinObj.network).getVrscSystem(), + params.src, + coinObj.network, + ); + }, + [ETH]: (coinObj, channel, params) => { + return vrpc.getCurrencyConversionPaths( + getWeb3ProviderForNetwork(coinObj.network).getVrscSystem(), + ETH_CONTRACT_ADDRESS, + coinObj.network, + ); } }; diff --git a/src/utils/constants/abis/verusBridgeDelegatorAbi.js b/src/utils/constants/abis/verusBridgeDelegatorAbi.js new file mode 100644 index 00000000..7d2e8f4d --- /dev/null +++ b/src/utils/constants/abis/verusBridgeDelegatorAbi.js @@ -0,0 +1,1235 @@ +export const VERUS_BRIDGE_DELEGATOR_GOERLI_ABI = [ + { + "inputs": [ + { + "internalType": "address[]", + "name": "_notaries", + "type": "address[]" + }, + { + "internalType": "address[]", + "name": "_notariesEthAddress", + "type": "address[]" + }, + { + "internalType": "address[]", + "name": "_notariesColdStoreEthAddress", + "type": "address[]" + }, + { + "internalType": "address[]", + "name": "_newContractAddress", + "type": "address[]" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "_readyExports", + "outputs": [ + { + "internalType": "bytes32", + "name": "exportHash", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "prevExportHash", + "type": "bytes32" + }, + { + "internalType": "uint64", + "name": "endHeight", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "bestForks", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "bridgeConverterActive", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "cceLastEndHeight", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "cceLastStartHeight", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "claimableFees", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "contracts", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "exportHeights", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "lastImportInfo", + "outputs": [ + { + "internalType": "bytes32", + "name": "hashOfTransfers", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "exporttxid", + "type": "bytes32" + }, + { + "internalType": "uint32", + "name": "exporttxoutnum", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "height", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "lastTxIdImport", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "notaries", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "notaryAddressMapping", + "outputs": [ + { + "internalType": "address", + "name": "main", + "type": "address" + }, + { + "internalType": "address", + "name": "recovery", + "type": "address" + }, + { + "internalType": "uint8", + "name": "state", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "pendingVoteState", + "outputs": [ + { + "internalType": "address", + "name": "txid", + "type": "address" + }, + { + "internalType": "uint32", + "name": "agree", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "count", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "startHeight", + "type": "uint32" + }, + { + "internalType": "uint8", + "name": "nullified", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "processedTxids", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "refunds", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "rollingUpgradeVotes", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "rollingVoteIndex", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "saltsUsed", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "storageGlobal", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "tokenList", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "verusToERC20mapping", + "outputs": [ + { + "internalType": "address", + "name": "erc20ContractAddress", + "type": "address" + }, + { + "internalType": "uint8", + "name": "flags", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "tokenIndex", + "type": "uint256" + }, + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "uint256", + "name": "tokenID", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint32", + "name": "version", + "type": "uint32" + }, + { + "components": [ + { + "internalType": "address", + "name": "currency", + "type": "address" + }, + { + "internalType": "uint64", + "name": "amount", + "type": "uint64" + } + ], + "internalType": "struct VerusObjects.CCurrencyValueMap", + "name": "currencyvalue", + "type": "tuple" + }, + { + "internalType": "uint32", + "name": "flags", + "type": "uint32" + }, + { + "internalType": "address", + "name": "feecurrencyid", + "type": "address" + }, + { + "internalType": "uint64", + "name": "fees", + "type": "uint64" + }, + { + "components": [ + { + "internalType": "uint8", + "name": "destinationtype", + "type": "uint8" + }, + { + "internalType": "bytes", + "name": "destinationaddress", + "type": "bytes" + } + ], + "internalType": "struct VerusObjectsCommon.CTransferDestination", + "name": "destination", + "type": "tuple" + }, + { + "internalType": "address", + "name": "destcurrencyid", + "type": "address" + }, + { + "internalType": "address", + "name": "destsystemid", + "type": "address" + }, + { + "internalType": "address", + "name": "secondreserveid", + "type": "address" + } + ], + "internalType": "struct VerusObjects.CReserveTransfer", + "name": "_transfer", + "type": "tuple" + } + ], + "name": "sendTransfer", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "components": [ + { + "internalType": "uint8", + "name": "version", + "type": "uint8" + }, + { + "internalType": "uint8", + "name": "typeC", + "type": "uint8" + }, + { + "components": [ + { + "internalType": "uint8", + "name": "branchType", + "type": "uint8" + }, + { + "components": [ + { + "internalType": "uint8", + "name": "CMerkleBranchBase", + "type": "uint8" + }, + { + "internalType": "uint32", + "name": "nIndex", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "nSize", + "type": "uint32" + }, + { + "internalType": "uint8", + "name": "extraHashes", + "type": "uint8" + }, + { + "internalType": "bytes32[]", + "name": "branch", + "type": "bytes32[]" + } + ], + "internalType": "struct VerusObjects.CMerkleBranch", + "name": "proofSequence", + "type": "tuple" + } + ], + "internalType": "struct VerusObjects.CTXProof[]", + "name": "txproof", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "uint8", + "name": "elType", + "type": "uint8" + }, + { + "internalType": "uint8", + "name": "elIdx", + "type": "uint8" + }, + { + "internalType": "bytes", + "name": "elVchObj", + "type": "bytes" + }, + { + "components": [ + { + "internalType": "uint8", + "name": "branchType", + "type": "uint8" + }, + { + "components": [ + { + "internalType": "uint8", + "name": "CMerkleBranchBase", + "type": "uint8" + }, + { + "internalType": "uint32", + "name": "nIndex", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "nSize", + "type": "uint32" + }, + { + "internalType": "uint8", + "name": "extraHashes", + "type": "uint8" + }, + { + "internalType": "bytes32[]", + "name": "branch", + "type": "bytes32[]" + } + ], + "internalType": "struct VerusObjects.CMerkleBranch", + "name": "proofSequence", + "type": "tuple" + } + ], + "internalType": "struct VerusObjects.CTXProof[]", + "name": "elProof", + "type": "tuple[]" + } + ], + "internalType": "struct VerusObjects.CComponents[]", + "name": "components", + "type": "tuple[]" + } + ], + "internalType": "struct VerusObjects.CPtransactionproof", + "name": "partialtransactionproof", + "type": "tuple" + }, + { + "internalType": "bytes", + "name": "serializedTransfers", + "type": "bytes" + } + ], + "internalType": "struct VerusObjects.CReserveTransferImport", + "name": "data", + "type": "tuple" + } + ], + "name": "submitImports", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_startBlock", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_endBlock", + "type": "uint256" + } + ], + "name": "getReadyExportsByRange", + "outputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "exportHash", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "prevExportHash", + "type": "bytes32" + }, + { + "internalType": "uint64", + "name": "startHeight", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "endHeight", + "type": "uint64" + }, + { + "components": [ + { + "internalType": "uint32", + "name": "version", + "type": "uint32" + }, + { + "components": [ + { + "internalType": "address", + "name": "currency", + "type": "address" + }, + { + "internalType": "uint64", + "name": "amount", + "type": "uint64" + } + ], + "internalType": "struct VerusObjects.CCurrencyValueMap", + "name": "currencyvalue", + "type": "tuple" + }, + { + "internalType": "uint32", + "name": "flags", + "type": "uint32" + }, + { + "internalType": "address", + "name": "feecurrencyid", + "type": "address" + }, + { + "internalType": "uint64", + "name": "fees", + "type": "uint64" + }, + { + "components": [ + { + "internalType": "uint8", + "name": "destinationtype", + "type": "uint8" + }, + { + "internalType": "bytes", + "name": "destinationaddress", + "type": "bytes" + } + ], + "internalType": "struct VerusObjectsCommon.CTransferDestination", + "name": "destination", + "type": "tuple" + }, + { + "internalType": "address", + "name": "destcurrencyid", + "type": "address" + }, + { + "internalType": "address", + "name": "destsystemid", + "type": "address" + }, + { + "internalType": "address", + "name": "secondreserveid", + "type": "address" + } + ], + "internalType": "struct VerusObjects.CReserveTransfer[]", + "name": "transfers", + "type": "tuple[]" + } + ], + "internalType": "struct VerusObjects.CReserveTransferSetCalled[]", + "name": "returnedExports", + "type": "tuple[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "serializedNotarization", + "type": "bytes" + }, + { + "internalType": "bytes32", + "name": "txid", + "type": "bytes32" + }, + { + "internalType": "uint32", + "name": "n", + "type": "uint32" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "setLatestData", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "launchContractTokens", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "start", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "end", + "type": "uint256" + } + ], + "name": "getTokenList", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "iaddress", + "type": "address" + }, + { + "internalType": "address", + "name": "erc20ContractAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "launchSystemID", + "type": "address" + }, + { + "internalType": "uint8", + "name": "flags", + "type": "uint8" + }, + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "ticker", + "type": "string" + }, + { + "internalType": "uint256", + "name": "tokenID", + "type": "uint256" + } + ], + "internalType": "struct VerusObjects.setupToken[]", + "name": "", + "type": "tuple[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_imports", + "type": "bytes32" + } + ], + "name": "checkImport", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "claimfees", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint176", + "name": "verusAddress", + "type": "uint176" + } + ], + "name": "claimRefund", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "publicKeyX", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "publicKeyY", + "type": "bytes32" + } + ], + "name": "sendfees", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "latest", + "type": "bool" + } + ], + "name": "getNewProof", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "height", + "type": "uint256" + } + ], + "name": "getProofByHeight", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "latest", + "type": "bool" + } + ], + "name": "getProofCost", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "upgradeContracts", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newcontract", + "type": "address" + }, + { + "internalType": "uint256", + "name": "contractNo", + "type": "uint256" + } + ], + "name": "replacecontract", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "runContractsUpgrade", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "revokeWithMainAddress", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "revokeWithMultiSig", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "recoverWithRecoveryAddress", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "recoverWithMultiSig", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "item", + "type": "uint256" + } + ], + "name": "getVoteState", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "txid", + "type": "address" + }, + { + "internalType": "address[]", + "name": "pendingContracts", + "type": "address[]" + }, + { + "internalType": "uint32", + "name": "agree", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "count", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "startHeight", + "type": "uint32" + }, + { + "internalType": "uint8", + "name": "nullified", + "type": "uint8" + } + ], + "internalType": "struct VerusObjects.voteState", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/src/utils/constants/sendModal.js b/src/utils/constants/sendModal.js index 3fbc37a3..3090b7a5 100644 --- a/src/utils/constants/sendModal.js +++ b/src/utils/constants/sendModal.js @@ -16,11 +16,13 @@ export const SEND_MODAL_PBAAS_CURRENCY_TO_ADD_FIELD = 'SEND_MODAL_PBAAS_CURRENCY export const SEND_MODAL_CONVERTTO_FIELD = 'SEND_MODAL_CONVERTTO_FIELD' export const SEND_MODAL_VIA_FIELD = 'SEND_MODAL_VIA_FIELD' export const SEND_MODAL_EXPORTTO_FIELD = 'SEND_MODAL_EXPORTTO_FIELD' +export const SEND_MODAL_MAPPING_FIELD = 'SEND_MODAL_MAPPING_FIELD' export const SEND_MODAL_IS_PRECONVERT = 'SEND_MODAL_IS_PRECONVERT' export const SEND_MODAL_PRICE_ESTIMATE = 'SEND_MODAL_PRICE_ESTIMATE' export const SEND_MODAL_SHOW_CONVERTTO_FIELD = 'SEND_MODAL_SHOW_CONVERTTO_FIELD' export const SEND_MODAL_SHOW_VIA_FIELD = 'SEND_MODAL_SHOW_VIA_FIELD' export const SEND_MODAL_SHOW_EXPORTTO_FIELD = 'SEND_MODAL_SHOW_EXPORTTO_FIELD' +export const SEND_MODAL_SHOW_MAPPING_FIELD = 'SEND_MODAL_SHOW_MAPPING_FIELD' export const SEND_MODAL_ADVANCED_FORM = 'SEND_MODAL_ADVANCED_FORM' export const SEND_MODAL_CONTRACT_ADDRESS_FIELD = 'SEND_MODAL_CONTRACT_ADDRESS_FIELD' diff --git a/src/utils/constants/web3Constants.js b/src/utils/constants/web3Constants.js index 0ad0772a..a038c1e7 100644 --- a/src/utils/constants/web3Constants.js +++ b/src/utils/constants/web3Constants.js @@ -5,4 +5,9 @@ export const FIAT_DECIMALS = 2 export const STABLECOIN_DECIMALS = 6 // Non-ERC20 utility contracts -export const RFOX_UTILITY_CONTRACT = "0xD82F7e3956d3FF391C927Cd7d0A7A57C360DF5b9" \ No newline at end of file +export const RFOX_UTILITY_CONTRACT = "0xD82F7e3956d3FF391C927Cd7d0A7A57C360DF5b9" + +export const VERUS_BRIDGE_DELEGATOR_GOERLI_CONTRACT = "0x85a7dE2278E52327471e174AeeB280cdFdC6A68a" + +export const ETH_BRIDGE_NAME = "Bridge.vETH" +export const ETH_CONTRACT_ADDRESS = "0x0000000000000000000000000000000000000000" \ No newline at end of file diff --git a/src/utils/web3/web3Interface.js b/src/utils/web3/web3Interface.js index 795aa931..5048d510 100644 --- a/src/utils/web3/web3Interface.js +++ b/src/utils/web3/web3Interface.js @@ -1,5 +1,8 @@ import ethers from 'ethers'; import { DEFAULT_ERC20_ABI } from '../constants/abi'; +import { VERUS_BRIDGE_DELEGATOR_GOERLI_CONTRACT } from '../constants/web3Constants'; +import { VERUS_BRIDGE_DELEGATOR_GOERLI_ABI } from '../constants/abis/verusBridgeDelegatorAbi'; +import { coinsList } from '../CoinData/CoinsList'; class Web3Interface { constructor(network, apiKeys) { @@ -76,6 +79,28 @@ class Web3Interface { this.DefaultProvider ); }; + + getVerusBridgeDelegatorContract = () => { + switch (this.network) { + case 'goerli': + return new ethers.Contract( + VERUS_BRIDGE_DELEGATOR_GOERLI_CONTRACT, + VERUS_BRIDGE_DELEGATOR_GOERLI_ABI, + this.DefaultProvider + ); + default: + throw new Error("No Verus bridge delegator for network " + this.network) + } + } + + getVrscSystem = () => { + switch (this.network) { + case 'homestead': + return coinsList.VRSC.currency_id; + default: + return coinsList.VRSCTEST.currency_id; + } + } } export default Web3Interface \ No newline at end of file From 7e7ea512ee8c8b06ee56d7884d239c6ee4fcc99c Mon Sep 17 00:00:00 2001 From: michaeltout Date: Tue, 22 Aug 2023 18:26:24 +0200 Subject: [PATCH 06/42] Checkpoint for ETH bridge support, add support for sending to mapped currencies from VRSC network without conversion, remove vETH being treated as separate PBaaS chain --- .../sendModal/dispatchers/sendModal.js | 2 +- .../FormModules/ExportFormModule.js | 27 +-- .../ConvertOrCrossChainSendForm.js | 170 +++++++++++++++++- src/containers/Coin/SendCoin/SendCoin.js | 6 +- .../Home/HomeWidgets/CurrencyWidget.js | 2 +- src/sagas/coins.js | 6 +- src/utils/CoinData/CoinDirectory.js | 10 +- src/utils/CoinData/CoinsList.js | 82 +-------- .../requests/getCurrencyConversionPaths.js | 63 ++++++- src/utils/api/routers/getConversionPaths.js | 6 +- src/utils/constants/web3Constants.js | 1 + 11 files changed, 265 insertions(+), 110 deletions(-) diff --git a/src/actions/actions/sendModal/dispatchers/sendModal.js b/src/actions/actions/sendModal/dispatchers/sendModal.js index 4b209158..d4672dc8 100644 --- a/src/actions/actions/sendModal/dispatchers/sendModal.js +++ b/src/actions/actions/sendModal/dispatchers/sendModal.js @@ -100,7 +100,7 @@ export const openConvertOrCrossChainSendModal = (coinObj, subWallet, data) => { [SEND_MODAL_SHOW_CONVERTTO_FIELD]: true, [SEND_MODAL_SHOW_EXPORTTO_FIELD]: true, [SEND_MODAL_SHOW_VIA_FIELD]: true, - [SEND_MODAL_SHOW_MAPPING_FIELD]: (coinObj.proto === 'erc20' || coinObj.proto === 'eth'), + [SEND_MODAL_SHOW_MAPPING_FIELD]: true, [SEND_MODAL_ADVANCED_FORM]: false } : data, diff --git a/src/components/FormModules/ExportFormModule.js b/src/components/FormModules/ExportFormModule.js index 6b69eaaa..9eae754c 100644 --- a/src/components/FormModules/ExportFormModule.js +++ b/src/components/FormModules/ExportFormModule.js @@ -3,6 +3,7 @@ import { View, TouchableOpacity } from 'react-native'; import { IconButton, Divider, Text, TextInput } from 'react-native-paper'; import Colors from '../../globals/colors'; import Styles from '../../styles'; +import { VETH } from '../../utils/constants/web3Constants'; const ExportFormModule = ({ isExport, @@ -58,15 +59,19 @@ const ExportFormModule = ({ ? Colors.quaternaryColor : Colors.verusDarkGray, }}> - {isExport - ? `To network: ${exportToField}` - : isConversion - ? `On ${ - localNetworkDefinition - ? localNetworkDefinition.fullyqualifiedname - : 'current' - } network` - : 'Select network to send to'} + { + isExport + ? exportToField.toLowerCase() === VETH.toLowerCase() + ? 'To Ethereum network' + : `To network: ${exportToField}` + : isConversion + ? `On ${ + localNetworkDefinition + ? localNetworkDefinition.fullyqualifiedname + : 'current' + } network` + : 'Select network to send to' + } @@ -74,12 +79,12 @@ const ExportFormModule = ({ } { - showMappingField && ( + showMappingField && !isConversion && ( {isPreconvert || advancedForm ? ( 0 ? "Currency to receive as (required)" : "Currency to receive as (optional)"} value={mappingField} mode="outlined" multiline={true} diff --git a/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js b/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js index 9ff57513..cc037c87 100644 --- a/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js +++ b/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js @@ -62,7 +62,8 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor const FIELD_TITLES = { [SEND_MODAL_EXPORTTO_FIELD]: "Destination network", [SEND_MODAL_VIA_FIELD]: "Convert via", - [SEND_MODAL_CONVERTTO_FIELD]: "Convert to" + [SEND_MODAL_CONVERTTO_FIELD]: "Convert to", + [SEND_MODAL_MAPPING_FIELD]: "Receive as" } const [searchMode, setSearchMode] = useState(false); @@ -91,6 +92,10 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor !!(sendModal.data[SEND_MODAL_EXPORTTO_FIELD] != null && sendModal.data[SEND_MODAL_EXPORTTO_FIELD].length > 0) ); + const [isMapping, setIsMapping] = useState( + !!(sendModal.data[SEND_MODAL_MAPPING_FIELD] != null && + sendModal.data[SEND_MODAL_MAPPING_FIELD].length > 0) + ); const [showConversionField, setShowConversionField] = useState( sendModal.data[SEND_MODAL_SHOW_CONVERTTO_FIELD] || @@ -107,6 +112,11 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor isExport ) + const [showMappingField, setShowMappingField] = useState( + sendModal.data[SEND_MODAL_MAPPING_FIELD] || + isMapping + ) + const fadeSearchMode = useRef(new Animated.Value(0)).current; const fadeNormalForm = useRef(new Animated.Value(1)).current; @@ -187,7 +197,9 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor const processConverttoSuggestionPaths = async (flatPaths, coinObj) => { switch (coinObj.proto) { case 'vrsc': - return flatPaths.map((path, index) => { + return flatPaths.filter(x => { + return !x.mapping + }).map((path, index) => { const priceFixed = Number(path.price.toFixed(2)) return { @@ -327,6 +339,60 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor keywords: [path.via.currencyid, path.via.fullyqualifiedname] }; }) + case 'eth': + case 'erc20': + return flatPaths.filter(x => { + if (isConversion) { + const destinationCurrency = sendModal.data[SEND_MODAL_CONVERTTO_FIELD]; + const destinationCurrencyMatch = !path.ethdest && (x.destination.currencyid === destinationCurrency || + destinationCurrency.toLowerCase() === + x.destination.fullyqualifiedname.toLowerCase()) + const destinationTokenMatch = path.ethdest && (x.destination.address === destinationCurrency || + destinationCurrency.toLowerCase() === + x.destination.address.toLowerCase()) + + return ( + x.via != null && + (destinationCurrencyMatch || destinationTokenMatch) + ); + } else return x.via != null; + }).map((path, index) => { + const priceFixed = Number(path.price.toFixed(2)) + const destname = path.ethdest + ? path.destination.address === ETH_CONTRACT_ADDRESS ? path.destination.name : `${path.destination.name} (${ + path.destination.address.substring(0, 8) + '...' + path.destination.address.substring(path.destination.address.length - 8) + })` + : path.destination.fullyqualifiedname; + + return { + title: path.via.fullyqualifiedname, + logoid: path.via.currencyid, + key: index.toString(), + description: `${ + path.exportto + ? path.gateway + ? `off-system to ${path.exportto.fullyqualifiedname} network ` + : `off-chain to ${path.exportto.fullyqualifiedname} network ` + : '' + }to ${destname}`, + values: { + [SEND_MODAL_VIA_FIELD]: path.via.fullyqualifiedname, + [SEND_MODAL_CONVERTTO_FIELD]: path.ethdest ? path.destination.address : path.destination.fullyqualifiedname, + [SEND_MODAL_EXPORTTO_FIELD]: path.exportto + ? path.exportto.fullyqualifiedname + : '', + [SEND_MODAL_PRICE_ESTIMATE]: path.price + }, + right: `${ + priceFixed === 0 + ? '<0.01' + : priceFixed === path.price + ? priceFixed + : `~${priceFixed}` + }`, + keywords: [path.via.currencyid, path.via.fullyqualifiedname] + }; + }) default: return [] } @@ -338,6 +404,16 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor const seenSystems = {} return flatPaths.filter(x => { if (x.exportto == null) return false; + if ( + !x.mapping && flatPaths.find( + y => + y.mapping && + y.exportto != null && + y.exportto.currencyid === x.exportto.currencyid, + ) != null + ) + return false; + const seen = seenSystems[x.exportto.currencyid] != null; @@ -345,7 +421,8 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor seenSystems[x.exportto.currencyid] = true; return true } else return false; - }).map((path, index) => { + }).map((path, index) => { + return { title: path.exportto.fullyqualifiedname, logoid: path.exportto.currencyid, @@ -353,6 +430,7 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor description: path.exportto.currencyid, values: { [SEND_MODAL_EXPORTTO_FIELD]: path.exportto.fullyqualifiedname, + [SEND_MODAL_MAPPING_FIELD]: path.mapping ? path.destination.symbol : coinObj.display_ticker }, right: "", keywords: [path.exportto.currencyid, path.exportto.fullyqualifiedname] @@ -363,6 +441,72 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor } } + const processMappingSuggestionPaths = async (flatPaths, coinObj) => { + switch (coinObj.proto) { + case 'vrsc': + return flatPaths + .filter(x => { + return x.mapping; + }) + .map((path, index) => { + const name = + path.destination.address === ETH_CONTRACT_ADDRESS + ? path.destination.name + : `${path.destination.name} (${ + path.destination.address.substring(0, 8) + + '...' + + path.destination.address.substring(path.destination.address.length - 8) + })`; + + return { + title: name, + logoid: path.destination.address, + logoproto: 'erc20', + key: index.toString(), + description: path.destination.address === ETH_CONTRACT_ADDRESS ? "receive as Ethereum" : `receive as the ${path.destination.symbol} ERC20 token`, + values: { + [SEND_MODAL_VIA_FIELD]: '', + [SEND_MODAL_MAPPING_FIELD]: path.destination.symbol, + [SEND_MODAL_EXPORTTO_FIELD]: path.exportto.fullyqualifiedname, + [SEND_MODAL_PRICE_ESTIMATE]: path.price, + }, + right: "", + keywords: [ + path.destination.symbol, + path.destination.name, + ], + }; + }); + case 'eth': + case 'erc20': + return flatPaths + .filter(x => { + return x.mapping; + }) + .map((path, index) => { + return { + title: path.destination.fullyqualifiedname, + logoid: path.destination.currencyid, + key: index.toString(), + description: `receive on ${path.exportto.fullyqualifiedname} network`, + values: { + [SEND_MODAL_VIA_FIELD]: '', + [SEND_MODAL_MAPPING_FIELD]: path.destination.fullyqualifiedname, + [SEND_MODAL_EXPORTTO_FIELD]: path.exportto.fullyqualifiedname, + [SEND_MODAL_PRICE_ESTIMATE]: path.price, + }, + right: "", + keywords: [ + path.destination.currencyid, + path.destination.fullyqualifiedname, + ], + }; + }); + default: + return []; + } + }; + const fetchSuggestionsBase = async (field) => { if (loadingSuggestions) return; let newSuggestionsBase = [] @@ -410,9 +554,12 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor setSuggestionBase(newSuggestionsBase); break; case SEND_MODAL_EXPORTTO_FIELD: - newSuggestionsBase = await processExporttoSuggestionPaths(flatPaths, sendModal.coinObj) + newSuggestionsBase = await processExporttoSuggestionPaths(flatPaths, sendModal.coinObj); setSuggestionBase(newSuggestionsBase); break; + case SEND_MODAL_MAPPING_FIELD: + newSuggestionsBase = await processMappingSuggestionPaths(flatPaths, sendModal.coinObj); + setSuggestionBase(newSuggestionsBase); default: setSuggestionBase(newSuggestionsBase); break; @@ -473,9 +620,9 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor address = channelAddress; - const response = await getCurrency(sendModal.coinObj.system_id, systemId); + const response = await getCurrency(systemId, sendModal.coinObj.system_id); - if (response.error) createAlert("Error", "Error fetching current network."); + if (response.error) Alert.alert("Error fetching current network", response.error.message); else { setLocalNetworkName(response.result.fullyqualifiedname); } @@ -507,6 +654,10 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor setShowExportField(sendModal.data[SEND_MODAL_SHOW_EXPORTTO_FIELD] || isExport) }, [sendModal.data[SEND_MODAL_SHOW_EXPORTTO_FIELD], isExport]) + useEffect(() => { + setShowMappingField(sendModal.data[SEND_MODAL_SHOW_MAPPING_FIELD] || isMapping) + }, [sendModal.data[SEND_MODAL_SHOW_MAPPING_FIELD], isMapping]) + useEffect(() => { setProcessedAmount(getProcessedAmount()) }, [sendModal.data[SEND_MODAL_AMOUNT_FIELD]]) @@ -530,6 +681,11 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor sendModal.data[SEND_MODAL_EXPORTTO_FIELD].length > 0)) }, [sendModal.data[SEND_MODAL_EXPORTTO_FIELD]]) + useEffect(() => { + setIsMapping(!!(sendModal.data[SEND_MODAL_MAPPING_FIELD] != null && + sendModal.data[SEND_MODAL_MAPPING_FIELD].length > 0)) + }, [sendModal.data[SEND_MODAL_MAPPING_FIELD]]) + const fillAmount = (amount) => { let displayAmount = BigNumber(amount); if (displayAmount.isLessThan(BigNumber(0))) { @@ -787,7 +943,7 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor } right={() => - selectedField !== SEND_MODAL_EXPORTTO_FIELD ? ( + selectedField !== SEND_MODAL_EXPORTTO_FIELD && selectedField !== SEND_MODAL_MAPPING_FIELD ? ( {sendModal.coinObj.display_ticker + ' price estimate'} diff --git a/src/containers/Coin/SendCoin/SendCoin.js b/src/containers/Coin/SendCoin/SendCoin.js index 2e5f578c..23f49f06 100644 --- a/src/containers/Coin/SendCoin/SendCoin.js +++ b/src/containers/Coin/SendCoin/SendCoin.js @@ -77,7 +77,7 @@ const SendCoin = ({ navigation }) => { [SEND_MODAL_IS_PRECONVERT]: false, [SEND_MODAL_SHOW_CONVERTTO_FIELD]: false, [SEND_MODAL_SHOW_EXPORTTO_FIELD]: true, - [SEND_MODAL_SHOW_MAPPING_FIELD]: (activeCoin.proto === 'erc20' || activeCoin.proto === 'eth'), + [SEND_MODAL_SHOW_MAPPING_FIELD]: true, [SEND_MODAL_SHOW_VIA_FIELD]: false, [SEND_MODAL_ADVANCED_FORM]: false }, @@ -126,6 +126,10 @@ const SendCoin = ({ navigation }) => { }, ]; + if (activeCoin.proto === 'eth' || activeCoin.proto === 'erc20') { + CONVERT_OR_CROSS_CHAIN_OPTIONS.splice(2, 1); + } + const [ convertOrCrossChainOptionsModalOpen, setConvertOrCrossChainOptionsModalOpen, diff --git a/src/containers/Home/HomeWidgets/CurrencyWidget.js b/src/containers/Home/HomeWidgets/CurrencyWidget.js index a1c41eac..7cae0b5a 100644 --- a/src/containers/Home/HomeWidgets/CurrencyWidget.js +++ b/src/containers/Home/HomeWidgets/CurrencyWidget.js @@ -138,7 +138,7 @@ const CurrencyWidget = props => { ? 'Testnet ERC20 Token' : coinObj.testnet ? 'Testnet Currency' - : coinObj.pbaas_options + : (coinObj.pbaas_options && !coinObj.compatible_channels.includes(GENERAL)) ? 'PBaaS Currency' : uniValueDisplay === '-' ? coinObj.proto === 'erc20' diff --git a/src/sagas/coins.js b/src/sagas/coins.js index 7ed4916d..c2efab48 100644 --- a/src/sagas/coins.js +++ b/src/sagas/coins.js @@ -10,6 +10,7 @@ import { } from '../utils/constants/storeType'; import { getDefaultSubWallets } from '../utils/defaultSubWallets'; import { getVerusIdCurrency } from '../utils/CoinData/CoinData'; +import { IS_PBAAS_CHAIN } from '../utils/constants/currencies'; export default function* setUserCoinsSaga() { yield all([takeEvery(SET_USER_COINS, handleFinishSetUserCoins)]); @@ -42,6 +43,8 @@ function* handleFinishSetUserCoins(action) { if (coin.tags.includes(IS_PBAAS) && !nonNativeSystems.includes(coin.system_id) && coin.system_id !== coinObj.system_id && + coin.system_options != null && + (coin.system_options & IS_PBAAS_CHAIN) === IS_PBAAS_CHAIN && coin.testnet === coinObj.testnet) { nonNativeSystems.push(coin.system_id); } @@ -49,7 +52,8 @@ function* handleFinishSetUserCoins(action) { } return addresses.map((addr, index) => { - const systems = [coinObj.system_id, ...nonNativeSystems] + const nonChainSystem = coinObj.system_options != null && (coinObj.system_options & IS_PBAAS_CHAIN) !== IS_PBAAS_CHAIN + const systems = nonChainSystem ? nonNativeSystems : [coinObj.system_id, ...nonNativeSystems] return systems.map(system => { const channelId = `${VRPC}.${addr}.${system}`; diff --git a/src/utils/CoinData/CoinDirectory.js b/src/utils/CoinData/CoinDirectory.js index ee2cee1c..9d578f4e 100644 --- a/src/utils/CoinData/CoinDirectory.js +++ b/src/utils/CoinData/CoinDirectory.js @@ -136,18 +136,22 @@ class _CoinDirectory { let endpoints = []; let secondsPerBlock = 60; + let systemOptions; try { const systemObj = this.findCoinObj(system, null, true); endpoints = systemObj.vrpc_endpoints; secondsPerBlock = systemObj.seconds_per_block; + systemOptions = systemObj.pbaas_options; } catch(e) { if (this.coinExistsInDirectory(system)) { endpoints = this.coins[system].vrpc_endpoints; secondsPerBlock = this.coins[system].seconds_per_block; + systemOptions = this.coins[system].pbaas_options; } else if (currencyDefinition.nodes) { - const firstNode = currencyDefinition.nodes[0].networkaddress - secondsPerBlock = currencyDefinition.blocktime + const firstNode = currencyDefinition.nodes[0].networkaddress; + secondsPerBlock = currencyDefinition.blocktime; + systemOptions = currencyDefinition.options; const firstNodeSplit = firstNode.split(':') const firstNodePort = firstNodeSplit[firstNodeSplit.length - 1] @@ -189,6 +193,7 @@ class _CoinDirectory { await this.addPbaasCurrency(systemDefinition.result, isTestnet, checkEndpoint); endpoints = this.coins[systemDefinition.result.currencyid].vrpc_endpoints; secondsPerBlock = this.coins[systemDefinition.result.currencyid].seconds_per_block; + systemOptions = this.coins[systemDefinition.result.currencyid].pbaas_options; } else { throw new Error('Failed to connect to find ' + system); } @@ -198,6 +203,7 @@ class _CoinDirectory { const currencyCoinObj = { testnet: isTestnet, pbaas_options: currencyDefinition.options, + system_options: systemOptions, id: currencyDefinition.currencyid, currency_id: currencyDefinition.currencyid, system_id: currencyDefinition.systemid, diff --git a/src/utils/CoinData/CoinsList.js b/src/utils/CoinData/CoinsList.js index d867f8d5..d66d5b93 100644 --- a/src/utils/CoinData/CoinsList.js +++ b/src/utils/CoinData/CoinsList.js @@ -64,6 +64,7 @@ export const coinsList = { bitgojs_network_key: 'verus', display_ticker: 'VRSC', display_name: 'Verus', + pbaas_options: 264, rate_url_params: {coin_paprika: 'vrsc-verus-coin'}, alt_names: ['verus'], theme_color: '#3165D4', @@ -83,6 +84,7 @@ export const coinsList = { testnet: true, mainnet_id: 'VRSC', id: 'VRSCTEST', + pbaas_options: 264, currency_id: 'iJhCezBExJHvtyH3fGhNnt2NhU4Ztkf2yq', system_id: 'iJhCezBExJHvtyH3fGhNnt2NhU4Ztkf2yq', bitgojs_network_key: 'verustest', @@ -101,86 +103,6 @@ export const coinsList = { default_app: 'wallet', apps: VERUS_APPS }, - iSojYsotVzXz4wh2eJriASGo6UidJDDhL2: { - testnet: true, - pbaas_options: 545, - id: "iSojYsotVzXz4wh2eJriASGo6UidJDDhL2", - currency_id: "iSojYsotVzXz4wh2eJriASGo6UidJDDhL2", - system_id: "iJhCezBExJHvtyH3fGhNnt2NhU4Ztkf2yq", - bitgojs_network_key: 'verustest', - display_ticker: 'Bridge.vETH', - display_name: 'Bridge.vETH', - alt_names: [], - theme_color: '#232323', - compatible_channels: [VERUSID, VRPC], - tags: [IS_VERUS, IS_ZCASH, IS_PBAAS], - proto: 'vrsc', - vrpc_endpoints: ['https://api.verustest.net'], - decimals: DEFAULT_DECIMALS, - seconds_per_block: 60, - default_app: 'wallet', - apps: VERUS_APPS - }, - iCtawpxUiCc2sEupt7Z4u8SDAncGZpgSKm: { - testnet: true, - pbaas_options: 136, - id: "iCtawpxUiCc2sEupt7Z4u8SDAncGZpgSKm", - currency_id: "iCtawpxUiCc2sEupt7Z4u8SDAncGZpgSKm", - system_id: "iJhCezBExJHvtyH3fGhNnt2NhU4Ztkf2yq", - bitgojs_network_key: 'verustest', - display_ticker: 'vETH', - display_name: 'vETH', - alt_names: [], - theme_color: '#232323', - compatible_channels: [VERUSID, VRPC], - tags: [IS_VERUS, IS_ZCASH, IS_PBAAS], - proto: 'vrsc', - vrpc_endpoints: ['https://api.verustest.net'], - decimals: DEFAULT_DECIMALS, - seconds_per_block: 60, - default_app: 'wallet', - apps: VERUS_APPS - }, - iCtawpxUiCc2sEupt7Z4u8SDAncGZpgSKm: { - testnet: true, - pbaas_options: 136, - id: "iCtawpxUiCc2sEupt7Z4u8SDAncGZpgSKm", - currency_id: "iCtawpxUiCc2sEupt7Z4u8SDAncGZpgSKm", - system_id: "iJhCezBExJHvtyH3fGhNnt2NhU4Ztkf2yq", - bitgojs_network_key: 'verustest', - display_ticker: 'vETH', - display_name: 'vETH', - alt_names: [], - theme_color: '#232323', - compatible_channels: [VERUSID, VRPC], - tags: [IS_VERUS, IS_ZCASH, IS_PBAAS], - proto: 'vrsc', - vrpc_endpoints: ['https://api.verustest.net'], - decimals: DEFAULT_DECIMALS, - seconds_per_block: 60, - default_app: 'wallet', - apps: VERUS_APPS - }, - iN9vbHXexEh6GTZ45fRoJGKTQThfbgUwMh: { - testnet: true, - pbaas_options: 32, - id: "iN9vbHXexEh6GTZ45fRoJGKTQThfbgUwMh", - currency_id: "iN9vbHXexEh6GTZ45fRoJGKTQThfbgUwMh", - system_id: "iCtawpxUiCc2sEupt7Z4u8SDAncGZpgSKm", - bitgojs_network_key: 'verustest', - display_ticker: 'DAI.vETH', - display_name: 'DAI.vETH', - alt_names: [], - theme_color: '#232323', - compatible_channels: [VERUSID, VRPC], - tags: [IS_VERUS, IS_ZCASH, IS_PBAAS], - proto: 'vrsc', - vrpc_endpoints: ['https://api.verustest.net'], - decimals: DEFAULT_DECIMALS, - seconds_per_block: 60, - default_app: 'wallet', - apps: VERUS_APPS - }, KMD: { id: 'KMD', display_name: 'Komodo', diff --git a/src/utils/api/channels/vrpc/requests/getCurrencyConversionPaths.js b/src/utils/api/channels/vrpc/requests/getCurrencyConversionPaths.js index 01839041..12b2e6c0 100644 --- a/src/utils/api/channels/vrpc/requests/getCurrencyConversionPaths.js +++ b/src/utils/api/channels/vrpc/requests/getCurrencyConversionPaths.js @@ -1,7 +1,7 @@ import { ethers } from "ethers"; import VrpcProvider from "../../../../vrpc/vrpcInterface" import { getCurrenciesMappedToEth } from "./getCurrenciesMappedToEth"; -import { ETH_BRIDGE_NAME, ETH_CONTRACT_ADDRESS } from "../../../../constants/web3Constants"; +import { ETH_BRIDGE_NAME, ETH_CONTRACT_ADDRESS, VETH } from "../../../../constants/web3Constants"; import { DEST_ETH } from "verus-typescript-primitives"; import { getWeb3ProviderForNetwork } from "../../../../web3/provider"; @@ -81,6 +81,8 @@ export const getCurrencyConversionPaths = async (systemId, src, ethNetwork) => { // Or bounce back to ETH through any currency mapped to your destination for (const contractAddress of erc20sMappedToCurrency) { + paths[contractAddress] = [] + try { if (contractAddress !== ETH_CONTRACT_ADDRESS) { const contract = getWeb3ProviderForNetwork(ethNetwork).getUnitializedContractInstance(contractAddress); @@ -88,7 +90,7 @@ export const getCurrencyConversionPaths = async (systemId, src, ethNetwork) => { const symbol = await contract.symbol(); const decimals = await contract.decimals(); - paths[convertableCurrencyId].push({ + paths[contractAddress].push({ via: convertableCurrencyDefinition, destination: { address: contractAddress, @@ -102,7 +104,7 @@ export const getCurrencyConversionPaths = async (systemId, src, ethNetwork) => { ethdest: true }); } else { - paths[convertableCurrencyId].push({ + paths[ETH_CONTRACT_ADDRESS].push({ via: convertableCurrencyDefinition, destination: { address: ETH_CONTRACT_ADDRESS, @@ -142,6 +144,8 @@ export const getCurrencyConversionPaths = async (systemId, src, ethNetwork) => { // Or bounce back to ETH through any currency mapped to your destination for (const contractAddress of erc20sMappedToCurrency) { + paths[contractAddress] = [] + try { if (contractAddress !== ETH_CONTRACT_ADDRESS) { const contract = getWeb3ProviderForNetwork(ethNetwork).getUnitializedContractInstance(contractAddress); @@ -149,7 +153,7 @@ export const getCurrencyConversionPaths = async (systemId, src, ethNetwork) => { const symbol = await contract.symbol(); const decimals = await contract.decimals(); - paths[convertableCurrencyId].push({ + paths[contractAddress].push({ via: bridgeCurrencyDefinition, destination: { address: contractAddress, @@ -163,7 +167,7 @@ export const getCurrencyConversionPaths = async (systemId, src, ethNetwork) => { ethdest: true }); } else { - paths[convertableCurrencyId].push({ + paths[ETH_CONTRACT_ADDRESS].push({ via: bridgeCurrencyDefinition, destination: { address: ETH_CONTRACT_ADDRESS, @@ -223,6 +227,55 @@ export const getCurrencyConversionPaths = async (systemId, src, ethNetwork) => { return !(offSystem && x.exportto == null); }); } + + const mappedCurrenciesResponse = await getCurrenciesMappedToEth(systemId, ethNetwork); + if (mappedCurrenciesResponse.error) throw new Error(sourceResponse.error.message); + const {currencyIdToContractAddressMap} = mappedCurrenciesResponse.result; + + if (currencyIdToContractAddressMap.has(sourceDefinition.currencyid)) { + const mappedEthCurrencies = currencyIdToContractAddressMap.get(sourceDefinition.currencyid); + + for (const contractAddress of mappedEthCurrencies) { + paths[contractAddress] = [] + + const vEthCurrencyResponse = await endpoint.getCurrency(VETH); + if (vEthCurrencyResponse.error) throw new Error(vEthCurrencyResponse.error.message); + const vEthCurrencyDefinition = vEthCurrencyResponse.result; + + if (contractAddress === ETH_CONTRACT_ADDRESS) { + paths[contractAddress].push({ + destination: { + address: ETH_CONTRACT_ADDRESS, + symbol: "ETH", + decimals: 18, + name: "Ethereum", + }, + exportto: vEthCurrencyDefinition, + price: 1, + gateway: true, + mapping: true + }) + } else { + const contract = getWeb3ProviderForNetwork(ethNetwork).getUnitializedContractInstance(contractAddress); + const name = await contract.name(); + const symbol = await contract.symbol(); + const decimals = await contract.decimals(); + + paths[contractAddress].push({ + destination: { + address: contractAddress, + symbol, + decimals, + name, + }, + exportto: vEthCurrencyDefinition, + price: 1, + gateway: true, + mapping: true + }) + } + } + } return paths; } diff --git a/src/utils/api/routers/getConversionPaths.js b/src/utils/api/routers/getConversionPaths.js index 2a1e2311..2c700c91 100644 --- a/src/utils/api/routers/getConversionPaths.js +++ b/src/utils/api/routers/getConversionPaths.js @@ -8,13 +8,17 @@ import * as wyre from "../channels/wyre/callCreators"; import * as vrpc from "../channels/vrpc/callCreators"; import { getWeb3ProviderForNetwork } from "../../web3/provider"; import { ETH_CONTRACT_ADDRESS } from "../../constants/web3Constants"; +import { + ETH_HOMESTEAD, + ETH_GOERLI +} from "../../../../env/index"; const CONVERSION_PATH_FUNCTION_MAP = { [WYRE_SERVICE]: wyre.getCurrencyConversionPaths, [VRPC]: (coinObj, channel, params) => { const [channelName, iAddress, systemId] = channel.split('.') - return vrpc.getCurrencyConversionPaths(systemId, params.src) + return vrpc.getCurrencyConversionPaths(systemId, params.src, coinObj.testnet ? ETH_GOERLI : ETH_HOMESTEAD); }, [ERC20]: (coinObj, channel, params) => { return vrpc.getCurrencyConversionPaths( diff --git a/src/utils/constants/web3Constants.js b/src/utils/constants/web3Constants.js index a038c1e7..7540eb53 100644 --- a/src/utils/constants/web3Constants.js +++ b/src/utils/constants/web3Constants.js @@ -10,4 +10,5 @@ export const RFOX_UTILITY_CONTRACT = "0xD82F7e3956d3FF391C927Cd7d0A7A57C360DF5b9 export const VERUS_BRIDGE_DELEGATOR_GOERLI_CONTRACT = "0x85a7dE2278E52327471e174AeeB280cdFdC6A68a" export const ETH_BRIDGE_NAME = "Bridge.vETH" +export const VETH = 'vETH' export const ETH_CONTRACT_ADDRESS = "0x0000000000000000000000000000000000000000" \ No newline at end of file From 6c9d3650626153519a6e43149e19a806913e3c31 Mon Sep 17 00:00:00 2001 From: michaeltout Date: Thu, 24 Aug 2023 13:45:05 +0200 Subject: [PATCH 07/42] Add IS_PBAAS_CHAIN constant --- src/utils/constants/currencies.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/utils/constants/currencies.js b/src/utils/constants/currencies.js index cab12a50..28b9c1b5 100644 --- a/src/utils/constants/currencies.js +++ b/src/utils/constants/currencies.js @@ -90,4 +90,5 @@ export const SUPPORTED_BANK_CURRENCIES = Object.keys(CURRENCY_NAMES) export const IS_GATEWAY_FLAG = 0x80 export const IS_TOKEN_FLAG = 0x20 -export const IS_FRACTIONAL_FLAG = 0x01 \ No newline at end of file +export const IS_FRACTIONAL_FLAG = 0x01 +export const IS_PBAAS_CHAIN = 0x100 \ No newline at end of file From 4597419f7880c939c9fe9dcf4e4415d3459b750d Mon Sep 17 00:00:00 2001 From: michaeltout Date: Thu, 14 Sep 2023 20:08:55 +0200 Subject: [PATCH 08/42] Checkpoint for ETH bridge support, integrate delegator contract support for launched vETH --- .../ConvertOrCrossChainSendConfirm.js | 15 +- .../ConvertOrCrossChainSendForm.js | 70 +++- src/utils/CoinData/CoinsList.js | 2 +- .../api/channels/erc20/requests/preflight.js | 389 +++++++++++++++++- src/utils/api/channels/erc20/requests/send.js | 52 ++- .../requests/getCurrencyConversionPaths.js | 18 +- .../api/channels/vrpc/requests/preflight.js | 1 + src/utils/api/routers/getIdentity.js | 32 ++ .../routers/preflightConvertOrCrossChain.js | 21 + .../api/routers/sendConvertOrCrossChain.js | 27 ++ src/utils/constants/web3Constants.js | 15 +- src/utils/math.js | 2 +- 12 files changed, 600 insertions(+), 44 deletions(-) create mode 100644 src/utils/api/routers/getIdentity.js create mode 100644 src/utils/api/routers/preflightConvertOrCrossChain.js create mode 100644 src/utils/api/routers/sendConvertOrCrossChain.js diff --git a/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendConfirm/ConvertOrCrossChainSendConfirm.js b/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendConfirm/ConvertOrCrossChainSendConfirm.js index 9a259cc2..598c9edc 100644 --- a/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendConfirm/ConvertOrCrossChainSendConfirm.js +++ b/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendConfirm/ConvertOrCrossChainSendConfirm.js @@ -25,6 +25,7 @@ import BigNumber from 'bignumber.js'; import {TransferDestination} from 'verus-typescript-primitives'; import {sendCurrencyTransfer} from '../../../../utils/api/channels/vrpc/callCreators'; import {CoinDirectory} from '../../../../utils/CoinData/CoinDirectory'; +import { sendConvertOrCrossChain } from '../../../../utils/api/routers/sendConvertOrCrossChain'; function ConvertOrCrossChainSendConfirm({ navigation, @@ -34,6 +35,7 @@ function ConvertOrCrossChainSendConfirm({ setPreventExit, }) { const sendModal = useSelector(state => state.sendModal); + const activeAccount = useSelector(state => state.authentication.activeAccount); const networkName = useSelector(state => { try { const subwallet = state.sendModal.subWallet; @@ -81,11 +83,6 @@ function ConvertOrCrossChainSendConfirm({ */ const nameMap = names; - /** - * @type {string} - */ - const txHex = hex; - const {change, fees, sent} = validation; // const validation = { @@ -236,7 +233,7 @@ function ConvertOrCrossChainSendConfirm({ )}` : !!(sendModal.data[SEND_MODAL_PRICE_ESTIMATE]) ? `${Number( ( - primaryCurrencyAmount * sendModal.data[SEND_MODAL_PRICE_ESTIMATE] + primaryCurrencyAmount * sendModal.data[SEND_MODAL_PRICE_ESTIMATE].price ).toFixed(8), )} ${tryRenderFriendlyName(convertto)}` : "", numLines: 100, @@ -351,11 +348,11 @@ function ConvertOrCrossChainSendConfirm({ ? names[destAddrString] : destAddrString; - const res = await sendCurrencyTransfer( + const res = await sendConvertOrCrossChain( sendModal.coinObj, + activeAccount, sendModal.subWallet.api_channels[API_SEND], - hex, - inputs, + params ); if (res.err) throw new Error(res.result); diff --git a/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js b/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js index cc037c87..a943fa4b 100644 --- a/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js +++ b/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js @@ -5,7 +5,7 @@ import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view import { TextInput, Button, Divider, Checkbox, List, Text, IconButton } from "react-native-paper"; import { useSelector } from 'react-redux'; import { createAlert } from "../../../../actions/actions/alert/dispatchers/alert"; -import { API_SEND, DLIGHT_PRIVATE, ETH } from "../../../../utils/constants/intervalConstants"; +import { API_SEND, DLIGHT_PRIVATE, ERC20, ETH } from "../../../../utils/constants/intervalConstants"; import { SEND_MODAL_ADVANCED_FORM, SEND_MODAL_AMOUNT_FIELD, @@ -29,7 +29,7 @@ import { useEffect } from "react"; import { getConversionPaths } from "../../../../utils/api/routers/getConversionPaths"; import AnimatedActivityIndicatorBox from "../../../AnimatedActivityIndicatorBox"; import { getCoinLogo } from "../../../../utils/CoinData/CoinData"; -import { getCurrency, getIdentity } from "../../../../utils/api/channels/verusid/callCreators"; +import { getCurrency } from "../../../../utils/api/channels/verusid/callCreators"; import selectAddresses from "../../../../selectors/address"; import MissingInfoRedirect from "../../../MissingInfoRedirect/MissingInfoRedirect"; import { preflightCurrencyTransfer } from "../../../../utils/api/channels/vrpc/callCreators"; @@ -42,6 +42,8 @@ import ExportFormModule from "../../../FormModules/ExportFormModule"; import { getAddressBalances } from "../../../../utils/api/routers/getAddressBalance"; import { coinsList } from "../../../../utils/CoinData/CoinsList"; import { ETH_CONTRACT_ADDRESS } from "../../../../utils/constants/web3Constants"; +import { preflightConvertOrCrossChain } from "../../../../utils/api/routers/preflightConvertOrCrossChain"; +import { getIdentity } from "../../../../utils/api/routers/getIdentity"; const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFormData, navigation }) => { const sendModal = useSelector(state => state.sendModal); @@ -225,7 +227,7 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor [SEND_MODAL_EXPORTTO_FIELD]: path.exportto ? path.exportto.fullyqualifiedname : '', - [SEND_MODAL_PRICE_ESTIMATE]: path.price + [SEND_MODAL_PRICE_ESTIMATE]: { price: path.price, name: path.destination.fullyqualifiedname } }, right: `${ priceFixed === 0 @@ -271,11 +273,11 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor : 'direct', values: { [SEND_MODAL_VIA_FIELD]: path.via ? path.via.fullyqualifiedname : '', - [SEND_MODAL_CONVERTTO_FIELD]: path.ethdest ? path.destination.address : path.destination.fullyqualifiedname, + [SEND_MODAL_CONVERTTO_FIELD]: path.ethdest ? path.destination.mapto.fullyqualifiedname : path.destination.fullyqualifiedname, [SEND_MODAL_EXPORTTO_FIELD]: path.exportto ? path.exportto.fullyqualifiedname : '', - [SEND_MODAL_PRICE_ESTIMATE]: path.price + [SEND_MODAL_PRICE_ESTIMATE]: { price: path.price, name: name } }, right: `${ priceFixed === 0 @@ -327,7 +329,7 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor [SEND_MODAL_EXPORTTO_FIELD]: path.exportto ? path.exportto.fullyqualifiedname : '', - [SEND_MODAL_PRICE_ESTIMATE]: path.price + [SEND_MODAL_PRICE_ESTIMATE]: { price: path.price, name: path.destination.fullyqualifiedname } }, right: `${ priceFixed === 0 @@ -377,11 +379,11 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor }to ${destname}`, values: { [SEND_MODAL_VIA_FIELD]: path.via.fullyqualifiedname, - [SEND_MODAL_CONVERTTO_FIELD]: path.ethdest ? path.destination.address : path.destination.fullyqualifiedname, + [SEND_MODAL_CONVERTTO_FIELD]: path.ethdest ? path.destination.mapto.fullyqualifiedname : path.destination.fullyqualifiedname, [SEND_MODAL_EXPORTTO_FIELD]: path.exportto ? path.exportto.fullyqualifiedname : '', - [SEND_MODAL_PRICE_ESTIMATE]: path.price + [SEND_MODAL_PRICE_ESTIMATE]: { price: path.price, name: destname } }, right: `${ priceFixed === 0 @@ -436,6 +438,29 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor keywords: [path.exportto.currencyid, path.exportto.fullyqualifiedname] }; }); + case 'eth': + case 'erc20': + return [ + { + title: coinObj.testnet + ? coinsList.VRSCTEST.display_name + : coinsList.VRSC.display_name, + logoid: coinObj.testnet ? coinsList.VRSCTEST.id : coinsList.VRSC.id, + key: 0, + description: coinObj.testnet + ? 'Send to the Verus testnet' + : 'Send to the Verus network', + values: { + [SEND_MODAL_EXPORTTO_FIELD]: coinObj.testnet + ? coinsList.VRSCTEST.display_ticker + : coinsList.VRSC.display_ticker, + }, + right: '', + keywords: coinObj.testnet + ? [coinsList.VRSCTEST.display_name, coinsList.VRSCTEST.id] + : [coinsList.VRSC.display_name, coinsList.VRSCTEST.id], + }, + ]; default: return [] } @@ -468,7 +493,7 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor [SEND_MODAL_VIA_FIELD]: '', [SEND_MODAL_MAPPING_FIELD]: path.destination.symbol, [SEND_MODAL_EXPORTTO_FIELD]: path.exportto.fullyqualifiedname, - [SEND_MODAL_PRICE_ESTIMATE]: path.price, + [SEND_MODAL_PRICE_ESTIMATE]: { price: path.price, name: name }, }, right: "", keywords: [ @@ -493,7 +518,7 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor [SEND_MODAL_VIA_FIELD]: '', [SEND_MODAL_MAPPING_FIELD]: path.destination.fullyqualifiedname, [SEND_MODAL_EXPORTTO_FIELD]: path.exportto.fullyqualifiedname, - [SEND_MODAL_PRICE_ESTIMATE]: path.price, + [SEND_MODAL_PRICE_ESTIMATE]: { price: path.price, name: path.destination.fullyqualifiedname }, }, right: "", keywords: [ @@ -612,7 +637,10 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor let address; if (sendModal.coinObj.proto === 'erc20' || sendModal.coinObj.proto === 'eth') { - address = activeUser.keys[sendModal.coinObj.id][ETH].addresses[0]; + address = + sendModal.coinObj.proto === 'erc20' + ? activeUser.keys[sendModal.coinObj.id][ERC20].addresses[0] + : activeUser.keys[sendModal.coinObj.id][ETH].addresses[0]; setLocalNetworkName(coinsList.ETH.display_ticker); } else { @@ -780,7 +808,7 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor let keyhash; if (addr.endsWith("@")) { - const identityRes = await getIdentity(coinObj.system_id, addr); + const identityRes = await getIdentity(coinObj, activeAccount, channel, addr); if (identityRes.error) throw new Error("Failed to fetch " + addr); @@ -812,6 +840,7 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor let output = { currency: coinObj.currency_id, + mapto: selectData(data[SEND_MODAL_MAPPING_FIELD]), convertto: selectData(data[SEND_MODAL_CONVERTTO_FIELD]), exportto: selectData(data[SEND_MODAL_EXPORTTO_FIELD]), via: selectData(data[SEND_MODAL_VIA_FIELD]), @@ -824,7 +853,7 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor if (output[key] == null) delete output[key] } - const res = await preflightCurrencyTransfer(coinObj, channel, activeAccount, output) + const res = await preflightConvertOrCrossChain(coinObj, activeAccount, channel, output) setModalHeight(696); @@ -835,10 +864,11 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor const { converterdef, submittedsats, - estimate + estimate, + output: resout } = res.result; - if (output.convertto != null && estimate == null && sendModal.data[SEND_MODAL_PRICE_ESTIMATE] == null) { + if (resout.convertto != null && estimate == null && sendModal.data[SEND_MODAL_PRICE_ESTIMATE] == null) { Alert.alert("Could not estimate conversion result", 'Failed to calculate an estimated result for this conversion.') } @@ -848,13 +878,13 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor }, a centralized currency. The controller, ${converterdef.fullyqualifiedname}@, has the ability to mint new supply.`) } - if (submittedsats !== output.satoshis) { + if (submittedsats !== resout.satoshis) { Alert.alert( 'Amount changed', `You have insufficient funds to send your submitted amount of ${satsToCoins( BigNumber(submittedsats), ).toString()} ${coinObj.display_ticker} with the transaction fee, so the transaction amount has been changed to the maximum sendable value of ${satsToCoins( - BigNumber(output.satoshis), + BigNumber(resout.satoshis), ).toString()} ${coinObj.display_ticker}.`, ); } @@ -961,7 +991,7 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor {item.right} )} left={props => { - const Logo = getCoinLogo(item.logoid, item.logoproto); + const Logo = getCoinLogo(item.logoid, item.logoproto, 'dark'); return ( @@ -1016,10 +1046,10 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor sendModal.data[SEND_MODAL_PRICE_ESTIMATE] != null && processedAmount != null ? `≈ ${Number( - (processedAmount * sendModal.data[SEND_MODAL_PRICE_ESTIMATE]).toFixed( + (processedAmount * sendModal.data[SEND_MODAL_PRICE_ESTIMATE].price).toFixed( 8, ), - )} ${sendModal.data[SEND_MODAL_CONVERTTO_FIELD]}` + )} ${sendModal.data[SEND_MODAL_PRICE_ESTIMATE].name}` : null } /> diff --git a/src/utils/CoinData/CoinsList.js b/src/utils/CoinData/CoinsList.js index d66d5b93..9b214a66 100644 --- a/src/utils/CoinData/CoinsList.js +++ b/src/utils/CoinData/CoinsList.js @@ -152,7 +152,7 @@ export const coinsList = { }, GETH: { id: 'GETH', - currency_id: '', + currency_id: '0x0000000000000000000000000000000000000000', system_id: '.eth', display_ticker: 'gETH', display_name: 'Testnet Ethereum', diff --git a/src/utils/api/channels/erc20/requests/preflight.js b/src/utils/api/channels/erc20/requests/preflight.js index 53cea0e7..8e75f6c3 100644 --- a/src/utils/api/channels/erc20/requests/preflight.js +++ b/src/utils/api/channels/erc20/requests/preflight.js @@ -1,8 +1,45 @@ import { ethers } from "ethers" import { getWeb3ProviderForNetwork } from '../../../../web3/provider' -import { ERC20, ETH } from "../../../../constants/intervalConstants" -import { scientificToDecimal } from "../../../../math" -import { ETHERS } from "../../../../constants/web3Constants" +import { ERC20, ETH, VRPC } from "../../../../constants/intervalConstants" +import { coinsToSats, coinsToUnits, satsToCoins, scientificToDecimal } from "../../../../math" +import { + DAI_VETH, + ETHERS, + ETH_BRIDGE_NAME, + ETH_VERUS_BRIDGE_CONTRACT_RESERVE_TRANSFER_FEE, + VETH, + ETH_VERUS_BRIDGE_DEST_SYSTEM_ID, + MINIMUM_GAS_PRICE_WEI_DELEGATOR_CONTRACT, + DELEGATOR_CONTRACT_GAS_MODIFIER, + GAS_TRANSACTION_IMPORT_FEE, + ETH_CONTRACT_ADDRESS, + INITIAL_GAS_LIMIT, + ERC20_BRIDGE_TRANSFER_GAS_LIMIT, + FALLBACK_GAS_BRIDGE_TRANSFER, +} from '../../../../constants/web3Constants'; +import { getCurrency, getIdentity } from "../../verusid/callCreators" +import { getSystemNameFromSystemId } from "../../../../CoinData/CoinData" +import { + DEST_ETH, + DEST_ID, + DEST_PKH, + FLAG_DEST_AUX, + FLAG_DEST_GATEWAY, + RESERVE_TRANSFER_CONVERT, + RESERVE_TRANSFER_IMPORT_TO_SOURCE, + RESERVE_TRANSFER_RESERVE_TO_RESERVE, + RESERVE_TRANSFER_VALID, + TransferDestination, + fromBase58Check, + toBase58Check, + toIAddress, +} from 'verus-typescript-primitives'; + +import BigNumber from "bignumber.js" +import { requestPrivKey } from "../../../../auth/authBox"; +import { BufferWriter } from "@bitgo/utxo-lib/dist/src/bufferutils"; +import { BN } from "bn.js"; +import { getStandardEthBalance } from "../../eth/callCreator"; // TODO: Add balance recalculation with eth gas export const txPreflight = async (coinObj, activeUser, address, amount, params) => { @@ -47,6 +84,352 @@ export const txPreflight = async (coinObj, activeUser, address, amount, params) } catch(e) { console.error(e) + return { + err: true, + result: e.message + } + } +} + +export const preflightBridgeTransfer = async (coinObj, channelId, activeUser, output, fallbackSubtractBalance = true, amountSubmitted = null) => { + try { + const Web3Provider = getWeb3ProviderForNetwork(coinObj.network); + const fromAddress = activeUser.keys[coinObj.id][coinObj.proto === 'erc20' ? ERC20 : ETH].addresses[0]; + const refundAddress = activeUser.keys[coinObj.id][VRPC].addresses[0]; + const submittedAmount = amountSubmitted == null ? output.satoshis : amountSubmitted; + + const signer = new ethers.VoidSigner(fromAddress, Web3Provider.DefaultProvider); + + const delegatorContract = Web3Provider.getVerusBridgeDelegatorContract().connect(signer); + const systemId = Web3Provider.getVrscSystem(); + const systemName = getSystemNameFromSystemId(systemId); + const tokenContract = coinObj.currency_id; + + const { + currency, + convertto, + exportto, + feecurrency, + via, + feesatoshis, + preconvert, + address, + mapto, + satoshis + } = output; + + const isConversion = convertto != null; + + let mappedCurrencyIAddress; + let isBridge = false; + const vEthIAddress = toIAddress(VETH, systemName); + const bridgeIAddress = toIAddress(ETH_BRIDGE_NAME, systemName); + + // Find the i address of the currency mapped to the erc20 you're sending as + if (mapto != null) { + // If mapto is defined, search for the currency trying to map to and get + // i-address + const mappedCurrencyRes = await getCurrency(systemId, mapto); + + if (mappedCurrencyRes.error) throw new Error(mappedCurrencyRes.error.message); + else { + if (mappedCurrencyRes.result.fullyqualifiedname === ETH_BRIDGE_NAME) { + isBridge = true; + } + + mappedCurrencyIAddress = mappedCurrencyRes.result.currencyid; + } + } else if (isConversion) { + // If mapto is undefined, assume conversion and look for which convertable + // currency is mapped to the current erc20 address + const convertableCurrencies = [ + systemId, + bridgeIAddress, + vEthIAddress, + toIAddress(DAI_VETH, systemName) + ]; + const tokenList = await getWeb3ProviderForNetwork(coinObj.network) + .getVerusBridgeDelegatorContract() + .callStatic.getTokenList(0, 0); + + for (const tokenInfo of tokenList) { + const [iAddrBytesHex, contractAddr, nftTokenId, flags] = tokenInfo; + + const iAddrBytes = Buffer.from(iAddrBytesHex.substring(2), 'hex'); + const iAddr = toBase58Check(iAddrBytes, 102); + + if (contractAddr === tokenContract && convertableCurrencies.includes(iAddr)) { + if (bridgeIAddress === iAddr) { + isBridge = true; + } + + mappedCurrencyIAddress = iAddr; + break; + } + } + + if (mappedCurrencyIAddress == null) { + throw new Error("Failed to find a currency mapped to sending erc20 token that can be converted"); + } + } else { + throw new Error("Cannot send to Verus network without a mapped currency"); + } + + const toEthAddress = (iAddr) => { + return "0x" + fromBase58Check(iAddr).hash.toString('hex'); + } + + // Turn the i-address you found into a hex address for bridge contract, this may not need to be done + // once bridge contract accepts serialization instead of JSON + const mappedCurrencyHexIAddress = toEthAddress(mappedCurrencyIAddress); + + // Calculate the flags for the reserve transfer + let flagsBN = RESERVE_TRANSFER_VALID; + let importToSource = false; + let reserveToReserve = false; + let isGateway = false; + let secondreserveid; + let destinationcurrency; + let converterDefinition; + + if (isConversion) { + flagsBN = flagsBN.xor(RESERVE_TRANSFER_CONVERT); + + if (isBridge) { + flagsBN = flagsBN.xor(RESERVE_TRANSFER_IMPORT_TO_SOURCE); + importToSource = true; + } + + if (via != null) { + flagsBN = flagsBN.xor(RESERVE_TRANSFER_RESERVE_TO_RESERVE); + reserveToReserve = true; + } + + // Get information about conversion currency to set destinationcurrency and potentially + // secondreserveid + const convertToRes = await getCurrency(systemId, convertto); + + if (convertToRes.error) throw new Error(convertToRes.error.message); + else { + const converterDefinition = convertToRes.result; + const finalDestinationCurrencyAddress = toEthAddress(converterDefinition.currencyid); + + if (via != null) { + secondreserveid = finalDestinationCurrencyAddress; + } + + if (importToSource) { + destinationcurrency = finalDestinationCurrencyAddress; + } else { + destinationcurrency = toEthAddress(bridgeIAddress); + } + } + } + + // Get the vETH hex address for the fee currency + const vEthHexAddress = toEthAddress(vEthIAddress); + let destinationtype, destinationaddress; + + const baseGasPrice = await Web3Provider.DefaultProvider.getGasPrice(); + const minGasPrice = ethers.BigNumber.from(MINIMUM_GAS_PRICE_WEI_DELEGATOR_CONTRACT); + const gasPriceModifier = ethers.BigNumber.from("5"); + const modifiedGasPrice = baseGasPrice.add(baseGasPrice.div(gasPriceModifier)); + let gasPrice; + + if (modifiedGasPrice.gte(minGasPrice)) { + gasPrice = modifiedGasPrice; + } else gasPrice = minGasPrice; + + const gasFeeWei = gasPrice.mul(ethers.BigNumber.from(GAS_TRANSACTION_IMPORT_FEE)); + const gasFeeString = ethers.utils.formatUnits( + gasFeeWei, + ETHERS + ); + const gasFeeSatsString = coinsToSats(BigNumber(gasFeeString)).toString(); + let approvalGasFee = ethers.BigNumber.from("0"); + + if (address.isETHAccount()) { + // Manually construct a CTransferDestination (remove when switching to serialized method from JSON) + const destAddrBytes = address.destination_bytes; + + const destination = new TransferDestination({ + type: DEST_ETH.xor(FLAG_DEST_GATEWAY).xor(FLAG_DEST_AUX), + destination_bytes: destAddrBytes, + gateway_id: vEthIAddress, + gateway_code: toBase58Check(Buffer.from('0x0000000000000000000000000000000000000000', 'hex'), 102), + fees: new BN(gasFeeSatsString), + aux_dests: [ + new TransferDestination({ + type: DEST_PKH, + destination_bytes: fromBase58Check(refundAddress).hash + }) + ] + }) + + isGateway = true; + destinationtype = destination.type.toNumber(); + destinationaddress = '0x' + destination.toBuffer().toString('hex').slice(4); + } else { + destinationtype = address.type.toNumber(); + destinationaddress = '0x' + address.destination_bytes.toString('hex'); + } + + const reserveTransfer = { + version: 1, + currencyvalue: { currency: mappedCurrencyHexIAddress, amount: satoshis }, + flags: flagsBN.toNumber(), + feecurrencyid: vEthHexAddress, + fees: ETH_VERUS_BRIDGE_CONTRACT_RESERVE_TRANSFER_FEE, + destcurrencyid: destinationcurrency, + secondreserveid, + destsystemid: ETH_VERUS_BRIDGE_DEST_SYSTEM_ID, + destination: { destinationtype, destinationaddress } + } + + const baseFee = ethers.BigNumber.from("3000000000000000"); + + let ethValueForContract = baseFee; + + if (isGateway) { + ethValueForContract = ethValueForContract.add(gasFeeWei); + } + + if (coinObj.currency_id === ETH_CONTRACT_ADDRESS) { + ethValueForContract = ethValueForContract.add( + ethers.BigNumber.from(satoshis).mul(ethers.BigNumber.from('10000000000')), + ); + } + + let transferGas; + + try { + transferGas = coinObj.currency_id !== ETH_CONTRACT_ADDRESS + ? ethers.BigNumber.from('0') + : await delegatorContract.estimateGas.sendTransfer(reserveTransfer, { + from: fromAddress, + gasLimit: INITIAL_GAS_LIMIT, + value: ethValueForContract.toString(), + }); + } catch(e) { + transferGas = ethers.BigNumber.from(FALLBACK_GAS_BRIDGE_TRANSFER); + } + + let gasEst = + coinObj.currency_id !== ETH_CONTRACT_ADDRESS + ? ethers.BigNumber.from(ERC20_BRIDGE_TRANSFER_GAS_LIMIT) + : transferGas.add(transferGas.div(gasPriceModifier)); + + if (coinObj.currency_id !== ETH_CONTRACT_ADDRESS) { + const contract = Web3Provider.getContract(coinObj.currency_id).connect(signer); + + approvalGasFee = (await contract.estimateGas.approve( + delegatorContract.address, + coinsToUnits(satsToCoins(BigNumber(satoshis)), coinObj.decimals).toString(), + { from: fromAddress, gasLimit: INITIAL_GAS_LIMIT }, + )) + approvalGasFee = approvalGasFee.add(approvalGasFee.div(gasPriceModifier)); + gasEst = gasEst.add(approvalGasFee) + } + + const gasLimit = gasEst; + + const maxTotalFee = (gasLimit.mul(gasPrice)).add(gasFeeWei).add(baseFee); + + const ethBalance = coinsToSats(await getStandardEthBalance(fromAddress, Web3Provider.network)); + + const maxTotalFeeSatoshis = coinsToSats(BigNumber(ethers.utils.formatEther(maxTotalFee))); + const satoshisBn = BigNumber(satoshis); + + if (maxTotalFeeSatoshis.plus(satoshisBn).isGreaterThan(ethBalance)) { + if (coinObj.currency_id === ETH_CONTRACT_ADDRESS && satoshisBn.isGreaterThan(0) && fallbackSubtractBalance) { + const newSatoshis = satoshisBn.minus(maxTotalFeeSatoshis); + + if (newSatoshis.isGreaterThan(0)) { + const modifiedOutput = { + ...output, + satoshis: newSatoshis.toString() + } + return preflightBridgeTransfer(coinObj, channelId, activeUser, modifiedOutput, false, satoshisBn.toString()); + } + } + + throw new Error("Wallet does not contain enough ETH to pay maximum fee of " + (ethers.utils.formatEther(maxTotalFee))); + } + + if (coinObj.currency_id === ETH_CONTRACT_ADDRESS) { + // Try making sure the tx can go through + await delegatorContract.callStatic.sendTransfer( + reserveTransfer, + {from: fromAddress, gasLimit: gasLimit.toNumber(), value: ethValueForContract.toString() }, + ); + } + + // result: { + // output, + // validation, + // hex: fundRes.result.hex, + // names: friendlyNames, + // deltas, + // source, + // inputs, + // converterdef: output.convertto ? currencyDefs.get(output.convertto) : output.convertto, + // submittedsats: submittedAmount, + // estimate: conversionEstimate + // }, + + const names = new Map( + coinObj.currency_id === ETH_CONTRACT_ADDRESS + ? [[ETH_CONTRACT_ADDRESS, 'ETH']] + : [ + [ETH_CONTRACT_ADDRESS, 'ETH'], + [coinObj.currency_id, coinObj.display_ticker], + ], + ); + + const validation = { + valid: true, + fees: { [ETH_CONTRACT_ADDRESS]: maxTotalFeeSatoshis.toString() }, + sent: { [coinObj.currency_id]: satoshis } + } + + const deltas = new Map(); + + for (const key in validation.sent) { + if (deltas.has(key)) deltas.set(key, deltas.get(key).minus(BigNumber(validation.sent[key]))) + else deltas.set(key, BigNumber(validation.sent[key]).multipliedBy(BigNumber(-1))) + } + + for (const key in validation.fees) { + if (deltas.has(key)) deltas.set(key, deltas.get(key).minus(BigNumber(validation.fees[key]))) + else deltas.set(key, BigNumber(validation.fees[key]).multipliedBy(BigNumber(-1))) + } + + return { + err: false, + result: { + output, + validation, + names, + deltas, + source: fromAddress, + submittedsats: submittedAmount, + converterdef: converterDefinition, + estimate: null, + approvalparams: coinObj.currency_id === ETH_CONTRACT_ADDRESS ? null : [ + delegatorContract.address, + coinsToUnits(satsToCoins(BigNumber(satoshis)), coinObj.decimals).toString(), + { from: fromAddress, gasLimit: approvalGasFee.toNumber() } + ], + transferparams: [ + reserveTransfer, + {from: fromAddress, gasLimit: gasLimit.sub(approvalGasFee).toNumber(), value: ethValueForContract.toString() } + ], + gasprice: gasPrice.toString() + }, + }; + } catch(e) { + console.error(e) + return { err: true, result: e.message diff --git a/src/utils/api/channels/erc20/requests/send.js b/src/utils/api/channels/erc20/requests/send.js index aa9626e1..4070b7e5 100644 --- a/src/utils/api/channels/erc20/requests/send.js +++ b/src/utils/api/channels/erc20/requests/send.js @@ -3,13 +3,13 @@ import { getWeb3ProviderForNetwork } from '../../../../web3/provider' import { ERC20, ETH } from "../../../../constants/intervalConstants" import { scientificToDecimal } from "../../../../math" import { requestPrivKey } from "../../../../auth/authBox" -import { ETHERS } from "../../../../constants/web3Constants" +import { ETHERS, ETH_CONTRACT_ADDRESS } from "../../../../constants/web3Constants" export const send = async (coinObj, activeUser, address, amount, params) => { try { const Web3Provider = getWeb3ProviderForNetwork(coinObj.network) - const privKey = await requestPrivKey(coinObj.id, ERC20) + const privKey = await requestPrivKey(coinObj.id, ERC20); const contract = Web3Provider.getContract(coinObj.currency_id) const gasPrice = await Web3Provider.DefaultProvider.getGasPrice() const amountBn = ethers.utils.parseUnits(scientificToDecimal(amount.toString()), coinObj.decimals) @@ -53,4 +53,52 @@ export const send = async (coinObj, activeUser, address, amount, params) => { result: e.message.includes('processing response error') ? "Error creating transaction" : e.message } } +} + +export const sendBridgeTransfer = async (coinObj, [reserveTransfer, transferOptions], approvalParams, maxGasPrice) => { + try { + const Web3Provider = getWeb3ProviderForNetwork(coinObj.network); + + const privKey = await requestPrivKey(coinObj.id, coinObj.proto); + const signer = new ethers.Wallet(ethers.utils.hexlify(privKey), Web3Provider.DefaultProvider); + const gasPrice = await Web3Provider.DefaultProvider.getGasPrice(); + const maxGasPriceBn = ethers.BigNumber.from(maxGasPrice); + + const delegatorContract = Web3Provider.getVerusBridgeDelegatorContract().connect(signer); + + if (gasPrice.gt(maxGasPriceBn)) { + throw new Error("Current gas price exceeds maximum confirmed value, try re-entering form data and sending again.") + } + + if (coinObj.currency_id !== ETH_CONTRACT_ADDRESS) { + const [delegatorAddress, approvalAmount, approvalOptions] = approvalParams + const contract = Web3Provider.getContract(coinObj.currency_id).connect(signer); + + const approval = await contract.approve(delegatorContract.address, approvalAmount, approvalOptions); + const reply = await approval.wait(); + + if (reply.status === 0) { + throw new Error("Authorising ERC20 token spend failed, please check your balance."); + } + } + + const response = await delegatorContract.sendTransfer( + reserveTransfer, + transferOptions + ); + + return { + err: false, + result: { + txid: response.hash + }, + }; + } catch(e) { + console.error(e) + + return { + err: true, + result: e.message + } + } } \ No newline at end of file diff --git a/src/utils/api/channels/vrpc/requests/getCurrencyConversionPaths.js b/src/utils/api/channels/vrpc/requests/getCurrencyConversionPaths.js index 12b2e6c0..02a8df11 100644 --- a/src/utils/api/channels/vrpc/requests/getCurrencyConversionPaths.js +++ b/src/utils/api/channels/vrpc/requests/getCurrencyConversionPaths.js @@ -2,7 +2,6 @@ import { ethers } from "ethers"; import VrpcProvider from "../../../../vrpc/vrpcInterface" import { getCurrenciesMappedToEth } from "./getCurrenciesMappedToEth"; import { ETH_BRIDGE_NAME, ETH_CONTRACT_ADDRESS, VETH } from "../../../../constants/web3Constants"; -import { DEST_ETH } from "verus-typescript-primitives"; import { getWeb3ProviderForNetwork } from "../../../../web3/provider"; export const getCurrencyConversionPaths = async (systemId, src, ethNetwork) => { @@ -97,6 +96,7 @@ export const getCurrencyConversionPaths = async (systemId, src, ethNetwork) => { symbol, decimals, name, + mapto: convertableCurrencyDefinition }, price: priceData.lastconversionprice, gateway: true, @@ -111,6 +111,7 @@ export const getCurrencyConversionPaths = async (systemId, src, ethNetwork) => { symbol: "ETH", decimals: 18, name: "Ethereum", + mapto: convertableCurrencyDefinition }, price: priceData.lastconversionprice, gateway: true, @@ -126,11 +127,12 @@ export const getCurrencyConversionPaths = async (systemId, src, ethNetwork) => { } else if (convertableCurrencyId !== currencyid) { // If you're convertable and not the bridge, you can convert to either the bridge (above) // or any of the bridge reserve currencies except for yourself via the bridge - const priceData = convertableCurrencyStates[convertableCurrencyId]; + const destPriceData = convertableCurrencyStates[convertableCurrencyId]; + const rootPriceData = convertableCurrencyStates[currencyid]; - const viapriceinroot = 1 / priceData.lastconversionprice; - const destpriceinvia = priceData.lastconversionprice; - const price = 1 / (viapriceinroot/destpriceinvia); + const viapriceinroot = 1 / rootPriceData.lastconversionprice; + const destpriceinvia = destPriceData.lastconversionprice; + const price = viapriceinroot*destpriceinvia; paths[convertableCurrencyId].push({ destination: convertableCurrencyDefinition, @@ -160,6 +162,7 @@ export const getCurrencyConversionPaths = async (systemId, src, ethNetwork) => { symbol, decimals, name, + mapto: convertableCurrencyDefinition }, price, gateway: true, @@ -174,6 +177,7 @@ export const getCurrencyConversionPaths = async (systemId, src, ethNetwork) => { symbol: "ETH", decimals: 18, name: "Ethereum", + mapto: convertableCurrencyDefinition }, price, gateway: true, @@ -248,7 +252,7 @@ export const getCurrencyConversionPaths = async (systemId, src, ethNetwork) => { address: ETH_CONTRACT_ADDRESS, symbol: "ETH", decimals: 18, - name: "Ethereum", + name: "Ethereum" }, exportto: vEthCurrencyDefinition, price: 1, @@ -266,7 +270,7 @@ export const getCurrencyConversionPaths = async (systemId, src, ethNetwork) => { address: contractAddress, symbol, decimals, - name, + name }, exportto: vEthCurrencyDefinition, price: 1, diff --git a/src/utils/api/channels/vrpc/requests/preflight.js b/src/utils/api/channels/vrpc/requests/preflight.js index eebac2a2..e8381f28 100644 --- a/src/utils/api/channels/vrpc/requests/preflight.js +++ b/src/utils/api/channels/vrpc/requests/preflight.js @@ -124,6 +124,7 @@ export const validateCurrencyTransferOutputParams = obj => { 'burn', 'burnweight', 'mintnew', + 'mapto' ]; const allKeys = [...requiredKeys, ...optionalKeys]; diff --git a/src/utils/api/routers/getIdentity.js b/src/utils/api/routers/getIdentity.js new file mode 100644 index 00000000..ff91ed67 --- /dev/null +++ b/src/utils/api/routers/getIdentity.js @@ -0,0 +1,32 @@ +// systemId, iAddressOrName, height, txproof, txproofheight +import { + ETH, + ERC20, + VRPC, +} from "../../constants/intervalConstants"; +import { getWeb3ProviderForNetwork } from "../../web3/provider"; +import { getIdentity as vrpcGetIdentity } from "../channels/verusid/callCreators"; + +const IDENTITY_FUNCTION_MAP = { + [ETH]: (coinObj, activeUser, channelId, iAddressOrName, height, txproof, txproofheight) => { + const Web3Provider = getWeb3ProviderForNetwork(coinObj.network); + return vrpcGetIdentity(Web3Provider.getVrscSystem(), iAddressOrName, height, txproof, txproofheight); + }, + [ERC20]: (coinObj, activeUser, channelId, iAddressOrName, height, txproof, txproofheight) => { + const Web3Provider = getWeb3ProviderForNetwork(coinObj.network); + return vrpcGetIdentity(Web3Provider.getVrscSystem(), iAddressOrName, height, txproof, txproofheight); + }, + [VRPC]: (coinObj, activeUser, channelId, iAddressOrName, height, txproof, txproofheight) => { + const [channelName, iAddress, systemId] = channelId.split('.'); + + return vrpcGetIdentity(systemId, iAddressOrName, height, txproof, txproofheight); + } +}; + +export const getIdentity = async (coinObj, activeUser, channelId, iAddressOrName, height, txproof, txproofheight) => { + const parentChannel = channelId.split('.')[0] + + if (IDENTITY_FUNCTION_MAP[parentChannel] == null) + throw new Error(`No get identity function available for channel ${channelId}`); + else return await IDENTITY_FUNCTION_MAP[parentChannel](coinObj, activeUser, channelId, iAddressOrName, height, txproof, txproofheight); +}; diff --git a/src/utils/api/routers/preflightConvertOrCrossChain.js b/src/utils/api/routers/preflightConvertOrCrossChain.js new file mode 100644 index 00000000..d2912e0d --- /dev/null +++ b/src/utils/api/routers/preflightConvertOrCrossChain.js @@ -0,0 +1,21 @@ +import { + ETH, + ERC20, + VRPC, +} from "../../constants/intervalConstants"; +import * as erc20 from "../channels/erc20/callCreator"; +import * as vrpc from "../channels/vrpc/callCreators"; + +const PREFLIGHT_FUNCTION_MAP = { + [ETH]: erc20.preflightBridgeTransfer, + [ERC20]: erc20.preflightBridgeTransfer, + [VRPC]: vrpc.preflightCurrencyTransfer +}; + +export const preflightConvertOrCrossChain = async (coinObj, activeUser, channelId, output) => { + const parentChannel = channelId.split('.')[0] + + if (PREFLIGHT_FUNCTION_MAP[parentChannel] == null) + throw new Error(`No preflight function available for channel ${channelId}`); + else return await PREFLIGHT_FUNCTION_MAP[parentChannel](coinObj, channelId, activeUser, output); +}; diff --git a/src/utils/api/routers/sendConvertOrCrossChain.js b/src/utils/api/routers/sendConvertOrCrossChain.js new file mode 100644 index 00000000..b23f6adc --- /dev/null +++ b/src/utils/api/routers/sendConvertOrCrossChain.js @@ -0,0 +1,27 @@ +import { + ETH, + ERC20, + VRPC, +} from "../../constants/intervalConstants"; +import * as erc20 from "../channels/erc20/callCreator"; +import * as vrpc from "../channels/vrpc/callCreators"; + +const SEND_FUNCTION_MAP = { + [ETH]: (coinObj, channelId, activeUser, { approvalparams, transferparams, gasprice }) => { + return erc20.sendBridgeTransfer(coinObj, transferparams, approvalparams, gasprice); + }, + [ERC20]: (coinObj, channelId, activeUser, { approvalparams, transferparams, gasprice }) => { + return erc20.sendBridgeTransfer(coinObj, transferparams, approvalparams, gasprice); + }, + [VRPC]: (coinObj, channelId, activeUser, { hex, inputs }) => { + return vrpc.sendCurrencyTransfer(coinObj, channelId, hex, inputs); + } +}; + +export const sendConvertOrCrossChain = async (coinObj, activeUser, channelId, params) => { + const parentChannel = channelId.split('.')[0] + + if (SEND_FUNCTION_MAP[parentChannel] == null) + throw new Error(`No send function available for channel ${channelId}`); + else return await SEND_FUNCTION_MAP[parentChannel](coinObj, channelId, activeUser, params); +}; diff --git a/src/utils/constants/web3Constants.js b/src/utils/constants/web3Constants.js index 7540eb53..96fe5a0f 100644 --- a/src/utils/constants/web3Constants.js +++ b/src/utils/constants/web3Constants.js @@ -11,4 +11,17 @@ export const VERUS_BRIDGE_DELEGATOR_GOERLI_CONTRACT = "0x85a7dE2278E52327471e174 export const ETH_BRIDGE_NAME = "Bridge.vETH" export const VETH = 'vETH' -export const ETH_CONTRACT_ADDRESS = "0x0000000000000000000000000000000000000000" \ No newline at end of file +export const DAI_VETH = 'DAI.VETH' +export const ETH_CONTRACT_ADDRESS = "0x0000000000000000000000000000000000000000" + +export const ETH_VERUS_BRIDGE_CONTRACT_RESERVE_TRANSFER_FEE = 300000 +export const ETH_VERUS_BRIDGE_DEST_SYSTEM_ID = "0x0000000000000000000000000000000000000000" + +export const MINIMUM_GAS_PRICE_WEI_DELEGATOR_CONTRACT = "10000000000" +export const DELEGATOR_CONTRACT_GAS_MODIFIER = "1.2" + +export const GAS_TRANSACTION_IMPORT_FEE = "1000000" + +export const INITIAL_GAS_LIMIT = 6000000 +export const FALLBACK_GAS_BRIDGE_TRANSFER = 800000 +export const ERC20_BRIDGE_TRANSFER_GAS_LIMIT = 800000 \ No newline at end of file diff --git a/src/utils/math.js b/src/utils/math.js index 414ece1c..134521c8 100644 --- a/src/utils/math.js +++ b/src/utils/math.js @@ -14,7 +14,7 @@ export const unitsToCoins = (units, decimals) => { } export const coinsToSats = (coins) => { - return coinsToUnits(coins, 8) + return BigNumber(coinsToUnits(coins, 8).toFixed(0)) } export const satsToCoins = (satoshis) => { From c9712e318fa86bed6eb27d06b1500980792d7257 Mon Sep 17 00:00:00 2001 From: michaeltout Date: Fri, 15 Sep 2023 18:09:52 +0200 Subject: [PATCH 09/42] Fix undefined var "path" bug in ConvertOrCrossChainSendForm --- .../ConvertOrCrossChainSendForm.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js b/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js index a943fa4b..767f2c0e 100644 --- a/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js +++ b/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js @@ -346,10 +346,10 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor return flatPaths.filter(x => { if (isConversion) { const destinationCurrency = sendModal.data[SEND_MODAL_CONVERTTO_FIELD]; - const destinationCurrencyMatch = !path.ethdest && (x.destination.currencyid === destinationCurrency || + const destinationCurrencyMatch = !x.ethdest && (x.destination.currencyid === destinationCurrency || destinationCurrency.toLowerCase() === x.destination.fullyqualifiedname.toLowerCase()) - const destinationTokenMatch = path.ethdest && (x.destination.address === destinationCurrency || + const destinationTokenMatch = x.ethdest && (x.destination.address === destinationCurrency || destinationCurrency.toLowerCase() === x.destination.address.toLowerCase()) From fd470f10aea3b2767d13b04696f1bed6574eaf4d Mon Sep 17 00:00:00 2001 From: michaeltout Date: Fri, 15 Sep 2023 18:32:01 +0200 Subject: [PATCH 10/42] Fix bug with invalid VerusQR invoice creation for VRSC/VRSCTEST --- src/utils/verusPay/versions/v0.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/verusPay/versions/v0.js b/src/utils/verusPay/versions/v0.js index 45765105..f53d2911 100644 --- a/src/utils/verusPay/versions/v0.js +++ b/src/utils/verusPay/versions/v0.js @@ -5,10 +5,10 @@ const readVerusPay = (data) => { } const writeVerusPayQR = (coinObj, amount, address, note) => { - const { currency_id, system_id, display_name, display_ticker } = coinObj; + const { id, system_id, display_name, display_ticker } = coinObj; return writeRequest( - currency_id, + id, system_id, display_name, display_ticker, From 10139fe82ef0f4b0e234409918686f41b4c592dd Mon Sep 17 00:00:00 2001 From: michaeltout Date: Mon, 18 Sep 2023 15:18:35 +0200 Subject: [PATCH 11/42] Add indicator for mapped currencies --- src/VerusMobile.js | 2 +- src/containers/Coin/DynamicHeader.js | 39 +++++++++++++-- .../Home/HomeWidgets/CurrencyWidget.js | 13 ++++- src/utils/CoinData/CoinDirectory.js | 49 ++++++++++++++++++- .../asyncStore/contractDefinitionStorage.js | 4 ++ src/utils/constants/web3Constants.js | 2 +- 6 files changed, 101 insertions(+), 8 deletions(-) diff --git a/src/VerusMobile.js b/src/VerusMobile.js index aa75a429..d1c6e059 100644 --- a/src/VerusMobile.js +++ b/src/VerusMobile.js @@ -113,8 +113,8 @@ class VerusMobile extends React.Component { await removeInactiveCurrencyDefinitions(usedCoins) await removeInactiveContractDefinitions(usedCoins) - await CoinDirectory.populatePbaasCurrencyDefinitionsFromStorage() await CoinDirectory.populateEthereumContractDefinitionsFromStorage() + await CoinDirectory.populatePbaasCurrencyDefinitionsFromStorage() return initCache() }) diff --git a/src/containers/Coin/DynamicHeader.js b/src/containers/Coin/DynamicHeader.js index 33326524..94e91ceb 100644 --- a/src/containers/Coin/DynamicHeader.js +++ b/src/containers/Coin/DynamicHeader.js @@ -12,7 +12,8 @@ import SnapCarousel from '../../components/SnapCarousel'; import { API_GET_BALANCES, API_GET_FIATPRICE, - API_GET_INFO + API_GET_INFO, + ERC20 } from '../../utils/constants/intervalConstants'; import Colors from '../../globals/colors'; import {Card, Avatar, Paragraph, Text} from 'react-native-paper'; @@ -25,6 +26,7 @@ import {CONNECTION_ERROR} from '../../utils/api/errors/errorMessages'; import {truncateDecimal} from '../../utils/math'; import {USD} from '../../utils/constants/currencies'; import { CoinDirectory } from '../../utils/CoinData/CoinDirectory'; +import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; class DynamicHeader extends Component { constructor(props) { @@ -32,7 +34,8 @@ class DynamicHeader extends Component { this.state = { carouselItems: [], currentIndex: 0, - loadingCarouselItems: true + loadingCarouselItems: true, + mappedCoinObj: null }; this.fadeAnimation = new Animated.Value(0); @@ -41,9 +44,17 @@ class DynamicHeader extends Component { componentDidMount() { this.fadeIn(); + let mappedCoinObj; + + if (this.props.activeCoin.mapped_to != null) { + try { + mappedCoinObj = CoinDirectory.getBasicCoinObj(this.props.activeCoin.mapped_to) + } catch(e) {} + } this.setState({ - loadingCarouselItems: true + loadingCarouselItems: true, + mappedCoinObj }, () => { this.setState({ carouselItems: this.prepareCarouselItems( @@ -309,6 +320,27 @@ class DynamicHeader extends Component { ) } + { + this.props.pendingBalance.isEqualTo(0) && + this.props.activeCoin.mapped_to != null && + this.state.mappedCoinObj != null && ( + + + { + ` mapped to ${this.state.mappedCoinObj.display_ticker}${ + this.state.mappedCoinObj.proto === ERC20 ? ' (ERC20)' : '' + }` + } + + + ) + } { return { chainTicker, displayTicker: state.coins.activeCoin.display_ticker, + activeCoin: state.coins.activeCoin, selectedSubWallet: state.coinMenus.activeSubWallets[chainTicker], allSubWallets: state.coinMenus.allSubWallets[chainTicker], balances: extractLedgerData( diff --git a/src/containers/Home/HomeWidgets/CurrencyWidget.js b/src/containers/Home/HomeWidgets/CurrencyWidget.js index 7cae0b5a..fca9635b 100644 --- a/src/containers/Home/HomeWidgets/CurrencyWidget.js +++ b/src/containers/Home/HomeWidgets/CurrencyWidget.js @@ -10,6 +10,8 @@ import {formatCurrency} from 'react-native-format-currency'; import SubWalletsLogo from '../../../images/customIcons/SubWallets.svg'; import {extractDisplaySubWallets} from '../../../utils/subwallet/extractSubWallets'; import {normalizeNum} from '../../../utils/normalizeNum'; +import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; +import Colors from '../../../globals/colors'; const CurrencyWidget = props => { const {currencyBalance, coinObj} = props; @@ -101,7 +103,7 @@ const CurrencyWidget = props => { fontSize: 16, marginLeft: 8, fontWeight: 'bold', - maxWidth: 90, + maxWidth: 80, }} numberOfLines={1}> {displayedName} @@ -120,6 +122,15 @@ const CurrencyWidget = props => { {allSubwallets[coinObj.id] ? allSubwallets[coinObj.id].length : 1} + { + coinObj.mapped_to != null && ( + + ) + } x.currency_id === contractAddress); + if (mappedCoin != null) { + mappedCurrencyId = mappedCoin.id; + } else { + const network = isTestnet ? 'goerli' : 'homestead' + const contract = getWeb3ProviderForNetwork( + network, + ).getUnitializedContractInstance(contractAddress); + + const name = await contract.name(); + const symbol = await contract.symbol(); + const decimals = await contract.decimals(); + + this.addErc20Token({ address: contractAddress, symbol, name, decimals }, network, true); + mappedCurrencyId = contractAddress; + } + } + } + } + } catch(e) { + console.warn(e) + } + try { const systemObj = this.findCoinObj(system, null, true); endpoints = systemObj.vrpc_endpoints; @@ -219,7 +263,8 @@ class _CoinDirectory { decimals: DEFAULT_DECIMALS, seconds_per_block: secondsPerBlock, default_app: 'wallet', - apps: VERUS_APPS + apps: VERUS_APPS, + mapped_to: mappedCurrencyId } if (storeResults) { diff --git a/src/utils/asyncStore/contractDefinitionStorage.js b/src/utils/asyncStore/contractDefinitionStorage.js index 4c423429..d3d8b497 100644 --- a/src/utils/asyncStore/contractDefinitionStorage.js +++ b/src/utils/asyncStore/contractDefinitionStorage.js @@ -41,6 +41,10 @@ export const removeInactiveContractDefinitions = async (activeCoinList) => { const activeCoinIds = {} activeCoinList.map(coin => { activeCoinIds[coin.id] = true + + if (coin.mapped_to != null) { + activeCoinIds[coin.mapped_to] = true + } }) for (const network in storedDefinitions) { diff --git a/src/utils/constants/web3Constants.js b/src/utils/constants/web3Constants.js index 96fe5a0f..0fd46f77 100644 --- a/src/utils/constants/web3Constants.js +++ b/src/utils/constants/web3Constants.js @@ -7,7 +7,7 @@ export const STABLECOIN_DECIMALS = 6 // Non-ERC20 utility contracts export const RFOX_UTILITY_CONTRACT = "0xD82F7e3956d3FF391C927Cd7d0A7A57C360DF5b9" -export const VERUS_BRIDGE_DELEGATOR_GOERLI_CONTRACT = "0x85a7dE2278E52327471e174AeeB280cdFdC6A68a" +export const VERUS_BRIDGE_DELEGATOR_GOERLI_CONTRACT = "0x85a7de2278e52327471e174aeeb280cdfdc6a68a" export const ETH_BRIDGE_NAME = "Bridge.vETH" export const VETH = 'vETH' From 9a5a9fd367a69ade4b9a662aeeeb54af86201aa5 Mon Sep 17 00:00:00 2001 From: michaeltout Date: Mon, 18 Sep 2023 17:41:19 +0200 Subject: [PATCH 12/42] Fix bug with 0 subwallets on ETH mapped pbaas currencies --- src/utils/CoinData/CoinsList.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/utils/CoinData/CoinsList.js b/src/utils/CoinData/CoinsList.js index 9b214a66..c7d27e47 100644 --- a/src/utils/CoinData/CoinsList.js +++ b/src/utils/CoinData/CoinsList.js @@ -59,6 +59,7 @@ export const VERUS_APPS = { export const coinsList = { VRSC: { id: 'VRSC', + system_options: 264, currency_id: 'i5w5MuNik5NtLcYmNzcvaoixooEebB6MGV', system_id: 'i5w5MuNik5NtLcYmNzcvaoixooEebB6MGV', bitgojs_network_key: 'verus', @@ -82,6 +83,7 @@ export const coinsList = { }, VRSCTEST: { testnet: true, + system_options: 264, mainnet_id: 'VRSC', id: 'VRSCTEST', pbaas_options: 264, From d89a39988b98dae2c4886301baa6d573f755ebc6 Mon Sep 17 00:00:00 2001 From: michaeltout Date: Tue, 19 Sep 2023 17:06:21 +0200 Subject: [PATCH 13/42] Add accomodation for using ETH bridge contract while bridge is in prelaunch phase, fix bug with erroneous "Connection Error" display, add ERC20 address to "mapped to" display --- .../wallet/dispatchers/UpdateLedgerValue.js | 3 +- src/containers/Coin/DynamicHeader.js | 16 ++++++++-- .../api/channels/erc20/requests/preflight.js | 32 +++++++++++++------ src/utils/constants/web3Constants.js | 2 ++ 4 files changed, 40 insertions(+), 13 deletions(-) diff --git a/src/actions/actions/wallet/dispatchers/UpdateLedgerValue.js b/src/actions/actions/wallet/dispatchers/UpdateLedgerValue.js index 13846782..ae6d7146 100644 --- a/src/actions/actions/wallet/dispatchers/UpdateLedgerValue.js +++ b/src/actions/actions/wallet/dispatchers/UpdateLedgerValue.js @@ -1,6 +1,7 @@ import { DLIGHT_PRIVATE } from '../../../../utils/constants/intervalConstants' import { getCoinObj } from '../../../../utils/CoinData/CoinData' import { dlightEnabled } from '../../../../utils/enabledChannels'; +import { CoinDirectory } from '../../../../utils/CoinData/CoinDirectory'; /** * Fetches the appropriate data from the store for the specified channel's type @@ -24,7 +25,7 @@ export const updateLedgerValue = async ( fetchChannels ) => { const activeUser = state.authentication.activeAccount; - const coinObj = getCoinObj(state.coins.activeCoinsForUser, chainTicker); + const coinObj = CoinDirectory.findCoinObj(chainTicker, activeUser.id); let channelsPassed = []; const channelMap = fetchChannels(activeUser) diff --git a/src/containers/Coin/DynamicHeader.js b/src/containers/Coin/DynamicHeader.js index 94e91ceb..58c7f0ce 100644 --- a/src/containers/Coin/DynamicHeader.js +++ b/src/containers/Coin/DynamicHeader.js @@ -333,8 +333,20 @@ class DynamicHeader extends Component { }}> { - ` mapped to ${this.state.mappedCoinObj.display_ticker}${ - this.state.mappedCoinObj.proto === ERC20 ? ' (ERC20)' : '' + ` mapped to ${ + this.state.mappedCoinObj.display_ticker.length > 15 + ? this.state.mappedCoinObj.display_ticker.substring(0, 15) + '...' + : this.state.mappedCoinObj.display_ticker + }${ + this.state.mappedCoinObj.proto === ERC20 + ? ` (${ + this.state.mappedCoinObj.currency_id.substring(0, 5) + + '...' + + this.state.mappedCoinObj.currency_id.substring( + this.state.mappedCoinObj.currency_id.length - 5, + ) + })` + : '' }` } diff --git a/src/utils/api/channels/erc20/requests/preflight.js b/src/utils/api/channels/erc20/requests/preflight.js index 8e75f6c3..5f9e8bf4 100644 --- a/src/utils/api/channels/erc20/requests/preflight.js +++ b/src/utils/api/channels/erc20/requests/preflight.js @@ -16,6 +16,8 @@ import { INITIAL_GAS_LIMIT, ERC20_BRIDGE_TRANSFER_GAS_LIMIT, FALLBACK_GAS_BRIDGE_TRANSFER, + NULL_ETH_ADDRESS, + ETH_VERUS_BRIDGE_CONTRACT_PRELAUNCH_RESERVE_TRANSFER_FEE, } from '../../../../constants/web3Constants'; import { getCurrency, getIdentity } from "../../verusid/callCreators" import { getSystemNameFromSystemId } from "../../../../CoinData/CoinData" @@ -95,7 +97,6 @@ export const preflightBridgeTransfer = async (coinObj, channelId, activeUser, ou try { const Web3Provider = getWeb3ProviderForNetwork(coinObj.network); const fromAddress = activeUser.keys[coinObj.id][coinObj.proto === 'erc20' ? ERC20 : ETH].addresses[0]; - const refundAddress = activeUser.keys[coinObj.id][VRPC].addresses[0]; const submittedAmount = amountSubmitted == null ? output.satoshis : amountSubmitted; const signer = new ethers.VoidSigner(fromAddress, Web3Provider.DefaultProvider); @@ -105,6 +106,8 @@ export const preflightBridgeTransfer = async (coinObj, channelId, activeUser, ou const systemName = getSystemNameFromSystemId(systemId); const tokenContract = coinObj.currency_id; + const pastPrelaunch = await delegatorContract.callStatic.bridgeConverterActive(); + const { currency, convertto, @@ -140,6 +143,10 @@ export const preflightBridgeTransfer = async (coinObj, channelId, activeUser, ou mappedCurrencyIAddress = mappedCurrencyRes.result.currencyid; } } else if (isConversion) { + if (!pastPrelaunch) { + throw new Error("Cannot make conversions while bridge is in pre-launch phase.") + } + // If mapto is undefined, assume conversion and look for which convertable // currency is mapped to the current erc20 address const convertableCurrencies = [ @@ -148,9 +155,7 @@ export const preflightBridgeTransfer = async (coinObj, channelId, activeUser, ou vEthIAddress, toIAddress(DAI_VETH, systemName) ]; - const tokenList = await getWeb3ProviderForNetwork(coinObj.network) - .getVerusBridgeDelegatorContract() - .callStatic.getTokenList(0, 0); + const tokenList = await delegatorContract.callStatic.getTokenList(0, 0); for (const tokenInfo of tokenList) { const [iAddrBytesHex, contractAddr, nftTokenId, flags] = tokenInfo; @@ -192,6 +197,10 @@ export const preflightBridgeTransfer = async (coinObj, channelId, activeUser, ou let destinationcurrency; let converterDefinition; + // Get the vETH hex address for the fee currency + const vEthHexAddress = toEthAddress(vEthIAddress); + const vrscHexAddress = toEthAddress(systemId); + if (isConversion) { flagsBN = flagsBN.xor(RESERVE_TRANSFER_CONVERT); @@ -224,10 +233,11 @@ export const preflightBridgeTransfer = async (coinObj, channelId, activeUser, ou destinationcurrency = toEthAddress(bridgeIAddress); } } + } else if (!pastPrelaunch) { + destinationcurrency = vrscHexAddress; + secondreserveid = NULL_ETH_ADDRESS; } - // Get the vETH hex address for the fee currency - const vEthHexAddress = toEthAddress(vEthIAddress); let destinationtype, destinationaddress; const baseGasPrice = await Web3Provider.DefaultProvider.getGasPrice(); @@ -249,6 +259,8 @@ export const preflightBridgeTransfer = async (coinObj, channelId, activeUser, ou let approvalGasFee = ethers.BigNumber.from("0"); if (address.isETHAccount()) { + const refundAddress = activeUser.keys[coinObj.id][VRPC].addresses[0]; + // Manually construct a CTransferDestination (remove when switching to serialized method from JSON) const destAddrBytes = address.destination_bytes; @@ -256,7 +268,7 @@ export const preflightBridgeTransfer = async (coinObj, channelId, activeUser, ou type: DEST_ETH.xor(FLAG_DEST_GATEWAY).xor(FLAG_DEST_AUX), destination_bytes: destAddrBytes, gateway_id: vEthIAddress, - gateway_code: toBase58Check(Buffer.from('0x0000000000000000000000000000000000000000', 'hex'), 102), + gateway_code: toBase58Check(Buffer.from(NULL_ETH_ADDRESS, 'hex'), 102), fees: new BN(gasFeeSatsString), aux_dests: [ new TransferDestination({ @@ -278,8 +290,8 @@ export const preflightBridgeTransfer = async (coinObj, channelId, activeUser, ou version: 1, currencyvalue: { currency: mappedCurrencyHexIAddress, amount: satoshis }, flags: flagsBN.toNumber(), - feecurrencyid: vEthHexAddress, - fees: ETH_VERUS_BRIDGE_CONTRACT_RESERVE_TRANSFER_FEE, + feecurrencyid: pastPrelaunch ? vEthHexAddress : vrscHexAddress, + fees: pastPrelaunch ? ETH_VERUS_BRIDGE_CONTRACT_RESERVE_TRANSFER_FEE : ETH_VERUS_BRIDGE_CONTRACT_PRELAUNCH_RESERVE_TRANSFER_FEE, destcurrencyid: destinationcurrency, secondreserveid, destsystemid: ETH_VERUS_BRIDGE_DEST_SYSTEM_ID, @@ -338,7 +350,7 @@ export const preflightBridgeTransfer = async (coinObj, channelId, activeUser, ou const ethBalance = coinsToSats(await getStandardEthBalance(fromAddress, Web3Provider.network)); const maxTotalFeeSatoshis = coinsToSats(BigNumber(ethers.utils.formatEther(maxTotalFee))); - const satoshisBn = BigNumber(satoshis); + const satoshisBn = coinObj.currency_id === ETH_CONTRACT_ADDRESS ? BigNumber(satoshis) : BigNumber(0); if (maxTotalFeeSatoshis.plus(satoshisBn).isGreaterThan(ethBalance)) { if (coinObj.currency_id === ETH_CONTRACT_ADDRESS && satoshisBn.isGreaterThan(0) && fallbackSubtractBalance) { diff --git a/src/utils/constants/web3Constants.js b/src/utils/constants/web3Constants.js index 0fd46f77..c2d1fbb7 100644 --- a/src/utils/constants/web3Constants.js +++ b/src/utils/constants/web3Constants.js @@ -12,9 +12,11 @@ export const VERUS_BRIDGE_DELEGATOR_GOERLI_CONTRACT = "0x85a7de2278e52327471e174 export const ETH_BRIDGE_NAME = "Bridge.vETH" export const VETH = 'vETH' export const DAI_VETH = 'DAI.VETH' +export const NULL_ETH_ADDRESS = '0x0000000000000000000000000000000000000000' export const ETH_CONTRACT_ADDRESS = "0x0000000000000000000000000000000000000000" export const ETH_VERUS_BRIDGE_CONTRACT_RESERVE_TRANSFER_FEE = 300000 +export const ETH_VERUS_BRIDGE_CONTRACT_PRELAUNCH_RESERVE_TRANSFER_FEE = 2000000 export const ETH_VERUS_BRIDGE_DEST_SYSTEM_ID = "0x0000000000000000000000000000000000000000" export const MINIMUM_GAS_PRICE_WEI_DELEGATOR_CONTRACT = "10000000000" From 3c9095200d7c1ef9b45a57f1b6c39756ad565455 Mon Sep 17 00:00:00 2001 From: michaeltout Date: Thu, 21 Sep 2023 15:17:53 +0200 Subject: [PATCH 14/42] Add ability to add ERC20 token through mapped PBaaS currency --- .../AddErc20TokenForm/AddErc20TokenForm.js | 39 ++++++++++++++++--- .../AddErc20TokenForm.render.js | 26 +++++++++++-- 2 files changed, 56 insertions(+), 9 deletions(-) diff --git a/src/components/SendModal/AddErc20Token/AddErc20TokenForm/AddErc20TokenForm.js b/src/components/SendModal/AddErc20Token/AddErc20TokenForm/AddErc20TokenForm.js index e29a2a4d..42eb3ed0 100644 --- a/src/components/SendModal/AddErc20Token/AddErc20TokenForm/AddErc20TokenForm.js +++ b/src/components/SendModal/AddErc20Token/AddErc20TokenForm/AddErc20TokenForm.js @@ -1,4 +1,4 @@ -import {useCallback} from 'react'; +import {useCallback, useState} from 'react'; import {useSelector, useDispatch} from 'react-redux'; import {Alert} from 'react-native'; import {createAlert} from '../../../../actions/actions/alert/dispatchers/alert'; @@ -8,11 +8,14 @@ import { } from '../../../../utils/constants/sendModal'; import {getWeb3ProviderForNetwork} from '../../../../utils/web3/provider'; import {AddErc20TokenFormRender} from './AddErc20TokenForm.render'; +import { getCurrency } from '../../../../utils/api/channels/verusid/callCreators'; const AddErc20TokenForm = props => { const dispatch = useDispatch(); const sendModal = useSelector(state => state.sendModal); + const [useMappedCurrency, setUseMappedCurrency] = useState(false); + const formHasError = useCallback(() => { const {data} = sendModal; @@ -38,12 +41,36 @@ const AddErc20TokenForm = props => { const {coinObj, data} = sendModal; - const contractAddress = data[SEND_MODAL_CONTRACT_ADDRESS_FIELD]; + const contractAddressField = data[SEND_MODAL_CONTRACT_ADDRESS_FIELD]; try { - const contract = getWeb3ProviderForNetwork( + const provider = getWeb3ProviderForNetwork( coinObj.network, - ).getUnitializedContractInstance(contractAddress); + ) + + let contractAddress; + + if (useMappedCurrency) { + const getCurrencyRes = await getCurrency(provider.getVrscSystem(), contractAddressField); + + if (getCurrencyRes.error) { + throw new Error(getCurrencyRes.error.message); + } + + const currencyDef = getCurrencyRes.result; + + const isMapped = currencyDef.proofprotocol === 3 && currencyDef.nativecurrencyid != null; + + if (!isMapped) { + throw new Error("Cannot get ERC20 contract from non-mapped PBaaS currency.") + } + + contractAddress = currencyDef.nativecurrencyid.address; + } else { + contractAddress = contractAddressField; + } + + const contract = provider.getUnitializedContractInstance(contractAddress); const name = await contract.name(); const symbol = await contract.symbol(); @@ -67,12 +94,14 @@ const AddErc20TokenForm = props => { } props.setLoading(false); - }, [formHasError, sendModal, dispatch, props]); + }, [formHasError, sendModal, dispatch, props, useMappedCurrency]); return AddErc20TokenFormRender({ submitData, updateSendFormData: props.updateSendFormData, formDataValue: sendModal.data[SEND_MODAL_CONTRACT_ADDRESS_FIELD], + useMappedCurrency, + setUseMappedCurrency }); }; diff --git a/src/components/SendModal/AddErc20Token/AddErc20TokenForm/AddErc20TokenForm.render.js b/src/components/SendModal/AddErc20Token/AddErc20TokenForm/AddErc20TokenForm.render.js index 8d0a0ff9..78521e25 100644 --- a/src/components/SendModal/AddErc20Token/AddErc20TokenForm/AddErc20TokenForm.render.js +++ b/src/components/SendModal/AddErc20Token/AddErc20TokenForm/AddErc20TokenForm.render.js @@ -1,10 +1,17 @@ import React from "react"; import { ScrollView, View, TouchableWithoutFeedback, Keyboard } from "react-native"; -import { TextInput, Button } from "react-native-paper"; +import { TextInput, Button, Checkbox } from "react-native-paper"; import Styles from "../../../../styles"; import { SEND_MODAL_CONTRACT_ADDRESS_FIELD } from "../../../../utils/constants/sendModal"; +import Colors from "../../../../globals/colors"; -export const AddErc20TokenFormRender = ({submitData, updateSendFormData, formDataValue}) => { +export const AddErc20TokenFormRender = ({ + submitData, + updateSendFormData, + formDataValue, + useMappedCurrency, + setUseMappedCurrency +}) => { return ( @@ -30,7 +37,18 @@ export const AddErc20TokenFormRender = ({submitData, updateSendFormData, formDat autoCorrect={false} /> - + + setUseMappedCurrency(!useMappedCurrency)} + mode="android" + /> + + From ffcd392afd1f5f79b8d8d553324871d07f5f2a3c Mon Sep 17 00:00:00 2001 From: michaeltout Date: Sat, 23 Sep 2023 14:26:27 +0200 Subject: [PATCH 15/42] Add address blocklist settings --- .../ConvertOrCrossChainSendForm.js | 6 + .../TraditionalCryptoSendForm.js | 8 +- .../MainStackScreens/MainStackScreens.js | 9 + .../AddressBlocklist/AddressBlocklist.js | 189 ++++++++++++++++++ .../AddressBlocklist.render.js | 72 +++++++ .../GeneralWalletSettings.js | 16 +- .../Settings/WalletSettings/WalletSettings.js | 15 ++ src/reducers/settings.js | 10 +- src/utils/addressBlocklist.js | 9 + src/utils/constants/constants.js | 7 +- src/utils/constants/sendModal.js | 3 +- 11 files changed, 334 insertions(+), 10 deletions(-) create mode 100644 src/containers/Settings/WalletSettings/AddressBlocklist/AddressBlocklist.js create mode 100644 src/containers/Settings/WalletSettings/AddressBlocklist/AddressBlocklist.render.js create mode 100644 src/utils/addressBlocklist.js diff --git a/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js b/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js index 767f2c0e..d45063cd 100644 --- a/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js +++ b/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js @@ -44,12 +44,15 @@ import { coinsList } from "../../../../utils/CoinData/CoinsList"; import { ETH_CONTRACT_ADDRESS } from "../../../../utils/constants/web3Constants"; import { preflightConvertOrCrossChain } from "../../../../utils/api/routers/preflightConvertOrCrossChain"; import { getIdentity } from "../../../../utils/api/routers/getIdentity"; +import { addressIsBlocked } from "../../../../utils/addressBlocklist"; +import selectAddressBlocklist from "../../../../selectors/settings"; const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFormData, navigation }) => { const sendModal = useSelector(state => state.sendModal); const activeUser = useSelector(state => state.authentication.activeAccount); const addresses = useSelector(state => selectAddresses(state)); const activeAccount = useSelector(state => state.authentication.activeAccount); + const addressBlocklist = useSelector(selectAddressBlocklist); const networkName = useSelector(state => { try { const subwallet = state.sendModal.subWallet; @@ -764,6 +767,9 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor if (!toAddress || toAddress.length < 1) { createAlert("Required Field", "Address is a required field."); return true; + } else if (addressIsBlocked(toAddress, addressBlocklist)) { + createAlert("Blocked Address", "The address you are trying to send to is included in your address blocklist."); + return true; } if (amount == null) { diff --git a/src/components/SendModal/TraditionalCryptoSend/TraditionalCryptoSendForm/TraditionalCryptoSendForm.js b/src/components/SendModal/TraditionalCryptoSend/TraditionalCryptoSendForm/TraditionalCryptoSendForm.js index 5729a0c5..259d11c5 100644 --- a/src/components/SendModal/TraditionalCryptoSend/TraditionalCryptoSendForm/TraditionalCryptoSendForm.js +++ b/src/components/SendModal/TraditionalCryptoSend/TraditionalCryptoSendForm/TraditionalCryptoSendForm.js @@ -14,10 +14,13 @@ import Colors from "../../../../globals/colors"; import Styles from "../../../../styles"; import { useEffect } from "react"; import { CoinDirectory } from "../../../../utils/CoinData/CoinDirectory"; +import selectAddressBlocklist from "../../../../selectors/settings"; +import { addressIsBlocked } from "../../../../utils/addressBlocklist"; const TraditionalCryptoSendForm = ({ setLoading, setModalHeight, updateSendFormData, navigation }) => { const [amountFiat, setAmountFiat] = useState(false); const sendModal = useSelector(state => state.sendModal); + const addressBlocklist = useSelector(selectAddressBlocklist); const balances = useSelector(state => { const chainTicker = state.sendModal.coinObj.id; const balance_channel = state.sendModal.subWallet.api_channels[API_GET_BALANCES]; @@ -136,11 +139,14 @@ const TraditionalCryptoSendForm = ({ setLoading, setModalHeight, updateSendFormD const toAddress = data[SEND_MODAL_TO_ADDRESS_FIELD] != null ? data[SEND_MODAL_TO_ADDRESS_FIELD].trim() : ""; - const amount = getProcessedAmount() + const amount = getProcessedAmount(); if (!toAddress || toAddress.length < 1) { createAlert("Required Field", "Address is a required field."); return true; + } else if (addressIsBlocked(toAddress, addressBlocklist)) { + createAlert("Blocked Address", "The address you are trying to send to is included in your address blocklist."); + return true; } if (amount == null) { diff --git a/src/containers/RootStack/MainStackScreens/MainStackScreens.js b/src/containers/RootStack/MainStackScreens/MainStackScreens.js index 585454c9..21e0bfbd 100644 --- a/src/containers/RootStack/MainStackScreens/MainStackScreens.js +++ b/src/containers/RootStack/MainStackScreens/MainStackScreens.js @@ -15,6 +15,7 @@ import CoinSettings from '../../Settings/WalletSettings/CoinSettings/CoinSetting import DeleteProfile from '../../Settings/ProfileSettings/DeleteProfile/DeleteProfile' import SecureLoading from '../../SecureLoading/SecureLoading' import HomeTabScreens from '../HomeTabScreens/HomeTabScreens'; +import AddressBlocklist from '../../Settings/WalletSettings/AddressBlocklist/AddressBlocklist'; const MainStack = createStackNavigator(); @@ -90,6 +91,14 @@ const MainStackScreens = props => { }} /> + + this.editBlockedAddress({ address: blockedAddress, details: '', lastModified: Math.floor(Date.now() / 1000) }, index)) + } + + editBlockedAddress(address, index) { + let addressBlocklist = this.state.addressBlocklistSettings.addressBlocklist ? this.state.addressBlocklistSettings.addressBlocklist : [] + + if (index == null) { + this.updateBlockedAddressList([...addressBlocklist, address]) + } else { + addressBlocklist[index] = address + this.updateBlockedAddressList(addressBlocklist) + } + } + + removeBlockedAddress(index) { + let addressBlocklist = this.state.addressBlocklistSettings.addressBlocklist ? this.state.addressBlocklistSettings.addressBlocklist : [] + addressBlocklist.splice(index, 1); + this.updateBlockedAddressList(addressBlocklist) + } + + openAddBlockedAddressModal() { + this.setState({ + addBlockedAddressModal: { + open: true, + label: "Block Address", + index: null + } + }) + } + + updateBlockedAddressList(list) { + this.setState( + { addressBlocklistSettings: { ...this.state.addressBlocklistSettings, addressBlocklist: list }, loading: true }, + async () => { + try { + await this.saveSettings() + } catch(e) { + Alert.alert('Error', e.message) + } + + this.setState({ loading: false }); + } + ); + } + + async saveSettings() { + const stateChanges = { + addressBlocklistDefinition: this.state.addressBlocklistSettings.addressBlocklistDefinition, + addressBlocklist: this.state.addressBlocklistSettings.addressBlocklist, + }; + + const res = await saveGeneralSettings(stateChanges); + this.props.dispatch(res); + }; + + loadSettings() { + this.setState({ + addressBlocklistSettings: { + addressBlocklist: this.props.settings.addressBlocklist == null ? [] : this.props.settings.addressBlocklist, + addressBlocklistDefinition: this.props.settings.addressBlocklistDefinition == null ? { + type: ADDRESS_BLOCKLIST_MANUAL, + data: null + } : this.props.settings.addressBlocklistDefinition + } + }) + } + + render() { + return AddressBlocklistRender.call(this); + } +} + +const mapStateToProps = (state) => { + return { + activeAccount: state.authentication.activeAccount, + settings: state.settings.generalWalletSettings + } +}; + +export default connect(mapStateToProps)(AddressBlocklist); \ No newline at end of file diff --git a/src/containers/Settings/WalletSettings/AddressBlocklist/AddressBlocklist.render.js b/src/containers/Settings/WalletSettings/AddressBlocklist/AddressBlocklist.render.js new file mode 100644 index 00000000..227d4e91 --- /dev/null +++ b/src/containers/Settings/WalletSettings/AddressBlocklist/AddressBlocklist.render.js @@ -0,0 +1,72 @@ +import React from "react"; +import { SafeAreaView, ScrollView } from "react-native"; +import { Divider, List, Portal } from "react-native-paper"; +import ListSelectionModal from "../../../../components/ListSelectionModal/ListSelectionModal"; +import TextInputModal from "../../../../components/TextInputModal/TextInputModal"; +import Styles from "../../../../styles"; +import { unixToDate } from "../../../../utils/math"; + +export const AddressBlocklistRender = function () { + return ( + + + + {this.state.addBlockedAddressModal.open && ( + {}} + cancel={(text) => + this.finishEditBlockedAddress( + text, + this.state.addBlockedAddressModal.index + ) + } + /> + )} + {this.state.editPropertyModal.open && ( + this.selectEditPropertyButton(item.key)} + data={this.EDIT_PROPERTY_BUTTONS} + cancel={() => this.closeEditPropertyModal()} + /> + )} + + {"Blocked Addresses"} + + {this.state.addressBlocklistSettings.addressBlocklist.map((blockedAddress, index) => { + return ( + + ( + + )} + onPress={() => + this.openEditPropertyModal( + `Address ${index + 1}`, + index + ) + } + /> + + + ); + }) + } + } + onPress={() => this.openAddBlockedAddressModal()} + /> + + + + ); +}; diff --git a/src/containers/Settings/WalletSettings/GeneralWalletSettings/GeneralWalletSettings.js b/src/containers/Settings/WalletSettings/GeneralWalletSettings/GeneralWalletSettings.js index 08164c41..e74d1415 100644 --- a/src/containers/Settings/WalletSettings/GeneralWalletSettings/GeneralWalletSettings.js +++ b/src/containers/Settings/WalletSettings/GeneralWalletSettings/GeneralWalletSettings.js @@ -26,6 +26,7 @@ import ListSelectionModal from '../../../../components/ListSelectionModal/ListSe import {saveGeneralSettings} from '../../../../actions/actionCreators'; import {createAlert} from '../../../../actions/actions/alert/dispatchers/alert'; import {NavigationActions} from '@react-navigation/compat'; +import { ADDRESS_BLOCKLIST_MANUAL } from '../../../../utils/constants/constants'; const NO_DEFAULT = 'None'; @@ -89,11 +90,18 @@ const WalletSettings = props => { maxTxCount: Number(settings.maxTxCount), displayCurrency: settings.displayCurrency, defaultAccount: - settings.defaultAccount === NO_DEFAULT - ? null - : settings.defaultAccount, + settings.defaultAccount === NO_DEFAULT ? null : settings.defaultAccount, homeCardDragDetection, - ackedCurrencyDisclaimer: settings.ackedCurrencyDisclaimer + ackedCurrencyDisclaimer: settings.ackedCurrencyDisclaimer, + addressBlocklistDefinition: + settings.addressBlocklistDefinition == null + ? { + type: ADDRESS_BLOCKLIST_MANUAL, + data: null, + } + : settings.addressBlocklistDefinition, + addressBlocklist: + settings.addressBlocklist == null ? [] : settings.addressBlocklist, }; const res = await saveGeneralSettings(stateToSave); dispatch(res); diff --git a/src/containers/Settings/WalletSettings/WalletSettings.js b/src/containers/Settings/WalletSettings/WalletSettings.js index 43bb0c74..d33d9e47 100644 --- a/src/containers/Settings/WalletSettings/WalletSettings.js +++ b/src/containers/Settings/WalletSettings/WalletSettings.js @@ -24,6 +24,7 @@ import { RenderSquareCoinLogo } from "../../../utils/CoinData/Graphics"; const GENERAL_WALLET_SETTINGS = "GeneralWalletSettings" const COIN_SETTINGS = "CoinSettings" +const ADDRESS_BLOCKLIST = "AddressBlocklist" class WalletSettings extends Component { constructor(props) { @@ -111,6 +112,20 @@ class WalletSettings extends Component { /> + this._openSettings(ADDRESS_BLOCKLIST)} + > + ( + + )} + right={(props) => ( + + )} + /> + + {"Wallet Actions"} this.clearCache()}> diff --git a/src/reducers/settings.js b/src/reducers/settings.js index 7417d281..d53d97f2 100644 --- a/src/reducers/settings.js +++ b/src/reducers/settings.js @@ -3,7 +3,7 @@ user-decided app settings. */ -import { MAX_VERIFICATION, DEFAULT_PRIVATE_ADDRS } from '../utils/constants/constants' +import { MAX_VERIFICATION, DEFAULT_PRIVATE_ADDRS, ADDRESS_BLOCKLIST_MANUAL } from '../utils/constants/constants' import { SET_COIN_LIST, SET_ALL_SETTINGS, @@ -14,7 +14,6 @@ import { } from '../utils/constants/storeType' import { DLIGHT_PRIVATE } from '../utils/constants/intervalConstants' import { USD } from '../utils/constants/currencies' -import { Platform } from 'react-native' export const settings = (state = { btcFeesAdvanced: false, @@ -27,7 +26,12 @@ export const settings = (state = { displayCurrency: USD, defaultAccount: null, homeCardDragDetection: false, - ackedCurrencyDisclaimer: false + ackedCurrencyDisclaimer: false, + addressBlocklistDefinition: { + type: ADDRESS_BLOCKLIST_MANUAL, + data: null + }, + addressBlocklist: [] }, buySellSettings: {}, //e.g. {user1': {buySellEnabled: true, wyreData: {}}, 'user2: {buySellEnabled: false, wyreData: {}}} coinSettings: {}, //e.g. {VRSC: {verificationLvl: 2, verificationLock: false, channels: ['dlight', 'electrum', 'general'], privateAddrs: 100}} diff --git a/src/utils/addressBlocklist.js b/src/utils/addressBlocklist.js new file mode 100644 index 00000000..2e7b5b10 --- /dev/null +++ b/src/utils/addressBlocklist.js @@ -0,0 +1,9 @@ +export const addressIsBlocked = (address, blockList) => { + for (const blockedAddress of blockList) { + if (blockedAddress.address.toLowerCase() === address.toLowerCase()) { + return true; + } + } + + return false; +} \ No newline at end of file diff --git a/src/utils/constants/constants.js b/src/utils/constants/constants.js index d4d54b84..b1eca276 100644 --- a/src/utils/constants/constants.js +++ b/src/utils/constants/constants.js @@ -187,4 +187,9 @@ export const ONE_HOUR = 60; export const ONE_MONTH = 31 * 24 * 60; export const ONE_YEAR = 365 * 24 * 60; export const KOMODO_DIVISOR = 10512000; -export const KOMODO_N_S7_HARDFORK_HEIGHT = 3484958; \ No newline at end of file +export const KOMODO_N_S7_HARDFORK_HEIGHT = 3484958; + +// Address block list +export const ADDRESS_BLOCKLIST_FROM_WEBSERVER = 'ADDRESS_BLOCKLIST_FROM_WEBSERVER'; +export const ADDRESS_BLOCKLIST_FROM_VERUSID = 'ADDRESS_BLOCKLIST_FROM_VERUSID'; +export const ADDRESS_BLOCKLIST_MANUAL = 'ADDRESS_BLOCKLIST_MANUAL'; \ No newline at end of file diff --git a/src/utils/constants/sendModal.js b/src/utils/constants/sendModal.js index 3090b7a5..0f2663cf 100644 --- a/src/utils/constants/sendModal.js +++ b/src/utils/constants/sendModal.js @@ -3,6 +3,8 @@ export const SEND_MODAL = 'send_modal' // Send field IDs export const SEND_MODAL_TO_ADDRESS_FIELD = 'SEND_MODAL_TO_ADDRESS_FIELD' export const SEND_MODAL_DESTINATION_FIELD = 'SEND_MODAL_DESTINATION_FIELD' +export const SEND_MODAL_CONTRACT_ADDRESS_FIELD = 'SEND_MODAL_CONTRACT_ADDRESS_FIELD' + export const SEND_MODAL_SOURCE_FIELD = 'SEND_MODAL_SOURCE_FIELD' export const SEND_MODAL_MEMO_FIELD = 'SEND_MODAL_MEMO_FIELD' export const SEND_MODAL_AMOUNT_FIELD = 'SEND_MODAL_AMOUNT_FIELD' @@ -24,7 +26,6 @@ export const SEND_MODAL_SHOW_VIA_FIELD = 'SEND_MODAL_SHOW_VIA_FIELD' export const SEND_MODAL_SHOW_EXPORTTO_FIELD = 'SEND_MODAL_SHOW_EXPORTTO_FIELD' export const SEND_MODAL_SHOW_MAPPING_FIELD = 'SEND_MODAL_SHOW_MAPPING_FIELD' export const SEND_MODAL_ADVANCED_FORM = 'SEND_MODAL_ADVANCED_FORM' -export const SEND_MODAL_CONTRACT_ADDRESS_FIELD = 'SEND_MODAL_CONTRACT_ADDRESS_FIELD' // Send modal types export const TRADITIONAL_CRYPTO_SEND_MODAL = 'TRADITIONAL_CRYPTO_SEND_MODAL' From 8fe185b5cd471072af646a854ebf8e89406001f8 Mon Sep 17 00:00:00 2001 From: michaeltout Date: Sat, 23 Sep 2023 14:27:55 +0200 Subject: [PATCH 16/42] Add address blocklist selector --- src/selectors/settings.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 src/selectors/settings.js diff --git a/src/selectors/settings.js b/src/selectors/settings.js new file mode 100644 index 00000000..535dc7aa --- /dev/null +++ b/src/selectors/settings.js @@ -0,0 +1,15 @@ +import { createSelector } from 'reselect'; + +const getAddressBlocklist = state => + state.settings.generalWalletSettings.addressBlocklist + ? state.settings.generalWalletSettings.addressBlocklist + : []; + +export const selectAddressBlocklist = createSelector( + [getAddressBlocklist], + (addressBlocklist) => { + return addressBlocklist + } +); + +export default selectAddressBlocklist; From 8b7af83c6d1f46df48923dede5da0367d4d3e9e8 Mon Sep 17 00:00:00 2001 From: michaeltout Date: Sun, 24 Sep 2023 17:04:30 +0200 Subject: [PATCH 17/42] Add check for address type to send and convert/cross chain forms --- .../ConvertOrCrossChainSendForm.js | 8 +++++++- src/utils/api/channels/vrpc/requests/preflight.js | 8 +++++++- src/utils/constants/constants.js | 6 +++++- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js b/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js index d45063cd..577ba762 100644 --- a/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js +++ b/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js @@ -46,6 +46,7 @@ import { preflightConvertOrCrossChain } from "../../../../utils/api/routers/pref import { getIdentity } from "../../../../utils/api/routers/getIdentity"; import { addressIsBlocked } from "../../../../utils/addressBlocklist"; import selectAddressBlocklist from "../../../../selectors/settings"; +import { I_ADDRESS_VERSION, R_ADDRESS_VERSION } from "../../../../utils/constants/constants"; const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFormData, navigation }) => { const sendModal = useSelector(state => state.sendModal); @@ -836,10 +837,15 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor }) } else { const { hash, version } = fromBase58Check(keyhash); + let type; + + if (version === R_ADDRESS_VERSION) type = DEST_PKH; + else if (version === I_ADDRESS_VERSION) type = DEST_ID; + else throw new Error("Incompatible address type."); return new TransferDestination({ destination_bytes: hash, - type: version === 60 ? DEST_PKH : DEST_ID + type }) } } diff --git a/src/utils/api/channels/vrpc/requests/preflight.js b/src/utils/api/channels/vrpc/requests/preflight.js index e8381f28..5bf3289d 100644 --- a/src/utils/api/channels/vrpc/requests/preflight.js +++ b/src/utils/api/channels/vrpc/requests/preflight.js @@ -14,6 +14,7 @@ import { IS_FRACTIONAL_FLAG } from "../../../../constants/currencies"; import { unpackOutput } from "@bitgo/utxo-lib/dist/src/smart_transactions"; import { coinsList } from "../../../../CoinData/CoinsList"; import { getSendCurrencyTransaction } from "./getSendCurrencyTransaction"; +import { I_ADDRESS_VERSION, R_ADDRESS_VERSION } from "../../../../constants/constants"; const { createUnfundedCurrencyTransfer, validateFundedCurrencyTransfer } = smarttxs //TODO: Calculate fee for each coin seperately @@ -33,10 +34,15 @@ export const preflight = async (coinObj, activeUser, address, amount, params, ch } else keyhash = addr; const { hash, version } = fromBase58Check(keyhash); + let type; + + if (version === R_ADDRESS_VERSION) type = DEST_PKH; + else if (version === I_ADDRESS_VERSION) type = DEST_ID; + else throw new Error("Incompatible address type."); return new TransferDestination({ destination_bytes: hash, - type: version === 60 ? DEST_PKH : DEST_ID + type }) } diff --git a/src/utils/constants/constants.js b/src/utils/constants/constants.js index b1eca276..a064d554 100644 --- a/src/utils/constants/constants.js +++ b/src/utils/constants/constants.js @@ -192,4 +192,8 @@ export const KOMODO_N_S7_HARDFORK_HEIGHT = 3484958; // Address block list export const ADDRESS_BLOCKLIST_FROM_WEBSERVER = 'ADDRESS_BLOCKLIST_FROM_WEBSERVER'; export const ADDRESS_BLOCKLIST_FROM_VERUSID = 'ADDRESS_BLOCKLIST_FROM_VERUSID'; -export const ADDRESS_BLOCKLIST_MANUAL = 'ADDRESS_BLOCKLIST_MANUAL'; \ No newline at end of file +export const ADDRESS_BLOCKLIST_MANUAL = 'ADDRESS_BLOCKLIST_MANUAL'; + +// Address byte versions +export const R_ADDRESS_VERSION = 60; +export const I_ADDRESS_VERSION = 102; \ No newline at end of file From e7be1c72be190f3c0f274a6726258a09bdcf73c7 Mon Sep 17 00:00:00 2001 From: michaeltout Date: Sun, 24 Sep 2023 19:58:18 +0200 Subject: [PATCH 18/42] Process amount string in ConvertOrCrossChainSendForm to handle "," decimal separator correctly --- .../ConvertOrCrossChainSendForm.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js b/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js index 577ba762..60fba8d3 100644 --- a/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js +++ b/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js @@ -850,6 +850,8 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor } } + const amount = getProcessedAmount(); + let output = { currency: coinObj.currency_id, mapto: selectData(data[SEND_MODAL_MAPPING_FIELD]), @@ -857,7 +859,7 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor exportto: selectData(data[SEND_MODAL_EXPORTTO_FIELD]), via: selectData(data[SEND_MODAL_VIA_FIELD]), address: await selectAddress(data[SEND_MODAL_TO_ADDRESS_FIELD]), - satoshis: coinsToSats(BigNumber(data[SEND_MODAL_AMOUNT_FIELD])).toString(), + satoshis: coinsToSats(BigNumber(amount)).toString(), preconvert: selectData(data[SEND_MODAL_IS_PRECONVERT]), } From b898b58fcb3d7438087780d437bb9874ac5acced Mon Sep 17 00:00:00 2001 From: michaeltout Date: Sun, 24 Sep 2023 20:54:34 +0200 Subject: [PATCH 19/42] Add label to overview if tx is sent to bridge contract, and calculate refund addr for bounceback txs from ERC20/ETH pubkey --- src/containers/Coin/Overview/Overview.js | 19 +++++++++++++++++-- .../api/channels/erc20/requests/preflight.js | 11 ++++++++--- src/utils/constants/web3Constants.js | 1 + 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/containers/Coin/Overview/Overview.js b/src/containers/Coin/Overview/Overview.js index 647914b9..60b8e53a 100644 --- a/src/containers/Coin/Overview/Overview.js +++ b/src/containers/Coin/Overview/Overview.js @@ -27,10 +27,11 @@ import { API_GET_SERVICE_TRANSFERS, API_GET_SERVICE_RATES, IS_PBAAS_ROOT, - IS_PBAAS + IS_PBAAS, + ERC20 } from "../../../utils/constants/intervalConstants"; import { selectTransactions } from '../../../selectors/transactions'; -import { DEFAULT_DECIMALS, ETHERS } from "../../../utils/constants/web3Constants"; +import { DEFAULT_DECIMALS, ETHERS, VERUS_BRIDGE_DELEGATOR_GOERLI_CONTRACT, VERUS_BRIDGE_DELEGATOR_MAINNET_CONTRACT } from "../../../utils/constants/web3Constants"; import { Portal, List, Text, Badge } from "react-native-paper"; import BigNumber from "bignumber.js"; import { TransactionLogos } from '../../../images/customIcons/index' @@ -202,6 +203,20 @@ class Overview extends Component { } else if (item.type === "sent") { AvatarImg = TX_LOGOS.out; subtitle = item.address == null ? "??" : item.address; + + if ( + this.props.activeCoin.proto === ETH || + this.props.activeCoin.proto === ERC20 + ) { + if ( + (this.props.activeCoin.testnet && + subtitle.toLowerCase() === VERUS_BRIDGE_DELEGATOR_GOERLI_CONTRACT.toLowerCase()) || + (!this.props.activeCoin.testnet && + subtitle.toLowerCase() === VERUS_BRIDGE_DELEGATOR_MAINNET_CONTRACT.toLowerCase()) + ) { + subtitle = 'Verus-Ethereum Bridge Contract'; + } + } } else if (item.type === "self") { if (item.amount !== "??" && amount.isLessThan(0)) { subtitle = "me"; diff --git a/src/utils/api/channels/erc20/requests/preflight.js b/src/utils/api/channels/erc20/requests/preflight.js index 5f9e8bf4..472476de 100644 --- a/src/utils/api/channels/erc20/requests/preflight.js +++ b/src/utils/api/channels/erc20/requests/preflight.js @@ -42,6 +42,7 @@ import { requestPrivKey } from "../../../../auth/authBox"; import { BufferWriter } from "@bitgo/utxo-lib/dist/src/bufferutils"; import { BN } from "bn.js"; import { getStandardEthBalance } from "../../eth/callCreator"; +import { ECPair, networks } from "@bitgo/utxo-lib"; // TODO: Add balance recalculation with eth gas export const txPreflight = async (coinObj, activeUser, address, amount, params) => { @@ -96,7 +97,8 @@ export const txPreflight = async (coinObj, activeUser, address, amount, params) export const preflightBridgeTransfer = async (coinObj, channelId, activeUser, output, fallbackSubtractBalance = true, amountSubmitted = null) => { try { const Web3Provider = getWeb3ProviderForNetwork(coinObj.network); - const fromAddress = activeUser.keys[coinObj.id][coinObj.proto === 'erc20' ? ERC20 : ETH].addresses[0]; + const activeUserKeys = activeUser.keys[coinObj.id][coinObj.proto === 'erc20' ? ERC20 : ETH] + const fromAddress = activeUserKeys.addresses[0]; const submittedAmount = amountSubmitted == null ? output.satoshis : amountSubmitted; const signer = new ethers.VoidSigner(fromAddress, Web3Provider.DefaultProvider); @@ -163,7 +165,7 @@ export const preflightBridgeTransfer = async (coinObj, channelId, activeUser, ou const iAddrBytes = Buffer.from(iAddrBytesHex.substring(2), 'hex'); const iAddr = toBase58Check(iAddrBytes, 102); - if (contractAddr === tokenContract && convertableCurrencies.includes(iAddr)) { + if (contractAddr.toLowerCase() === tokenContract.toLowerCase() && convertableCurrencies.includes(iAddr)) { if (bridgeIAddress === iAddr) { isBridge = true; } @@ -259,7 +261,10 @@ export const preflightBridgeTransfer = async (coinObj, channelId, activeUser, ou let approvalGasFee = ethers.BigNumber.from("0"); if (address.isETHAccount()) { - const refundAddress = activeUser.keys[coinObj.id][VRPC].addresses[0]; + const refundAddress = ECPair.fromPublicKeyBuffer( + Buffer.from(activeUserKeys.pubKey, 'hex'), + networks.verus, + ).getAddress(); // Manually construct a CTransferDestination (remove when switching to serialized method from JSON) const destAddrBytes = address.destination_bytes; diff --git a/src/utils/constants/web3Constants.js b/src/utils/constants/web3Constants.js index c2d1fbb7..fbbb48de 100644 --- a/src/utils/constants/web3Constants.js +++ b/src/utils/constants/web3Constants.js @@ -8,6 +8,7 @@ export const STABLECOIN_DECIMALS = 6 export const RFOX_UTILITY_CONTRACT = "0xD82F7e3956d3FF391C927Cd7d0A7A57C360DF5b9" export const VERUS_BRIDGE_DELEGATOR_GOERLI_CONTRACT = "0x85a7de2278e52327471e174aeeb280cdfdc6a68a" +export const VERUS_BRIDGE_DELEGATOR_MAINNET_CONTRACT = null // TODO: Add mainnet contract export const ETH_BRIDGE_NAME = "Bridge.vETH" export const VETH = 'vETH' From 4e555d98eecf411e8ba5d6b80f8a2ec78287afbf Mon Sep 17 00:00:00 2001 From: michaeltout Date: Sun, 24 Sep 2023 20:55:59 +0200 Subject: [PATCH 20/42] Use constant for Bridge contract in CoinDirectory --- src/utils/CoinData/CoinDirectory.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/utils/CoinData/CoinDirectory.js b/src/utils/CoinData/CoinDirectory.js index 7c4927cf..c08003e2 100644 --- a/src/utils/CoinData/CoinDirectory.js +++ b/src/utils/CoinData/CoinDirectory.js @@ -4,7 +4,7 @@ import { electrumServers } from './electrum/servers'; import { ENABLE_VERUS_IDENTITIES } from '../../../env/index' import { VerusdRpcInterface } from "verusd-rpc-ts-client"; import { VERUS_APPS, coinsList } from "./CoinsList"; -import { DEFAULT_DECIMALS, VERUS_BRIDGE_DELEGATOR_GOERLI_CONTRACT } from "../constants/web3Constants"; +import { DEFAULT_DECIMALS, VERUS_BRIDGE_DELEGATOR_GOERLI_CONTRACT, VERUS_BRIDGE_DELEGATOR_MAINNET_CONTRACT } from "../constants/web3Constants"; import { getStoredCurrencyDefinitions, storeCurrencyDefinitionForCurrencyId } from "../asyncStore/currencyDefinitionStorage"; import { timeout } from "../promises"; import { getStoredContractDefinitions, storeContractDefinitionForNetwork } from "../asyncStore/contractDefinitionStorage"; @@ -145,7 +145,9 @@ class _CoinDirectory { currencyDefinition.proofprotocol === 3 && currencyDefinition.nativecurrencyid != null; let mappedCurrencyId = null; - const delegatorContractAddr = isTestnet ? VERUS_BRIDGE_DELEGATOR_GOERLI_CONTRACT : null; + const delegatorContractAddr = isTestnet + ? VERUS_BRIDGE_DELEGATOR_GOERLI_CONTRACT + : VERUS_BRIDGE_DELEGATOR_MAINNET_CONTRACT; try { if (isMapped) { From 453d5d39a677801455422970dc404cb3a1756638 Mon Sep 17 00:00:00 2001 From: michaeltout Date: Mon, 25 Sep 2023 13:10:13 +0200 Subject: [PATCH 21/42] Show ETH logo in coin details for custom ERC20 tokens --- src/components/CoinDetailsModal/CoinDetailsModal.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/CoinDetailsModal/CoinDetailsModal.js b/src/components/CoinDetailsModal/CoinDetailsModal.js index 0a77d191..78097e15 100644 --- a/src/components/CoinDetailsModal/CoinDetailsModal.js +++ b/src/components/CoinDetailsModal/CoinDetailsModal.js @@ -150,10 +150,11 @@ class CoinDetailsModal extends Component { theme_color, display_name, id, + proto, website } = data const ticker = id == null ? 'VRSC' : id - const Logo = getCoinLogo(ticker) + const Logo = getCoinLogo(ticker, proto) return ( Date: Mon, 25 Sep 2023 13:11:24 +0200 Subject: [PATCH 22/42] Show more accurate time estimate for Ethereum network bouncebacks --- .../ConvertOrCrossChainSendConfirm.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendConfirm/ConvertOrCrossChainSendConfirm.js b/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendConfirm/ConvertOrCrossChainSendConfirm.js index 598c9edc..c5c3924e 100644 --- a/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendConfirm/ConvertOrCrossChainSendConfirm.js +++ b/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendConfirm/ConvertOrCrossChainSendConfirm.js @@ -9,6 +9,8 @@ import { API_GET_FIATPRICE, API_GET_TRANSACTIONS, API_SEND, + ERC20, + ETH, } from '../../../../utils/constants/intervalConstants'; import { SEND_MODAL_FORM_STEP_FORM, @@ -248,11 +250,11 @@ function ConvertOrCrossChainSendConfirm({ { key: 'Estimated Time Until Arrival', data: - exportto != null + exportto != null || ((sendModal.coinObj.proto === ETH || sendModal.coinObj.proto === ERC20) && convertto != null) ? '20-30 minutes' : convertto != null - ? '2-10 minutes' - : '1-5 minutes', + ? '2-10 minutes' + : '1-5 minutes', numLines: 100, }, { From 4c933739d305ae89b7f0fedc6b28766785b8c3e0 Mon Sep 17 00:00:00 2001 From: michaeltout Date: Mon, 25 Sep 2023 13:17:31 +0200 Subject: [PATCH 23/42] Refactor AddCoin screen to fix FlatList height style issue --- src/containers/AddCoin/AddCoin.js | 228 +++++++++++++----------------- 1 file changed, 98 insertions(+), 130 deletions(-) diff --git a/src/containers/AddCoin/AddCoin.js b/src/containers/AddCoin/AddCoin.js index e4ed24c6..6fe407c9 100644 --- a/src/containers/AddCoin/AddCoin.js +++ b/src/containers/AddCoin/AddCoin.js @@ -5,70 +5,55 @@ qr. */ -import React, {Component} from 'react'; -import {SearchBar} from 'react-native-elements'; +import React, {useState, useEffect} from 'react'; import {FlatList, TouchableOpacity, View} from 'react-native'; -import {List, FAB} from 'react-native-paper'; -import {Searchbar, Portal} from 'react-native-paper'; -import {connect} from 'react-redux'; +import {List, Portal, Searchbar} from 'react-native-paper'; +import {useSelector} from 'react-redux'; import Styles from '../../styles/index'; import {RenderSquareCoinLogo} from '../../utils/CoinData/Graphics'; import CoinDetailsModal from '../../components/CoinDetailsModal/CoinDetailsModal'; import {WYRE_SERVICE} from '../../utils/constants/intervalConstants'; -import { CoinDirectory } from '../../utils/CoinData/CoinDirectory'; - -class AddCoin extends Component { - constructor(props) { - super(props); - this.state = { - loading: true, - error: null, - query: '', - coinList: [], - fullCoinDetails: null +import {CoinDirectory} from '../../utils/CoinData/CoinDirectory'; + +const AddCoin = props => { + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [query, setQuery] = useState(''); + const [coinList, setCoinList] = useState([]); + const [fullCoinDetails, setFullCoinDetails] = useState(null); + + const testAccount = useSelector( + state => + Object.keys(state.authentication.activeAccount.testnetOverrides).length > + 0, + ); + const activeAccount = useSelector( + state => state.authentication.activeAccount, + ); + const activeCoinsForUser = useSelector( + state => state.coins.activeCoinsForUser, + ); + const activeCoinList = useSelector(state => state.coins.activeCoinList); + + useEffect(() => { + setCoinList(getCoinList()); + }, [query, activeCoinsForUser]); + + useEffect(() => { + return () => { + if (props.route.params && props.route.params.refresh) { + props.route.params.refresh(); + } }; - } - - componentDidMount() { - this.setState({coinList: this.getCoinList()}); - } - - componentDidUpdate(lastProps, lastState) { - if (lastState.query !== this.state.query || this.props.activeCoinsForUser !== lastProps.activeCoinsForUser) { - this.setState({coinList: this.getCoinList()}); - } - } - - componentWillUnmount() { - if (this.props.route.params && this.props.route.params.refresh) { - this.props.route.params.refresh(); - } - } - - renderHeader = () => { - return ( - this.searchFilterFunction(text)} - autoCorrect={false} - /> - ); - }; - - _openDetails = item => { - this.setState({fullCoinDetails: item.coinObj}); - }; + }, []); - getCoinList = () => { - const {query} = this.state; - const displayedCoinList = this.props.testAccount + const getCoinList = () => { + const displayedCoinList = testAccount ? CoinDirectory.testCoinList - : this.props.activeAccount.disabledServices[WYRE_SERVICE] + : activeAccount.disabledServices[WYRE_SERVICE] ? CoinDirectory.enabledNameList : CoinDirectory.supportedCoinList; - const activeCoinIds = this.props.activeCoinsForUser.map( - coinObj => coinObj.id, - ); + const activeCoinIds = activeCoinsForUser.map(coinObj => coinObj.id); return displayedCoinList .map(x => { @@ -78,7 +63,7 @@ class AddCoin extends Component { }; }) .filter(item => { - const { coinObj } = item; + const {coinObj} = item; const queryLc = query.toLowerCase(); const coinIdLc = coinObj.id.toLowerCase(); const coinNameLc = coinObj.display_name.toLowerCase(); @@ -113,84 +98,67 @@ class AddCoin extends Component { }); }; - onEndReached = () => { - this.setState({loading: false}); + const onEndReached = () => { + setLoading(false); }; - render() { - const activeCoinIds = this.props.activeCoinsForUser.map( - coinObj => coinObj.id, - ); - - return ( - - - - this.setState({ - fullCoinDetails: null, - }) - } - visible={this.state.fullCoinDetails != null} - animationType="slide" - /> - - this.setState({query})} - value={this.state.query} - autoCorrect={false} - /> + const activeCoinIds = activeCoinsForUser.map(coinObj => coinObj.id); + + return ( + + + { - const { added, coinObj } = item - const {display_name, display_ticker} = coinObj; - - return ( - this._openDetails(item)}> - RenderSquareCoinLogo(coinObj.id)} - right={props => { - return added ? ( - - ) : null; - }} - style={{ - backgroundColor: 'white', - }} - /> - - ); - }} - keyExtractor={item => item.coinObj.id} + activeAccount={activeAccount} + activeCoinList={activeCoinList} + cancel={() => setFullCoinDetails(null)} + visible={fullCoinDetails != null} + animationType="slide" /> - - ); - } -} - -const mapStateToProps = state => { - return { - testAccount: Object.keys(state.authentication.activeAccount.testnetOverrides).length > 0, - activeAccount: state.authentication.activeAccount, - activeCoinsForUser: state.coins.activeCoinsForUser, - activeCoinList: state.coins.activeCoinList, - }; + + setQuery(text)} + value={query} + autoCorrect={false} + /> + } + style={{...Styles.fullWidth, ...Styles.backgroundColorWhite}} + data={coinList} + onEndReached={onEndReached} + onEndReachedThreshold={50} + renderItem={({item}) => { + const {added, coinObj} = item; + const {display_name, display_ticker} = coinObj; + + return ( + setFullCoinDetails(item.coinObj)}> + RenderSquareCoinLogo(coinObj.id)} + right={props => { + return added ? ( + + ) : null; + }} + style={{ + backgroundColor: 'white', + }} + /> + + ); + }} + keyExtractor={item => item.coinObj.id} + /> + + ); }; -export default connect(mapStateToProps)(AddCoin); +export default AddCoin; From 674b3754ee3aa4d355c8ee0cccad687b371e24c9 Mon Sep 17 00:00:00 2001 From: michaeltout Date: Tue, 26 Sep 2023 21:31:36 +0200 Subject: [PATCH 24/42] Add beginnings of preconvert service, add different types of address blocklists --- src/containers/Coin/SendCoin/SendCoin.js | 6 +- src/containers/Services/Service/Service.js | 4 +- .../PbaasPreconvertService.js | 73 +++++++++++ .../PbaasPreconvertService.render.js | 38 ++++++ .../PbaasPreconvertServiceOverview.js | 113 ++++++++++++++++++ .../AddressBlocklist/AddressBlocklist.js | 113 +++++++++++++++++- .../AddressBlocklist.render.js | 57 +++++++++ .../GeneralWalletSettings.js | 4 +- src/reducers/services.js | 4 +- src/reducers/settings.js | 4 +- src/utils/constants/services.js | 14 ++- 11 files changed, 414 insertions(+), 16 deletions(-) create mode 100644 src/containers/Services/ServiceComponents/PbaasPreconvertService/PbaasPreconvertService.js create mode 100644 src/containers/Services/ServiceComponents/PbaasPreconvertService/PbaasPreconvertService.render.js create mode 100644 src/containers/Services/ServiceComponents/PbaasPreconvertService/PbaasPreconvertServiceOverview/PbaasPreconvertServiceOverview.js diff --git a/src/containers/Coin/SendCoin/SendCoin.js b/src/containers/Coin/SendCoin/SendCoin.js index 23f49f06..bea85c93 100644 --- a/src/containers/Coin/SendCoin/SendCoin.js +++ b/src/containers/Coin/SendCoin/SendCoin.js @@ -84,8 +84,8 @@ const SendCoin = ({ navigation }) => { }, { key: 'preconvert', - title: 'Pre-convert', - description: `Participate in a currency pre-convert that accepts ${activeCoin.display_ticker}`, + title: 'Preconvert', + description: `Participate in a currency preconvert that accepts ${activeCoin.display_ticker}`, data: { [SEND_MODAL_TO_ADDRESS_FIELD]: '', [SEND_MODAL_AMOUNT_FIELD]: '', @@ -106,7 +106,7 @@ const SendCoin = ({ navigation }) => { { key: 'advanced', title: 'Advanced', - description: 'Send off-chain, convert, or pre-convert using an unguided form', + description: 'Send off-chain, convert, or preconvert using an unguided form', data: { [SEND_MODAL_TO_ADDRESS_FIELD]: '', [SEND_MODAL_AMOUNT_FIELD]: '', diff --git a/src/containers/Services/Service/Service.js b/src/containers/Services/Service/Service.js index 1678e0f3..83593652 100644 --- a/src/containers/Services/Service/Service.js +++ b/src/containers/Services/Service/Service.js @@ -3,9 +3,10 @@ */ import React, { Component } from "react" -import { VERUSID_SERVICE_ID, WYRE_SERVICE_ID } from "../../../utils/constants/services"; +import { PBAAS_PRECONVERT_SERVICE_ID, VERUSID_SERVICE_ID, WYRE_SERVICE_ID } from "../../../utils/constants/services"; import VerusIdService from "../ServiceComponents/VerusIdService/VerusIdService"; import WyreService from "../ServiceComponents/WyreService/WyreService"; +import PbaasPreconvertService from "../ServiceComponents/PbaasPreconvertService/PbaasPreconvertService"; class Service extends Component { constructor(props) { @@ -17,6 +18,7 @@ class Service extends Component { this.SERVICE_COMPONENTS = { [WYRE_SERVICE_ID]: , [VERUSID_SERVICE_ID]: , + [PBAAS_PRECONVERT_SERVICE_ID]: } } diff --git a/src/containers/Services/ServiceComponents/PbaasPreconvertService/PbaasPreconvertService.js b/src/containers/Services/ServiceComponents/PbaasPreconvertService/PbaasPreconvertService.js new file mode 100644 index 00000000..5278ee42 --- /dev/null +++ b/src/containers/Services/ServiceComponents/PbaasPreconvertService/PbaasPreconvertService.js @@ -0,0 +1,73 @@ +import React, { useState, useEffect } from "react"; +import { useDispatch, useSelector } from 'react-redux'; +import { setServiceLoading } from "../../../../actions/actionCreators"; +import { createAlert } from "../../../../actions/actions/alert/dispatchers/alert"; +import { PBAAS_PRECONVERT_SERVICE_ID } from "../../../../utils/constants/services"; +import { PbaasPreconvertServiceRender } from "./PbaasPreconvertService.render"; +import { listCurrencies } from "../../../../utils/api/channels/vrpc/requests/listCurrencies"; +import { getInfo } from "../../../../utils/api/channels/vrpc/callCreators"; +import BigNumber from "bignumber.js"; +import { coinsList } from "../../../../utils/CoinData/CoinsList"; + +const PbaasPreconvertService = (props) => { + const [currenciesInPreconvert, setCurrenciesInPreconvert] = useState(null); + const loading = useSelector(state => state.services.loading[PBAAS_PRECONVERT_SERVICE_ID]); + const activeAccount = useSelector(state => state.authentication.activeAccount); + const dispatch = useDispatch(); + + props.navigation.setOptions({ title: 'Preconvert' }); + + const getCurrenciesInPreconvert = async () => { + dispatch(setServiceLoading(true, PBAAS_PRECONVERT_SERVICE_ID)); + + try { + const isTestnet = Object.keys(activeAccount.testnetOverrides).length > 0; + const systemId = isTestnet ? coinsList.VRSCTEST.system_id : coinsList.VRSC.system_id; + const allImportedCurrencies = await listCurrencies(systemId, "imported"); + const allLocalCurrencies = await listCurrencies(systemId, "local"); + const allPbaasCurrencies = await listCurrencies(systemId, "pbaas"); + + const chainInfo = await getInfo(systemId); + + if ( + allImportedCurrencies.error || + allLocalCurrencies.error || + allPbaasCurrencies.error || + chainInfo.error + ) + throw new Error('Error fetching currencies'); + + const longestChain = BigNumber(chainInfo.result.longestchain); + const preconvertCurrenciesMap = new Map(); + const allCurrencies = [...allImportedCurrencies.result, ...allLocalCurrencies.result, ...allPbaasCurrencies.result]; + + for (const currency of allCurrencies) { + const blocksLeft = BigNumber(currency.currencydefinition.startblock).minus(longestChain); + const inPreconvert = blocksLeft.isGreaterThan(BigNumber(0)); + + if (inPreconvert && !preconvertCurrenciesMap.has(currency.currencydefinition.currencyid)) { + preconvertCurrenciesMap.set(currency.currencydefinition.currencyid, {...currency, blocks_left: blocksLeft.toNumber()}) + } + } + + setCurrenciesInPreconvert(Array.from(preconvertCurrenciesMap.values())) + } catch (e) { + createAlert('Error Loading Linked VerusIDs', e.message); + } + + dispatch(setServiceLoading(false, PBAAS_PRECONVERT_SERVICE_ID)); + }; + + useEffect(() => { + getCurrenciesInPreconvert() + }, []) + + return PbaasPreconvertServiceRender({ + navigation: props.navigation, + loading, + currenciesInPreconvert, + refreshCurrenciesInPreconvert: getCurrenciesInPreconvert, + }); +} + +export default PbaasPreconvertService; \ No newline at end of file diff --git a/src/containers/Services/ServiceComponents/PbaasPreconvertService/PbaasPreconvertService.render.js b/src/containers/Services/ServiceComponents/PbaasPreconvertService/PbaasPreconvertService.render.js new file mode 100644 index 00000000..6fb12c55 --- /dev/null +++ b/src/containers/Services/ServiceComponents/PbaasPreconvertService/PbaasPreconvertService.render.js @@ -0,0 +1,38 @@ +import React from "react"; +import { View } from 'react-native' +import styles from "../../../../styles"; +import AnimatedActivityIndicator from "../../../../components/AnimatedActivityIndicator"; +import PbaasPreconvertServiceOverview from "./PbaasPreconvertServiceOverview/PbaasPreconvertServiceOverview"; + +export const PbaasPreconvertServiceRender = ({ navigation, loading, currenciesInPreconvert, refreshCurrenciesInPreconvert }) => { + return ( + + {(loading || currenciesInPreconvert == null) && ( + + + + )} + {!loading && currenciesInPreconvert != null && ( + + + + )} + + ); +}; \ No newline at end of file diff --git a/src/containers/Services/ServiceComponents/PbaasPreconvertService/PbaasPreconvertServiceOverview/PbaasPreconvertServiceOverview.js b/src/containers/Services/ServiceComponents/PbaasPreconvertService/PbaasPreconvertServiceOverview/PbaasPreconvertServiceOverview.js new file mode 100644 index 00000000..0bd98e8d --- /dev/null +++ b/src/containers/Services/ServiceComponents/PbaasPreconvertService/PbaasPreconvertServiceOverview/PbaasPreconvertServiceOverview.js @@ -0,0 +1,113 @@ +/* + This component is for displaying a list of all the default coins + that the user can add to their wallet. These coins should come + available with the wallet download and do not need to be added by + qr. +*/ + +import React, {useState, useEffect} from 'react'; +import {FlatList, TouchableOpacity, View} from 'react-native'; +import {List, Searchbar} from 'react-native-paper'; +import Styles from '../../../../../styles/index'; +import { blocksToTime } from '../../../../../utils/math'; +import { getCoinLogo } from '../../../../../utils/CoinData/CoinData'; + +const PbaasPreconvertServiceOverview = props => { + const { navigation, currenciesInPreconvert, refreshCurrenciesInPreconvert } = props; + + const [query, setQuery] = useState(''); + const [currencyList, setCurrencyList] = useState([]); + + useEffect(() => { + setCurrencyList(getCurrencyList()); + }, [query, currenciesInPreconvert]); + + const getCurrencyList = () => { + return currenciesInPreconvert + .map(x => { + return { + ...x, + time_left_string: blocksToTime(x.blocks_left) + } + }) + .filter(item => { + const {currencydefinition} = item; + const {fullyqualifiedname, currencyid} = currencydefinition; + + const queryLc = query.toLowerCase(); + const fqnLc = fullyqualifiedname.toLowerCase(); + const currencyIdLc = currencyid.toLowerCase(); + + return ( + query.length == 0 || + fqnLc.includes(queryLc) || + currencyIdLc.includes(queryLc) + ); + }) + .sort((a, b) => { + if (a.blocks_left <= b.blocks_left) return 1; + else return -1; + }); + }; + + return ( + + setQuery(text)} + value={query} + autoCorrect={false} + /> + } + style={{...Styles.fullWidth, ...Styles.backgroundColorWhite}} + data={currencyList} + onEndReachedThreshold={50} + refreshing={false} + onRefresh={refreshCurrenciesInPreconvert} + renderItem={({item}) => { + const { currencydefinition, time_left_string } = item; + const { fullyqualifiedname, currencyid } = currencydefinition; + const Logo = getCoinLogo(currencyid, 'vrsc'); + + return ( + setFullCoinDetails(item.coinObj)}> + ( + + + + )} + description={`${time_left_string} left`} + descriptionNumberOfLines={1} + titleNumberOfLines={1} + right={props => { + return ; + }} + style={{ + backgroundColor: 'white', + display: 'flex', + }} + /> + + ); + }} + keyExtractor={item => item.currencydefinition.currencyid} + /> + + ); +}; + +export default PbaasPreconvertServiceOverview; diff --git a/src/containers/Settings/WalletSettings/AddressBlocklist/AddressBlocklist.js b/src/containers/Settings/WalletSettings/AddressBlocklist/AddressBlocklist.js index 31714adc..273a91c3 100644 --- a/src/containers/Settings/WalletSettings/AddressBlocklist/AddressBlocklist.js +++ b/src/containers/Settings/WalletSettings/AddressBlocklist/AddressBlocklist.js @@ -1,7 +1,7 @@ import { Component } from "react" import { connect } from 'react-redux' import { AddressBlocklistRender } from "./AddressBlocklist.render" -import { ADDRESS_BLOCKLIST_MANUAL } from "../../../../utils/constants/constants"; +import { ADDRESS_BLOCKLIST_FROM_VERUSID, ADDRESS_BLOCKLIST_FROM_WEBSERVER, ADDRESS_BLOCKLIST_MANUAL } from "../../../../utils/constants/constants"; import { Alert } from "react-native"; import { saveGeneralSettings } from "../../../../actions/actionCreators"; @@ -15,7 +15,7 @@ class AddressBlocklist extends Component { addressBlocklistSettings: { addressBlocklist: [], addressBlocklistDefinition: { - type: ADDRESS_BLOCKLIST_MANUAL, + type: ADDRESS_BLOCKLIST_FROM_WEBSERVER, data: null } }, @@ -29,6 +29,16 @@ class AddressBlocklist extends Component { label: "", index: null }, + selectBlockTypeModal: { + open: false, + label: "", + index: null + }, + editBlockDefinitionDataModal: { + open: false, + label: "", + index: null + }, loading: false }; @@ -39,6 +49,29 @@ class AddressBlocklist extends Component { key: REMOVE, title: "Remove" }] + + this.BLOCKLIST_TYPE_BUTTONS = [{ + key: ADDRESS_BLOCKLIST_FROM_WEBSERVER, + title: "Fetch blocklist from web server" + }, { + key: ADDRESS_BLOCKLIST_MANUAL, + title: "Manage blocklist manually" + }] + + this.ADDRESS_BLOCKLIST_TYPE_DESCRIPTORS = { + [ADDRESS_BLOCKLIST_FROM_WEBSERVER]: { + title: 'Server', + description: 'On app launch, your blocklist will be fetched from the internet' + }, + [ADDRESS_BLOCKLIST_MANUAL]: { + title: 'Manual', + description: 'You set your address blocklist manually' + }, + [ADDRESS_BLOCKLIST_FROM_VERUSID]: { + title: 'VerusID', + description: '' + } + } } componentDidMount() { @@ -55,6 +88,16 @@ class AddressBlocklist extends Component { }) } + closeEditBlockDefinitionDataModal() { + this.setState({ + editBlockDefinitionDataModal: { + open: false, + label: "", + index: null + } + }) + } + closeEditPropertyModal() { this.setState({ editPropertyModal: { @@ -75,6 +118,25 @@ class AddressBlocklist extends Component { }) } + closeSelectBlockTypeModal() { + this.setState({ + selectBlockTypeModal: { + open: false, + label: "", + index: null + } + }) + } + + openSelectBlockTypeModal(label) { + this.setState({ + selectBlockTypeModal: { + open: true, + label + } + }) + } + selectEditPropertyButton(button) { switch (button) { case EDIT: @@ -94,6 +156,13 @@ class AddressBlocklist extends Component { } } + selectBlockTypeButton(button) { + this.updateBlockedAddressListDefinition({ + type: button, + data: null + }) + } + componentDidUpdate(lastProps) { if (lastProps.settings !== this.props.settings) { this.loadSettings() @@ -137,6 +206,16 @@ class AddressBlocklist extends Component { }) } + openEditBlockDefinitionDataModal() { + this.setState({ + editBlockDefinitionDataModal: { + open: true, + label: "Edit Blocklist Details", + index: null + } + }) + } + updateBlockedAddressList(list) { this.setState( { addressBlocklistSettings: { ...this.state.addressBlocklistSettings, addressBlocklist: list }, loading: true }, @@ -152,6 +231,34 @@ class AddressBlocklist extends Component { ); } + finishEditBlockDefinitionData(data) { + this.setState({ + editBlockDefinitionDataModal: { + open: false, + label: "", + index: null + } + }, () => this.updateBlockedAddressListDefinition({ + type: this.state.addressBlocklistSettings.addressBlocklistDefinition.type, + data: data.length === 0 ? null : data + }, this.state.addressBlocklistSettings.addressBlocklist)) + } + + updateBlockedAddressListDefinition(definition, blocklist = []) { + this.setState( + { addressBlocklistSettings: { addressBlocklist: blocklist, addressBlocklistDefinition: definition }, loading: true }, + async () => { + try { + await this.saveSettings() + } catch(e) { + Alert.alert('Error', e.message) + } + + this.setState({ loading: false }); + } + ); + } + async saveSettings() { const stateChanges = { addressBlocklistDefinition: this.state.addressBlocklistSettings.addressBlocklistDefinition, @@ -167,7 +274,7 @@ class AddressBlocklist extends Component { addressBlocklistSettings: { addressBlocklist: this.props.settings.addressBlocklist == null ? [] : this.props.settings.addressBlocklist, addressBlocklistDefinition: this.props.settings.addressBlocklistDefinition == null ? { - type: ADDRESS_BLOCKLIST_MANUAL, + type: ADDRESS_BLOCKLIST_FROM_WEBSERVER, data: null } : this.props.settings.addressBlocklistDefinition } diff --git a/src/containers/Settings/WalletSettings/AddressBlocklist/AddressBlocklist.render.js b/src/containers/Settings/WalletSettings/AddressBlocklist/AddressBlocklist.render.js index 227d4e91..9a5cf350 100644 --- a/src/containers/Settings/WalletSettings/AddressBlocklist/AddressBlocklist.render.js +++ b/src/containers/Settings/WalletSettings/AddressBlocklist/AddressBlocklist.render.js @@ -5,8 +5,12 @@ import ListSelectionModal from "../../../../components/ListSelectionModal/ListSe import TextInputModal from "../../../../components/TextInputModal/TextInputModal"; import Styles from "../../../../styles"; import { unixToDate } from "../../../../utils/math"; +import { ADDRESS_BLOCKLIST_MANUAL } from "../../../../utils/constants/constants"; export const AddressBlocklistRender = function () { + const blocklistType = this.state.addressBlocklistSettings.addressBlocklistDefinition.type; + const { title: blocklistTypeTitle, description: blocklistTypeDescription } = this.ADDRESS_BLOCKLIST_TYPE_DESCRIPTORS[blocklistType]; + return ( @@ -25,6 +29,19 @@ export const AddressBlocklistRender = function () { } /> )} + {this.state.editBlockDefinitionDataModal.open && ( + {}} + cancel={(text) => + this.finishEditBlockDefinitionData( + text + ) + } + /> + )} {this.state.editPropertyModal.open && ( this.closeEditPropertyModal()} /> )} + {this.state.selectBlockTypeModal.open && ( + this.selectBlockTypeButton(item.key)} + data={this.BLOCKLIST_TYPE_BUTTONS} + cancel={() => this.closeSelectBlockTypeModal()} + /> + )} + + {"Address Blocklist Details"} + + + this.openSelectBlockTypeModal( + `Blocklist Type` + ) + } + /> + + { + blocklistType !== ADDRESS_BLOCKLIST_MANUAL && ( + + this.openEditBlockDefinitionDataModal()} + /> + + + ) + } {"Blocked Addresses"} {this.state.addressBlocklistSettings.addressBlocklist.map((blockedAddress, index) => { diff --git a/src/containers/Settings/WalletSettings/GeneralWalletSettings/GeneralWalletSettings.js b/src/containers/Settings/WalletSettings/GeneralWalletSettings/GeneralWalletSettings.js index e74d1415..4a8cfdfe 100644 --- a/src/containers/Settings/WalletSettings/GeneralWalletSettings/GeneralWalletSettings.js +++ b/src/containers/Settings/WalletSettings/GeneralWalletSettings/GeneralWalletSettings.js @@ -26,7 +26,7 @@ import ListSelectionModal from '../../../../components/ListSelectionModal/ListSe import {saveGeneralSettings} from '../../../../actions/actionCreators'; import {createAlert} from '../../../../actions/actions/alert/dispatchers/alert'; import {NavigationActions} from '@react-navigation/compat'; -import { ADDRESS_BLOCKLIST_MANUAL } from '../../../../utils/constants/constants'; +import { ADDRESS_BLOCKLIST_FROM_WEBSERVER } from '../../../../utils/constants/constants'; const NO_DEFAULT = 'None'; @@ -96,7 +96,7 @@ const WalletSettings = props => { addressBlocklistDefinition: settings.addressBlocklistDefinition == null ? { - type: ADDRESS_BLOCKLIST_MANUAL, + type: ADDRESS_BLOCKLIST_FROM_WEBSERVER, data: null, } : settings.addressBlocklistDefinition, diff --git a/src/reducers/services.js b/src/reducers/services.js index edf5e60b..86d51202 100644 --- a/src/reducers/services.js +++ b/src/reducers/services.js @@ -2,7 +2,7 @@ The personal reducer stores data used by services */ -import { VERUSID_SERVICE_ID, WYRE_SERVICE_ID } from "../utils/constants/services"; +import { VERUSID_SERVICE_ID, WYRE_SERVICE_ID, PBAAS_PRECONVERT_SERVICE_ID } from "../utils/constants/services"; import { SET_SERVICE_ACCOUNT, SIGN_OUT, @@ -23,6 +23,7 @@ export const services = ( loading: { [WYRE_SERVICE_ID]: false, [VERUSID_SERVICE_ID]: false, + [PBAAS_PRECONVERT_SERVICE_ID]: false }, }, action, @@ -84,6 +85,7 @@ export const services = ( loading: { [WYRE_SERVICE_ID]: false, [VERUSID_SERVICE_ID]: false, + [PBAAS_PRECONVERT_SERVICE_ID]: false }, }; default: diff --git a/src/reducers/settings.js b/src/reducers/settings.js index d53d97f2..20f06f78 100644 --- a/src/reducers/settings.js +++ b/src/reducers/settings.js @@ -3,7 +3,7 @@ user-decided app settings. */ -import { MAX_VERIFICATION, DEFAULT_PRIVATE_ADDRS, ADDRESS_BLOCKLIST_MANUAL } from '../utils/constants/constants' +import { MAX_VERIFICATION, DEFAULT_PRIVATE_ADDRS, ADDRESS_BLOCKLIST_FROM_WEBSERVER } from '../utils/constants/constants' import { SET_COIN_LIST, SET_ALL_SETTINGS, @@ -28,7 +28,7 @@ export const settings = (state = { homeCardDragDetection: false, ackedCurrencyDisclaimer: false, addressBlocklistDefinition: { - type: ADDRESS_BLOCKLIST_MANUAL, + type: ADDRESS_BLOCKLIST_FROM_WEBSERVER, data: null }, addressBlocklist: [] diff --git a/src/utils/constants/services.js b/src/utils/constants/services.js index c20ff697..974ed627 100644 --- a/src/utils/constants/services.js +++ b/src/utils/constants/services.js @@ -2,16 +2,22 @@ import { WYRE_SERVICE } from "./intervalConstants" export const WYRE_SERVICE_ID = 'wyre_service' export const VERUSID_SERVICE_ID = 'verusid_service' +export const PBAAS_PRECONVERT_SERVICE_ID = 'pbaas_preconvert' export const CONNECTED_SERVICE_DISPLAY_INFO = { + [WYRE_SERVICE_ID]: { + title: "Wyre", + description: "Connecting your wallet with Wyre allows you to convert between cryptocurrency and fiat" + }, [VERUSID_SERVICE_ID]: { title: "VerusID", description: "By connecting your VerusIDs to your wallet, you can use them to hold funds and/or sign into services", decentralized: true }, - [WYRE_SERVICE_ID]: { - title: "Wyre", - description: "Connecting your wallet with Wyre allows you to convert between cryptocurrency and fiat" + [PBAAS_PRECONVERT_SERVICE_ID]: { + title: "Preconvert Currency", + description: "Participate in a PBaaS currency launch by sending your funds to the currency before it starts", + decentralized: true } } @@ -19,7 +25,7 @@ export const CONNECTED_SERVICE_CHANNELS = { [WYRE_SERVICE_ID]: WYRE_SERVICE } -export const CONNECTED_SERVICES = [VERUSID_SERVICE_ID, WYRE_SERVICE_ID] +export const CONNECTED_SERVICES = [VERUSID_SERVICE_ID, /*PBAAS_PRECONVERT_SERVICE_ID ,*/ WYRE_SERVICE_ID] // Wyre specific constants export const WYRE_INDIVIDUAL_NAME = 'individualLegalName' From 4ab5c70fc3cb8e53c1ddaf905bdbcfa611146c5c Mon Sep 17 00:00:00 2001 From: michaeltout Date: Tue, 26 Sep 2023 23:26:53 +0200 Subject: [PATCH 25/42] Add blocklist fetching from server --- .../actions/account/dispatchers/account.js | 35 +++++++++++++++++-- .../AddressBlocklist/AddressBlocklist.js | 2 +- .../AddressBlocklist.render.js | 16 ++++----- src/selectors/settings.js | 4 +-- .../addressBlocklist/getAddressBlocklist.js | 22 ++++++++++++ src/utils/constants/constants.js | 5 ++- 6 files changed, 70 insertions(+), 14 deletions(-) create mode 100644 src/utils/api/channels/general/addressBlocklist/getAddressBlocklist.js diff --git a/src/actions/actions/account/dispatchers/account.js b/src/actions/actions/account/dispatchers/account.js index 35bc35b8..22e2b6a1 100644 --- a/src/actions/actions/account/dispatchers/account.js +++ b/src/actions/actions/account/dispatchers/account.js @@ -1,4 +1,4 @@ -import { LOADING_ACCOUNT, VALIDATING_ACCOUNT } from "../../../../utils/constants/constants"; +import { ADDRESS_BLOCKLIST_FROM_WEBSERVER, LOADING_ACCOUNT, VALIDATING_ACCOUNT } from "../../../../utils/constants/constants"; import { signIntoAuthenticatedAccount } from "../../../actionCreators"; import { COIN_MANAGER_MAP, fetchActiveCoins, setUserCoins } from "../../coins/Coins"; import { @@ -13,6 +13,7 @@ import { fetchUsers, validateLogin } from "../../UserData"; import { initSettings, saveGeneralSettings } from "../../WalletSettings"; import { DISABLED_CHANNELS } from '../../../../../env/index' import store from "../../../../store"; +import { getAddressBlocklistFromServer } from "../../../../utils/api/channels/general/addressBlocklist/getAddressBlocklist"; export const initializeAccountData = async ( account, @@ -40,7 +41,37 @@ export const initializeAccountData = async ( ); const {activeCoinsForUser} = setUserCoinsAction.payload; - store.dispatch(await initSettings()); + const settingsAction = await initSettings() + store.dispatch(settingsAction); + + try { + const { addressBlocklistDefinition, addressBlocklist } = store.getState().settings.generalWalletSettings; + + if (addressBlocklistDefinition.type === ADDRESS_BLOCKLIST_FROM_WEBSERVER) { + const fetchedBlocklist = await getAddressBlocklistFromServer(); + const currentBlocklist = [...addressBlocklist]; + + for (const address of fetchedBlocklist) { + if (currentBlocklist.find(x => x.address === address) == null) { + currentBlocklist.unshift({ + address, + details: '', + lastModified: Math.floor(Date.now() / 1000) + }); + } + } + + const saveGeneralSettingsAction = await saveGeneralSettings({ + addressBlocklist: currentBlocklist + }); + + store.dispatch(saveGeneralSettingsAction); + } + } catch(e) { + console.warn("Failed to fetch address blocklist"); + console.warn(e); + } + store.dispatch(accountAuthenticator); store.dispatch(coinList); diff --git a/src/containers/Settings/WalletSettings/AddressBlocklist/AddressBlocklist.js b/src/containers/Settings/WalletSettings/AddressBlocklist/AddressBlocklist.js index 273a91c3..541c520c 100644 --- a/src/containers/Settings/WalletSettings/AddressBlocklist/AddressBlocklist.js +++ b/src/containers/Settings/WalletSettings/AddressBlocklist/AddressBlocklist.js @@ -61,7 +61,7 @@ class AddressBlocklist extends Component { this.ADDRESS_BLOCKLIST_TYPE_DESCRIPTORS = { [ADDRESS_BLOCKLIST_FROM_WEBSERVER]: { title: 'Server', - description: 'On app launch, your blocklist will be fetched from the internet' + description: 'On login, your blocklist will be fetched from the internet' }, [ADDRESS_BLOCKLIST_MANUAL]: { title: 'Manual', diff --git a/src/containers/Settings/WalletSettings/AddressBlocklist/AddressBlocklist.render.js b/src/containers/Settings/WalletSettings/AddressBlocklist/AddressBlocklist.render.js index 9a5cf350..c33d4191 100644 --- a/src/containers/Settings/WalletSettings/AddressBlocklist/AddressBlocklist.render.js +++ b/src/containers/Settings/WalletSettings/AddressBlocklist/AddressBlocklist.render.js @@ -5,7 +5,7 @@ import ListSelectionModal from "../../../../components/ListSelectionModal/ListSe import TextInputModal from "../../../../components/TextInputModal/TextInputModal"; import Styles from "../../../../styles"; import { unixToDate } from "../../../../utils/math"; -import { ADDRESS_BLOCKLIST_MANUAL } from "../../../../utils/constants/constants"; +import { ADDRESS_BLOCKLIST_MANUAL, DEFAULT_ADDRESS_BLOCKLIST_WEBSERVER } from "../../../../utils/constants/constants"; export const AddressBlocklistRender = function () { const blocklistType = this.state.addressBlocklistSettings.addressBlocklistDefinition.type; @@ -84,7 +84,7 @@ export const AddressBlocklistRender = function () { this.state.addressBlocklistSettings.addressBlocklistDefinition.data ? this.state.addressBlocklistSettings.addressBlocklistDefinition .data - : 'Default server' + : DEFAULT_ADDRESS_BLOCKLIST_WEBSERVER } description={'Address blocklist source'} onPress={() => this.openEditBlockDefinitionDataModal()} @@ -95,6 +95,12 @@ export const AddressBlocklistRender = function () { } {"Blocked Addresses"} + } + onPress={() => this.openAddBlockedAddressModal()} + /> + {this.state.addressBlocklistSettings.addressBlocklist.map((blockedAddress, index) => { return ( @@ -117,12 +123,6 @@ export const AddressBlocklistRender = function () { ); }) } - } - onPress={() => this.openAddBlockedAddressModal()} - /> - ); diff --git a/src/selectors/settings.js b/src/selectors/settings.js index 535dc7aa..341a20a8 100644 --- a/src/selectors/settings.js +++ b/src/selectors/settings.js @@ -1,12 +1,12 @@ import { createSelector } from 'reselect'; -const getAddressBlocklist = state => +const getAddressBlocklistFromServer = state => state.settings.generalWalletSettings.addressBlocklist ? state.settings.generalWalletSettings.addressBlocklist : []; export const selectAddressBlocklist = createSelector( - [getAddressBlocklist], + [getAddressBlocklistFromServer], (addressBlocklist) => { return addressBlocklist } diff --git a/src/utils/api/channels/general/addressBlocklist/getAddressBlocklist.js b/src/utils/api/channels/general/addressBlocklist/getAddressBlocklist.js new file mode 100644 index 00000000..227b1322 --- /dev/null +++ b/src/utils/api/channels/general/addressBlocklist/getAddressBlocklist.js @@ -0,0 +1,22 @@ +import { DEFAULT_ADDRESS_BLOCKLIST_WEBSERVER } from '../../../../constants/constants'; +import { isJson } from '../../../../objectManip' +import axios from 'axios' + +export const getAddressBlocklistFromServer = async (source) => { + const server = source == null ? DEFAULT_ADDRESS_BLOCKLIST_WEBSERVER : source; + + const res = await axios.get(server); + + if (!isJson(res.data)) { + throw new Error( + "Invalid JSON for address blocklist" + ); + } + + const lists = Object.values(res.data); + + return lists.flat().map(x => { + if (typeof x !== 'string') throw new Error("Invalid format in blocklist response"); + else return x + }); +} \ No newline at end of file diff --git a/src/utils/constants/constants.js b/src/utils/constants/constants.js index a064d554..2932d8f1 100644 --- a/src/utils/constants/constants.js +++ b/src/utils/constants/constants.js @@ -196,4 +196,7 @@ export const ADDRESS_BLOCKLIST_MANUAL = 'ADDRESS_BLOCKLIST_MANUAL'; // Address byte versions export const R_ADDRESS_VERSION = 60; -export const I_ADDRESS_VERSION = 102; \ No newline at end of file +export const I_ADDRESS_VERSION = 102; + +// Address blocklist +export const DEFAULT_ADDRESS_BLOCKLIST_WEBSERVER = 'https://rtethtest.verus.services/exclude.json' \ No newline at end of file From c472ab1abda28fd9abf8ff007c8822a24d5d439a Mon Sep 17 00:00:00 2001 From: michaeltout Date: Tue, 26 Sep 2023 23:30:04 +0200 Subject: [PATCH 26/42] Add MKR to bridge converters --- src/utils/api/channels/erc20/requests/preflight.js | 4 +++- src/utils/constants/web3Constants.js | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/utils/api/channels/erc20/requests/preflight.js b/src/utils/api/channels/erc20/requests/preflight.js index 472476de..610a7634 100644 --- a/src/utils/api/channels/erc20/requests/preflight.js +++ b/src/utils/api/channels/erc20/requests/preflight.js @@ -18,6 +18,7 @@ import { FALLBACK_GAS_BRIDGE_TRANSFER, NULL_ETH_ADDRESS, ETH_VERUS_BRIDGE_CONTRACT_PRELAUNCH_RESERVE_TRANSFER_FEE, + MKR_VETH, } from '../../../../constants/web3Constants'; import { getCurrency, getIdentity } from "../../verusid/callCreators" import { getSystemNameFromSystemId } from "../../../../CoinData/CoinData" @@ -155,7 +156,8 @@ export const preflightBridgeTransfer = async (coinObj, channelId, activeUser, ou systemId, bridgeIAddress, vEthIAddress, - toIAddress(DAI_VETH, systemName) + toIAddress(DAI_VETH, systemName), + toIAddress(MKR_VETH, systemName) ]; const tokenList = await delegatorContract.callStatic.getTokenList(0, 0); diff --git a/src/utils/constants/web3Constants.js b/src/utils/constants/web3Constants.js index fbbb48de..68d0368d 100644 --- a/src/utils/constants/web3Constants.js +++ b/src/utils/constants/web3Constants.js @@ -13,6 +13,7 @@ export const VERUS_BRIDGE_DELEGATOR_MAINNET_CONTRACT = null // TODO: Add mainnet export const ETH_BRIDGE_NAME = "Bridge.vETH" export const VETH = 'vETH' export const DAI_VETH = 'DAI.VETH' +export const MKR_VETH = 'MKR.VETH' export const NULL_ETH_ADDRESS = '0x0000000000000000000000000000000000000000' export const ETH_CONTRACT_ADDRESS = "0x0000000000000000000000000000000000000000" From 99cf3dbfd539758df0c8e9f9e75dde613fb265f8 Mon Sep 17 00:00:00 2001 From: michaeltout Date: Wed, 27 Sep 2023 15:27:32 +0200 Subject: [PATCH 27/42] Fix crash on ETH/ERC20 overview if mainnet delegator contract is not present --- src/containers/Coin/Overview/Overview.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/containers/Coin/Overview/Overview.js b/src/containers/Coin/Overview/Overview.js index 60b8e53a..986aac71 100644 --- a/src/containers/Coin/Overview/Overview.js +++ b/src/containers/Coin/Overview/Overview.js @@ -210,8 +210,10 @@ class Overview extends Component { ) { if ( (this.props.activeCoin.testnet && + VERUS_BRIDGE_DELEGATOR_GOERLI_CONTRACT != null && subtitle.toLowerCase() === VERUS_BRIDGE_DELEGATOR_GOERLI_CONTRACT.toLowerCase()) || (!this.props.activeCoin.testnet && + VERUS_BRIDGE_DELEGATOR_MAINNET_CONTRACT != null && subtitle.toLowerCase() === VERUS_BRIDGE_DELEGATOR_MAINNET_CONTRACT.toLowerCase()) ) { subtitle = 'Verus-Ethereum Bridge Contract'; From 7bf34cd81be6d6c9927b915940a89fd670e7ed01 Mon Sep 17 00:00:00 2001 From: michaeltout Date: Wed, 27 Sep 2023 17:13:33 +0200 Subject: [PATCH 28/42] Add "maximum" to erc20/eth conversion tx fees, fix conversion path calculation on non-VRSC/VRSCTEST chains --- .../ConvertOrCrossChainSendConfirm.js | 2 +- .../requests/getCurrencyConversionPaths.js | 90 ++++++++++--------- 2 files changed, 51 insertions(+), 41 deletions(-) diff --git a/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendConfirm/ConvertOrCrossChainSendConfirm.js b/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendConfirm/ConvertOrCrossChainSendConfirm.js index c5c3924e..31169fe4 100644 --- a/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendConfirm/ConvertOrCrossChainSendConfirm.js +++ b/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendConfirm/ConvertOrCrossChainSendConfirm.js @@ -295,7 +295,7 @@ function ConvertOrCrossChainSendConfirm({ sent, ), createAccordion( - 'Transaction Fees', + (sendModal.coinObj.proto === ETH || sendModal.coinObj.proto === ERC20) ? 'Maximum Transaction Fees' : 'Transaction Fees', 'Fees deducted from your wallet to pay for this transaction', props => , fees, diff --git a/src/utils/api/channels/vrpc/requests/getCurrencyConversionPaths.js b/src/utils/api/channels/vrpc/requests/getCurrencyConversionPaths.js index 02a8df11..5b96e56f 100644 --- a/src/utils/api/channels/vrpc/requests/getCurrencyConversionPaths.js +++ b/src/utils/api/channels/vrpc/requests/getCurrencyConversionPaths.js @@ -3,11 +3,17 @@ import VrpcProvider from "../../../../vrpc/vrpcInterface" import { getCurrenciesMappedToEth } from "./getCurrenciesMappedToEth"; import { ETH_BRIDGE_NAME, ETH_CONTRACT_ADDRESS, VETH } from "../../../../constants/web3Constants"; import { getWeb3ProviderForNetwork } from "../../../../web3/provider"; +import { toIAddress } from "verus-typescript-primitives"; +import { getSystemNameFromSystemId } from "../../../../CoinData/CoinData"; export const getCurrencyConversionPaths = async (systemId, src, ethNetwork) => { const ethSrc = src === ETH_CONTRACT_ADDRESS || ethers.utils.isAddress(src); const endpoint = VrpcProvider.getEndpoint(systemId); + const systemCurrencyResponse = await endpoint.getCurrency(systemId); + if (systemCurrencyResponse.error) throw new Error(systemCurrencyResponse.error.message); + const systemDefinition = systemCurrencyResponse.result; + if (ethSrc) { const isEth = src === ETH_CONTRACT_ADDRESS; const paths = {}; @@ -26,12 +32,6 @@ export const getCurrencyConversionPaths = async (systemId, src, ethNetwork) => { if (contractAddressToCurrencyDefinitionMap.has(src)) { const mappedToSource = contractAddressToCurrencyDefinitionMap.get(src); - const systemCurrencyResponse = await endpoint.getCurrency(systemId); - - if (systemCurrencyResponse.error) throw new Error(systemCurrencyResponse.error.message); - - const systemDefinition = systemCurrencyResponse.result; - // Establish which currencies the ETH currency can be mapped to for (const [key, currency] of mappedToSource) { const { currencydefinition } = currency; @@ -242,41 +242,51 @@ export const getCurrencyConversionPaths = async (systemId, src, ethNetwork) => { for (const contractAddress of mappedEthCurrencies) { paths[contractAddress] = [] - const vEthCurrencyResponse = await endpoint.getCurrency(VETH); - if (vEthCurrencyResponse.error) throw new Error(vEthCurrencyResponse.error.message); - const vEthCurrencyDefinition = vEthCurrencyResponse.result; + // Find vETH on VRSC/VRSCTEST + const vEthCurrencyResponse = await endpoint.getCurrency( + toIAddress( + VETH, + systemDefinition.parent + ? getSystemNameFromSystemId(systemDefinition.parent) + : getSystemNameFromSystemId(systemDefinition.currencyid) + ), + ); - if (contractAddress === ETH_CONTRACT_ADDRESS) { - paths[contractAddress].push({ - destination: { - address: ETH_CONTRACT_ADDRESS, - symbol: "ETH", - decimals: 18, - name: "Ethereum" - }, - exportto: vEthCurrencyDefinition, - price: 1, - gateway: true, - mapping: true - }) - } else { - const contract = getWeb3ProviderForNetwork(ethNetwork).getUnitializedContractInstance(contractAddress); - const name = await contract.name(); - const symbol = await contract.symbol(); - const decimals = await contract.decimals(); - - paths[contractAddress].push({ - destination: { - address: contractAddress, - symbol, - decimals, - name - }, - exportto: vEthCurrencyDefinition, - price: 1, - gateway: true, - mapping: true - }) + if (vEthCurrencyResponse.error == null) { + const vEthCurrencyDefinition = vEthCurrencyResponse.result; + + if (contractAddress === ETH_CONTRACT_ADDRESS) { + paths[contractAddress].push({ + destination: { + address: ETH_CONTRACT_ADDRESS, + symbol: "ETH", + decimals: 18, + name: "Ethereum" + }, + exportto: vEthCurrencyDefinition, + price: 1, + gateway: true, + mapping: true + }) + } else { + const contract = getWeb3ProviderForNetwork(ethNetwork).getUnitializedContractInstance(contractAddress); + const name = await contract.name(); + const symbol = await contract.symbol(); + const decimals = await contract.decimals(); + + paths[contractAddress].push({ + destination: { + address: contractAddress, + symbol, + decimals, + name + }, + exportto: vEthCurrencyDefinition, + price: 1, + gateway: true, + mapping: true + }) + } } } } From 2e660b69ef224f178010ba1cd45f036f70cd60d9 Mon Sep 17 00:00:00 2001 From: michaeltout Date: Wed, 27 Sep 2023 20:27:30 +0200 Subject: [PATCH 29/42] Increase time estimate for bounceback bridge txs --- .../ConvertOrCrossChainSendConfirm.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendConfirm/ConvertOrCrossChainSendConfirm.js b/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendConfirm/ConvertOrCrossChainSendConfirm.js index 31169fe4..04315aa0 100644 --- a/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendConfirm/ConvertOrCrossChainSendConfirm.js +++ b/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendConfirm/ConvertOrCrossChainSendConfirm.js @@ -250,11 +250,14 @@ function ConvertOrCrossChainSendConfirm({ { key: 'Estimated Time Until Arrival', data: - exportto != null || ((sendModal.coinObj.proto === ETH || sendModal.coinObj.proto === ERC20) && convertto != null) - ? '20-30 minutes' - : convertto != null - ? '2-10 minutes' - : '1-5 minutes', + ((sendModal.coinObj.proto === ETH || + sendModal.coinObj.proto === ERC20) && convertto != null && exportto == null) ? + '40-60 minutes' : + exportto != null + ? '20-30 minutes' + : convertto != null + ? '2-10 minutes' + : '1-5 minutes', numLines: 100, }, { From 39c10d71b31fcf24746de4b6011bbae574f10043 Mon Sep 17 00:00:00 2001 From: michaeltout Date: Wed, 27 Sep 2023 20:28:37 +0200 Subject: [PATCH 30/42] Fix bug when sending accross bridge to mapped currency, decrease fee for bridge bounceback txs, prevent bounceback to same currency --- .../api/channels/erc20/requests/preflight.js | 19 +++++++++++++++++-- src/utils/constants/web3Constants.js | 3 ++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/utils/api/channels/erc20/requests/preflight.js b/src/utils/api/channels/erc20/requests/preflight.js index 610a7634..e9b70bf8 100644 --- a/src/utils/api/channels/erc20/requests/preflight.js +++ b/src/utils/api/channels/erc20/requests/preflight.js @@ -19,6 +19,7 @@ import { NULL_ETH_ADDRESS, ETH_VERUS_BRIDGE_CONTRACT_PRELAUNCH_RESERVE_TRANSFER_FEE, MKR_VETH, + MINIMUM_GAS_PRICE_WEI_DELEGATOR_CONTRACT_BOUNCEBACK, } from '../../../../constants/web3Constants'; import { getCurrency, getIdentity } from "../../verusid/callCreators" import { getSystemNameFromSystemId } from "../../../../CoinData/CoinData" @@ -126,6 +127,12 @@ export const preflightBridgeTransfer = async (coinObj, channelId, activeUser, ou const isConversion = convertto != null; + if (address.isETHAccount() && !isConversion) { + throw new Error("Cannot send to ETH address across bridge without converting. Maybe you meant to use a Verus address?") + } else if (address.isETHAccount() && exportto != null) { + throw new Error("Cannot send to ETH address across bridge to non-eth network. Maybe you meant to use a Verus address?") + } + let mappedCurrencyIAddress; let isBridge = false; const vEthIAddress = toIAddress(VETH, systemName); @@ -204,6 +211,7 @@ export const preflightBridgeTransfer = async (coinObj, channelId, activeUser, ou // Get the vETH hex address for the fee currency const vEthHexAddress = toEthAddress(vEthIAddress); const vrscHexAddress = toEthAddress(systemId); + const bridgeHexAddress = toEthAddress(bridgeIAddress); if (isConversion) { flagsBN = flagsBN.xor(RESERVE_TRANSFER_CONVERT); @@ -234,18 +242,25 @@ export const preflightBridgeTransfer = async (coinObj, channelId, activeUser, ou if (importToSource) { destinationcurrency = finalDestinationCurrencyAddress; } else { - destinationcurrency = toEthAddress(bridgeIAddress); + destinationcurrency = bridgeHexAddress; } } } else if (!pastPrelaunch) { destinationcurrency = vrscHexAddress; secondreserveid = NULL_ETH_ADDRESS; + } else { + destinationcurrency = bridgeHexAddress; + secondreserveid = NULL_ETH_ADDRESS; } let destinationtype, destinationaddress; const baseGasPrice = await Web3Provider.DefaultProvider.getGasPrice(); - const minGasPrice = ethers.BigNumber.from(MINIMUM_GAS_PRICE_WEI_DELEGATOR_CONTRACT); + const minGasPrice = ethers.BigNumber.from( + address.isETHAccount() + ? MINIMUM_GAS_PRICE_WEI_DELEGATOR_CONTRACT_BOUNCEBACK + : MINIMUM_GAS_PRICE_WEI_DELEGATOR_CONTRACT, + ); const gasPriceModifier = ethers.BigNumber.from("5"); const modifiedGasPrice = baseGasPrice.add(baseGasPrice.div(gasPriceModifier)); let gasPrice; diff --git a/src/utils/constants/web3Constants.js b/src/utils/constants/web3Constants.js index 68d0368d..a7d94145 100644 --- a/src/utils/constants/web3Constants.js +++ b/src/utils/constants/web3Constants.js @@ -21,7 +21,8 @@ export const ETH_VERUS_BRIDGE_CONTRACT_RESERVE_TRANSFER_FEE = 300000 export const ETH_VERUS_BRIDGE_CONTRACT_PRELAUNCH_RESERVE_TRANSFER_FEE = 2000000 export const ETH_VERUS_BRIDGE_DEST_SYSTEM_ID = "0x0000000000000000000000000000000000000000" -export const MINIMUM_GAS_PRICE_WEI_DELEGATOR_CONTRACT = "10000000000" +export const MINIMUM_GAS_PRICE_WEI_DELEGATOR_CONTRACT = "3000000000" +export const MINIMUM_GAS_PRICE_WEI_DELEGATOR_CONTRACT_BOUNCEBACK = "6000000000" export const DELEGATOR_CONTRACT_GAS_MODIFIER = "1.2" export const GAS_TRANSACTION_IMPORT_FEE = "1000000" From 9450a61893b02afc2591428fde00dfde2ec0b4e5 Mon Sep 17 00:00:00 2001 From: michaeltout Date: Wed, 4 Oct 2023 21:19:00 +0200 Subject: [PATCH 31/42] Update gesture handler, mainnet bridge contract, and fix issue with personal documents on Android --- android/build.gradle | 4 ++-- package.json | 4 ++-- .../PersonalImages/PersonalImages.render.js | 11 ----------- .../addressBlocklist/getAddressBlocklist.js | 2 +- src/utils/constants/constants.js | 2 +- src/utils/constants/web3Constants.js | 2 +- src/utils/personal/displayUtils.js | 19 +++++++++++++------ yarn.lock | 16 ++++++++-------- 8 files changed, 28 insertions(+), 32 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index 5cc12fc6..de8df98d 100755 --- a/android/build.gradle +++ b/android/build.gradle @@ -4,8 +4,8 @@ buildscript { ext { buildToolsVersion = "31.0.0" minSdkVersion = 21 - compileSdkVersion = 31 - targetSdkVersion = 31 + compileSdkVersion = 33 + targetSdkVersion = 33 if (System.properties['os.arch'] == "aarch64") { // For M1 Users we need to use the NDK 24 which added support for aarch64 diff --git a/package.json b/package.json index 253a6c21..aea16301 100755 --- a/package.json +++ b/package.json @@ -73,9 +73,9 @@ "react-native-elements": "1.0.0", "react-native-format-currency": "https://github.com/michaeltout/react-native-format-currency.git", "react-native-fs": "2.18.0", - "react-native-gesture-handler": "2.8.0", + "react-native-gesture-handler": "2.9.0", "react-native-haptic-feedback": "1.11.0", - "react-native-image-picker": "4.0.6", + "react-native-image-picker": "5.7.0", "react-native-keyboard-aware-scroll-view": "0.9.5", "react-native-keychain": "6.2.0", "react-native-level-fs": "3.0.0", diff --git a/src/containers/Personal/PersonalImages/PersonalImages.render.js b/src/containers/Personal/PersonalImages/PersonalImages.render.js index 78bee16b..386b3d3e 100644 --- a/src/containers/Personal/PersonalImages/PersonalImages.render.js +++ b/src/containers/Personal/PersonalImages/PersonalImages.render.js @@ -24,17 +24,6 @@ export const PersonalImagesRender = function () { { - return image.uris.length == 0 ? null : ( - - ); - }} title={documentRender.title} description={documentRender.description} right={(props) => ( diff --git a/src/utils/api/channels/general/addressBlocklist/getAddressBlocklist.js b/src/utils/api/channels/general/addressBlocklist/getAddressBlocklist.js index 227b1322..d65e9a4d 100644 --- a/src/utils/api/channels/general/addressBlocklist/getAddressBlocklist.js +++ b/src/utils/api/channels/general/addressBlocklist/getAddressBlocklist.js @@ -3,7 +3,7 @@ import { isJson } from '../../../../objectManip' import axios from 'axios' export const getAddressBlocklistFromServer = async (source) => { - const server = source == null ? DEFAULT_ADDRESS_BLOCKLIST_WEBSERVER : source; + const server = source == null || source.length === 0 ? DEFAULT_ADDRESS_BLOCKLIST_WEBSERVER : source; const res = await axios.get(server); diff --git a/src/utils/constants/constants.js b/src/utils/constants/constants.js index 2932d8f1..59bdc235 100644 --- a/src/utils/constants/constants.js +++ b/src/utils/constants/constants.js @@ -199,4 +199,4 @@ export const R_ADDRESS_VERSION = 60; export const I_ADDRESS_VERSION = 102; // Address blocklist -export const DEFAULT_ADDRESS_BLOCKLIST_WEBSERVER = 'https://rtethtest.verus.services/exclude.json' \ No newline at end of file +export const DEFAULT_ADDRESS_BLOCKLIST_WEBSERVER = 'https://eth.verusbridge.io/exclude.json' \ No newline at end of file diff --git a/src/utils/constants/web3Constants.js b/src/utils/constants/web3Constants.js index a7d94145..fdf280a0 100644 --- a/src/utils/constants/web3Constants.js +++ b/src/utils/constants/web3Constants.js @@ -8,7 +8,7 @@ export const STABLECOIN_DECIMALS = 6 export const RFOX_UTILITY_CONTRACT = "0xD82F7e3956d3FF391C927Cd7d0A7A57C360DF5b9" export const VERUS_BRIDGE_DELEGATOR_GOERLI_CONTRACT = "0x85a7de2278e52327471e174aeeb280cdfdc6a68a" -export const VERUS_BRIDGE_DELEGATOR_MAINNET_CONTRACT = null // TODO: Add mainnet contract +export const VERUS_BRIDGE_DELEGATOR_MAINNET_CONTRACT = "0x71518580f36FeCEFfE0721F06bA4703218cD7F63" export const ETH_BRIDGE_NAME = "Bridge.vETH" export const VETH = 'vETH' diff --git a/src/utils/personal/displayUtils.js b/src/utils/personal/displayUtils.js index 4de30cda..610d406f 100644 --- a/src/utils/personal/displayUtils.js +++ b/src/utils/personal/displayUtils.js @@ -6,6 +6,7 @@ import { PERSONAL_IMAGE_TYPE_SCHEMA, PERSONAL_IMAGE_SUBTYPE_SCHEMA, } from "../constants/personal"; +import { Platform } from 'react-native'; var RNFS = require('react-native-fs'); export const renderPersonalFullName = (name) => { @@ -41,11 +42,17 @@ export const renderPersonalBirthday = (birthday) => { }; export const getPersonalImageDisplayUri = uri => { - return uri && uri.includes('file://') - ? uri - : uri != null - ? RNFS.DocumentDirectoryPath + `/${uri}` - : ''; + if (uri && uri.startsWith('file://')) { + return uri; + } else if (uri != null) { + const reconstructedUri = RNFS.DocumentDirectoryPath + `/${uri}`; + + if (reconstructedUri.startsWith('file://')) return reconstructedUri; + else if (Platform.OS === 'android') return `file://${reconstructedUri}`; + else return reconstructedUri; + } else { + return ''; + } }; export const renderPersonalDocument = (document) => { @@ -58,7 +65,7 @@ export const renderPersonalDocument = (document) => { {...props} size={96} source={{ - uri: path.includes("file://"), + uri: path, }} /> ); diff --git a/yarn.lock b/yarn.lock index bfb4f87a..802075ba 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9380,10 +9380,10 @@ react-native-fs@2.18.0: base-64 "^0.1.0" utf8 "^3.0.0" -react-native-gesture-handler@2.8.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/react-native-gesture-handler/-/react-native-gesture-handler-2.8.0.tgz#ef9857871c10663c95a51546225b6e00cd4740cf" - integrity sha512-poOSfz/w0IyD6Qwq7aaIRRfEaVTl1ecQFoyiIbpOpfNTjm2B1niY2FLrdVQIOtIOe+K9nH55Qal04nr4jGkHdQ== +react-native-gesture-handler@2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/react-native-gesture-handler/-/react-native-gesture-handler-2.9.0.tgz#2f63812e523c646f25b9ad660fc6f75948e51241" + integrity sha512-a0BcH3Qb1tgVqUutc6d3VuWQkI1AM3+fJx8dkxzZs9t06qA27QgURYFoklpabuWpsUTzuKRpxleykp25E8m7tg== dependencies: "@egjs/hammerjs" "^2.0.17" hoist-non-react-statics "^3.3.0" @@ -9401,10 +9401,10 @@ react-native-haptic-feedback@1.11.0: resolved "https://registry.yarnpkg.com/react-native-haptic-feedback/-/react-native-haptic-feedback-1.11.0.tgz#adfd841f3b67046532f912c6ec827aea0037d8ad" integrity sha512-KTIy7lExwMtB6pOpCARyUzFj5EzYTh+A1GN/FB5Eb0LrW5C6hbb1kdj9K2/RHyZC+wyAJD1M823ZaDCU6n6cLA== -react-native-image-picker@4.0.6: - version "4.0.6" - resolved "https://registry.yarnpkg.com/react-native-image-picker/-/react-native-image-picker-4.0.6.tgz#0dfa5bb83bcf6ff58635ebb9201a185c2271e8cc" - integrity sha512-Pp3UWKUADuMG1mz12m6dSO/R2KnvXVEd77bldrfTMFpz4PFc4iVKo+bHeS79It0mUBezfzDMgfesg/OPLSugvQ== +react-native-image-picker@5.7.0: + version "5.7.0" + resolved "https://registry.yarnpkg.com/react-native-image-picker/-/react-native-image-picker-5.7.0.tgz#3c146550bc89daddfca2fe123aaa0c753b58a5e2" + integrity sha512-LGBrFQzKEAt/u4z+IcY7COZA7N6lCeLQ2dMu1PubK10+d9YBRS8n/ABNj48CJhEpUhcRPt7H3iWbwIuOlcZrDA== react-native-iphone-x-helper@^1.0.3, react-native-iphone-x-helper@^1.3.1: version "1.3.1" From 745f90e532059ef1e27f28b0e4e00f91e5834b78 Mon Sep 17 00:00:00 2001 From: michaeltout Date: Wed, 4 Oct 2023 22:39:35 +0200 Subject: [PATCH 32/42] Handle ERC20 tokens without name, decimals, or symbol functions in their ABI --- .../AddErc20TokenConfirm.js | 26 +++++++++- .../AddErc20TokenForm/AddErc20TokenForm.js | 6 +-- src/utils/CoinData/CoinDirectory.js | 8 +-- src/utils/QrScanner/QrScanner.js | 4 +- .../requests/getCurrencyConversionPaths.js | 15 ++---- src/utils/web3/web3Interface.js | 51 +++++++++++++++++++ 6 files changed, 82 insertions(+), 28 deletions(-) diff --git a/src/components/SendModal/AddErc20Token/AddErc20TokenConfirm/AddErc20TokenConfirm.js b/src/components/SendModal/AddErc20Token/AddErc20TokenConfirm/AddErc20TokenConfirm.js index fe75464a..9b2b4b4b 100644 --- a/src/components/SendModal/AddErc20Token/AddErc20TokenConfirm/AddErc20TokenConfirm.js +++ b/src/components/SendModal/AddErc20Token/AddErc20TokenConfirm/AddErc20TokenConfirm.js @@ -12,6 +12,7 @@ import { import {AddErc20TokenConfirmRender} from './AddErc20TokenConfirm.render'; import { CoinDirectory } from '../../../../utils/CoinData/CoinDirectory'; import { coinsList } from '../../../../utils/CoinData/CoinsList'; +import { ERC20 } from '../../../../utils/constants/intervalConstants'; const AddErc20TokenConfirm = props => { const [contract, setCurrency] = useState(props.route.params.contract); @@ -22,6 +23,8 @@ const AddErc20TokenConfirm = props => { state => state.authentication.activeAccount, ); const activeCoinList = useSelector(state => state.coins.activeCoinList); + const activeCoinsForUser = useSelector(state => state.coins.activeCoinsForUser); + const testAccount = useSelector(state => (Object.keys(state.authentication.activeAccount.testnetOverrides).length > 0)) const goBack = useCallback(() => { props.setModalHeight(); @@ -34,9 +37,28 @@ const AddErc20TokenConfirm = props => { try { const {coinObj} = sendModal; + let fullCoinData; - await CoinDirectory.addErc20Token(contract, coinObj.network); - const fullCoinData = CoinDirectory.findCoinObj(contract.address) + for (const key in coinsList) { + if ( + coinsList[key].proto === ERC20 && + ((coinsList[key].testnet && testAccount) || + (!coinsList[key].testnet && !testAccount)) && + coinsList[key].currency_id.toLowerCase() === contract.address.toLowerCase() + ) { + fullCoinData = CoinDirectory.findCoinObj(key); + } + } + + if (fullCoinData == null) { + await CoinDirectory.addErc20Token(contract, coinObj.network); + fullCoinData = CoinDirectory.findCoinObj(contract.address); + } + + const activeCoinIndex = activeCoinsForUser.findIndex(coin => { + return coin.id === fullCoinData.id + }); + if (activeCoinIndex > -1) throw new Error(`${fullCoinData.display_ticker} already added.`) dispatch( await addKeypairs( diff --git a/src/components/SendModal/AddErc20Token/AddErc20TokenForm/AddErc20TokenForm.js b/src/components/SendModal/AddErc20Token/AddErc20TokenForm/AddErc20TokenForm.js index 42eb3ed0..5438f394 100644 --- a/src/components/SendModal/AddErc20Token/AddErc20TokenForm/AddErc20TokenForm.js +++ b/src/components/SendModal/AddErc20Token/AddErc20TokenForm/AddErc20TokenForm.js @@ -70,11 +70,7 @@ const AddErc20TokenForm = props => { contractAddress = contractAddressField; } - const contract = provider.getUnitializedContractInstance(contractAddress); - - const name = await contract.name(); - const symbol = await contract.symbol(); - const decimals = await contract.decimals(); + const { name, symbol, decimals } = await provider.getContractInfo(contractAddress); Alert.alert( 'Warning', diff --git a/src/utils/CoinData/CoinDirectory.js b/src/utils/CoinData/CoinDirectory.js index c08003e2..2f07887b 100644 --- a/src/utils/CoinData/CoinDirectory.js +++ b/src/utils/CoinData/CoinDirectory.js @@ -166,13 +166,7 @@ class _CoinDirectory { mappedCurrencyId = mappedCoin.id; } else { const network = isTestnet ? 'goerli' : 'homestead' - const contract = getWeb3ProviderForNetwork( - network, - ).getUnitializedContractInstance(contractAddress); - - const name = await contract.name(); - const symbol = await contract.symbol(); - const decimals = await contract.decimals(); + const { name, symbol, decimals } = await getWeb3ProviderForNetwork(network).getContractInfo(contractAddress); this.addErc20Token({ address: contractAddress, symbol, name, decimals }, network, true); mappedCurrencyId = contractAddress; diff --git a/src/utils/QrScanner/QrScanner.js b/src/utils/QrScanner/QrScanner.js index 8a7009b2..671807e1 100644 --- a/src/utils/QrScanner/QrScanner.js +++ b/src/utils/QrScanner/QrScanner.js @@ -129,7 +129,7 @@ class QrScanner { if (coinName && address && amount) { //Find coin ticker from coin data here, URL uses full name - for (key in coinsList) { + for (const key in coinsList) { if ( coinsList[key] && (removeSpaces(coinsList[key].display_name).toLowerCase() === coinName.toLowerCase() || @@ -154,7 +154,7 @@ class QrScanner { let coinName = secondTry[0].substring(0, secondTry[0].indexOf(":")); let address = secondTry[0].substring(secondTry[0].indexOf(":") + 1); - for (key in coinsList) { + for (const key in coinsList) { const coinObj = coinsList[key]; if ( diff --git a/src/utils/api/channels/vrpc/requests/getCurrencyConversionPaths.js b/src/utils/api/channels/vrpc/requests/getCurrencyConversionPaths.js index 5b96e56f..fa88ddff 100644 --- a/src/utils/api/channels/vrpc/requests/getCurrencyConversionPaths.js +++ b/src/utils/api/channels/vrpc/requests/getCurrencyConversionPaths.js @@ -84,10 +84,7 @@ export const getCurrencyConversionPaths = async (systemId, src, ethNetwork) => { try { if (contractAddress !== ETH_CONTRACT_ADDRESS) { - const contract = getWeb3ProviderForNetwork(ethNetwork).getUnitializedContractInstance(contractAddress); - const name = await contract.name(); - const symbol = await contract.symbol(); - const decimals = await contract.decimals(); + const { name, symbol, decimals } = await getWeb3ProviderForNetwork(ethNetwork).getContractInfo(contractAddress); paths[contractAddress].push({ via: convertableCurrencyDefinition, @@ -150,10 +147,7 @@ export const getCurrencyConversionPaths = async (systemId, src, ethNetwork) => { try { if (contractAddress !== ETH_CONTRACT_ADDRESS) { - const contract = getWeb3ProviderForNetwork(ethNetwork).getUnitializedContractInstance(contractAddress); - const name = await contract.name(); - const symbol = await contract.symbol(); - const decimals = await contract.decimals(); + const { name, symbol, decimals } = await getWeb3ProviderForNetwork(ethNetwork).getContractInfo(contractAddress); paths[contractAddress].push({ via: bridgeCurrencyDefinition, @@ -269,10 +263,7 @@ export const getCurrencyConversionPaths = async (systemId, src, ethNetwork) => { mapping: true }) } else { - const contract = getWeb3ProviderForNetwork(ethNetwork).getUnitializedContractInstance(contractAddress); - const name = await contract.name(); - const symbol = await contract.symbol(); - const decimals = await contract.decimals(); + const { name, symbol, decimals } = await getWeb3ProviderForNetwork(ethNetwork).getContractInfo(contractAddress); paths[contractAddress].push({ destination: { diff --git a/src/utils/web3/web3Interface.js b/src/utils/web3/web3Interface.js index 5048d510..db1f7549 100644 --- a/src/utils/web3/web3Interface.js +++ b/src/utils/web3/web3Interface.js @@ -3,6 +3,7 @@ import { DEFAULT_ERC20_ABI } from '../constants/abi'; import { VERUS_BRIDGE_DELEGATOR_GOERLI_CONTRACT } from '../constants/web3Constants'; import { VERUS_BRIDGE_DELEGATOR_GOERLI_ABI } from '../constants/abis/verusBridgeDelegatorAbi'; import { coinsList } from '../CoinData/CoinsList'; +import { ERC20 } from '../constants/intervalConstants'; class Web3Interface { constructor(network, apiKeys) { @@ -80,6 +81,56 @@ class Web3Interface { ); }; + getContractInfo = async (contractAddress) => { + const contract = this.getUnitializedContractInstance(contractAddress); + let name, symbol, decimals; + + for (const key in coinsList) { + if ( + coinsList[key].proto === ERC20 && + ((coinsList[key].testnet && this.network === 'goerli') || + (!coinsList[key].testnet && this.network === 'homestead')) && + coinsList[key].currency_id.toLowerCase() === contractAddress.toLowerCase() + ) { + return { + name: coinsList[key].display_name, + symbol: coinsList[key].display_ticker, + decimals: coinsList[key].decimals, + }; + } + } + + try { + name = await contract.name(); + + if (typeof name !== 'string') throw new Error("Name is not string"); + } catch(e) { + name = contractAddress; + } + + try { + symbol = await contract.symbol(); + + if (typeof name !== 'string') throw new Error("Symbol is not string"); + } catch(e) { + symbol = contractAddress.substring(0, 6); + } + + try { + decimals = await contract.decimals(); + + if (typeof decimals !== 'number') throw new Error("Decimals is not number"); + } catch(e) { + decimals = 0; + } + + return { + name, + symbol, + decimals + } + } + getVerusBridgeDelegatorContract = () => { switch (this.network) { case 'goerli': From 6e409417d95cd59335f8c58f5fce9783048623ae Mon Sep 17 00:00:00 2001 From: michaeltout Date: Wed, 4 Oct 2023 22:40:10 +0200 Subject: [PATCH 33/42] Update podfile lock --- ios/Podfile.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 6a677cf1..5b95dec6 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -259,7 +259,7 @@ PODS: - React - react-native-cameraroll (5.0.4): - React-Core - - react-native-image-picker (4.0.6): + - react-native-image-picker (5.7.0): - React-Core - react-native-netinfo (6.0.2): - React-Core @@ -354,7 +354,7 @@ PODS: - React-Core - RNFS (2.18.0): - React - - RNGestureHandler (2.8.0): + - RNGestureHandler (2.9.0): - React-Core - RNKeychain (6.2.0): - React @@ -813,7 +813,7 @@ SPEC CHECKSUMS: React-logger: 98f663b292a60967ebbc6d803ae96c1381183b6d react-native-camera: a49f960eb362b9fbff87667c0ce639eef1161530 react-native-cameraroll: 38b40d9033e4077b6c603f92f95c6d05fa7907df - react-native-image-picker: a6e56460d34905c849ada551db30897dc7f3d535 + react-native-image-picker: 3269f75c251cdcd61ab51b911dd30d6fff8c6169 react-native-netinfo: 92e6e4476eb8bf6fc2d7c0a6ca0a1406f663d73a react-native-randombytes: 421f1c7d48c0af8dbcd471b0324393ebf8fe7846 react-native-safe-area-context: f0906bf8bc9835ac9a9d3f97e8bde2a997d8da79 @@ -837,7 +837,7 @@ SPEC CHECKSUMS: RNCMaskedView: 5a8ec07677aa885546a0d98da336457e2bea557f RNDateTimePicker: e073697ac3e8a378968d68ab0581fef542b8af8a RNFS: 3ab21fa6c56d65566d1fb26c2228e2b6132e5e32 - RNGestureHandler: 62232ba8f562f7dea5ba1b3383494eb5bf97a4d3 + RNGestureHandler: 071d7a9ad81e8b83fe7663b303d132406a7d8f39 RNKeychain: b8e0711b959a19c5b057d1e970d3c83d159b6da5 RNOS: 728cdea6d232de48cfede274443dbe3f669b86e9 RNPermissions: 4b54095940aea8c03fa3e6c92d4ac3647b31ed4e @@ -866,6 +866,6 @@ SPEC CHECKSUMS: Yoga: c4d61225a466f250c35c1ee78d2d0b3d41fe661c ZcashLightClientKit: 4e4273f2e841e982fedad461a9008cca19089f5c -PODFILE CHECKSUM: 2ea7addfb99cd8f4e5f9742ed28cac88e62bc28c +PODFILE CHECKSUM: 2dd5cff0057e34322095c85f7a7d5ca86846e0e1 -COCOAPODS: 1.11.3 +COCOAPODS: 1.13.0 From f7d88558e08b49d160d10443bd353758552324e8 Mon Sep 17 00:00:00 2001 From: michaeltout Date: Wed, 4 Oct 2023 23:20:41 +0200 Subject: [PATCH 34/42] Hide via field on preconvert form --- src/components/FormModules/ConvertFormModule.js | 2 +- src/components/FormModules/ExportFormModule.js | 2 +- src/containers/Coin/SendCoin/SendCoin.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/FormModules/ConvertFormModule.js b/src/components/FormModules/ConvertFormModule.js index f1d8ec51..02244859 100644 --- a/src/components/FormModules/ConvertFormModule.js +++ b/src/components/FormModules/ConvertFormModule.js @@ -25,7 +25,7 @@ const ConvertFormModule = ({ { showConversionField ? ( - + { (isPreconvert || advancedForm) ? ( - + { (isPreconvert || advancedForm) ? ( { [SEND_MODAL_SHOW_CONVERTTO_FIELD]: true, [SEND_MODAL_SHOW_EXPORTTO_FIELD]: true, [SEND_MODAL_SHOW_MAPPING_FIELD]: false, - [SEND_MODAL_SHOW_VIA_FIELD]: true, + [SEND_MODAL_SHOW_VIA_FIELD]: false, [SEND_MODAL_ADVANCED_FORM]: false }, }, From e4bee532991cfdb196880b255c6a879876cc1c37 Mon Sep 17 00:00:00 2001 From: michaeltout Date: Thu, 5 Oct 2023 14:49:43 +0200 Subject: [PATCH 35/42] Remove MKR from testnet bridge conversion paths --- src/utils/api/channels/erc20/requests/preflight.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/utils/api/channels/erc20/requests/preflight.js b/src/utils/api/channels/erc20/requests/preflight.js index e9b70bf8..2a8620ee 100644 --- a/src/utils/api/channels/erc20/requests/preflight.js +++ b/src/utils/api/channels/erc20/requests/preflight.js @@ -159,7 +159,12 @@ export const preflightBridgeTransfer = async (coinObj, channelId, activeUser, ou // If mapto is undefined, assume conversion and look for which convertable // currency is mapped to the current erc20 address - const convertableCurrencies = [ + const convertableCurrencies = coinObj.testnet ? [ + systemId, + bridgeIAddress, + vEthIAddress, + toIAddress(DAI_VETH, systemName) + ] : [ systemId, bridgeIAddress, vEthIAddress, From 3ab9f203d48147ad4409a358c1f46482b6ea209a Mon Sep 17 00:00:00 2001 From: michaeltout Date: Fri, 6 Oct 2023 01:12:48 +0200 Subject: [PATCH 36/42] Add preconvert suggestions to convert or cross chain modal, update fee for delegator contract --- .../FormModules/ConvertFormModule.js | 7 ++-- .../FormModules/ExportFormModule.js | 7 ++-- .../ConvertOrCrossChainSendForm.js | 38 +++++++++++++------ src/utils/CoinData/CoinDirectory.js | 1 + .../api/channels/erc20/requests/preflight.js | 12 ++---- .../requests/getCurrencyConversionPaths.js | 12 ++++++ src/utils/constants/web3Constants.js | 4 +- yarn.lock | 2 +- 8 files changed, 50 insertions(+), 33 deletions(-) diff --git a/src/components/FormModules/ConvertFormModule.js b/src/components/FormModules/ConvertFormModule.js index 02244859..fc0f4e52 100644 --- a/src/components/FormModules/ConvertFormModule.js +++ b/src/components/FormModules/ConvertFormModule.js @@ -6,7 +6,6 @@ import Styles from '../../styles'; const ConvertFormModule = ({ isConversion, - isPreconvert, advancedForm, convertToField, viaField, @@ -25,9 +24,9 @@ const ConvertFormModule = ({ { showConversionField ? ( - + { - (isPreconvert || advancedForm) ? ( + advancedForm ? ( { - (isPreconvert || advancedForm) ? ( + advancedForm ? ( - + { - (isPreconvert || advancedForm) ? ( + advancedForm ? ( - {isPreconvert || advancedForm ? ( + {advancedForm ? ( 0 ? "Currency to receive as (required)" : "Currency to receive as (optional)"} diff --git a/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js b/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js index 60fba8d3..491fe743 100644 --- a/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js +++ b/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js @@ -32,7 +32,7 @@ import { getCoinLogo } from "../../../../utils/CoinData/CoinData"; import { getCurrency } from "../../../../utils/api/channels/verusid/callCreators"; import selectAddresses from "../../../../selectors/address"; import MissingInfoRedirect from "../../../MissingInfoRedirect/MissingInfoRedirect"; -import { preflightCurrencyTransfer } from "../../../../utils/api/channels/vrpc/callCreators"; +import { getInfo, preflightCurrencyTransfer } from "../../../../utils/api/channels/vrpc/callCreators"; import { DEST_ETH, DEST_ID, DEST_PKH, TransferDestination, fromBase58Check } from "verus-typescript-primitives"; import { CoinDirectory } from "../../../../utils/CoinData/CoinDirectory"; import { ethers } from "ethers"; @@ -202,9 +202,11 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor const processConverttoSuggestionPaths = async (flatPaths, coinObj) => { switch (coinObj.proto) { - case 'vrsc': + case 'vrsc': return flatPaths.filter(x => { - return !x.mapping + if (sendModal.data[SEND_MODAL_IS_PRECONVERT]) { + return !x.mapping && x.prelaunch + } else return !x.mapping && !x.prelaunch }).map((path, index) => { const priceFixed = Number(path.price.toFixed(2)) @@ -410,16 +412,28 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor const seenSystems = {} return flatPaths.filter(x => { if (x.exportto == null) return false; - if ( - !x.mapping && flatPaths.find( - y => - y.mapping && - y.exportto != null && - y.exportto.currencyid === x.exportto.currencyid, - ) != null - ) - return false; + if (sendModal.data[SEND_MODAL_IS_PRECONVERT]) { + if ( + !x.prelaunch || (!x.mapping && flatPaths.find( + y => + y.mapping && + y.exportto != null && + y.exportto.currencyid === x.exportto.currencyid, + ) != null) + ) + return false; + } else { + if ( + x.prelaunch || (!x.mapping && flatPaths.find( + y => + y.mapping && + y.exportto != null && + y.exportto.currencyid === x.exportto.currencyid, + ) != null) + ) + return false; + } const seen = seenSystems[x.exportto.currencyid] != null; diff --git a/src/utils/CoinData/CoinDirectory.js b/src/utils/CoinData/CoinDirectory.js index 2f07887b..4e641521 100644 --- a/src/utils/CoinData/CoinDirectory.js +++ b/src/utils/CoinData/CoinDirectory.js @@ -247,6 +247,7 @@ class _CoinDirectory { id: currencyDefinition.currencyid, currency_id: currencyDefinition.currencyid, system_id: currencyDefinition.systemid, + launch_system_id: currencyDefinition.launchsystemid, bitgojs_network_key: isTestnet ? 'verustest' : 'verus', display_ticker: currencyDefinition.fullyqualifiedname, display_name: currencyDefinition.fullyqualifiedname, diff --git a/src/utils/api/channels/erc20/requests/preflight.js b/src/utils/api/channels/erc20/requests/preflight.js index 2a8620ee..26048c86 100644 --- a/src/utils/api/channels/erc20/requests/preflight.js +++ b/src/utils/api/channels/erc20/requests/preflight.js @@ -10,7 +10,6 @@ import { VETH, ETH_VERUS_BRIDGE_DEST_SYSTEM_ID, MINIMUM_GAS_PRICE_WEI_DELEGATOR_CONTRACT, - DELEGATOR_CONTRACT_GAS_MODIFIER, GAS_TRANSACTION_IMPORT_FEE, ETH_CONTRACT_ADDRESS, INITIAL_GAS_LIMIT, @@ -18,8 +17,7 @@ import { FALLBACK_GAS_BRIDGE_TRANSFER, NULL_ETH_ADDRESS, ETH_VERUS_BRIDGE_CONTRACT_PRELAUNCH_RESERVE_TRANSFER_FEE, - MKR_VETH, - MINIMUM_GAS_PRICE_WEI_DELEGATOR_CONTRACT_BOUNCEBACK, + MKR_VETH } from '../../../../constants/web3Constants'; import { getCurrency, getIdentity } from "../../verusid/callCreators" import { getSystemNameFromSystemId } from "../../../../CoinData/CoinData" @@ -261,12 +259,8 @@ export const preflightBridgeTransfer = async (coinObj, channelId, activeUser, ou let destinationtype, destinationaddress; const baseGasPrice = await Web3Provider.DefaultProvider.getGasPrice(); - const minGasPrice = ethers.BigNumber.from( - address.isETHAccount() - ? MINIMUM_GAS_PRICE_WEI_DELEGATOR_CONTRACT_BOUNCEBACK - : MINIMUM_GAS_PRICE_WEI_DELEGATOR_CONTRACT, - ); - const gasPriceModifier = ethers.BigNumber.from("5"); + const minGasPrice = ethers.BigNumber.from(MINIMUM_GAS_PRICE_WEI_DELEGATOR_CONTRACT); + const gasPriceModifier = ethers.BigNumber.from("2"); const modifiedGasPrice = baseGasPrice.add(baseGasPrice.div(gasPriceModifier)); let gasPrice; diff --git a/src/utils/api/channels/vrpc/requests/getCurrencyConversionPaths.js b/src/utils/api/channels/vrpc/requests/getCurrencyConversionPaths.js index fa88ddff..8b4b4d3a 100644 --- a/src/utils/api/channels/vrpc/requests/getCurrencyConversionPaths.js +++ b/src/utils/api/channels/vrpc/requests/getCurrencyConversionPaths.js @@ -217,12 +217,24 @@ export const getCurrencyConversionPaths = async (systemId, src, ethNetwork) => { // } const paths = await endpoint.getCurrencyConversionPaths(sourceDefinition, destDefinition); + + const prelaunchCurrenciesRes = await endpoint.listCurrencies({ launchstate: "prelaunch" }); + const prelaunchCurrencyIds = prelaunchCurrenciesRes.result + ? prelaunchCurrenciesRes.result.map(x => x.currencydefinition.currencyid) + : null; for (const destinationid in paths) { paths[destinationid] = paths[destinationid].filter(x => { const offSystem = (x.destination.systemid != systemId) || (x.via != null && x.via.systemid != systemId); return !(offSystem && x.exportto == null); + }).map(x => { + if (prelaunchCurrencyIds != null && prelaunchCurrencyIds.includes(x.destination.currencyid)) { + return { + ...x, + prelaunch: true + } + } else return x; }); } diff --git a/src/utils/constants/web3Constants.js b/src/utils/constants/web3Constants.js index fdf280a0..f271ea52 100644 --- a/src/utils/constants/web3Constants.js +++ b/src/utils/constants/web3Constants.js @@ -22,10 +22,8 @@ export const ETH_VERUS_BRIDGE_CONTRACT_PRELAUNCH_RESERVE_TRANSFER_FEE = 2000000 export const ETH_VERUS_BRIDGE_DEST_SYSTEM_ID = "0x0000000000000000000000000000000000000000" export const MINIMUM_GAS_PRICE_WEI_DELEGATOR_CONTRACT = "3000000000" -export const MINIMUM_GAS_PRICE_WEI_DELEGATOR_CONTRACT_BOUNCEBACK = "6000000000" -export const DELEGATOR_CONTRACT_GAS_MODIFIER = "1.2" -export const GAS_TRANSACTION_IMPORT_FEE = "1000000" +export const GAS_TRANSACTION_IMPORT_FEE = "1400000" export const INITIAL_GAS_LIMIT = 6000000 export const FALLBACK_GAS_BRIDGE_TRANSFER = 800000 diff --git a/yarn.lock b/yarn.lock index 802075ba..74ec2f71 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11543,7 +11543,7 @@ version_compare@0.0.3: "verusd-rpc-ts-client@git+https://github.com/VerusCoin/verusd-rpc-ts-client.git": version "0.1.0" - resolved "git+https://github.com/VerusCoin/verusd-rpc-ts-client.git#6886b2b9135fac65afa4bcb80099f4c72d5bc784" + resolved "git+https://github.com/VerusCoin/verusd-rpc-ts-client.git#eecc109266b9c6bc96c959d648654b44f974a4c6" dependencies: axios "0.27.2" verus-typescript-primitives "https://github.com/VerusCoin/verus-typescript-primitives.git" From 5a78680962cf84efc54705052f9c79b62d379955 Mon Sep 17 00:00:00 2001 From: michaeltout Date: Fri, 6 Oct 2023 21:48:08 +0200 Subject: [PATCH 37/42] Change default ERC20 decimals to 18 --- src/utils/web3/web3Interface.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/web3/web3Interface.js b/src/utils/web3/web3Interface.js index db1f7549..2e84ef9c 100644 --- a/src/utils/web3/web3Interface.js +++ b/src/utils/web3/web3Interface.js @@ -1,6 +1,6 @@ import ethers from 'ethers'; import { DEFAULT_ERC20_ABI } from '../constants/abi'; -import { VERUS_BRIDGE_DELEGATOR_GOERLI_CONTRACT } from '../constants/web3Constants'; +import { ETHERS, VERUS_BRIDGE_DELEGATOR_GOERLI_CONTRACT } from '../constants/web3Constants'; import { VERUS_BRIDGE_DELEGATOR_GOERLI_ABI } from '../constants/abis/verusBridgeDelegatorAbi'; import { coinsList } from '../CoinData/CoinsList'; import { ERC20 } from '../constants/intervalConstants'; @@ -121,7 +121,7 @@ class Web3Interface { if (typeof decimals !== 'number') throw new Error("Decimals is not number"); } catch(e) { - decimals = 0; + decimals = ETHERS; } return { From 927b1f4bd5f8c486769774a578b8d8f3ff51a19e Mon Sep 17 00:00:00 2001 From: michaeltout Date: Sat, 7 Oct 2023 00:39:26 +0200 Subject: [PATCH 38/42] Add derived keys on eth and dlight under recover seed --- .../DisplaySeed/DisplaySeed.js | 99 +++++++++++++++---- src/utils/keys.js | 4 + yarn.lock | 2 +- 3 files changed, 83 insertions(+), 22 deletions(-) diff --git a/src/containers/Settings/ProfileSettings/DisplaySeed/DisplaySeed.js b/src/containers/Settings/ProfileSettings/DisplaySeed/DisplaySeed.js index b1d14376..41844e9e 100644 --- a/src/containers/Settings/ProfileSettings/DisplaySeed/DisplaySeed.js +++ b/src/containers/Settings/ProfileSettings/DisplaySeed/DisplaySeed.js @@ -15,8 +15,11 @@ import QRCode from 'react-native-qrcode-svg'; import Styles from '../../../../styles/index' import Colors from "../../../../globals/colors"; import { CommonActions } from '@react-navigation/native'; -import { DLIGHT_PRIVATE, ELECTRUM, WYRE_SERVICE } from "../../../../utils/constants/intervalConstants"; +import { DLIGHT_PRIVATE, ELECTRUM, ETH, WYRE_SERVICE } from "../../../../utils/constants/intervalConstants"; import { Card, Paragraph, Title, Button } from 'react-native-paper' +import { deriveKeyPair, dlightSeedToBytes, isSeedPhrase } from "../../../../utils/keys"; +import { createAlert } from "../../../../actions/actions/alert/dispatchers/alert"; +import { coinsList } from "../../../../utils/CoinData/CoinsList"; class DisplaySeed extends Component { constructor() { @@ -26,12 +29,16 @@ class DisplaySeed extends Component { fromDeleteAccount: false, selectedSeedType: ELECTRUM, dualSameSeed: false, + derivedKeys: {}, + toggleDerivedKey: {}, + fetchingDerivedKey: {} }; this.SEED_NAMES = { - [DLIGHT_PRIVATE]: "Secondary (Z-Address) Seed", - [ELECTRUM]: "Primary Seed", - [WYRE_SERVICE]: "Wyre Account Seed" + [DLIGHT_PRIVATE]: "Secondary (Z-Address)", + [ELECTRUM]: "Primary", + [ETH]: "Ethereum/ERC20", + [WYRE_SERVICE]: "Wyre Account" } } @@ -44,7 +51,7 @@ class DisplaySeed extends Component { const seedsArray = Object.values(data.seeds) this.setState({ - seeds: data.seeds, + seeds: {...data.seeds, [ETH]: data.seeds[ELECTRUM]}, dualSameSeed: seedsArray.every(s => seedsArray.length > 1 && s === seedsArray[0]) }); } @@ -83,8 +90,49 @@ class DisplaySeed extends Component { this.props.navigation.dispatch(NavigationActions.back()); }; + toggleDerived = async (key) => { + if (!this.state.derivedKeys[key]) { + try { + this.setState({ fetchingDerivedKey: { ...this.state.fetchingDerivedKey, [key]: true } }); + const derivedKey = await this.deriveKeyFromSeed(this.state.seeds[key], key); + this.setState({ + derivedKeys: { ...this.state.derivedKeys, [key]: derivedKey }, + fetchingDerivedKey: { ...this.state.fetchingDerivedKey, [key]: false }, + toggleDerivedKey: { ...this.state.toggleDerivedKey, [key]: true } + }); + } catch(e) { + createAlert("Failed to fetch derived key", e.message); + this.setState({ fetchingDerivedKey: { ...this.state.fetchingDerivedKey, [key]: false } }); + } + } else { + this.setState(prevState => ({ + toggleDerivedKey: { + ...prevState.toggleDerivedKey, + [key]: !prevState.toggleDerivedKey[key] + } + })); + } + } + + // Method to derive the key from seed. Replace this with your actual implementation + deriveKeyFromSeed = async (seed, key) => { + switch (key) { + case DLIGHT_PRIVATE: + return Buffer.from(await dlightSeedToBytes(seed)).toString('hex'); + case ETH: + return (await deriveKeyPair( + seed, + coinsList.ETH, + key, + this.props.activeAccount.keyDerivationVersion, + )).privKey; + default: + return seed + } + } + render() { - const { seeds } = this.state; + const { seeds, toggleDerivedKey, fetchingDerivedKey, derivedKeys } = this.state; return ( @@ -95,21 +143,30 @@ class DisplaySeed extends Component { }} > - {Object.keys(seeds).map((key, index) => { - return seeds[key] == null ? null : ( - - - - {this.SEED_NAMES[key]} - {seeds[key]} - - - - - - - ); - })} + {Object.keys(seeds).map((key, index) => { + const isToggleOn = toggleDerivedKey[key]; + const displayedValue = isToggleOn ? derivedKeys[key] : seeds[key]; + + return seeds[key] == null ? null : ( + + + + {this.SEED_NAMES[key]} + {displayedValue} + + + + + {((key === DLIGHT_PRIVATE && isSeedPhrase(seeds[key])) || key === ETH) && ( + + )} + + + + ); + })} diff --git a/src/utils/keys.js b/src/utils/keys.js index fb5bdebc..3319ad77 100644 --- a/src/utils/keys.js +++ b/src/utils/keys.js @@ -166,6 +166,10 @@ export const parseDlightSeed = async (seed) => { } catch(e) { throw e } } +export const dlightSeedToBytes = (seed) => { + return VerusLightClient.deterministicSeedBytes(seed) +} + export const isSeedPhrase = (seed, minWordLength = 12) => { return ( seed.split(/\s+/g).length >= minWordLength && validateMnemonic(seed) diff --git a/yarn.lock b/yarn.lock index 74ec2f71..d1dbdf97 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9622,7 +9622,7 @@ react-native-url-polyfill@1.3.0: "react-native-verus-light-client@git+https://github.com/VerusCoin/react-native-verus-light-client.git": version "1.0.0" - resolved "git+https://github.com/VerusCoin/react-native-verus-light-client.git#9ee102f3cf00a8ca08911d52289cb9a3612d06a0" + resolved "git+https://github.com/VerusCoin/react-native-verus-light-client.git#e73aeb774b4a7da5f08eb85e15c0c92af24c5955" dependencies: react "17.0.1" react-native "0.64.0" From bb702e0d15a960e066205bc44f3e01b9ba67f84d Mon Sep 17 00:00:00 2001 From: Asher Dawes Date: Fri, 6 Oct 2023 21:17:17 -0700 Subject: [PATCH 39/42] update version --- android/app/build.gradle | 4 ++-- env/main.android.json | 2 +- env/main.ios.json | 2 +- ios/assets/env/main.json | 2 +- ios/verusMobile.xcodeproj/project.pbxproj | 8 ++++---- package.json | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 3bc69bd5..7c3c6734 100755 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -4,8 +4,8 @@ import com.android.build.OutputFile def versionMajor = 1 def versionMinor = 0 -def versionRevision = 0 -def versionBuild = 1 +def versionRevision = 1 +def versionBuild = 0 def keystorePropertiesFile = rootProject.file("keystore.properties"); diff --git a/env/main.android.json b/env/main.android.json index f2087c91..996689f4 100644 --- a/env/main.android.json +++ b/env/main.android.json @@ -1,5 +1,5 @@ { - "APP_VERSION": "1.0.0-1", + "APP_VERSION": "1.0.1", "ELECTRUM_PROTOCOL_CHANGE": 1.4, "KEY_DERIVATION_VERSION": 1, diff --git a/env/main.ios.json b/env/main.ios.json index b263aadb..99b83142 100644 --- a/env/main.ios.json +++ b/env/main.ios.json @@ -1,5 +1,5 @@ { - "APP_VERSION": "1.0.0-1", + "APP_VERSION": "1.0.1", "ELECTRUM_PROTOCOL_CHANGE": 1.4, "KEY_DERIVATION_VERSION": 1, diff --git a/ios/assets/env/main.json b/ios/assets/env/main.json index b263aadb..99b83142 100644 --- a/ios/assets/env/main.json +++ b/ios/assets/env/main.json @@ -1,5 +1,5 @@ { - "APP_VERSION": "1.0.0-1", + "APP_VERSION": "1.0.1", "ELECTRUM_PROTOCOL_CHANGE": 1.4, "KEY_DERIVATION_VERSION": 1, diff --git a/ios/verusMobile.xcodeproj/project.pbxproj b/ios/verusMobile.xcodeproj/project.pbxproj index b64ef6ed..03ff731f 100644 --- a/ios/verusMobile.xcodeproj/project.pbxproj +++ b/ios/verusMobile.xcodeproj/project.pbxproj @@ -669,7 +669,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 11; + CURRENT_PROJECT_VERSION = 3; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = J8Y97B5NMQ; ENABLE_BITCODE = NO; @@ -721,7 +721,7 @@ "\"${PODS_CONFIGURATION_BUILD_DIR}/react-native-webview\"", "\"${PODS_CONFIGURATION_BUILD_DIR}/rn-fetch-blob\"", ); - MARKETING_VERSION = 1.0.0; + MARKETING_VERSION = 1.0.1; PRODUCT_BUNDLE_IDENTIFIER = org.reactjs.native.verusmobile; PRODUCT_NAME = verusmobile; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -740,7 +740,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 11; + CURRENT_PROJECT_VERSION = 3; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = J8Y97B5NMQ; ENABLE_BITCODE = NO; @@ -792,7 +792,7 @@ "\"${PODS_CONFIGURATION_BUILD_DIR}/react-native-webview\"", "\"${PODS_CONFIGURATION_BUILD_DIR}/rn-fetch-blob\"", ); - MARKETING_VERSION = 1.0.0; + MARKETING_VERSION = 1.0.1; PRODUCT_BUNDLE_IDENTIFIER = org.reactjs.native.verusmobile; PRODUCT_NAME = verusmobile; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/package.json b/package.json index c9f87ee5..5dabf544 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "verusmobile", - "version": "1.0.0", + "version": "1.0.1", "private": true, "scripts": { "postinstall": "./node_modules/.bin/rn-nodeify --hack --install --yarn && npx jetify", From 226b9c398c3af31053238bafd83792ccb12037c2 Mon Sep 17 00:00:00 2001 From: michaeltout Date: Wed, 11 Oct 2023 22:52:47 +0200 Subject: [PATCH 40/42] Add support for sending VRSC to ETH network before bridge launch, fix issue with preconvert conversion paths --- .../FormModules/ConvertFormModule.js | 7 ++- .../AddErc20TokenForm/AddErc20TokenForm.js | 17 +++++- .../ConvertOrCrossChainSendForm.js | 35 ++++++++++-- src/containers/Coin/DynamicHeader.js | 15 ++++- .../requests/calculateCurrencyTransferFee.js | 12 ++-- .../vrpc/requests/getCurrenciesMappedToEth.js | 2 +- .../requests/getSendCurrencyTransaction.js | 21 +++---- .../api/channels/vrpc/requests/preflight.js | 56 +++++++++++-------- .../constants/abis/verusBridgeDelegatorAbi.js | 2 +- src/utils/web3/web3Interface.js | 21 ++++++- yarn.lock | 2 +- 11 files changed, 133 insertions(+), 57 deletions(-) diff --git a/src/components/FormModules/ConvertFormModule.js b/src/components/FormModules/ConvertFormModule.js index fc0f4e52..3d7d7a2b 100644 --- a/src/components/FormModules/ConvertFormModule.js +++ b/src/components/FormModules/ConvertFormModule.js @@ -6,6 +6,7 @@ import Styles from '../../styles'; const ConvertFormModule = ({ isConversion, + isPreconvert, advancedForm, convertToField, viaField, @@ -29,7 +30,7 @@ const ConvertFormModule = ({ advancedForm ? ( {isConversion - ? `Convert to: ${convertToField}` - : 'Select currency to convert to'} + ? isPreconvert ? `Preconvert to: ${convertToField}` : `Convert to: ${convertToField}` + : isPreconvert ? 'Select currency to preconvert to' : 'Select currency to convert to'} diff --git a/src/components/SendModal/AddErc20Token/AddErc20TokenForm/AddErc20TokenForm.js b/src/components/SendModal/AddErc20Token/AddErc20TokenForm/AddErc20TokenForm.js index 5438f394..e4fdfd0b 100644 --- a/src/components/SendModal/AddErc20Token/AddErc20TokenForm/AddErc20TokenForm.js +++ b/src/components/SendModal/AddErc20Token/AddErc20TokenForm/AddErc20TokenForm.js @@ -9,6 +9,7 @@ import { import {getWeb3ProviderForNetwork} from '../../../../utils/web3/provider'; import {AddErc20TokenFormRender} from './AddErc20TokenForm.render'; import { getCurrency } from '../../../../utils/api/channels/verusid/callCreators'; +import { getCurrenciesMappedToEth } from '../../../../utils/api/channels/vrpc/requests/getCurrenciesMappedToEth'; const AddErc20TokenForm = props => { const dispatch = useDispatch(); @@ -59,13 +60,25 @@ const AddErc20TokenForm = props => { const currencyDef = getCurrencyRes.result; - const isMapped = currencyDef.proofprotocol === 3 && currencyDef.nativecurrencyid != null; + const mappedCurrenciesRes = await getCurrenciesMappedToEth(provider.getVrscSystem(), provider.network); + + if (mappedCurrenciesRes.error) { + throw new Error(mappedCurrenciesRes.error.message); + } + + const mappedCurrenciesResult = mappedCurrenciesRes.result; + + const isMapped = mappedCurrenciesResult.currencyIdToContractAddressMap.has(currencyDef.currencyid); if (!isMapped) { throw new Error("Cannot get ERC20 contract from non-mapped PBaaS currency.") } - contractAddress = currencyDef.nativecurrencyid.address; + contractAddress = mappedCurrenciesResult.currencyIdToContractAddressMap + .get(currencyDef.currencyid) + .values() + .next().value; + } else { contractAddress = contractAddressField; } diff --git a/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js b/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js index 491fe743..e5800105 100644 --- a/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js +++ b/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js @@ -28,12 +28,12 @@ import Styles from "../../../../styles"; import { useEffect } from "react"; import { getConversionPaths } from "../../../../utils/api/routers/getConversionPaths"; import AnimatedActivityIndicatorBox from "../../../AnimatedActivityIndicatorBox"; -import { getCoinLogo } from "../../../../utils/CoinData/CoinData"; +import { getCoinLogo, getSystemNameFromSystemId } from "../../../../utils/CoinData/CoinData"; import { getCurrency } from "../../../../utils/api/channels/verusid/callCreators"; import selectAddresses from "../../../../selectors/address"; import MissingInfoRedirect from "../../../MissingInfoRedirect/MissingInfoRedirect"; import { getInfo, preflightCurrencyTransfer } from "../../../../utils/api/channels/vrpc/callCreators"; -import { DEST_ETH, DEST_ID, DEST_PKH, TransferDestination, fromBase58Check } from "verus-typescript-primitives"; +import { DEST_ETH, DEST_ID, DEST_PKH, TransferDestination, fromBase58Check, toIAddress } from "verus-typescript-primitives"; import { CoinDirectory } from "../../../../utils/CoinData/CoinDirectory"; import { ethers } from "ethers"; import CoreSendFormModule from "../../../FormModules/CoreSendFormModule"; @@ -41,12 +41,13 @@ import ConvertFormModule from "../../../FormModules/ConvertFormModule"; import ExportFormModule from "../../../FormModules/ExportFormModule"; import { getAddressBalances } from "../../../../utils/api/routers/getAddressBalance"; import { coinsList } from "../../../../utils/CoinData/CoinsList"; -import { ETH_CONTRACT_ADDRESS } from "../../../../utils/constants/web3Constants"; +import { ETH_CONTRACT_ADDRESS, VETH } from "../../../../utils/constants/web3Constants"; import { preflightConvertOrCrossChain } from "../../../../utils/api/routers/preflightConvertOrCrossChain"; import { getIdentity } from "../../../../utils/api/routers/getIdentity"; import { addressIsBlocked } from "../../../../utils/addressBlocklist"; import selectAddressBlocklist from "../../../../selectors/settings"; import { I_ADDRESS_VERSION, R_ADDRESS_VERSION } from "../../../../utils/constants/constants"; +import { getWeb3ProviderForNetwork } from "../../../../utils/web3/provider"; const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFormData, navigation }) => { const sendModal = useSelector(state => state.sendModal); @@ -68,7 +69,7 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor const FIELD_TITLES = { [SEND_MODAL_EXPORTTO_FIELD]: "Destination network", [SEND_MODAL_VIA_FIELD]: "Convert via", - [SEND_MODAL_CONVERTTO_FIELD]: "Convert to", + [SEND_MODAL_CONVERTTO_FIELD]: sendModal.data[SEND_MODAL_IS_PRECONVERT] ? "Preconvert to" : "Convert to", [SEND_MODAL_MAPPING_FIELD]: "Receive as" } @@ -874,7 +875,31 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor via: selectData(data[SEND_MODAL_VIA_FIELD]), address: await selectAddress(data[SEND_MODAL_TO_ADDRESS_FIELD]), satoshis: coinsToSats(BigNumber(amount)).toString(), - preconvert: selectData(data[SEND_MODAL_IS_PRECONVERT]), + preconvert: selectData(data[SEND_MODAL_IS_PRECONVERT]) + } + + if ( + (coinObj.proto === 'vrsc' && + output.exportto != null && + output.exportto.toLowerCase() === + toIAddress(VETH, coinObj.testnet ? 'VRSCTEST' : 'VRSC').toLowerCase()) || + output.exportto.toLowerCase() === 'veth' + ) { + try { + const provider = getWeb3ProviderForNetwork( + coinObj.testnet ? 'goerli' : 'homestead', + ); + + if (!(await provider.isVerusBridgeDelegatorActive())) { + output.feecurrency = toIAddress( + VETH, + coinObj.testnet ? 'VRSCTEST' : 'VRSC', + ); + output.bridgeprelaunch = true; + } + } catch (e) { + console.warn(e); + } } for (const key in output) { diff --git a/src/containers/Coin/DynamicHeader.js b/src/containers/Coin/DynamicHeader.js index 58c7f0ce..d7c60903 100644 --- a/src/containers/Coin/DynamicHeader.js +++ b/src/containers/Coin/DynamicHeader.js @@ -27,6 +27,7 @@ import {truncateDecimal} from '../../utils/math'; import {USD} from '../../utils/constants/currencies'; import { CoinDirectory } from '../../utils/CoinData/CoinDirectory'; import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; +import { VERUS_BRIDGE_DELEGATOR_GOERLI_CONTRACT, VERUS_BRIDGE_DELEGATOR_MAINNET_CONTRACT } from '../../utils/constants/web3Constants'; class DynamicHeader extends Component { constructor(props) { @@ -265,6 +266,14 @@ class DynamicHeader extends Component { } render() { + const mappedToEth = + this.props.activeCoin.mapped_to != null && + this.state.mappedCoinObj != null && + (this.state.mappedCoinObj.currency_id.toLowerCase() === + VERUS_BRIDGE_DELEGATOR_GOERLI_CONTRACT.toLowerCase() || + this.state.mappedCoinObj.currency_id.toLowerCase() === + VERUS_BRIDGE_DELEGATOR_MAINNET_CONTRACT.toLowerCase()); + return ( { ` mapped to ${ - this.state.mappedCoinObj.display_ticker.length > 15 + (mappedToEth) + ? 'Ethereum' + : this.state.mappedCoinObj.display_ticker.length > 15 ? this.state.mappedCoinObj.display_ticker.substring(0, 15) + '...' : this.state.mappedCoinObj.display_ticker }${ - this.state.mappedCoinObj.proto === ERC20 + !mappedToEth && this.state.mappedCoinObj.proto === ERC20 ? ` (${ this.state.mappedCoinObj.currency_id.substring(0, 5) + '...' + diff --git a/src/utils/api/channels/vrpc/requests/calculateCurrencyTransferFee.js b/src/utils/api/channels/vrpc/requests/calculateCurrencyTransferFee.js index e05c5643..a410deb6 100644 --- a/src/utils/api/channels/vrpc/requests/calculateCurrencyTransferFee.js +++ b/src/utils/api/channels/vrpc/requests/calculateCurrencyTransferFee.js @@ -4,23 +4,25 @@ import { getCurrency } from "../../verusid/callCreators"; import { coinsToSats } from "../../../../math"; export const calculateCurrencyTransferFee = async (systemId, currency, exportto, convertto, feecurrency, via, source, address) => { - if (feecurrency !== systemId) throw new Error("Fee currencies different from system not yet supported."); - - if (exportto == null) { + if (feecurrency !== systemId && exportto == null) { return "25000"; } else { const { error, result: destinationSystem } = await getCurrency(systemId, exportto); if (error) throw new Error(error.message); - const sendCurrencyResponse = await VrpcProvider.getEndpoint(systemId).sendCurrency(source, [{ + const params = { currency, amount: 0, exportto, convertto, via, address - }], 1, 0.0001, true); + } + + if (feecurrency != null) params.feecurrency = feecurrency; + + const sendCurrencyResponse = await VrpcProvider.getEndpoint(systemId).sendCurrency(source, [params], 1, 0.0001, true); if (sendCurrencyResponse.error) throw new Error(sendCurrencyResponse.error.message); diff --git a/src/utils/api/channels/vrpc/requests/getCurrenciesMappedToEth.js b/src/utils/api/channels/vrpc/requests/getCurrenciesMappedToEth.js index 2dab6b7e..b2cb061b 100644 --- a/src/utils/api/channels/vrpc/requests/getCurrenciesMappedToEth.js +++ b/src/utils/api/channels/vrpc/requests/getCurrenciesMappedToEth.js @@ -6,7 +6,7 @@ import { getWeb3ProviderForNetwork } from "../../../../web3/provider"; * Returns a map of key: contract address and values: array of currencydefinitions * @param {string} systemId * @param {string} ethNetwork - * @returns {Promise<{result?: Map, error?: string}>} + * @returns {Promise<{result?: {contractAddressToCurrencyDefinitionMap: Map, currencyIdToContractAddressMap: Map}, error?: string}>} */ export const getCurrenciesMappedToEth = async (systemId, ethNetwork) => { // TODO: Switch to using subset getter when support is added diff --git a/src/utils/api/channels/vrpc/requests/getSendCurrencyTransaction.js b/src/utils/api/channels/vrpc/requests/getSendCurrencyTransaction.js index a4e9c2f1..3b7da58b 100644 --- a/src/utils/api/channels/vrpc/requests/getSendCurrencyTransaction.js +++ b/src/utils/api/channels/vrpc/requests/getSendCurrencyTransaction.js @@ -11,20 +11,21 @@ export const getSendCurrencyTransaction = async ( via, source = '*', ) => { - if (feecurrency !== systemId) - throw new Error('Fee currencies different from system not yet supported.'); + const params = { + currency, + amount, + exportto, + convertto, + via, + address, + }; + + if (feecurrency != null) params.feecurrency = feecurrency; return await VrpcProvider.getEndpoint(systemId).sendCurrency( source, [ - { - currency, - amount, - exportto, - convertto, - via, - address, - }, + params ], 1, 0.0001, diff --git a/src/utils/api/channels/vrpc/requests/preflight.js b/src/utils/api/channels/vrpc/requests/preflight.js index 5bf3289d..f3fcb129 100644 --- a/src/utils/api/channels/vrpc/requests/preflight.js +++ b/src/utils/api/channels/vrpc/requests/preflight.js @@ -1,7 +1,7 @@ import BigNumber from "bignumber.js"; import { getAddressBalances } from "./getAddressBalances"; import { coinsToSats, satsToCoins } from "../../../../math"; -import { DEST_ID, DEST_PKH, ReserveTransfer, TransferDestination, fromBase58Check } from "verus-typescript-primitives"; +import { DEST_ID, DEST_PKH, ReserveTransfer, TransferDestination, fromBase58Check, toIAddress } from "verus-typescript-primitives"; import { Transaction, networks, smarttxs } from "@bitgo/utxo-lib"; import { calculateCurrencyTransferFee } from "./calculateCurrencyTransferFee"; import { getInfo } from "./getInfo"; @@ -15,6 +15,7 @@ import { unpackOutput } from "@bitgo/utxo-lib/dist/src/smart_transactions"; import { coinsList } from "../../../../CoinData/CoinsList"; import { getSendCurrencyTransaction } from "./getSendCurrencyTransaction"; import { I_ADDRESS_VERSION, R_ADDRESS_VERSION } from "../../../../constants/constants"; +import { VETH } from "../../../../constants/web3Constants"; const { createUnfundedCurrencyTransfer, validateFundedCurrencyTransfer } = smarttxs //TODO: Calculate fee for each coin seperately @@ -212,6 +213,9 @@ export const preflightCurrencyTransfer = async (coinObj, channelId, activeUser, const friendlyNames = new Map(); const currencyDefs = new Map(); let nativeFeesPaid = BigNumber(0); + let _feeamount; + const ethBridgeDelegatorActive = !(!!(output.bridgeprelaunch)); + delete output.bridgeprelaunch try { validateCurrencyTransferOutputParams(output) @@ -288,7 +292,7 @@ export const preflightCurrencyTransfer = async (coinObj, channelId, activeUser, exportto === "i9nwxtKuVYX4MSbeULLiK2ttVi6rUEhh4X" || // vETH i-addr on VRSC exportto === "iCtawpxUiCc2sEupt7Z4u8SDAncGZpgSKm"); // vETH i-addr on VRSCTEST - let _feeamount = feesatoshis; + _feeamount = feesatoshis; nativeFeesPaid = coinsToSats(BigNumber(parentTransactionFee)); let importToSource = false; @@ -298,11 +302,6 @@ export const preflightCurrencyTransfer = async (coinObj, channelId, activeUser, importToSource = convertto != null && via == null && sourceDefinition.currencies.includes(convertto); } - if (feecurrency != null && _feeamount == null) - throw new Error( - 'Providing a non-default fee currency requires also providing a fee amount.', - ); - if (_feeamount == null && isConversionOrExport) { _feeamount = await calculateCurrencyTransferFee( systemId, @@ -332,8 +331,11 @@ export const preflightCurrencyTransfer = async (coinObj, channelId, activeUser, output.satoshis = txSatsBn.minus(nativeFeesPaid).toString(); const newTxSatsBn = BigNumber(output.satoshis); - if ((newTxSatsBn.plus(nativeFeesPaid)).isGreaterThan(balanceBn)) { - throw new Error("Insufficient funds.") + if ( + newTxSatsBn.plus(nativeFeesPaid).isGreaterThan(balanceBn) || + newTxSatsBn.isLessThanOrEqualTo(BigNumber(0)) + ) { + throw new Error('Insufficient funds.'); } } @@ -382,22 +384,24 @@ export const preflightCurrencyTransfer = async (coinObj, channelId, activeUser, */ const transDest = outputInfo.params[0].data; - if (!transDest.transfer_destination.isGateway()) throw new Error("Expected gateway output"); - if (!transDest.transfer_destination.hasAuxDests()) throw new Error("Expected output with aux dests"); - if (transDest.transfer_destination.getAddressString() !== addrDest) throw new Error("Expected output to match destination address"); - if (transDest.transfer_destination.gateway_id !== exportto) throw new Error("Expected gateway_id to match exportto"); - if (transDest.transfer_destination.gateway_code !== "i3UXS5QPRQGNRDDqVnyWTnmFCTHDbzmsYk") throw new Error("Expected null gateway_code"); - - for (const aux_dest of transDest.transfer_destination.aux_dests) { - if (aux_dest.hasAuxDests()) { - throw new Error("Nested aux destinations not supported"); + if (ethBridgeDelegatorActive) { + if (!transDest.transfer_destination.isGateway()) throw new Error("Expected gateway output"); + if (transDest.transfer_destination.gateway_id !== exportto) throw new Error("Expected gateway_id to match exportto"); + if (transDest.transfer_destination.gateway_code !== "i3UXS5QPRQGNRDDqVnyWTnmFCTHDbzmsYk") throw new Error("Expected null gateway_code"); + if (!transDest.transfer_destination.hasAuxDests()) throw new Error("Expected output with aux dests"); + for (const aux_dest of transDest.transfer_destination.aux_dests) { + if (aux_dest.hasAuxDests()) { + throw new Error("Nested aux destinations not supported"); + } + + if (aux_dest.isGateway()) throw new Error("Expected non gateway output in aux dest"); + + const addrString =aux_dest.getAddressString(); + if (addrString !== addrDest && addrString !== source) throw new Error(`Aux dest ${addrString} does not match source or destination`); } - - if (aux_dest.isGateway()) throw new Error("Expected non gateway output in aux dest"); - - const addrString =aux_dest.getAddressString(); - if (addrString !== addrDest && addrString !== source) throw new Error(`Aux dest ${addrString} does not match source or destination`); } + + if (transDest.transfer_destination.getAddressString() !== addrDest) throw new Error("Expected output to match destination address"); unfundedTxHex = sendCurrencyRes.result.hextx; } else { @@ -555,7 +559,11 @@ export const preflightCurrencyTransfer = async (coinObj, channelId, activeUser, : systemName; const nativeFeeAmount = satsToCoins(nativeFeesPaid).toString(); - message = `Insufficient funds. Ensure you have at least ${nativeFeeAmount} ${feeCurrencyName} on the ${systemName} network to fund the fee for transaction.`; + message = `Insufficient funds. Ensure you have at least ${ + output.feecurrency === systemId + ? nativeFeeAmount + : satsToCoins(BigNumber(_feeamount)).toString() + } ${feeCurrencyName} on the ${systemName} network to fund the fee for transaction.`; } return { diff --git a/src/utils/constants/abis/verusBridgeDelegatorAbi.js b/src/utils/constants/abis/verusBridgeDelegatorAbi.js index 7d2e8f4d..1d736ad5 100644 --- a/src/utils/constants/abis/verusBridgeDelegatorAbi.js +++ b/src/utils/constants/abis/verusBridgeDelegatorAbi.js @@ -1,4 +1,4 @@ -export const VERUS_BRIDGE_DELEGATOR_GOERLI_ABI = [ +export const VERUS_BRIDGE_DELEGATOR_ABI = [ { "inputs": [ { diff --git a/src/utils/web3/web3Interface.js b/src/utils/web3/web3Interface.js index 2e84ef9c..7f564bb6 100644 --- a/src/utils/web3/web3Interface.js +++ b/src/utils/web3/web3Interface.js @@ -1,7 +1,7 @@ import ethers from 'ethers'; import { DEFAULT_ERC20_ABI } from '../constants/abi'; -import { ETHERS, VERUS_BRIDGE_DELEGATOR_GOERLI_CONTRACT } from '../constants/web3Constants'; -import { VERUS_BRIDGE_DELEGATOR_GOERLI_ABI } from '../constants/abis/verusBridgeDelegatorAbi'; +import { ETHERS, VERUS_BRIDGE_DELEGATOR_GOERLI_CONTRACT, VERUS_BRIDGE_DELEGATOR_MAINNET_CONTRACT } from '../constants/web3Constants'; +import { VERUS_BRIDGE_DELEGATOR_ABI } from '../constants/abis/verusBridgeDelegatorAbi'; import { coinsList } from '../CoinData/CoinsList'; import { ERC20 } from '../constants/intervalConstants'; @@ -82,6 +82,8 @@ class Web3Interface { }; getContractInfo = async (contractAddress) => { + if (!ethers.utils.isAddress(contractAddress)) throw new Error("Invalid contract address '" + contractAddress + "'") + const contract = this.getUnitializedContractInstance(contractAddress); let name, symbol, decimals; @@ -136,7 +138,13 @@ class Web3Interface { case 'goerli': return new ethers.Contract( VERUS_BRIDGE_DELEGATOR_GOERLI_CONTRACT, - VERUS_BRIDGE_DELEGATOR_GOERLI_ABI, + VERUS_BRIDGE_DELEGATOR_ABI, + this.DefaultProvider + ); + case 'homestead': + return new ethers.Contract( + VERUS_BRIDGE_DELEGATOR_MAINNET_CONTRACT, + VERUS_BRIDGE_DELEGATOR_ABI, this.DefaultProvider ); default: @@ -144,6 +152,13 @@ class Web3Interface { } } + isVerusBridgeDelegatorActive = () => { + const delegatorContract = this.getVerusBridgeDelegatorContract(); + const signer = new ethers.VoidSigner(delegatorContract.address, this.DefaultProvider); + + return delegatorContract.connect(signer).callStatic.bridgeConverterActive(); + } + getVrscSystem = () => { switch (this.network) { case 'homestead': diff --git a/yarn.lock b/yarn.lock index d1dbdf97..eb168bd0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11543,7 +11543,7 @@ version_compare@0.0.3: "verusd-rpc-ts-client@git+https://github.com/VerusCoin/verusd-rpc-ts-client.git": version "0.1.0" - resolved "git+https://github.com/VerusCoin/verusd-rpc-ts-client.git#eecc109266b9c6bc96c959d648654b44f974a4c6" + resolved "git+https://github.com/VerusCoin/verusd-rpc-ts-client.git#615002f1b39e7c989e40188d4483ef0cce4e7bfe" dependencies: axios "0.27.2" verus-typescript-primitives "https://github.com/VerusCoin/verus-typescript-primitives.git" From b39cc9912c47aec27564634c5f9404a58df06498 Mon Sep 17 00:00:00 2001 From: michaeltout Date: Wed, 11 Oct 2023 23:53:11 +0200 Subject: [PATCH 41/42] Add bridge pbaas chains and erc20 tokens --- src/containers/AddCoin/AddCoin.js | 20 ++++- src/utils/CoinData/CoinsList.js | 122 ++++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+), 2 deletions(-) diff --git a/src/containers/AddCoin/AddCoin.js b/src/containers/AddCoin/AddCoin.js index 6fe407c9..7cbcd343 100644 --- a/src/containers/AddCoin/AddCoin.js +++ b/src/containers/AddCoin/AddCoin.js @@ -83,13 +83,29 @@ const AddCoin = props => { if ( currencyB.id === 'VRSC' || currencyB.id === 'BTC' || - currencyB.id === 'VRSCTEST' + currencyB.id === 'VRSCTEST' || + currencyB.id === "iGBs4DWztRNvNEJBt4mqHszLxfKTNHTkhM" || + currencyB.id === "iCkKJuJScy4Z6NSDK7Mt42ZAB2NEnAE1o4" || + currencyB.id === "i9nwxtKuVYX4MSbeULLiK2ttVi6rUEhh4X" || + currencyB.id === "i3f7tSctFkiPpiedY8QR5Tep9p4qDVebDx" || + currencyB.id === '0xBc2738BA63882891094C99E59a02141Ca1A1C36a' || + currencyB.id === '0xE6052Dcc60573561ECef2D9A4C0FEA6d3aC5B9A2' || + currencyB.id === 'MKR' || + currencyB.id === 'DAI' ) { return 1; } else if ( currencyA.id === 'VRSC' || currencyA.id === 'BTC' || - currencyA.id === 'VRSCTEST' + currencyA.id === 'VRSCTEST' || + currencyA.id === "iGBs4DWztRNvNEJBt4mqHszLxfKTNHTkhM" || + currencyA.id === "iCkKJuJScy4Z6NSDK7Mt42ZAB2NEnAE1o4" || + currencyA.id === "i9nwxtKuVYX4MSbeULLiK2ttVi6rUEhh4X" || + currencyA.id === "i3f7tSctFkiPpiedY8QR5Tep9p4qDVebDx" || + currencyA.id === '0xBc2738BA63882891094C99E59a02141Ca1A1C36a' || + currencyA.id === '0xE6052Dcc60573561ECef2D9A4C0FEA6d3aC5B9A2' || + currencyA.id === 'MKR' || + currencyA.id === 'DAI' ) { return -1; } else { diff --git a/src/utils/CoinData/CoinsList.js b/src/utils/CoinData/CoinsList.js index c7d27e47..b6d22f14 100644 --- a/src/utils/CoinData/CoinsList.js +++ b/src/utils/CoinData/CoinsList.js @@ -105,6 +105,97 @@ export const coinsList = { default_app: 'wallet', apps: VERUS_APPS }, + ["iGBs4DWztRNvNEJBt4mqHszLxfKTNHTkhM"]: { + pbaas_options: 32, + system_options: 128, + id: "iGBs4DWztRNvNEJBt4mqHszLxfKTNHTkhM", + currency_id: "iGBs4DWztRNvNEJBt4mqHszLxfKTNHTkhM", + system_id: "i9nwxtKuVYX4MSbeULLiK2ttVi6rUEhh4X", + launch_system_id: "i9nwxtKuVYX4MSbeULLiK2ttVi6rUEhh4X", + bitgojs_network_key: "verus", + display_ticker: "DAI.vETH", + display_name: "DAI on Verus", + alt_names: [], + theme_color: "#232323", + compatible_channels: [VERUSID, VRPC], + tags: [IS_VERUS, IS_ZCASH, IS_PBAAS], + proto: "vrsc", + vrpc_endpoints: ["https://api.verus.services"], + decimals: 8, + seconds_per_block: 60, + default_app: "wallet", + apps: VERUS_APPS, + rate_url_params: {coin_paprika: 'dai-dai-coin'}, + website: 'https://verus.io' + }, + ["iCkKJuJScy4Z6NSDK7Mt42ZAB2NEnAE1o4"]: { + pbaas_options: 32, + system_options: 128, + id: "iCkKJuJScy4Z6NSDK7Mt42ZAB2NEnAE1o4", + currency_id: "iCkKJuJScy4Z6NSDK7Mt42ZAB2NEnAE1o4", + system_id: "i9nwxtKuVYX4MSbeULLiK2ttVi6rUEhh4X", + launch_system_id: "i9nwxtKuVYX4MSbeULLiK2ttVi6rUEhh4X", + bitgojs_network_key: "verus", + display_ticker: "MKR.vETH", + display_name: "Maker on Verus", + alt_names: [], + theme_color: "#232323", + compatible_channels: [VERUSID, VRPC], + tags: [IS_VERUS, IS_ZCASH, IS_PBAAS], + proto: "vrsc", + vrpc_endpoints: ["https://api.verus.services"], + decimals: 8, + seconds_per_block: 60, + default_app: "wallet", + apps: VERUS_APPS, + rate_url_params: {coin_paprika: 'mkr-maker'}, + website: 'https://verus.io' + }, + ["i9nwxtKuVYX4MSbeULLiK2ttVi6rUEhh4X"]: { + pbaas_options: 128, + system_options: 264, + id: "i9nwxtKuVYX4MSbeULLiK2ttVi6rUEhh4X", + currency_id: "i9nwxtKuVYX4MSbeULLiK2ttVi6rUEhh4X", + system_id: "i5w5MuNik5NtLcYmNzcvaoixooEebB6MGV", + launch_system_id: "i5w5MuNik5NtLcYmNzcvaoixooEebB6MGV", + bitgojs_network_key: "verus", + display_ticker: "vETH", + display_name: "Ethereum on Verus", + alt_names: [], + theme_color: "#232323", + compatible_channels: [VERUSID, VRPC], + tags: [IS_VERUS, IS_ZCASH, IS_PBAAS], + proto: "vrsc", + vrpc_endpoints: ["https://api.verus.services"], + decimals: 8, + seconds_per_block: 60, + default_app: "wallet", + apps: VERUS_APPS, + rate_url_params: {coin_paprika: 'eth-ethereum'}, + website: 'https://verus.io' + }, + ["i3f7tSctFkiPpiedY8QR5Tep9p4qDVebDx"]: { + pbaas_options: 545, + system_options: 264, + id: "i3f7tSctFkiPpiedY8QR5Tep9p4qDVebDx", + currency_id: "i3f7tSctFkiPpiedY8QR5Tep9p4qDVebDx", + system_id: "i5w5MuNik5NtLcYmNzcvaoixooEebB6MGV", + launch_system_id: "i5w5MuNik5NtLcYmNzcvaoixooEebB6MGV", + bitgojs_network_key: "verus", + display_ticker: "Bridge.vETH", + display_name: "Bridge.vETH", + alt_names: [], + theme_color: "#232323", + compatible_channels: [VERUSID, VRPC], + tags: [IS_VERUS, IS_ZCASH, IS_PBAAS], + proto: "vrsc", + vrpc_endpoints: ["https://api.verus.services"], + decimals: 8, + seconds_per_block: 60, + default_app: "wallet", + apps: VERUS_APPS, + website: 'https://verus.io' + }, KMD: { id: 'KMD', display_name: 'Komodo', @@ -624,6 +715,37 @@ export const coinsList = { proto: 'erc20', rate_url_params: {coin_paprika: 'sushi-sushi'}, }, + ["0xBc2738BA63882891094C99E59a02141Ca1A1C36a"]: { + id: '0xBc2738BA63882891094C99E59a02141Ca1A1C36a', + currency_id: '0xBc2738BA63882891094C99E59a02141Ca1A1C36a', + system_id: '.eth', + display_ticker: 'VRSC', + display_name: 'Verus on Ethereum', + alt_names: [], + website: 'https://verus.io/', + compatible_channels: [ERC20, GENERAL], + rate_url_params: {coin_paprika: 'vrsc-verus-coin'}, + theme_color: '#141C30', + dominant_channel: ERC20, + decimals: ETHERS, + tags: [], + proto: 'erc20', + }, + ["0xE6052Dcc60573561ECef2D9A4C0FEA6d3aC5B9A2"]: { + id: '0xE6052Dcc60573561ECef2D9A4C0FEA6d3aC5B9A2', + currency_id: '0xE6052Dcc60573561ECef2D9A4C0FEA6d3aC5B9A2', + system_id: '.eth', + display_ticker: 'VBRID', + display_name: 'Bridge.vETH on Ethereum', + alt_names: [], + website: 'https://verus.io/', + compatible_channels: [ERC20, GENERAL], + theme_color: '#141C30', + dominant_channel: ERC20, + decimals: ETHERS, + tags: [], + proto: 'erc20', + }, MKR: { id: 'MKR', currency_id: '0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2', From 9ac6a9edad6bc193c8202b7f6c86c40b49cd7336 Mon Sep 17 00:00:00 2001 From: michaeltout Date: Thu, 12 Oct 2023 08:49:27 +0200 Subject: [PATCH 42/42] Update wording, fix bug with sending non exportto conversions --- .../ConvertOrCrossChainSendForm.js | 6 ++---- src/utils/api/channels/erc20/requests/preflight.js | 2 +- .../channels/vrpc/requests/calculateCurrencyTransferFee.js | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js b/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js index e5800105..05071726 100644 --- a/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js +++ b/src/components/SendModal/ConvertOrCrossChainSend/ConvertOrCrossChainSendForm/ConvertOrCrossChainSendForm.js @@ -879,11 +879,9 @@ const ConvertOrCrossChainSendForm = ({ setLoading, setModalHeight, updateSendFor } if ( - (coinObj.proto === 'vrsc' && + coinObj.proto === 'vrsc' && output.exportto != null && - output.exportto.toLowerCase() === - toIAddress(VETH, coinObj.testnet ? 'VRSCTEST' : 'VRSC').toLowerCase()) || - output.exportto.toLowerCase() === 'veth' + (output.exportto.toLowerCase() === toIAddress(VETH, coinObj.testnet ? 'VRSCTEST' : 'VRSC').toLowerCase() || output.exportto.toLowerCase() === 'veth') ) { try { const provider = getWeb3ProviderForNetwork( diff --git a/src/utils/api/channels/erc20/requests/preflight.js b/src/utils/api/channels/erc20/requests/preflight.js index 26048c86..0401d952 100644 --- a/src/utils/api/channels/erc20/requests/preflight.js +++ b/src/utils/api/channels/erc20/requests/preflight.js @@ -191,7 +191,7 @@ export const preflightBridgeTransfer = async (coinObj, channelId, activeUser, ou throw new Error("Failed to find a currency mapped to sending erc20 token that can be converted"); } } else { - throw new Error("Cannot send to Verus network without a mapped currency"); + throw new Error("Cannot send to Verus network without a mapped currency, please select a currency to receive as"); } const toEthAddress = (iAddr) => { diff --git a/src/utils/api/channels/vrpc/requests/calculateCurrencyTransferFee.js b/src/utils/api/channels/vrpc/requests/calculateCurrencyTransferFee.js index a410deb6..c92f0511 100644 --- a/src/utils/api/channels/vrpc/requests/calculateCurrencyTransferFee.js +++ b/src/utils/api/channels/vrpc/requests/calculateCurrencyTransferFee.js @@ -4,7 +4,7 @@ import { getCurrency } from "../../verusid/callCreators"; import { coinsToSats } from "../../../../math"; export const calculateCurrencyTransferFee = async (systemId, currency, exportto, convertto, feecurrency, via, source, address) => { - if (feecurrency !== systemId && exportto == null) { + if (feecurrency === systemId && exportto == null) { return "25000"; } else { const { error, result: destinationSystem } = await getCurrency(systemId, exportto);