diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 9cc6a6d21b..1786efe55c 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -13,7 +13,7 @@ PODS: - DoubleConversion - glog - glog (0.3.5) - - MultiplatformBleAdapter (0.1.5) + - MultiplatformBleAdapter (0.1.6) - Permission-Camera (2.1.5): - RNPermissions - React (0.60.0): @@ -64,8 +64,8 @@ PODS: - React-jsinspector (0.60.0) - react-native-background-timer (2.2.0): - React - - react-native-ble-plx (2.0.0): - - MultiplatformBleAdapter (= 0.1.5) + - react-native-ble-plx (2.0.1): + - MultiplatformBleAdapter (= 0.1.6) - React - react-native-camera (3.30.0): - React @@ -77,7 +77,7 @@ PODS: - React - react-native-config (0.12.0): - React - - react-native-haskell-shelley (0.0.5): + - react-native-haskell-shelley (0.0.8): - React - react-native-netinfo (5.6.2): - React @@ -115,7 +115,7 @@ PODS: - React - RNCAsyncStorage (1.5.1): - React - - RNDeviceInfo (5.5.7): + - RNDeviceInfo (6.0.1): - React - RNFS (2.13.3): - React @@ -272,7 +272,7 @@ SPEC CHECKSUMS: DoubleConversion: 5805e889d232975c086db112ece9ed034df7a0b2 Folly: 30e7936e1c45c08d884aa59369ed951a8e68cf51 glog: 1f3da668190260b06b429bb211bfbee5cd790c28 - MultiplatformBleAdapter: 3c4391d428382738a47662ae1f665a29ce78ff39 + MultiplatformBleAdapter: 652f47ecd7f5036756517cf66833210709a72eab Permission-Camera: afad27bf90337684d4a86f3825112d648c8c4d3b React: 4b3c068e793e96672dcd186a2b572fac43e4b031 React-Core: 3dc86b22920597f813c62a96db3165950b64826b @@ -283,10 +283,10 @@ SPEC CHECKSUMS: React-jsiexecutor: 7a3554f703a58963ec80b860144ea0f0e9b910e1 React-jsinspector: d4ed52225912efe0019bb7f1a225aec20f23049a react-native-background-timer: 1f7d560647b40e6a60b01c452ba29c54bf581fc4 - react-native-ble-plx: 21acd4201f9de46ab690b24898e8d3ad02f518fd + react-native-ble-plx: d8dcca81ecc617eda824a4ace85f7ffd55d90c6c react-native-camera: eeb5b5f4ba4c1365fed3563af8a72007cbad7aab react-native-config: f2c2ae45625a068c35681a16b9bfb1ca58b0adc7 - react-native-haskell-shelley: 963db135ddf00646fb4170b9b5f6f8c4da449036 + react-native-haskell-shelley: 241bdb4e191404ed8ee4e877388482fb0d73f03d react-native-netinfo: 73303369946c2487c600418961bfdc87748b832f react-native-randombytes: 3638d24759d67c68f6ccba60c52a7a8a8faa6a23 react-native-splash-screen: 200d11d188e2e78cea3ad319964f6142b6384865 @@ -303,7 +303,7 @@ SPEC CHECKSUMS: React-RCTWebSocket: fca087d583724aa0e5fef7d911f0f2a28d0f2736 RNCardano: ffdd908e7dde81508914dbff2fbd739183b2bf34 RNCAsyncStorage: b63d6e83fc629b01df6b624688f17944cea5637f - RNDeviceInfo: e2102056bde3ad5d137fd029d8d431510a00486a + RNDeviceInfo: 6dd5c07a482f2a5e4a29fe8a9d99eadd4277175a RNFS: c9bbde46b0d59619f8e7b735991c60e0f73d22c1 RNKeychain: 3aa3cf891a09a0d18d306862ab2bb9e106079b24 RNPermissions: ad71dd4f767ec254f2cd57592fbee02afee75467 diff --git a/src/components/Delegation/DelegationConfirmation.stories.js b/src/components/Delegation/DelegationConfirmation.stories.js index 55966b4b5d..f4a4c056e8 100644 --- a/src/components/Delegation/DelegationConfirmation.stories.js +++ b/src/components/Delegation/DelegationConfirmation.stories.js @@ -1,4 +1,5 @@ // @flow +/* eslint-disable max-len */ import React from 'react' import {BigNumber} from 'bignumber.js' @@ -6,6 +7,10 @@ import {storiesOf} from '@storybook/react-native' import DelegationConfirmation from './DelegationConfirmation' +const transactionData = { + totalAmountToDelegate: new BigNumber('100000000'), // 100 ADA +} + storiesOf('DelegationConfirmation', module).add('Default', ({navigation}) => { navigation.getParam = (param) => { switch (param) { @@ -19,6 +24,8 @@ storiesOf('DelegationConfirmation', module).add('Default', ({navigation}) => { return new BigNumber('100000000') // 100 ADA case 'transactionFee': return new BigNumber('2000000') // 2 ADA + case 'transactionData': + return transactionData default: return '' } diff --git a/src/components/Delegation/StakingCenter.js b/src/components/Delegation/StakingCenter.js index 4710fc8175..d00e7fac6b 100644 --- a/src/components/Delegation/StakingCenter.js +++ b/src/components/Delegation/StakingCenter.js @@ -2,22 +2,30 @@ import React from 'react' import {View} from 'react-native' import {WebView} from 'react-native-webview' +import {connect} from 'react-redux' import {compose} from 'redux' import {withHandlers, withStateHandlers} from 'recompose' import {injectIntl, defineMessages} from 'react-intl' -// import {STAKING_CENTER_ROUTES} from '../../RoutesList' +import {STAKING_CENTER_ROUTES} from '../../RoutesList' import {withNavigationTitle} from '../../utils/renderUtils' import {CONFIG} from '../../config/config' import {Logger} from '../../utils/logging' -// import walletManager from '../../crypto/walletManager' -// import {getShelleyTxFee} from '../../crypto/jormungandr/transactions/utils' -// import {InsufficientFunds} from '../../crypto/errors' -import globalMessages from '../../i18n/global-messages' +import walletManager from '../../crypto/walletManager' +import globalMessages, {errorMessages} from '../../i18n/global-messages' import {showErrorDialog} from '../../actions' -// import {NetworkError, ApiError} from '../../api/errors' import {PleaseWaitModal} from '../UiKit' import PoolWarningModal from './PoolWarningModal' +import {ObjectValues} from '../../utils/flow' +import { + isOnlineSelector, + utxosSelector, + accountBalanceSelector, +} from '../../selectors' +import UtxoAutoRefresher from '../Send/UtxoAutoRefresher' +import AccountAutoRefresher from './AccountAutoRefresher' +import {NetworkError, ApiError} from '../../api/errors' +import {InsufficientFunds} from '../../crypto/errors' import styles from './styles/DelegationCenter.style' @@ -50,25 +58,19 @@ const noPoolDataDialog = defineMessages({ /** * Prepares WebView's target staking URI - * @param {*} userAda : needs to be in ADA (not Lovelaces) as per Seiza API * @param {*} poolList : Array of delegated pool hash */ -const prepareStakingURL = ( - userAda: string, - poolList: Array, -): null | string => { - // eslint-disable-next-line max-len - // Refer: https://github.com/Emurgo/yoroi-frontend/blob/2f06f7afa5283365f1070b6a042bcfedba51646f/app/containers/wallet/staking/StakingPage.js#L60 +const prepareStakingURL = (poolList: ?Array): null | string => { // source=mobile is constant and already included // TODO: add locale parameter - let finalURL = CONFIG.NETWORKS.JORMUNGANDR.SEIZA_STAKING_SIMPLE(userAda) + let finalURL = CONFIG.NETWORKS.HASKELL_SHELLEY.POOL_EXPLORER if (poolList != null) { finalURL += `&delegated=${encodeURIComponent(JSON.stringify(poolList))}` } return finalURL } -const DelegationCenter = ({ +const StakingCenter = ({ navigation, intl, handleOnMessage, @@ -79,13 +81,15 @@ const DelegationCenter = ({ selectedPools, reputationInfo, }) => { - const approxAdaToDelegate = navigation.getParam('approxAdaToDelegate') - const poolList = navigation.getParam('pools') + // pools user is currently delegating to + const poolList: ?Array = navigation.getParam('poolList') return ( <> + + handleOnMessage(event)} /> @@ -111,7 +115,7 @@ const DelegationCenter = ({ } type SelectedPool = {| - +name: null | string, + +poolName: null | string, +poolHash: string, |} @@ -123,6 +127,11 @@ type ExternalProps = {| export default injectIntl( (compose( withNavigationTitle(({intl}) => intl.formatMessage(messages.title)), + connect((state) => ({ + utxos: utxosSelector(state), + accountBalance: accountBalanceSelector(state), + isOnline: isOnlineSelector(state), + })), withStateHandlers( { busy: false, @@ -150,7 +159,37 @@ export default injectIntl( }, }), withHandlers({ - navigateToDelegationConfirm: () => () => ({}), + navigateToDelegationConfirm: ({ + navigation, + accountBalance, + utxos, + intl, + }) => async (selectedPools: Array) => { + try { + const selectedPool = selectedPools[0] + const transactionData = await walletManager.createDelegationTx( + selectedPool.poolHash, + accountBalance, + utxos, + ) + const transactionFee = await transactionData.signTxRequest.fee(false) + navigation.navigate(STAKING_CENTER_ROUTES.DELEGATION_CONFIRM, { + poolName: selectedPool.poolName, + poolHash: selectedPool.poolHash, + transactionData, + transactionFee, + }) + } catch (e) { + if (e instanceof InsufficientFunds) { + await showErrorDialog(errorMessages.insufficientBalance, intl) + } else { + Logger.error(e) + await showErrorDialog(errorMessages.generalError, intl, { + message: e.message, + }) + } + } + }, }), withHandlers({ handleOnMessage: ({ @@ -161,34 +200,57 @@ export default injectIntl( setShowPoolWarning, intl, }) => async (event) => { - const selectedPools: Array = JSON.parse( - decodeURI(event.nativeEvent.data), - ) - const poolsReputation = navigation.getParam('poolsReputation') - Logger.debug(`From Seiza: ${JSON.stringify(selectedPools)}`) - if ( - selectedPools && - selectedPools.length >= 1 && - selectedPools[0].poolHash != null - ) { - setSelectedPools(selectedPools) - // check if pool in blacklist - const poolsInBlackList = [] - for (const pool of selectedPools) { - if (pool.poolHash in poolsReputation) { - poolsInBlackList.push(pool.poolHash) + try { + const selectedPoolHashes: Array = JSON.parse( + decodeURI(event.nativeEvent.data), + ) + Logger.debug('selected pools from explorer:', selectedPoolHashes) + + const poolInfoResponse = await walletManager.fetchPoolInfo({ + poolIds: selectedPoolHashes, + }) + const poolInfo = ObjectValues(poolInfoResponse)[0] + Logger.debug('poolInfo', poolInfo) + + // TODO: fetch reputation info once an endpoint is implemented + const poolsReputation: {[key: string]: mixed} = {} + if (poolInfo?.info != null) { + const selectedPools: Array = [ + { + poolName: poolInfo.info.name, + poolHash: selectedPoolHashes[0], + }, + ] + setSelectedPools(selectedPools) + // check if pool in blacklist + const poolsInBlackList = [] + for (const pool of selectedPoolHashes) { + if (pool in poolsReputation) { + poolsInBlackList.push(pool) + } + } + if (poolsInBlackList.length > 0) { + setReputationInfo(poolsReputation[poolsInBlackList[0]]) + setShowPoolWarning(true) + } else { + navigateToDelegationConfirm(selectedPools) } + } else { + await showErrorDialog(noPoolDataDialog, intl) } - if (poolsInBlackList.length > 0) { - setReputationInfo(poolsReputation[poolsInBlackList[0]]) - setShowPoolWarning(true) + } catch (e) { + if (e instanceof NetworkError) { + await showErrorDialog(errorMessages.networkError, intl) + } else if (e instanceof ApiError) { + await showErrorDialog(noPoolDataDialog, intl) } else { - navigateToDelegationConfirm() + Logger.error(e) + await showErrorDialog(errorMessages.generalError, intl, { + message: e.message, + }) } - } else { - await showErrorDialog(noPoolDataDialog, intl) } }, }), - )(DelegationCenter): ComponentType), + )(StakingCenter): ComponentType), ) diff --git a/src/components/Delegation/StakingCenter.stories.js b/src/components/Delegation/StakingCenter.stories.js index 29bb1e5b73..9200abbaab 100644 --- a/src/components/Delegation/StakingCenter.stories.js +++ b/src/components/Delegation/StakingCenter.stories.js @@ -14,9 +14,7 @@ storiesOf('StakingCenter', module).add( case 'approxAdaToDelegate': return '100' case 'pools': - return [ - 'bd885c7fa6bcceaa6e530fe4f285daa4c631fab6ff31544dfc37b88eade9763e', - ] + return ['af22f95915a19cd57adb14c558dcc4a175f60c6193dc23b8bd2d8beb'] default: return '' } diff --git a/src/components/Delegation/StakingCenterNavigator.js b/src/components/Delegation/StakingCenterNavigator.js index 4a36eb29b5..315da95a92 100644 --- a/src/components/Delegation/StakingCenterNavigator.js +++ b/src/components/Delegation/StakingCenterNavigator.js @@ -3,7 +3,7 @@ import React from 'react' import {createStackNavigator} from 'react-navigation' import {Button} from '../UiKit' -import StakeByIdScreen from './StakeByIdScreen' +import StakingCenter from './StakingCenter' import BiometricAuthScreen from '../Send/BiometricAuthScreen' import DelegationConfirmation from './DelegationConfirmation' import { @@ -24,8 +24,7 @@ import styles from '../TxHistory/styles/SettingsButton.style' const StakingNavigatorCenter = createStackNavigator( { [STAKING_CENTER_ROUTES.MAIN]: { - // this should be temporal. Implement StakingCenter in next iteration - screen: StakeByIdScreen, + screen: StakingCenter, navigationOptions: ({navigation}) => ({ title: navigation.getParam('title'), headerRight: ( diff --git a/src/components/Delegation/StakingDashboard.stories.js b/src/components/Delegation/StakingDashboard.stories.js index 9256adc2bc..7092c43003 100644 --- a/src/components/Delegation/StakingDashboard.stories.js +++ b/src/components/Delegation/StakingDashboard.stories.js @@ -9,10 +9,10 @@ import StakingDashboard from './StakingDashboard' const poolInfo = { info: { - ticker: '3EMUR', - name: 'EMURGO’ STAKEPOOL', + ticker: 'EMUR1', + name: 'Emurgo #1', description: - 'EMURGO’s official Stake Pool. EMURGO is one of three organizations that contribute to the development of Cardano. Let’s make this Testnet successful by delegation to multiple stakepools.', + 'EMURGO is a multinational blockchain technology company providing solutions for developers, startups, enterprises, and governments.', homepage: 'https://emurgo.io', }, history: [ diff --git a/src/config/networks.js b/src/config/networks.js index ce680a8d1b..836eee501f 100644 --- a/src/config/networks.js +++ b/src/config/networks.js @@ -32,6 +32,7 @@ export const NETWORKS = { IS_MAINNET: true, EXPLORER_URL_FOR_ADDRESS: (address: string) => `https://explorer.cardano.org/en/address?address=${address}`, + POOL_EXPLORER: 'https://adapools.yoroiwallet.com/?source=mobile', BACKEND: { API_ROOT: 'https://iohk-mainnet.yoroiwallet.com/api', ..._DEFAULT_BACKEND_RULES,