Repository: joinmarket-webui/joinmarket-webui (js)

Issue:
Don't allow scheduled transactions when there are no spendable UTXOs
See https://github.com/joinmarket-webui/joinmarket-webui/pull/242.

This can lead to weird states in the backend so we'll simply make sure that users don't start a transaction schedule without having something to transact with.

This should be a s simple as going over all accounts and check if there are any non-locked UTXOs.

Pull request description:
fix: prevent starting scheduler when utxo preconditions are not met
Closes #244 

(Summary copied from the comments below)

Preconditions are currently as follows:
```js
const SCHEDULE_PRECONDITIONS = {
  MIN_NUMBER_OF_UTXOS: 1, // min amount of utxos available
  MIN_OVERALL_REMAINING_RETRIES: 1, // amount of overall retries available
  MIN_CONFIRMATIONS_OF_SINGLE_UTXO: 5, // at least one utxo needs X confirmations
}
```

Read: At least `MIN_NUMBER_OF_UTXOS` utxos must be available. One of them needs at least `MIN_CONFIRMATIONS_OF_SINGLE_UTXO` confirmations. All retries combined must be more than `MIN_OVERALL_REMAINING_RETRIES`.

In the current settings this means a minimum requirement is "1 utxo with 5 confirmations with at least 1 retry left".

The json is still rendered so we do not forget to reiterate on the wording. If the preconditions seem fine from a technical perspective, we should come up with a good way to communicate them to the user. Do you have a suggestion?

## 📸 
See comments below.

##### Displays the remaining tries in `CurrentWalletAdvanced` component
<img src="https://user-images.githubusercontent.com/3358649/168799032-70d969be-db94-4ca9-86df-ca191c1ab9b8.png" width=400 />



Patch:
diff --git a/src/components/App.jsx b/src/components/App.jsx
index eaeaec6..2e352bc 100644
--- a/src/components/App.jsx
+++ b/src/components/App.jsx
@@ -22,7 +22,7 @@ import { setSession, clearSession } from '../session'
 import Onboarding from './Onboarding'
 import Cheatsheet from './Cheatsheet'
 import { routes } from '../constants/routes'
-import { isFeatureEnabled } from '../constants/featureFlags'
+import { isFeatureEnabled } from '../constants/features'
 import packageInfo from '../../package.json'
 
 export default function App() {
@@ -39,7 +39,6 @@ export default function App() {
   const [showCheatsheet, setShowCheatsheet] = useState(false)
 
   const cheatsheetEnabled = currentWallet && isFeatureEnabled('cheatsheet')
-  const scheduleEnabled = currentWallet && isFeatureEnabled('schedule')
 
   const startWallet = useCallback(
     (name, token) => {
@@ -126,7 +125,7 @@ export default function App() {
                 <Route path={routes.home} element={<Wallets startWallet={startWallet} stopWallet={stopWallet} />} />
                 {currentWallet && (
                   <>
-                    {scheduleEnabled && <Route path={routes.jam} element={<Jam />} />}
+                    <Route path={routes.jam} element={<Jam />} />
                     <Route path={routes.send} element={<Send />} />
                     <Route path={routes.earn} element={<Earn />} />
                     <Route path={routes.receive} element={<Receive />} />
diff --git a/src/components/CreateWallet.jsx b/src/components/CreateWallet.jsx
index 4361003..ca4587b 100644
--- a/src/components/CreateWallet.jsx
+++ b/src/components/CreateWallet.jsx
@@ -10,7 +10,7 @@ import { walletDisplayName } from '../utils'
 import { useServiceInfo } from '../context/ServiceInfoContext'
 import * as Api from '../libs/JmWalletApi'
 import { routes } from '../constants/routes'
-import { isFeatureEnabled } from '../constants/featureFlags'
+import { isFeatureEnabled } from '../constants/features'
 import styles from './CreateWallet.module.css'
 
 const PreventLeavingPageByMistake = () => {
diff --git a/src/components/DisplayUTXOs.jsx b/src/components/DisplayUTXOs.jsx
index a020a4c..6b693b9 100644
--- a/src/components/DisplayUTXOs.jsx
+++ b/src/components/DisplayUTXOs.jsx
@@ -83,27 +83,34 @@ const Utxo = ({ utxo, ...props }) => {
               <div>
                 <small className="text-secondary">{utxo.confirmations} Confirmations</small>
               </div>
+              <div>
+                <small className={utxo.tries === 0 ? `text-secondary` : `text-warning`}>
+                  {utxo.tries_remaining} of {utxo.tries + utxo.tries_remaining} attempts remaining
+                </small>
+              </div>
               {showFreezeActionButton && (
-                <div>
+                <div className="mt-1">
                   <rb.Button
                     size="sm"
                     variant={utxo.frozen ? 'outline-warning' : 'outline-info'}
                     disabled={isSending}
                     onClick={() => onClickFreeze(utxo)}
                   >
-                    {isSending && (
-                      <rb.Spinner
-                        as="span"
-                        animation="border"
-                        size="sm"
-                        role="status"
-                        aria-hidden="true"
-                        className="ms-1 me-2"
-                      />
-                    )}
-                    {utxo.frozen
-                      ? t('current_wallet_advanced.button_unfreeze')
-                      : t('current_wallet_advanced.button_freeze')}
+                    <div className="d-flex justify-content-center align-items-center">
+                      {isSending && (
+                        <rb.Spinner
+                          as="span"
+                          animation="border"
+                          size="sm"
+                          role="status"
+                          aria-hidden="true"
+                          className="ms-1 me-2"
+                        />
+                      )}
+                      {utxo.frozen
+                        ? t('current_wallet_advanced.button_unfreeze')
+                        : t('current_wallet_advanced.button_freeze')}
+                    </div>
                   </rb.Button>
                 </div>
               )}
diff --git a/src/components/Jam.jsx b/src/components/Jam.jsx
index f474ffc..b27da75 100644
--- a/src/components/Jam.jsx
+++ b/src/components/Jam.jsx
@@ -1,11 +1,12 @@
 import React, { useState, useEffect, useCallback, useMemo } from 'react'
 import * as rb from 'react-bootstrap'
-import { useTranslation } from 'react-i18next'
+import { Trans, useTranslation } from 'react-i18next'
 import { Formik, useFormikContext } from 'formik'
 import * as Api from '../libs/JmWalletApi'
 import { useSettings } from '../context/SettingsContext'
 import { useServiceInfo, useReloadServiceInfo } from '../context/ServiceInfoContext'
 import { useCurrentWallet, useCurrentWalletInfo, useReloadCurrentWalletInfo } from '../context/WalletContext'
+import { isDebugFeatureEnabled } from '../constants/debugFeatures'
 import styles from './Jam.module.css'
 import PageTitle from './PageTitle'
 import ToggleSwitch from './ToggleSwitch'
@@ -21,6 +22,14 @@ const INTERNAL_DEST_ACCOUNTS = [0, 1, 2]
 const SCHEDULE_REQUEST_INTERVAL = process.env.NODE_ENV === 'development' ? 10_000 : 60_000
 const SCHEDULER_STOP_RESPONSE_DELAY_MS = 2_000
 
+const SCHEDULER_START_ACCOUNT = 0
+const SCHEDULER_PRECONDITIONS = {
+  MIN_NUMBER_OF_UTXOS: 1, // min amount of utxos available
+  // https://github.com/JoinMarket-Org/joinmarket-clientserver/blob/v0.9.6/docs/SOURCING-COMMITMENTS.md#wait-for-at-least-5-confirmations
+  MIN_CONFIRMATIONS_OF_SINGLE_UTXO: 5, // at least one utxo needs X confirmations
+  MIN_OVERALL_REMAINING_RETRIES: 1, // amount of overall retries available
+}
+
 const ValuesListener = ({ handler }) => {
   const { values } = useFormikContext()
 
@@ -33,6 +42,61 @@ const ValuesListener = ({ handler }) => {
   return null
 }
 
+const DEFAULT_PRECONDITION_SUMMARY = {
+  isFulfilled: false,
+  numberOfMissingUtxos: SCHEDULER_PRECONDITIONS.MIN_NUMBER_OF_UTXOS,
+  amountOfMissingConfirmations: SCHEDULER_PRECONDITIONS.MIN_CONFIRMATIONS_OF_SINGLE_UTXO,
+  amountOfMissingOverallRetries: SCHEDULER_PRECONDITIONS.MIN_OVERALL_REMAINING_RETRIES,
+}
+
+const useSchedulerPreconditionSummary = (walletInfoOrNull, startAccountIndex) => {
+  const eligibleUtxos = useMemo(() => {
+    if (!walletInfoOrNull) return []
+
+    const utxos = walletInfoOrNull.data.utxos.utxos || []
+    return utxos
+      .filter((it) => it.mixdepth === startAccountIndex)
+      .filter((it) => !it.frozen)
+      .filter((it) => !it.locktime)
+  }, [walletInfoOrNull, startAccountIndex])
+
+  const [summary, setSummary] = useState(DEFAULT_PRECONDITION_SUMMARY)
+
+  useEffect(() => {
+    if (!eligibleUtxos) {
+      setSummary(DEFAULT_PRECONDITION_SUMMARY)
+      return
+    }
+
+    const numberOfMissingUtxos = Math.max(0, SCHEDULER_PRECONDITIONS.MIN_NUMBER_OF_UTXOS - eligibleUtxos.length)
+
+    const overallRetriesRemaining = eligibleUtxos.reduce((acc, utxo) => acc + utxo.tries_remaining, 0)
+    const amountOfMissingOverallRetries = Math.max(
+      0,
+      SCHEDULER_PRECONDITIONS.MIN_OVERALL_REMAINING_RETRIES - overallRetriesRemaining
+    )
+
+    const maxConfirmations =
+      eligibleUtxos.length === 0 ? 0 : eligibleUtxos.reduce((acc, utxo) => Math.max(acc, utxo.confirmations), 0)
+    const amountOfMissingConfirmations = Math.max(
+      0,
+      SCHEDULER_PRECONDITIONS.MIN_CONFIRMATIONS_OF_SINGLE_UTXO - maxConfirmations
+    )
+
+    const isFulfilled =
+      numberOfMissingUtxos === 0 && amountOfMissingOverallRetries === 0 && amountOfMissingConfirmations === 0
+
+    setSummary({
+      isFulfilled,
+      numberOfMissingUtxos,
+      amountOfMissingConfirmations,
+      amountOfMissingOverallRetries,
+    })
+  }, [eligibleUtxos])
+
+  return summary
+}
+
 export default function Jam() {
   const { t } = useTranslation()
   const settings = useSettings()
@@ -48,6 +112,12 @@ export default function Jam() {
   const [collaborativeOperationRunning, setCollaborativeOperationRunning] = useState(false)
   const [schedule, setSchedule] = useState(null)
 
+  const schedulerPreconditionSummary = useSchedulerPreconditionSummary(walletInfo, SCHEDULER_START_ACCOUNT)
+  const isSchedulerPreconditionsFulfilled = useMemo(
+    () => schedulerPreconditionSummary.isFulfilled,
+    [schedulerPreconditionSummary]
+  )
+
   // Returns one fresh address for each requested mixdepth.
   const getNewAddressesForAccounts = useCallback(
     (mixdepths) => {
@@ -74,8 +144,6 @@ export default function Jam() {
     [walletInfo]
   )
 
-  // Todo: Testing toggle is deactivated until https://github.com/JoinMarket-Org/joinmarket-clientserver/pull/1260 is merged.
-  const deactivateTestingToggle = true
   const [useInsecureTestingSettings, setUseInsecureTestingSettings] = useState(false)
 
   const initialFormValues = useMemo(() => {
@@ -172,7 +240,7 @@ export default function Jam() {
   }, [collaborativeOperationRunning, reloadSchedule])
 
   const startSchedule = async (values) => {
-    if (isLoading || collaborativeOperationRunning) {
+    if (isLoading || collaborativeOperationRunning || !isSchedulerPreconditionsFulfilled) {
       return
     }
 
@@ -183,8 +251,9 @@ export default function Jam() {
 
     const body = { destination_addresses: destinations }
 
-    if (process.env.NODE_ENV === 'development' && useInsecureTestingSettings && !deactivateTestingToggle) {
-      body.scheduler_options = {
+    // Make sure schedule testing is really only used in dev mode.
+    if (isDebugFeatureEnabled('insecureScheduleTesting') && useInsecureTestingSettings) {
+      body.tumbler_options = {
         addrcount: 3,
         minmakercount: 1,
         makercountrange: [1, 0],
@@ -230,6 +299,7 @@ export default function Jam() {
     <>
       <PageTitle title={t('scheduler.title')} subtitle={t('scheduler.subtitle')} />
       {alert && <rb.Alert variant={alert.variant}>{alert.message}</rb.Alert>}
+
       {isLoading ? (
         <rb.Placeholder as="div" animation="wave">
           <rb.Placeholder xs={12} className={styles['input-loader']} />
@@ -246,6 +316,56 @@ export default function Jam() {
               {t('send.text_coinjoin_already_running')}
             </rb.Alert>
           )}
+          <rb.Fade
+            in={!collaborativeOperationRunning && !isSchedulerPreconditionsFulfilled}
+            mountOnEnter={true}
+            unmountOnExit={true}
+          >
+            <rb.Alert variant="warning" className="mb-4">
+              <>
+                {schedulerPreconditionSummary.numberOfMissingUtxos > 0 ? (
+                  <Trans i18nKey="scheduler.precondition.hint_missing_utxos">
+                    To run the scheduler you need at least one UTXO with{' '}
+                    <strong>{{ minConfirmations: SCHEDULER_PRECONDITIONS.MIN_CONFIRMATIONS_OF_SINGLE_UTXO }}</strong>{' '}
+                    confirmations. Fund your wallet and wait for{' '}
+                    <strong>{{ minConfirmations: SCHEDULER_PRECONDITIONS.MIN_CONFIRMATIONS_OF_SINGLE_UTXO }}</strong>{' '}
+                    blocks.
+                  </Trans>
+                ) : schedulerPreconditionSummary.amountOfMissingConfirmations > 0 ? (
+                  <Trans i18nKey="scheduler.precondition.hint_missing_confirmations">
+                    The scheduler requires one of your UTXOs to have{' '}
+                    <strong>
+                      {{
+                        /* this comment is a hack for "prettier" and prevents the removal of "{' '}" 
+                           (which is essential for parameterized translations to work). */
+                        minConfirmations: SCHEDULER_PRECONDITIONS.MIN_CONFIRMATIONS_OF_SINGLE_UTXO,
+                      }}
+                    </strong>{' '}
+                    or more confirmations. Wait for{' '}
+                    <strong>
+                      {{ amountOfMissingConfirmations: schedulerPreconditionSummary.amountOfMissingConfirmations }}
+                    </strong>{' '}
+                    more block(s).
+                  </Trans>
+                ) : (
+                  schedulerPreconditionSummary.amountOfMissingOverallRetries > 0 && (
+                    <Trans i18nKey="scheduler.precondition.hint_missing_overall_retries">
+                      You've tried running the scheduler unsuccessfully too many times in a row. For security reasons,
+                      you need a fresh UTXO to try again. See{' '}
+                      <a
+                        href="https://github.com/JoinMarket-Org/joinmarket/wiki/Sourcing-commitments-for-joins#sourcing-external-commitments"
+                        target="_blank"
+                        rel="noopener noreferrer"
+                      >
+                        the docs
+                      </a>{' '}
+                      for more information.
+                    </Trans>
+                  )
+                )}
+              </>
+            </rb.Alert>
+          </rb.Fade>
           {!collaborativeOperationRunning && wallet && walletInfo && (
             <>
               <div className="d-flex align-items-center justify-content-between mb-4">
@@ -363,7 +483,7 @@ export default function Jam() {
                             disabled={isSubmitting}
                           />
                         </rb.Form.Group>
-                        {process.env.NODE_ENV === 'development' && !deactivateTestingToggle && (
+                        {isDebugFeatureEnabled('insecureScheduleTesting') && (
                           <rb.Form.Group className="mb-4" controlId="offertype">
                             <ToggleSwitch
                               label={'Use insecure testing settings'}
@@ -404,7 +524,11 @@ export default function Jam() {
                       className={styles.submit}
                       variant="dark"
                       type="submit"
-                      disabled={!collaborativeOperationRunning && (isSubmitting || isLoading || !isValid)}
+                      disabled={
+                        isSubmitting ||
+                        isLoading ||
+                        (!collaborativeOperationRunning && (!isValid || !isSchedulerPreconditionsFulfilled))
+                      }
                     >
                       <div className="d-flex justify-content-center align-items-center">
                         {collaborativeOperationRunning ? t('scheduler.button_stop') : t('scheduler.button_start')}
diff --git a/src/components/Navbar.jsx b/src/components/Navbar.jsx
index 5bfa04e..82de393 100644
--- a/src/components/Navbar.jsx
+++ b/src/components/Navbar.jsx
@@ -10,7 +10,6 @@ import { useCurrentWallet, useCurrentWalletInfo } from '../context/WalletContext
 import { useServiceInfo, useSessionConnectionError } from '../context/ServiceInfoContext'
 import { walletDisplayName } from '../utils'
 import { routes } from '../constants/routes'
-import { isFeatureEnabled } from '../constants/featureFlags'
 
 const WalletPreview = ({ wallet, walletInfo, unit, showBalance }) => {
   return (
@@ -38,8 +37,6 @@ const WalletPreview = ({ wallet, walletInfo, unit, showBalance }) => {
 const CenterNav = ({ makerRunning, cjRunning, onClick }) => {
   const { t } = useTranslation()
 
-  const scheduleEnabled = isFeatureEnabled('schedule')
-
   return (
     <rb.Nav className="justify-content-center align-items-stretch">
       <rb.Nav.Item className="d-flex align-items-stretch">
@@ -53,22 +50,20 @@ const CenterNav = ({ makerRunning, cjRunning, onClick }) => {
           {t('navbar.tab_receive')}
         </NavLink>
       </rb.Nav.Item>
-      {scheduleEnabled && (
-        <rb.Nav.Item className="d-flex align-items-stretch">
-          <NavLink
-            to={routes.jam}
-            onClick={onClick}
-            className={({ isActive }) =>
-              'center-nav-link nav-link d-flex align-items-center justify-content-center' + (isActive ? ' active' : '')
-            }
-          >
-            <div className="d-flex align-items-start">
-              {t('Jam')}
-              <TabActivityIndicator isOn={cjRunning} />
-            </div>
-          </NavLink>
-        </rb.Nav.Item>
-      )}
+      <rb.Nav.Item className="d-flex align-items-stretch">
+        <NavLink
+          to={routes.jam}
+          onClick={onClick}
+          className={({ isActive }) =>
+            'center-nav-link nav-link d-flex align-items-center justify-content-center' + (isActive ? ' active' : '')
+          }
+        >
+          <div className="d-flex align-items-start">
+            {t('Jam')}
+            <TabActivityIndicator isOn={cjRunning} />
+          </div>
+        </NavLink>
+      </rb.Nav.Item>
       <rb.Nav.Item className="d-flex align-items-stretch">
         <NavLink
           to={routes.earn}
diff --git a/src/constants/debugFeatures.ts b/src/constants/debugFeatures.ts
new file mode 100644
index 0000000..ab9bf5d
--- /dev/null
+++ b/src/constants/debugFeatures.ts
@@ -0,0 +1,24 @@
+interface DebugFeatures {
+  insecureScheduleTesting: boolean
+}
+
+const devMode = process.env.NODE_ENV === 'development'
+
+const debugFeatures: DebugFeatures = {
+  insecureScheduleTesting: true,
+}
+
+type DebugFeature = keyof DebugFeatures
+
+export const isDebugFeatureEnabled = (name: DebugFeature): boolean => {
+  if (!devMode) {
+    return false
+  }
+
+  return debugFeatures[name] || false
+}
+
+// only to be used in tests
+export const __testSetDebugFeatureEnabled = (name: DebugFeature, enabled: boolean): boolean => {
+  return (debugFeatures[name] = enabled)
+}
diff --git a/src/constants/featureFlags.ts b/src/constants/featureFlags.ts
deleted file mode 100644
index a844711..0000000
--- a/src/constants/featureFlags.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-interface FeatureFlags {
-  skipWalletBackupConfirmation: boolean
-  cheatsheet: boolean
-  schedule: boolean
-}
-
-const devMode = process.env.NODE_ENV === 'development'
-
-const featureFlags: FeatureFlags = {
-  skipWalletBackupConfirmation: devMode,
-  cheatsheet: devMode,
-  schedule: devMode,
-}
-
-type FeatureFlag = keyof FeatureFlags
-
-export const isFeatureEnabled = (name: FeatureFlag): boolean => {
-  return featureFlags[name] || false
-}
-
-// only to be used in tests
-export const __testSetFeatureEnabled = (name: FeatureFlag, enabled: boolean): boolean => {
-  return (featureFlags[name] = enabled)
-}
diff --git a/src/constants/features.ts b/src/constants/features.ts
new file mode 100644
index 0000000..dc0d692
--- /dev/null
+++ b/src/constants/features.ts
@@ -0,0 +1,22 @@
+interface Features {
+  skipWalletBackupConfirmation: boolean
+  cheatsheet: boolean
+}
+
+const devMode = process.env.NODE_ENV === 'development'
+
+const features: Features = {
+  skipWalletBackupConfirmation: devMode,
+  cheatsheet: devMode,
+}
+
+type Feature = keyof Features
+
+export const isFeatureEnabled = (name: Feature): boolean => {
+  return features[name] || false
+}
+
+// only to be used in tests
+export const __testSetFeatureEnabled = (name: Feature, enabled: boolean): boolean => {
+  return (features[name] = enabled)
+}
diff --git a/src/i18n/locales/en/translation.json b/src/i18n/locales/en/translation.json
index fef179a..5535e1b 100644
--- a/src/i18n/locales/en/translation.json
+++ b/src/i18n/locales/en/translation.json
@@ -282,6 +282,11 @@
     "progress_current_state": "Waiting for transaction <1>{{ current }}</1> of <3>{{ total }}</3> to process...",
     "progress_done": "All transactions completed successfully. The scheduler will stop soon.",
     "error_loading_schedule_failed": "Loading schedule progress failed.",
-    "error_address_reuse": "Reusing addresses is prohibited. Make sure to use unique and unused addresses."
+    "error_address_reuse": "Reusing addresses is prohibited. Make sure to use unique and unused addresses.",
+    "precondition": {
+      "hint_missing_utxos": "To run the scheduler you need at least one UTXO with <2>{{ minConfirmations }}</2> confirmations. Fund your wallet and wait for <6>{{ minConfirmations }}</6> blocks.",
+      "hint_missing_confirmations": "The scheduler requires one of your UTXOs to have <2>{{ minConfirmations }}</2> or more confirmations. Wait for <6>{{ amountOfMissingConfirmations }}</6> more block(s).",
+      "hint_missing_overall_retries": "You've tried running the scheduler unsuccessfully too many times in a row. For security reasons, you need a fresh UTXO to try again. See <2>the docs</2> for more information."
+    }
   }
 }
diff --git a/src/libs/JmWalletApi.ts b/src/libs/JmWalletApi.ts
index 8789da2..b3729d5 100644
--- a/src/libs/JmWalletApi.ts
+++ b/src/libs/JmWalletApi.ts
@@ -94,10 +94,10 @@ interface ConfigSetRequest {
 
 interface StartSchedulerRequest {
   destination_addresses: BitcoinAddress[]
-  scheduler_options?: SchedulerOptions
+  tumbler_options?: TumblerOptions
 }
 
-interface SchedulerOptions {
+interface TumblerOptions {
   mixdepthsrc?: number
   restart?: boolean
   schedulefile?: string
@@ -276,14 +276,11 @@ const postFreeze = async (
   })
 }
 
-const postSchedulerStart = async (
-  { token, signal, walletName }: WalletRequestContext,
-  { destination_addresses, scheduler_options }: StartSchedulerRequest
-) => {
+const postSchedulerStart = async ({ token, signal, walletName }: WalletRequestContext, req: StartSchedulerRequest) => {
   return await fetch(`${basePath()}/v1/wallet/${walletName}/taker/schedule`, {
     method: 'POST',
     headers: { ...Authorization(token) },
-    body: JSON.stringify({ destination_addresses: destination_addresses, tumbler_options: scheduler_options }),
+    body: JSON.stringify({ ...req }),
     signal,
   })
 }


Test patch:
diff --git a/src/components/CreateWallet.test.jsx b/src/components/CreateWallet.test.jsx
index 3b127e2..df3b8be 100644
--- a/src/components/CreateWallet.test.jsx
+++ b/src/components/CreateWallet.test.jsx
@@ -2,7 +2,7 @@ import React from 'react'
 import user from '@testing-library/user-event'
 import { render, screen, waitFor } from '../testUtils'
 import { act } from 'react-dom/test-utils'
-import { __testSetFeatureEnabled } from '../constants/featureFlags'
+import { __testSetFeatureEnabled } from '../constants/features'
 
 import * as apiMock from '../libs/JmWalletApi'