diff --git a/packages/contractkit/src/identity/odis/phone-number-identifier.test.ts b/packages/contractkit/src/identity/odis/phone-number-identifier.test.ts index 853b5de771e..d528350420b 100644 --- a/packages/contractkit/src/identity/odis/phone-number-identifier.test.ts +++ b/packages/contractkit/src/identity/odis/phone-number-identifier.test.ts @@ -2,6 +2,7 @@ import fetchMock from 'fetch-mock' import { getPepperFromThresholdSignature, getPhoneNumberIdentifier, + isBalanceSufficientForSigRetrieval, } from './phone-number-identifier' import { AuthenticationMethod, EncryptionKeySigner, ErrorMessages, ServiceContext } from './query' @@ -30,6 +31,14 @@ const authSigner: EncryptionKeySigner = { rawKey: '41e8e8593108eeedcbded883b8af34d2f028710355c57f4c10a056b72486aa04', } +describe(isBalanceSufficientForSigRetrieval, () => { + it('identifies sufficient balance correctly', () => { + expect(isBalanceSufficientForSigRetrieval(0.009, 0.004)).toBe(false) + expect(isBalanceSufficientForSigRetrieval(0.01, 0)).toBe(true) + expect(isBalanceSufficientForSigRetrieval(0, 0.005)).toBe(true) + }) +}) + describe(getPhoneNumberIdentifier, () => { afterEach(() => { fetchMock.reset() diff --git a/packages/contractkit/src/identity/odis/phone-number-identifier.ts b/packages/contractkit/src/identity/odis/phone-number-identifier.ts index 9384582211a..2c253cbe7ff 100644 --- a/packages/contractkit/src/identity/odis/phone-number-identifier.ts +++ b/packages/contractkit/src/identity/odis/phone-number-identifier.ts @@ -1,4 +1,5 @@ import { getPhoneHash, isE164Number } from '@celo/base/lib/phoneNumbers' +import BigNumber from 'bignumber.js' import { createHash } from 'crypto' import debugFactory from 'debug' import { soliditySha3 } from 'web3-utils' @@ -11,6 +12,11 @@ import { SignMessageResponse, } from './query' +// ODIS minimum dollar balance for sig retrieval +export const ODIS_MINIMUM_DOLLAR_BALANCE = 0.01 +// ODIS minimum celo balance for sig retrieval +export const ODIS_MINIMUM_CELO_BALANCE = 0.005 + const debug = debugFactory('kit:odis:phone-number-identifier') const sha3 = (v: string) => soliditySha3({ type: 'string', value: v }) @@ -85,3 +91,16 @@ export function getPepperFromThresholdSignature(sigBuf: Buffer) { .digest('base64') .slice(0, PEPPER_CHAR_LENGTH) } + +/** + * Check if balance is sufficient for quota retrieval + */ +export function isBalanceSufficientForSigRetrieval( + dollarBalance: BigNumber.Value, + celoBalance: BigNumber.Value +) { + return ( + new BigNumber(dollarBalance).isGreaterThanOrEqualTo(ODIS_MINIMUM_DOLLAR_BALANCE) || + new BigNumber(celoBalance).isGreaterThanOrEqualTo(ODIS_MINIMUM_CELO_BALANCE) + ) +} diff --git a/packages/docs/developer-resources/contractkit/reference/interfaces/_identity_odis_phone_number_identifier_.phonenumberhashdetails.md b/packages/docs/developer-resources/contractkit/reference/interfaces/_identity_odis_phone_number_identifier_.phonenumberhashdetails.md index 53260a84b81..71874976efe 100644 --- a/packages/docs/developer-resources/contractkit/reference/interfaces/_identity_odis_phone_number_identifier_.phonenumberhashdetails.md +++ b/packages/docs/developer-resources/contractkit/reference/interfaces/_identity_odis_phone_number_identifier_.phonenumberhashdetails.md @@ -18,7 +18,7 @@ • **e164Number**: *string* -*Defined in [contractkit/src/identity/odis/phone-number-identifier.ts:21](https://github.com/celo-org/celo-monorepo/blob/master/packages/contractkit/src/identity/odis/phone-number-identifier.ts#L21)* +*Defined in [contractkit/src/identity/odis/phone-number-identifier.ts:27](https://github.com/celo-org/celo-monorepo/blob/master/packages/contractkit/src/identity/odis/phone-number-identifier.ts#L27)* ___ @@ -26,7 +26,7 @@ ___ • **pepper**: *string* -*Defined in [contractkit/src/identity/odis/phone-number-identifier.ts:23](https://github.com/celo-org/celo-monorepo/blob/master/packages/contractkit/src/identity/odis/phone-number-identifier.ts#L23)* +*Defined in [contractkit/src/identity/odis/phone-number-identifier.ts:29](https://github.com/celo-org/celo-monorepo/blob/master/packages/contractkit/src/identity/odis/phone-number-identifier.ts#L29)* ___ @@ -34,4 +34,4 @@ ___ • **phoneHash**: *string* -*Defined in [contractkit/src/identity/odis/phone-number-identifier.ts:22](https://github.com/celo-org/celo-monorepo/blob/master/packages/contractkit/src/identity/odis/phone-number-identifier.ts#L22)* +*Defined in [contractkit/src/identity/odis/phone-number-identifier.ts:28](https://github.com/celo-org/celo-monorepo/blob/master/packages/contractkit/src/identity/odis/phone-number-identifier.ts#L28)* diff --git a/packages/docs/developer-resources/contractkit/reference/modules/_identity_odis_phone_number_identifier_.md b/packages/docs/developer-resources/contractkit/reference/modules/_identity_odis_phone_number_identifier_.md index 161e5fa3750..f9b4f0047cd 100644 --- a/packages/docs/developer-resources/contractkit/reference/modules/_identity_odis_phone_number_identifier_.md +++ b/packages/docs/developer-resources/contractkit/reference/modules/_identity_odis_phone_number_identifier_.md @@ -6,10 +6,32 @@ * [PhoneNumberHashDetails](../interfaces/_identity_odis_phone_number_identifier_.phonenumberhashdetails.md) +### Variables + +* [ODIS_MINIMUM_CELO_BALANCE](_identity_odis_phone_number_identifier_.md#const-odis_minimum_celo_balance) +* [ODIS_MINIMUM_DOLLAR_BALANCE](_identity_odis_phone_number_identifier_.md#const-odis_minimum_dollar_balance) + ### Functions * [getPepperFromThresholdSignature](_identity_odis_phone_number_identifier_.md#getpepperfromthresholdsignature) * [getPhoneNumberIdentifier](_identity_odis_phone_number_identifier_.md#getphonenumberidentifier) +* [isBalanceSufficientForSigRetrieval](_identity_odis_phone_number_identifier_.md#isbalancesufficientforsigretrieval) + +## Variables + +### `Const` ODIS_MINIMUM_CELO_BALANCE + +• **ODIS_MINIMUM_CELO_BALANCE**: *0.005* = 0.005 + +*Defined in [contractkit/src/identity/odis/phone-number-identifier.ts:18](https://github.com/celo-org/celo-monorepo/blob/master/packages/contractkit/src/identity/odis/phone-number-identifier.ts#L18)* + +___ + +### `Const` ODIS_MINIMUM_DOLLAR_BALANCE + +• **ODIS_MINIMUM_DOLLAR_BALANCE**: *0.01* = 0.01 + +*Defined in [contractkit/src/identity/odis/phone-number-identifier.ts:16](https://github.com/celo-org/celo-monorepo/blob/master/packages/contractkit/src/identity/odis/phone-number-identifier.ts#L16)* ## Functions @@ -17,7 +39,7 @@ ▸ **getPepperFromThresholdSignature**(`sigBuf`: Buffer): *string* -*Defined in [contractkit/src/identity/odis/phone-number-identifier.ts:81](https://github.com/celo-org/celo-monorepo/blob/master/packages/contractkit/src/identity/odis/phone-number-identifier.ts#L81)* +*Defined in [contractkit/src/identity/odis/phone-number-identifier.ts:87](https://github.com/celo-org/celo-monorepo/blob/master/packages/contractkit/src/identity/odis/phone-number-identifier.ts#L87)* **Parameters:** @@ -33,7 +55,7 @@ ___ ▸ **getPhoneNumberIdentifier**(`e164Number`: string, `account`: string, `signer`: [AuthSigner](_identity_odis_query_.md#authsigner), `context`: [ServiceContext](../interfaces/_identity_odis_query_.servicecontext.md), `selfPhoneHash?`: undefined | string, `clientVersion?`: undefined | string, `blsBlindingClient?`: [BlsBlindingClient](../interfaces/_identity_odis_bls_blinding_client_.blsblindingclient.md)): *Promise‹[PhoneNumberHashDetails](../interfaces/_identity_odis_phone_number_identifier_.phonenumberhashdetails.md)›* -*Defined in [contractkit/src/identity/odis/phone-number-identifier.ts:29](https://github.com/celo-org/celo-monorepo/blob/master/packages/contractkit/src/identity/odis/phone-number-identifier.ts#L29)* +*Defined in [contractkit/src/identity/odis/phone-number-identifier.ts:35](https://github.com/celo-org/celo-monorepo/blob/master/packages/contractkit/src/identity/odis/phone-number-identifier.ts#L35)* Retrieve the on-chain identifier for the provided phone number @@ -50,3 +72,22 @@ Name | Type | `blsBlindingClient?` | [BlsBlindingClient](../interfaces/_identity_odis_bls_blinding_client_.blsblindingclient.md) | **Returns:** *Promise‹[PhoneNumberHashDetails](../interfaces/_identity_odis_phone_number_identifier_.phonenumberhashdetails.md)›* + +___ + +### isBalanceSufficientForSigRetrieval + +▸ **isBalanceSufficientForSigRetrieval**(`dollarBalance`: BigNumber.Value, `celoBalance`: BigNumber.Value): *boolean* + +*Defined in [contractkit/src/identity/odis/phone-number-identifier.ts:98](https://github.com/celo-org/celo-monorepo/blob/master/packages/contractkit/src/identity/odis/phone-number-identifier.ts#L98)* + +Check if balance is sufficient for quota retrieval + +**Parameters:** + +Name | Type | +------ | ------ | +`dollarBalance` | BigNumber.Value | +`celoBalance` | BigNumber.Value | + +**Returns:** *boolean* diff --git a/packages/mobile/src/account/Settings.test.tsx b/packages/mobile/src/account/Settings.test.tsx index 93f243d982c..01a35e2d5d3 100644 --- a/packages/mobile/src/account/Settings.test.tsx +++ b/packages/mobile/src/account/Settings.test.tsx @@ -5,6 +5,7 @@ import * as renderer from 'react-test-renderer' import Settings from 'src/account/Settings' import { Screens } from 'src/navigator/Screens' import { createMockStore, getMockStackScreenProps } from 'test/utils' +import { mockE164Number, mockE164NumberPepper } from 'test/values' describe('Account', () => { beforeAll(() => { @@ -17,7 +18,16 @@ describe('Account', () => { it('renders correctly', () => { const tree = renderer.create( - + ) @@ -28,8 +38,12 @@ describe('Account', () => { const tree = renderer.create( @@ -38,4 +52,12 @@ describe('Account', () => { ) expect(tree).toMatchSnapshot() }) + it('renders correctly when verification is not possible', () => { + const tree = renderer.create( + + + + ) + expect(tree).toMatchSnapshot() + }) }) diff --git a/packages/mobile/src/account/Settings.tsx b/packages/mobile/src/account/Settings.tsx index 0327716dc65..70fd1ef5419 100644 --- a/packages/mobile/src/account/Settings.tsx +++ b/packages/mobile/src/account/Settings.tsx @@ -33,7 +33,7 @@ import { setRequirePinOnAppOpen, setSessionId, } from 'src/app/actions' -import { sessionIdSelector } from 'src/app/selectors' +import { sessionIdSelector, verificationPossibleSelector } from 'src/app/selectors' import Dialog from 'src/components/Dialog' import SessionId from 'src/components/SessionId' import { AVAILABLE_LANGUAGES, TOS_LINK } from 'src/config' @@ -70,6 +70,7 @@ interface StateProps { devModeActive: boolean analyticsEnabled: boolean numberVerified: boolean + verificationPossible: boolean pincodeType: PincodeType backupCompleted: boolean requirePinOnAppOpen: boolean @@ -91,6 +92,7 @@ const mapStateToProps = (state: RootState): StateProps => { e164PhoneNumber: state.account.e164PhoneNumber, analyticsEnabled: state.app.analyticsEnabled, numberVerified: state.app.numberVerified, + verificationPossible: verificationPossibleSelector(state), pincodeType: pincodeTypeSelector(state), requirePinOnAppOpen: state.app.requirePinOnAppOpen, fornoEnabled: state.web3.fornoMode, @@ -303,7 +305,7 @@ export class Account extends React.Component { } render() { - const { t, i18n, numberVerified } = this.props + const { t, i18n, numberVerified, verificationPossible } = this.props const promptFornoModal = this.props.route.params?.promptFornoModal ?? false const promptConfirmRemovalModal = this.props.route.params?.promptConfirmRemovalModal ?? false const currentLanguage = AVAILABLE_LANGUAGES.find((l) => l.code === i18n.language) @@ -318,7 +320,7 @@ export class Account extends React.Component { - {!numberVerified && ( + {!numberVerified && verificationPossible && ( )} `; + +exports[`Account renders correctly when verification is not possible 1`] = ` + + + + + + + + + + + + + + global:settings + + + + + + + + + editProfile + + + + + + + + + + + + + + languageSettings + + + + + global:unknown + + + + + + + + + + + + + + + + localCurrencySetting + + + + + MXN + + + + + + + + + + + + securityAndData + + + + + + + + requirePinOnAppOpen + + + + + + + + + + + + enableDataSaver + + + + + + + dataSaverDetail + + + + + + + + + + shareAnalytics + + + + + + + shareAnalytics_detail + + + + + + + legal + + + + + + + + + licenses + + + + + + + + + + + + + + termsOfServiceLink + + + + + + + + + + + + + + + + + + + removeAccountTitle + + + + + + removeAccountDetails + + + + + + + + + + + + + + + restartModalSwitchOff.header + + + restartModalSwitchOff.body + + + + + + + global:cancel + + + + + restartModalSwitchOff.restart + + + + + + + + + + + + + + + + promptFornoModal.header + + + promptFornoModal.body + + + + + + + global:goBack + + + + + promptFornoModal.switchToDataSaver + + + + + + + + + + + + + + + + accountKeyModal.header + + + accountKeyModal.body1 + + + + accountKeyModal.body2 + + + + + + + global:cancel + + + + + global:continue + + + + + + + + + + + + + + + + promptConfirmRemovalModal.header + + + promptConfirmRemovalModal.body + + + + + + + global:cancel + + + + + promptConfirmRemovalModal.resetNow + + + + + + + + + + +`; diff --git a/packages/mobile/src/app/selectors.test.ts b/packages/mobile/src/app/selectors.test.ts new file mode 100644 index 00000000000..2a24a8b9aa4 --- /dev/null +++ b/packages/mobile/src/app/selectors.test.ts @@ -0,0 +1,55 @@ +import { verificationPossibleSelector } from 'src/app/selectors' +import { getMockStoreData } from 'test/utils' +import { mockE164Number, mockE164NumberPepper } from 'test/values' + +describe(verificationPossibleSelector, () => { + it('returns true when the number pepper is already cached', () => { + // cached salt + expect( + verificationPossibleSelector( + getMockStoreData({ + account: { e164PhoneNumber: mockE164Number }, + stableToken: { balance: '0' }, + goldToken: { balance: '0' }, + identity: { e164NumberToSalt: { [mockE164Number]: mockE164NumberPepper } }, + }) + ) + ).toBe(true) + }) + it('returns true when balance is sufficient', () => { + // balance is sufficient + expect( + verificationPossibleSelector( + getMockStoreData({ + account: { e164PhoneNumber: mockE164Number }, + stableToken: { balance: '0.01' }, + goldToken: { balance: '0' }, + identity: { e164NumberToSalt: {} }, + }) + ) + ).toBe(true) + expect( + verificationPossibleSelector( + getMockStoreData({ + account: { e164PhoneNumber: mockE164Number }, + stableToken: { balance: '0' }, + goldToken: { balance: '0.005' }, + identity: { e164NumberToSalt: {} }, + }) + ) + ).toBe(true) + }) + it('returns true when balance is not sufficient', () => { + // balance is not sufficient + expect( + verificationPossibleSelector( + getMockStoreData({ + account: { e164PhoneNumber: mockE164Number }, + stableToken: { balance: '0.009' }, + goldToken: { balance: '0.004' }, + identity: { e164NumberToSalt: {} }, + }) + ) + ).toBe(false) + }) +}) diff --git a/packages/mobile/src/app/selectors.ts b/packages/mobile/src/app/selectors.ts index 0b0915ee492..57f160b5d5a 100644 --- a/packages/mobile/src/app/selectors.ts +++ b/packages/mobile/src/app/selectors.ts @@ -1,4 +1,9 @@ +import { isBalanceSufficientForSigRetrieval } from '@celo/contractkit/lib/identity/odis/phone-number-identifier' +import { e164NumberSelector } from 'src/account/selectors' +import { celoTokenBalanceSelector } from 'src/goldToken/selectors' +import { e164NumberToSaltSelector } from 'src/identity/reducer' import { RootState } from 'src/redux/reducers' +import { stableTokenBalanceSelector } from 'src/stableToken/reducer' export const getRequirePinOnAppOpen = (state: RootState) => { return state.app.requirePinOnAppOpen @@ -19,3 +24,14 @@ export const getLastTimeBackgrounded = (state: RootState) => { export const sessionIdSelector = (state: RootState) => { return state.app.sessionId } + +export const verificationPossibleSelector = (state: RootState) => { + const e164Number = e164NumberSelector(state) + const dollarBalance = stableTokenBalanceSelector(state) || 0 + const celoBalance = celoTokenBalanceSelector(state) || 0 + const saltCache = e164NumberToSaltSelector(state) + return !!( + (e164Number && saltCache[e164Number]) || + isBalanceSufficientForSigRetrieval(dollarBalance, celoBalance) + ) +} diff --git a/packages/mobile/src/exchange/CeloExchangeButtons.tsx b/packages/mobile/src/exchange/CeloExchangeButtons.tsx index e97633f0381..ac2bd614b76 100644 --- a/packages/mobile/src/exchange/CeloExchangeButtons.tsx +++ b/packages/mobile/src/exchange/CeloExchangeButtons.tsx @@ -12,7 +12,7 @@ import { CeloExchangeEvents } from 'src/analytics/Events' import ValoraAnalytics from 'src/analytics/ValoraAnalytics' import { DOLLAR_TRANSACTION_MIN_AMOUNT, GOLD_TRANSACTION_MIN_AMOUNT } from 'src/config' import { exchangeRatePairSelector } from 'src/exchange/reducer' -import { getCeloBalance } from 'src/goldToken/selectors' +import { celoTokenBalanceSelector } from 'src/goldToken/selectors' import { Namespaces } from 'src/i18n' import { Screens } from 'src/navigator/Screens' import { StackParamList } from 'src/navigator/types' @@ -26,7 +26,7 @@ export default function CeloExchangeButtons({ navigation }: Props) { const { t } = useTranslation(Namespaces.exchangeFlow9) const dollarBalance = useSelector(stableTokenBalanceSelector) - const goldBalance = useSelector(getCeloBalance) + const goldBalance = useSelector(celoTokenBalanceSelector) const exchangeRate = useSelector(exchangeRatePairSelector) const hasDollars = new BigNumber(dollarBalance || 0).isGreaterThan(DOLLAR_TRANSACTION_MIN_AMOUNT) diff --git a/packages/mobile/src/goldToken/selectors.ts b/packages/mobile/src/goldToken/selectors.ts index c8be3ba15e2..fab9e6d5870 100644 --- a/packages/mobile/src/goldToken/selectors.ts +++ b/packages/mobile/src/goldToken/selectors.ts @@ -1,3 +1,3 @@ import { RootState } from 'src/redux/reducers' -export const getCeloBalance = (state: RootState) => state.goldToken.balance +export const celoTokenBalanceSelector = (state: RootState) => state.goldToken.balance diff --git a/packages/mobile/src/home/NotificationBox.test.tsx b/packages/mobile/src/home/NotificationBox.test.tsx index 164f1a78bdc..bc276fe1075 100644 --- a/packages/mobile/src/home/NotificationBox.test.tsx +++ b/packages/mobile/src/home/NotificationBox.test.tsx @@ -4,7 +4,7 @@ import { Provider } from 'react-redux' import { DAYS_TO_BACKUP } from 'src/backup/utils' import NotificationBox from 'src/home/NotificationBox' import { createMockStore, getElementText } from 'test/utils' -import { mockPaymentRequests } from 'test/values' +import { mockE164Number, mockE164NumberPepper, mockPaymentRequests } from 'test/values' const TWO_DAYS_MS = 2 * 24 * 60 * 1000 const RECENT_BACKUP_TIME = new Date().getTime() - TWO_DAYS_MS @@ -40,6 +40,13 @@ describe('NotificationBox', () => { it('renders correctly for with all notifications', () => { const store = createMockStore({ ...storeDataNotificationsEnabled, + account: { + ...storeDataNotificationsEnabled.account, + e164PhoneNumber: mockE164Number, + }, + identity: { e164NumberToSalt: { [mockE164Number]: mockE164NumberPepper } }, + stableToken: { balance: '0.00' }, + goldToken: { balance: '0.00' }, }) const tree = render( @@ -174,7 +181,10 @@ describe('NotificationBox', () => { account: { ...storeDataNotificationsDisabled.account, dismissedGetVerified: false, + e164PhoneNumber: mockE164Number, }, + identity: { e164NumberToSalt: { [mockE164Number]: mockE164NumberPepper } }, + stableToken: { balance: '0.00' }, }) const { getByText } = render( @@ -183,4 +193,16 @@ describe('NotificationBox', () => { ) expect(getByText('nuxVerification2:notification.body')).toBeTruthy() }) + + it('does not render verification reminder when insufficient balance', () => { + const store = createMockStore({ + ...storeDataNotificationsDisabled, + }) + const { queryByText } = render( + + + + ) + expect(queryByText('nuxVerification2:notification.body')).toBeFalsy() + }) }) diff --git a/packages/mobile/src/home/NotificationBox.tsx b/packages/mobile/src/home/NotificationBox.tsx index 02e85e0bdf0..5b7f1aeab33 100644 --- a/packages/mobile/src/home/NotificationBox.tsx +++ b/packages/mobile/src/home/NotificationBox.tsx @@ -9,6 +9,7 @@ import { dismissGetVerified, dismissGoldEducation, dismissInviteFriends } from ' import { HomeEvents } from 'src/analytics/Events' import { ScrollDirection } from 'src/analytics/types' import ValoraAnalytics from 'src/analytics/ValoraAnalytics' +import { verificationPossibleSelector } from 'src/app/selectors' import { EscrowedPayment } from 'src/escrow/actions' import EscrowedPaymentReminderSummaryNotification from 'src/escrow/EscrowedPaymentReminderSummaryNotification' import { getReclaimableEscrowPayments } from 'src/escrow/reducer' @@ -55,6 +56,7 @@ interface StateProps { goldEducationCompleted: boolean dismissedInviteFriends: boolean dismissedGetVerified: boolean + verificationPossible: boolean dismissedGoldEducation: boolean incomingPaymentRequests: PaymentRequest[] outgoingPaymentRequests: PaymentRequest[] @@ -79,6 +81,7 @@ const mapStateToProps = (state: RootState): StateProps => ({ outgoingPaymentRequests: getOutgoingPaymentRequests(state), dismissedInviteFriends: state.account.dismissedInviteFriends, dismissedGetVerified: state.account.dismissedGetVerified, + verificationPossible: verificationPossibleSelector(state), dismissedGoldEducation: state.account.dismissedGoldEducation, backupTooLate: isBackupTooLate(state), reclaimableEscrowPayments: getReclaimableEscrowPayments(state), @@ -142,6 +145,7 @@ export class NotificationBox extends React.Component { goldEducationCompleted, dismissedInviteFriends, dismissedGetVerified, + verificationPossible, dismissedGoldEducation, } = this.props const actions = [] @@ -166,7 +170,7 @@ export class NotificationBox extends React.Component { }) } - if (!dismissedGetVerified && !numberVerified) { + if (!dismissedGetVerified && !numberVerified && verificationPossible) { actions.push({ title: t('nuxVerification2:notification.title'), text: t('nuxVerification2:notification.body'), diff --git a/packages/mobile/src/identity/privateHashing.test.ts b/packages/mobile/src/identity/privateHashing.test.ts index 51e40080aed..b2b75a254cb 100644 --- a/packages/mobile/src/identity/privateHashing.test.ts +++ b/packages/mobile/src/identity/privateHashing.test.ts @@ -5,6 +5,7 @@ import { call, select } from 'redux-saga/effects' import { PincodeType } from 'src/account/reducer' import { e164NumberSelector } from 'src/account/selectors' import { ErrorMessages } from 'src/app/ErrorMessages' +import { celoTokenBalanceSelector } from 'src/goldToken/selectors' import { updateE164PhoneNumberSalts } from 'src/identity/actions' import { fetchPhoneHashPrivate } from 'src/identity/privateHashing' import { e164NumberToSaltSelector } from 'src/identity/reducer' @@ -77,7 +78,8 @@ describe('Fetch phone hash details', () => { await expectSaga(fetchPhoneHashPrivate, mockE164Number) .provide([ [call(getConnectedAccount), mockAccount], - [select(stableTokenBalanceSelector), 0.09], + [select(stableTokenBalanceSelector), 0.009], + [select(celoTokenBalanceSelector), 0.004], [select(e164NumberSelector), mockE164Number2], [select(e164NumberToSaltSelector), {}], [matchers.call.fn(isAccountUpToDate), true], diff --git a/packages/mobile/src/identity/privateHashing.ts b/packages/mobile/src/identity/privateHashing.ts index 5faceb2868a..53580bcf9c9 100644 --- a/packages/mobile/src/identity/privateHashing.ts +++ b/packages/mobile/src/identity/privateHashing.ts @@ -2,15 +2,14 @@ import { OdisUtils } from '@celo/contractkit' import { PhoneNumberHashDetails } from '@celo/contractkit/lib/identity/odis/phone-number-identifier' import { AuthSigner, ServiceContext } from '@celo/contractkit/lib/identity/odis/query' import { getPhoneHash, isE164Number, PhoneNumberUtils } from '@celo/utils/src/phoneNumbers' -import BigNumber from 'bignumber.js' import DeviceInfo from 'react-native-device-info' import { call, put, select } from 'redux-saga/effects' import { e164NumberSelector } from 'src/account/selectors' import { IdentityEvents } from 'src/analytics/Events' import ValoraAnalytics from 'src/analytics/ValoraAnalytics' import { ErrorMessages } from 'src/app/ErrorMessages' -import { ODIS_MINIMUM_DOLLAR_BALANCE } from 'src/config' import networkConfig from 'src/geth/networkConfig' +import { celoTokenBalanceSelector } from 'src/goldToken/selectors' import { updateE164PhoneNumberSalts } from 'src/identity/actions' import { ReactBlsBlindingClient } from 'src/identity/bls-blinding-client' import { e164NumberToSaltSelector, E164NumberToSaltType } from 'src/identity/reducer' @@ -76,7 +75,7 @@ function* doFetchPhoneHashPrivate(e164Number: string) { } Logger.debug(`${TAG}@fetchPrivatePhoneHash`, 'Salt was not cached, fetching') - const isBalanceSufficientForQuota = yield call(balanceSufficientForQuotaRetrieval) + const isBalanceSufficientForQuota = yield call(balanceSufficientForSigRetrieval) if (!isBalanceSufficientForQuota) { throw new Error(ErrorMessages.ODIS_INSUFFICIENT_BALANCE) } @@ -94,11 +93,13 @@ function* doFetchPhoneHashPrivate(e164Number: string) { return details } -// TODO move to Contract kit ODIS utils -export function* balanceSufficientForQuotaRetrieval() { - // TODO add CELO balance lookup as well - const userBalance = yield select(stableTokenBalanceSelector) - return new BigNumber(userBalance).isGreaterThanOrEqualTo(ODIS_MINIMUM_DOLLAR_BALANCE) +export function* balanceSufficientForSigRetrieval() { + const userDollarBalance = yield select(stableTokenBalanceSelector) + const userCeloBalance = yield select(celoTokenBalanceSelector) + return OdisUtils.PhoneNumberIdentifier.isBalanceSufficientForSigRetrieval( + userDollarBalance, + userCeloBalance + ) } // Unlike the getPhoneHash in utils, this leverages the phone number diff --git a/packages/mobile/src/send/Send.tsx b/packages/mobile/src/send/Send.tsx index 3200e46e816..26a990dd309 100644 --- a/packages/mobile/src/send/Send.tsx +++ b/packages/mobile/src/send/Send.tsx @@ -13,6 +13,7 @@ import { hideAlert, showError } from 'src/alert/actions' import { RequestEvents, SendEvents } from 'src/analytics/Events' import ValoraAnalytics from 'src/analytics/ValoraAnalytics' import { ErrorMessages } from 'src/app/ErrorMessages' +import { verificationPossibleSelector } from 'src/app/selectors' import { estimateFee, FeeType } from 'src/fees/actions' import i18n, { Namespaces, withTranslation } from 'src/i18n' import ContactPermission from 'src/icons/ContactPermission' @@ -59,6 +60,7 @@ interface StateProps { defaultCountryCode: string | null e164PhoneNumber: string | null numberVerified: boolean + verificationPossible: boolean devModeActive: boolean recentRecipients: Recipient[] allRecipients: NumberToRecipient @@ -81,6 +83,7 @@ const mapStateToProps = (state: RootState): StateProps => ({ defaultCountryCode: state.account.defaultCountryCode, e164PhoneNumber: state.account.e164PhoneNumber, numberVerified: state.app.numberVerified, + verificationPossible: verificationPossibleSelector(state), devModeActive: state.account.devModeActive, recentRecipients: state.send.recentRecipients, allRecipients: recipientCacheSelector(state), @@ -265,12 +268,12 @@ class Send extends React.Component { } renderListHeader = () => { - const { t, numberVerified } = this.props + const { t, numberVerified, verificationPossible } = this.props const { hasGivenContactPermission } = this.state return ( <> - {!numberVerified && ( + {!numberVerified && verificationPossible && ( } header={t('verificationCta.header')} diff --git a/packages/mobile/test/schemas.ts b/packages/mobile/test/schemas.ts index 21898d3f73d..2c11c4d583c 100644 --- a/packages/mobile/test/schemas.ts +++ b/packages/mobile/test/schemas.ts @@ -256,7 +256,6 @@ export const v0Schema = { dismissedGetVerified: false, dismissedEarnRewards: false, dismissedInviteFriends: false, - dismissedGoldEducation: false, promptFornoIfNeeded: false, acceptedTerms: false, }, @@ -370,6 +369,7 @@ export const v5Schema = { ...v3Schema.account, incomingPaymentRequests: undefined, outgoingPaymentRequests: undefined, + dismissedGoldEducation: false, }, paymentRequest: { incomingPaymentRequests: [],