Page MenuHomePhabricator

[Cashtab] Cashtab 2.0.0 - Migrate to new wallet management API
ClosedPublic

Authored by bytesofman on Mar 2 2024, 23:10.

Details

Summary

Cashtab implemented multi-wallet support years ago and without automated integration testing. The current architecture is bad. So bad that I have found it almost impossible to change or test without completely rebuilding it.

So, here, it is completely rebuilt (and tested).

Before

  • Active wallet stored at localforage key wallet and state var wallet
  • savedWallets stored at localforage key savedWallets and not in state, so every time config changes it, it is getting and setting localforage directly
  • When we activate a wallet...we are moving that wallet from savedWallets and into the wallet key, in state and localforage. Also we check if it needs to migrate only on activation (so savedWallets may have n invalid wallets at different stages of migration).

After this diff

  • All wallets are stored at wallets key of localforage and cashtabState.wallets in state
  • We migrate all invalid wallets on Cashtab startup
  • wallets[0] is the active wallet
  • We add new wallets to the end of wallets on creation, so they are easy to find. wallets is sorted alphabetically on Cashtab bootup (except active wallet which is at wallets[0]).
  • Integration tests for all expected wallet manipulation from Configure.js (feature parity from the old way, but now tested).
  • Migration support from legacy wallet mgmt to new wallets method

This will make it possible to improve the structure of the cashtab wallet object, enabling things like HD wallets and better token mgmt in in-node chronik.

Attempting to fix the technical debt related to how savedWallet and wallet are managed proved impractical without addressing and resolving another issue in Cashtab.

  1. We were running the update method in useWallet.js at a regular interval. This was causing re-entrancy issues. Now that we have reliable websocket connections, there is no need for this type of interval. Instead, we only call update
  2. when an active wallet is loaded
  3. when a wallet is changed
  4. when a tx to the active wallet is sent or received

This greatly simplified integration testing and dramatically improves the speed of switching wallets and loadin the app overall.

  1. We were experiencing some state / localforage leakage in between integration tests due to cashtabState being a variable-defined object. Convert it (and its dependent objects) to a Class, so it can be initialized as new.

While it is probably possible to separate these changes into smaller diffs, I do not think this offers better impact. I would need to design and implement shims to preserve the existing technical debt. Then remove them like bad scaffolding.

Instead, I think it is best to do this all in one major version bump change. Tests are in place for migration, and the diff does not overwrite legacy storage if we need to revert for some reason.

The key changes implemented in this diff are in Configure.js and useWallet.js

Test Plan

npm test

The prod version of this diff is available at https://cashtab-local-dev.netlify.app/

While the integration tests cover the changes introduced by this diff, it is worth spending some time on the deployed version trying to break things. Add wallets. Delete wallets. Rename wallets to existing names. Switch wallets rapidly, a lot. Confirm you are still subscribed to the right scripts in the websocket.

Diff Detail

Repository
rABC Bitcoin ABC
Branch
use-new-wallet-fn
Lint
Lint Errors
SeverityLocationCodeMessage
Errorcashtab/src/components/__tests__/App.test.js:937ESLINTjest/no-focused-tests
Errorcashtab/src/hooks/useWallet.js:598ESLINTno-unused-vars
Unit
No Test Coverage
Build Status
Buildable 27638
Build 54839: Build Diffcashtab-tests
Build 54838: arc lint + arc unit

Event Timeline

There are a very large number of changes, so older changes are hidden. Show Older Changes

Failed tests logs:

====== CashTab Unit Tests: <App /> Clicking "reply" on a Cashtab Msg correctly populates the SendXec to address and amount fields ======
Error: Unable to find an element by: [data-testid="cashtab-msg-reply"]

Ignored nodes: comments, script, style
<body>
  <div
    class="ant-notification ant-notification-topRight css-dev-only-do-not-override-1rqnfsa ant-notification-stack ant-notification-stack-expanded"
    style="right: 0px; top: 24px;"
  >
    <div
      class="ant-notification-notice-wrapper"
      style="transform: translate3d(0, 0, 0);"
    >
      <div
        class="ant-notification-notice ant-notification-notice-success ant-notification-notice-closable"
      >
        <div
          class="ant-notification-notice-content"
        >
          <div
            class="ant-notification-notice-with-icon"
            role="alert"
          >
            <span
              aria-label="check-circle"
              class="anticon anticon-check-circle ant-notification-notice-icon ant-notification-notice-icon-success"
              role="img"
            >
              <svg
                aria-hidden="true"
                data-icon="check-circle"
                fill="currentColor"
                focusable="false"
                height="1em"
                viewBox="64 64 896 896"
                width="1em"
              >
                <path
                  d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm193.5 301.7l-210.6 292a31.8 31.8 0 01-51.7 0L318.5 484.9c-3.8-5.3 0-12.7 6.5-12.7h46.9c10.2 0 19.9 4.9 25.9 13.3l71.2 98.8 157.2-218c6-8.3 15.6-13.3 25.9-13.3H699c6.5 0 10.3 7.4 6.5 12.7z"
                />
              </svg>
            </span>
            <div
              class="ant-notification-notice-message"
            >
              Success
            </div>
            <div
              class="ant-notification-notice-description"
            >
              ecash:qphlhe78677sz227k83hrh542qeehh8el5lcjwk72y added to Contact List
            </div>
          </div>
        </div>
        <a
          class="ant-notification-notice-close"
          tabindex="0"
        >
          <span
            class="ant-notification-notice-close-x"
          >
            <span
              aria-label="close"
              class="anticon anticon-close ant-notification-notice-close-icon"
              role="img"
            >
              <svg
                aria-hidden="true"
                data-icon="close"
                fill="currentColor"
                fill-rule="evenodd"
                focusable="false"
                height="1em"
                viewBox="64 64 896 896"
                width="1em"
              >
                <path
                  d="M799.86 166.31c.02 0 .04.02.08.06l57.69 57.7c.04.03.05.05.06.08a.12.12 0 010 .06c0 .03-.02.05-.06.09L569.93 512l287.7 287.7c.04.04.05.06.06.09a.12.12 0 010 .07c0 .02-.02.04-.06.08l-57.7 57.69c-.03.04-.05.05-.07.06a.12.12 0 01-.07 0c-.03 0-.05-.02-.09-.06L512 569.93l-287.7 287.7c-.04.04-.06.05-.09.06a.12.12 0 01-.07 0c-.02 0-.04-.02-.08-.06l-57.69-57.7c-.04-.03-.05-.05-.06-.07a.12.12 0 010-.07c0-.03.02-.05.06-.09L454.07 512l-287.7-287.7c-.04-.04-.05-.06-.06-.09a.12.12 0 010-.07c0-.02.02-.04.06-.08l57.7-57.69c.03-.04.05-.05.07-.06a.12.12 0 01.07 0c.03 0 .05.02.09.06L512 454.07l287.7-287.7c.04-.04.06-.05.09-.06a.12.12 0 01.07 0z"
                />
              </svg>
            </span>
          </span>
        </a>
      </div>
    </div>
  </div>
  <div>
    <div
      class="ant-spin-nested-loading css-dev-only-do-not-override-1rqnfsa"
    >
      <div>
        <div
          aria-busy="true"
          aria-live="polite"
          class="ant-spin ant-spin-spinning css-dev-only-do-not-override-1rqnfsa"
        >
          <span
            aria-label="loading"
            class="anticon anticon-loading anticon-spin cashLoadingIcon ant-spin-dot"
            role="img"
          >
            <svg
              aria-hidden="true"
              data-icon="loading"
              fill="currentColor"
              focusable="false"
              height="1em"
              viewBox="0 0 1024 1024"
              width="1em"
            >
              <path
                d="M988 548c-19.9 0-36-16.1-36-36 0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 00-94.3-139.9 437.71 437.71 0 00-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.3C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3.1 19.9-16 36-35.9 36z"
              />
            </svg>
          </span>
        </div>
      </div>
      <div
        class="ant-spin-container ant-spin-blur"
      >
        <div
          class="sc-iBEsjs hYXNZl"
        >
          <div
            class="sc-RcBXQ krXqvz"
          >
            <div
              class="sc-iSDuPN cdWENt"
            >
              <div
                class="sc-chbbiW kmKhFr"
              >
                <div
                  class="sc-fZwumE jmbiQP"
                >
                  <img
                    alt="cashtab"
                    class="sc-fQejPQ iclGva"
                    src="test-file-stub"
                  />
                </div>
                <div
                  class="sc-elJkPf geNavj"
                  data-testid="wallet-info-ctn"
                >
                  <div
                    class="sc-jtggT gArshC"
                  >
                    <div
                      class="sc-ebFjAB kYfZhR"
                    >
                      [Burned] useWallet Mock
                    </div>
                    <a
                      href="/configure"
                    >
                      <svg
                        class="sc-jTzLTM bTdWCF"
                      />
                    </a>
                    <div>
                       
                      <button
                        aria-checked="true"
                        class="ant-switch ant-switch-small css-dev-only-do-not-override-1rqnfsa ant-switch-checked"
                        role="switch"
                        type="button"
                      >
                        <div
                          class="ant-switch-handle"
                        />
                        <span
                          class="ant-switch-inner"
                        >
                          <span
                            class="ant-switch-inner-checked"
                          >
                            <svg
                              class="sc-cSHVUG bgwEHu"
                            />
                          </span>
                          <span
                            class="ant-switch-inner-unchecked"
                          >
                            <svg
                              class="sc-kAzzGY fUTkYj"
                            />
                          </span>
                        </span>
                      </button>
                    </div>
                  </div>
                  <div
                    class="sc-jKVCRD gWKHre"
                    data-testid="balance-xec"
                  >
                    10,000.00
                     
                    XEC
                     
                  </div>
                  <div
                    class="sc-kaNhvL HkBfR"
                    data-testid="balance-fiat"
                  >
                    $
                    0.30
                     
                    USD
                  </div>
                  <p
                    class="sc-LKuAh djatKT"
                    data-testid="ecash-price"
                  >
                    1 
                    XEC
                     = 
                    0.00003000
                     
                    USD
                  </p>
                </div>
              </div>
              <div
                class="sc-jwKygS hNrbtP"
                data-testid="loading-ctn"
              />
            </div>
            <div
              class="sc-hzNEM kSokJe"
            >
              <button
                class="sc-kZmsYB dElRnp"
              >
                <svg />
              </button>
              <button
                class="sc-kZmsYB cHYLGA"
                data-testid="nav-btn-send"
              >
                <svg
                  class="sc-kEYyzF dWIuoY"
                  style="margin-top: -9px;"
                />
              </button>
              <button
                class="sc-kZmsYB cHYLGA"
                data-testid="nav-btn-etokens"
              >
                <span
                  aria-label="appstore-add"
                  class="anticon anticon-appstore-add"
                  role="img"
                  style="font-size: 24px;"
                >
                  <svg
                    aria-hidden="true"
                    data-icon="appstore-add"
                    fill="currentColor"
                    focusable="false"
                    height="1em"
                    viewBox="64 64 896 896"
                    width="1em"
                  >
                    <defs />
                    <path
                      d="M464 144H160c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V160c0-8.8-7.2-16-16-16zm-52 268H212V212h200v200zm452-268H560c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V160c0-8.8-7.2-16-16-16zm-52 268H612V212h200v200zm52 132H560c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V560c0-8.8-7.2-16-16-16zm-52 268H612V612h200v200zM424 712H296V584c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8v128H104c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h128v128c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V776h128c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8z"
                    />
                  </svg>
                </span>
              </button>
              <button
                class="sc-kZmsYB cHYLGA"
                data-testid="nav-btn-receive"
              >
                <svg />
              </button>
              <div
                class="sc-kxynE dXtHrl"
                data-testid="hamburger"
              >
                <span
                  class="sc-cooIXK Cjyti"
                />
                <div
                  class="sc-fcdeBU cIfnQb"
                  data-testid="hamburger-menu"
                >
                  <button
                    class="sc-gmeYpB bOTOSp"
                    data-testid="nav-btn-airdrop"
                  >
                     
                    <p>
                      Airdrop
                    </p>
                    <svg
                      height="33px"
                      width="30px"
                    />
                  </button>
                  <button
                    class="sc-gmeYpB bOTOSp"
                    data-testid="nav-btn-swap"
                  >
                     
                    <p>
                      Swap
                    </p>
                    <span
                      aria-label="swap"
                      class="anticon anticon-swap"
                      role="img"
                      style="font-size: 24px;"
                    >
                      <svg
                        aria-hidden="true"
                        data-icon="swap"
                        fill="currentColor"
                        focusable="false"
                        height="1em"
                        viewBox="64 64 896 896"
                        width="1em"
                      >
                        <path
                          d="M847.9 592H152c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h605.2L612.9 851c-4.1 5.2-.4 13 6.3 13h72.5c4.9 0 9.5-2.2 12.6-6.1l168.8-214.1c16.5-21 1.6-51.8-25.2-51.8zM872 356H266.8l144.3-183c4.1-5.2.4-13-6.3-13h-72.5c-4.9 0-9.5 2.2-12.6 6.1L150.9 380.2c-16.5 21-1.6 51.8 25.1 51.8h696c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8z"
                        />
                      </svg>
                    </span>
                  </button>
                  <button
                    class="sc-gmeYpB bOTOSp"
                    data-testid="nav-btn-signverifymsg"
                  >
                    <p>
                      Sign & Verify
                    </p>
                    <svg
                      class="sc-chPdSV kiHcnD"
                    />
                  </button>
                  <button
                    class="sc-gmeYpB bOTOSp"
                    data-testid="nav-btn-configure"
                  >
                    <p>
                      Settings
                    </p>
                    <svg
                      height="33px"
                      width="30px"
                    />
                  </button>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</body>
    at waitForWrapper (/work/cashtab/node_modules/@testing-library/dom/dist/wait-for.js:163:27)
    at Object.<anonymous> (/work/cashtab/src/components/__tests__/App.test.js:348:22)
====== CashTab Unit Tests: <App /> A new user can import a mnemonic ======
Error: Unable to find an element by: [data-testid="balance-xec"]

Ignored nodes: comments, script, style
<body>
  <div
    class="ant-notification ant-notification-topRight css-dev-only-do-not-override-1rqnfsa ant-notification-stack ant-notification-stack-expanded"
    style="right: 0px; top: 24px;"
  >
    <div
      class="ant-notification-notice-wrapper"
      style="transform: translate3d(0, 16px, 0) scaleX(1); height: 0px;"
    >
      <div
        class="ant-notification-notice ant-notification-notice-success ant-notification-notice-closable"
      >
        <div
          class="ant-notification-notice-content"
        >
          <div
            class="ant-notification-notice-with-icon"
            role="alert"
          >
            <span
              class="ant-notification-notice-icon"
            >
              <div
                class="ant-image css-dev-only-do-not-override-1rqnfsa"
                style="width: 24px; height: 24px;"
              >
                <img
                  class="ant-image-img css-dev-only-do-not-override-1rqnfsa"
                  height="24px"
                  src="test-file-stub"
                  style="height: 24px;"
                  width="24px"
                />
              </div>
            </span>
            <div
              class="ant-notification-notice-message"
            >
              Success
            </div>
            <div
              class="ant-notification-notice-description"
            >
              <a
                href="https://explorer.e.cash/tx/ce727c96439dfe365cb47f780c37ebb2e756051db62375e992419d5db3c81b1e"
                rel="noopener noreferrer"
                target="_blank"
              >
                Transaction successful. Click to view in block explorer.
              </a>
            </div>
          </div>
        </div>
        <a
          class="ant-notification-notice-close"
          tabindex="0"
        >
          <span
            class="ant-notification-notice-close-x"
          >
            <span
              aria-label="close"
              class="anticon anticon-close ant-notification-notice-close-icon"
              role="img"
            >
              <svg
                aria-hidden="true"
                data-icon="close"
                fill="currentColor"
                fill-rule="evenodd"
                focusable="false"
                height="1em"
                viewBox="64 64 896 896"
                width="1em"
              >
                <path
                  d="M799.86 166.31c.02 0 .04.02.08.06l57.69 57.7c.04.03.05.05.06.08a.12.12 0 010 .06c0 .03-.02.05-.06.09L569.93 512l287.7 287.7c.04.04.05.06.06.09a.12.12 0 010 .07c0 .02-.02.04-.06.08l-57.7 57.69c-.03.04-.05.05-.07.06a.12.12 0 01-.07 0c-.03 0-.05-.02-.09-.06L512 569.93l-287.7 287.7c-.04.04-.06.05-.09.06a.12.12 0 01-.07 0c-.02 0-.04-.02-.08-.06l-57.69-57.7c-.04-.03-.05-.05-.06-.07a.12.12 0 010-.07c0-.03.02-.05.06-.09L454.07 512l-287.7-287.7c-.04-.04-.05-.06-.06-.09a.12.12 0 010-.07c0-.02.02-.04.06-.08l57.7-57.69c.03-.04.05-.05.07-.06a.12.12 0 01.07 0c.03 0 .05.02.09.06L512 454.07l287.7-287.7c.04-.04.06-.05.09-.06a.12.12 0 01.07 0z"
                />
              </svg>
            </span>
          </span>
        </a>
      </div>
    </div>
    <div
      class="ant-notification-notice-wrapper"
      style="transform: translate3d(0, 0, 0);"
    >
      <div
        class="ant-notification-notice ant-notification-notice-success ant-notification-notice-closable"
      >
        <div
          class="ant-notification-notice-content"
        >
          <div
            class="ant-notification-notice-with-icon"
            role="alert"
          >
            <span
              class="ant-notification-notice-icon"
            >
              <div
                class="ant-image css-dev-only-do-not-override-1rqnfsa"
                style="width: 24px; height: 24px;"
              >
                <img
                  class="ant-image-img css-dev-only-do-not-override-1rqnfsa"
                  height="24px"
                  src="test-file-stub"
                  style="height: 24px;"
                  width="24px"
                />
              </div>
            </span>
            <div
              class="ant-notification-notice-message"
            >
              eToken transaction received: TBC
            </div>
            <div
              class="ant-notification-notice-description"
            >
              You received 10000000000 tabcash
            </div>
          </div>
        </div>
        <a
          class="ant-notification-notice-close"
          tabindex="0"
        >
          <span
            class="ant-notification-notice-close-x"
          >
            <span
              aria-label="close"
              class="anticon anticon-close ant-notification-notice-close-icon"
              role="img"
            >
              <svg
                aria-hidden="true"
                data-icon="close"
                fill="currentColor"
                fill-...
    at waitForWrapper (/work/cashtab/node_modules/@testing-library/dom/dist/wait-for.js:163:27)
    at /work/cashtab/node_modules/@testing-library/dom/dist/query-helpers.js:86:33
    at Object.findByTestId (/work/cashtab/src/components/__tests__/App.test.js:812:29)
====== CashTab Unit Tests: <SendXec /> Pass a valid address and bip21 query string with valid amount param to Send To field ======
Error: expect(element).not.toHaveStyle()

Compared values have no visual difference.
    at Object.toHaveStyle (/work/cashtab/src/components/Send/__tests__/SendXec.test.js:494:66)
====== CashTab Unit Tests: <SendXec /> Pass a valid alias and bip21 query string with valid amount param to Send To field ======
Error: expect(element).not.toHaveStyle()

Compared values have no visual difference.
    at Object.toHaveStyle (/work/cashtab/src/components/Send/__tests__/SendXec.test.js:562:66)
====== CashTab Unit Tests: <SendXec /> Pass a valid address and bip21 query string with valid amount and op_return_raw params to Send To field ======
Error: expect(element).not.toHaveStyle()

Compared values have no visual difference.
    at Object.toHaveStyle (/work/cashtab/src/components/Send/__tests__/SendXec.test.js:898:66)
====== CashTab Unit Tests: <SendXec /> Clicking "Send" will send a valid tx with op_return_raw after entry of a valid address and bip21 query string with valid amount and op_return_raw params to Send To field ======
Error: expect(element).not.toHaveStyle()

Compared values have no visual difference.
    at Object.toHaveStyle (/work/cashtab/src/components/Send/__tests__/SendXec.test.js:1024:66)
====== CashTab Unit Tests: <SendXec /> We can calculate max send amount with and without a cashtab msg, and send a max sat tx with a cashtab msg ======
Error: Unable to find an element with the text: Transaction successful. Click to view in block explorer.. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.

Ignored nodes: comments, script, style
<body>
  <div>
    <div
      class="ant-spin-nested-loading css-dev-only-do-not-override-1rqnfsa"
    >
      <div>
        <div
          aria-busy="true"
          aria-live="polite"
          class="ant-spin ant-spin-spinning css-dev-only-do-not-override-1rqnfsa"
        >
          <span
            aria-label="loading"
            class="anticon anticon-loading anticon-spin cashLoadingIcon ant-spin-dot"
            role="img"
          >
            <svg
              aria-hidden="true"
              data-icon="loading"
              fill="currentColor"
              focusable="false"
              height="1em"
              viewBox="0 0 1024 1024"
              width="1em"
            >
              <path
                d="M988 548c-19.9 0-36-16.1-36-36 0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 00-94.3-139.9 437.71 437.71 0 00-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.3C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3.1 19.9-16 36-35.9 36z"
              />
            </svg>
          </span>
        </div>
      </div>
      <div
        class="ant-spin-container ant-spin-blur"
      >
        <div
          class="sc-iBEsjs hYXNZl"
        >
          <div
            class="sc-RcBXQ krXqvz"
          >
            <div
              class="sc-iSDuPN cdWENt"
            >
              <div
                class="sc-chbbiW kmKhFr"
              >
                <div
                  class="sc-fZwumE jmbiQP"
                >
                  <img
                    alt="cashtab"
                    class="sc-fQejPQ iclGva"
                    src="test-file-stub"
                  />
                </div>
                <div
                  class="sc-elJkPf geNavj"
                  data-testid="wallet-info-ctn"
                >
                  <div
                    class="sc-jtggT gArshC"
                  >
                    <div
                      class="sc-ebFjAB kYfZhR"
                    >
                      Transaction Fixtures
                    </div>
                    <a
                      href="/configure"
                    >
                      <svg
                        class="sc-jTzLTM bTdWCF"
                      />
                    </a>
                    <div>
                       
                      <button
                        aria-checked="true"
                        class="ant-switch ant-switch-small css-dev-only-do-not-override-1rqnfsa ant-switch-checked"
                        role="switch"
                        type="button"
                      >
                        <div
                          class="ant-switch-handle"
                        />
                        <span
                          class="ant-switch-inner"
                        >
                          <span
                            class="ant-switch-inner-checked"
                          >
                            <svg
                              class="sc-cSHVUG bgwEHu"
                            />
                          </span>
                          <span
                            class="ant-switch-inner-unchecked"
                          >
                            <svg
                              class="sc-kAzzGY fUTkYj"
                            />
                          </span>
                        </span>
                      </button>
                    </div>
                  </div>
                  <div
                    class="sc-jKVCRD gWKHre"
                    data-testid="balance-xec"
                  >
                    9,513.12
                     
                    XEC
                     
                  </div>
                  <div
                    class="sc-kaNhvL HkBfR"
                    data-testid="balance-fiat"
                  >
                    $
                    0.29
                     
                    USD
                  </div>
                  <p
                    class="sc-LKuAh djatKT"
                    data-testid="ecash-price"
                  >
                    1 
                    XEC
                     = 
                    0.00003000
                     
                    USD
                  </p>
                </div>
              </div>
              <div
                class="sc-lhVmIH gJoLAm"
                data-testid="send-xec-ctn"
              >
                <div
                  class="ant-row css-dev-only-do-not-override-1rqnfsa"
                  type="flex"
                >
                  <div
                    class="ant-col ant-col-24 css-dev-only-do-not-override-1rqnfsa"
                  >
                    <form
                      class="ant-form ant-form-horizontal css-dev-only-do-not-override-1rqnfsa"
                      style="width: auto; margin-top: 40px;"
 ...
    at waitForWrapper (/work/cashtab/node_modules/@testing-library/dom/dist/wait-for.js:163:27)
    at /work/cashtab/node_modules/@testing-library/dom/dist/query-helpers.js:86:33
    at Object.findByText (/work/cashtab/src/components/Send/__tests__/SendXec.test.js:1152:52)
====== CashTab Unit Tests: <SendXec /> If the user has minFeeSends set to true but no longer has the right token amount, the feature is disabled ======
Error: expect(element).not.toHaveStyle()

Compared values have no visual difference.
    at Object.toHaveStyle (/work/cashtab/src/components/Send/__tests__/SendXec.test.js:1228:66)
====== CashTab Unit Tests: <Configure /> We can rename the active wallet or a saved wallet, we can add a wallet, we can import a wallet, we can delete a wallet ======
TestingLibraryElementError: Unable to find an accessible element with the role "button" and name "plus-square New Wallet"

Here are the accessible roles:

  img:

  Name "cashtab":
  <img
    alt="cashtab"
    class="sc-fQejPQ iclGva"
    src="test-file-stub"
  />

  Name "copy":
  <span
    aria-label="copy"
    class="anticon anticon-copy sc-bwzfXH gJwWNq"
    role="img"
  />

  Name "exclamation-circle":
  <span
    aria-label="exclamation-circle"
    class="anticon anticon-exclamation-circle ant-alert-icon"
    role="img"
  />

  Name "right":
  <span
    aria-label="right"
    class="anticon anticon-right ant-collapse-arrow"
    role="img"
  />

  Name "wallet":
  <span
    aria-label="wallet"
    class="anticon anticon-wallet sc-bxivhb iBBRHU"
    role="img"
  />

  Name "loading":
  <span
    aria-label="loading"
    class="anticon anticon-loading anticon-spin"
    data-testid="cash-loader"
    role="img"
  />

  Name "right":
  <span
    aria-label="right"
    class="anticon anticon-right ant-collapse-arrow"
    role="img"
  />

  Name "contacts":
  <span
    aria-label="contacts"
    class="anticon anticon-contacts sc-gzVnrw eROEQT"
    role="img"
    tabindex="-1"
  />

  Name "contacts":
  <span
    aria-label="contacts"
    class="anticon anticon-contacts sc-gzVnrw eROEQT"
    data-testid="add-saved-wallet-to-contact-btn"
    role="img"
    tabindex="-1"
  />

  Name "contacts":
  <span
    aria-label="contacts"
    class="anticon anticon-contacts sc-gzVnrw eROEQT"
    data-testid="add-saved-wallet-to-contact-btn"
    role="img"
    tabindex="-1"
  />

  Name "contacts":
  <span
    aria-label="contacts"
    class="anticon anticon-contacts sc-gzVnrw eROEQT"
    data-testid="add-saved-wallet-to-contact-btn"
    role="img"
    tabindex="-1"
  />

  Name "contacts":
  <span
    aria-label="contacts"
    class="anticon anticon-contacts sc-gzVnrw eROEQT"
    data-testid="add-saved-wallet-to-contact-btn"
    role="img"
    tabindex="-1"
  />

  Name "right":
  <span
    aria-label="right"
    class="anticon anticon-right ant-collapse-arrow"
    role="img"
  />

  Name "dollar":
  <span
    aria-label="dollar"
    class="anticon anticon-dollar sc-htpNat lgbLiL"
    role="img"
  />

  Name "setting":
  <span
    aria-label="setting"
    class="anticon anticon-setting sc-EHOje fuKjcV"
    role="img"
  />

  Name "lock":
  <span
    aria-label="lock"
    class="anticon anticon-lock"
    role="img"
  />

  Name "check":
  <span
    aria-label="check"
    class="anticon anticon-check"
    role="img"
  />

  Name "close":
  <span
    aria-label="close"
    class="anticon anticon-close"
    role="img"
  />

  Name "lock":
  <span
    aria-label="lock"
    class="anticon anticon-lock"
    role="img"
  />

  Name "check":
  <span
    aria-label="check"
    class="anticon anticon-check"
    role="img"
  />

  Name "close":
  <span
    aria-label="close"
    class="anticon anticon-close"
    role="img"
  />

  Name "github":
  <span
    aria-label="github"
    class="anticon anticon-github sc-jKJlTe gilblv"
    role="img"
  />

  Name "appstore-add":
  <span
    aria-label="appstore-add"
    class="anticon anticon-appstore-add"
    role="img"
    style="font-size: 24px;"
  />

  Name "swap":
  <span
    aria-label="swap"
    class="anticon anticon-swap"
    role="img"
    style="font-size: 24px;"
  />

  --------------------------------------------------
  link:

  Name "":
  <a
    href="/configure"
  />

  Name "notify admin":
  <a
    href="https://t.me/eCashDevelopment"
    rel="noreferrer"
    target="_blank"
  />

  Name "":
  <a
    class="sc-kpOJdX kTvxAf"
    href="https://x.com/cashtabwallet"
    rel="noreferrer"
    target="_blank"
  />

  Name "":
  <a
    class="sc-kpOJdX kTvxAf"
    href="https://www.facebook.com/Cashtab"
    rel="noreferrer"
    target="_blank"
  />

  Name "github":
  <a
    class="sc-kpOJdX kTvxAf"
    href="https://github.com/Bitcoin-ABC/bitcoin-abc/tree/master/cashtab"
    rel="noreferrer"
    target="_blank"
  />

  --------------------------------------------------
  switch:

  Name "":
  <button
    aria-checked="true"
    class="ant-switch ant-switch-small css-dev-only-do-not-override-1rqnfsa ant-switch-checked"
    role="switch"
    type="button"
  />

  Name "check close":
  <button
    aria-checked="false"
    class="ant-switch ant-switch-small css-dev-only-do-not-override-1rqnfsa"
    data-testid="send-confirmations-switch"
    role="switch"
    type="button"
  />

  Name "check close":
  <button
    aria-checked="false"
    class="ant-switch ant-switch-small css-dev-only-do-not-override-1rqnfsa"
    role="switch"
    type="button"
  />

  --------------------------------------------------
  heading:

  Name "copy Backup your wallet":
  <h2 />

  Name "wallet Manage Wallets":
  <h2 />

  Name "ACTIVE WALLET":
  <h3
    class="notranslate"
  />

  Name "Currently active":
  <h4 />

  Name "bravo":
  <h3
    class="overflow notranslate"
  />

  Name "charlie":
  <h3
    class="overflow notranslate"
  />

  Name "delta":
  <h3
    class="overflow notranslate"
  />

  Name "echo":
  <h3
    class="overflow notranslate"
  />

  Name "dollar Fiat Currency":
  <h2 />

  Name "setting General Settings":
  <h2 />

  --------------------------------------------------
  alert:

  Name "":
  <div
    class="ant-alert ant-alert-warning ant-alert-with-description css-dev-only-do-not-override-1rqnfsa"
    data-show="true"
    role="alert"
    style="margin-bottom: 12px;"
  />

  --------------------------------------------------
  button:

  Name "right Click to reveal seed phrase":
  <div
    aria-disabled="false"
    aria-expanded="false"
    class="ant-collapse-header"
    role="button"
    tabindex="0"
  />

  Name "right Saved wallets":
  <div
    aria-disabled="false"
    aria-expanded="true"
    class="ant-collapse-header"
    role="button"
    tabindex="0"
  />

  Name "Activate":
  <button />

  Name "Activate":
  <button />

  Name "Activate":
  <button />

  Name "Activate":
  <button />

  Name "right Contact List":
  <div
    aria-disabled="false"
    aria-expanded="false"
    class="ant-collapse-header"
    role="button"
    tabindex="0"
  />

  Name "":
  <button
    class="sc-kZmsYB cHYLGA"
  />

  Name "":
  <button
    class="sc-kZmsYB cHYLGA"
    data-testid="nav-btn-send"
  />

  Name "appstore-add":
  <button
    class="sc-kZmsYB cHYLGA"
    data-testid="nav-btn-etokens"
  />

  Name "":
  <button
    class="sc-kZmsYB cHYLGA"
    data-testid="nav-btn-receive"
  />

  Name "Airdrop":
  <button
    class="sc-gmeYpB bOTOSp"
    data-testid="nav-btn-airdrop"
  />

  Name "Swap swap":
  <button
    class="sc-gmeYpB bOTOSp"
    data-testid="nav-btn-swap"
  />

  Name "Sign & Verify":
  <button
    class="sc-gmeYpB bOTOSp"
    data-testid="nav-btn-signverifymsg"
  />

  Name "Settings":
  <button
    class="sc-gmeYpB hxXmSP"
    data-testid="nav-btn-configure"
  />

  --------------------------------------------------
  combobox:

  Name "":
  <input
    aria-autocomplete="list"
    aria-controls="rc_select_TEST_OR_SSR_list"
    aria-expanded="false"
    aria-haspopup="listbox"
    aria-owns="rc_select_TEST_OR_SSR_list"
    autocomplete="off"
    class="ant-select-selection-search-input"
    id="rc_select_TEST_OR_SSR"
    readonly=""
    role="combobox"
    style="opacity: 0;"
    type="search"
    unselectable="on"
    value=""
  />

  --------------------------------------------------

Ignored nodes: comments, script, style
<body>
  <div
    class="ant-notification ant-notification-topRight css-dev-only-do-not-override-1rqnfsa ant-notification-stack ant-notification-stack-expanded"
    style="right: 0px; top: 24px;"
  />
  <div>
    <div
      class="ant-spin-nested-loading css-dev-only-do-not-override-1rqnfsa"
    >
      <div
        class="ant-spin-container"
      >
        <div
          class="sc-iBEsjs hYXNZl"
        >
          <div
            class="sc-RcBXQ krXqvz"
          >
            <div
              class="sc-iSDuPN cdWENt"
            >
              <div
                class="sc-chbbiW kmKhFr"
              >
                <div
                  class="sc-fZwumE jmbiQP"
                >
                  <img
                    alt="cashtab"
                    class="sc-fQejPQ iclGva"
                    src="test-file-stub"
                  />
                  <div
                    class="sc-jXQZqI jNpDSO"
                  >
                    Settings
                    <svg
                      height="33px"
                      width="30px"
                    />
                  </div>
                </div>
                <div
                  class="sc-elJkPf geNavj"
                  data-testid="wallet-info-ctn"
                >
                  <div
                    class="sc-jtggT gArshC"
                  >
                    <div
                      class="sc-ebFjAB kYfZhR"
                    >
                      ACTIVE WALLET
                    </div>
                    <a
                      href="/configure"
                    >
                      <svg
                        class="sc-jTzLTM bTdWCF"
                      />
                    </a>
                    <div>
                       
                      <button
                        aria-checked="true"
                        class="ant-switch ant-switch-small css-dev-only-do-not-override-1rqnfsa ant-switch-checked"
                        role="switch"
                        type="button"
                      >
                        <div
                          class="ant-switch-handle"
                        />
                        <span
                          class="ant-switch-inner"
                        >
                          <span
                            class="ant-switch-inner-checked"
                          >
                            <svg
                              class="sc-cSHVUG bgwEHu"
                            />
                          </span>
                          <span
                            class="ant-switch-inner-unchecked"
                          >
                            <svg
                              class="sc-kAzzGY fUTkYj"
                            />
                          </span>
                        </span>
                      </button>
                    </div>
                  </div>
                  <div
                    class="sc-jKVCRD gWKHre"
                    data-testid="balance-xec"
                  >
                    9,513.12
                     
                    XEC
                     
                  </div>
                  <div
                    class="sc-kaNhvL HkBfR"
                    data-testid="balance-fiat"
                  >
                    $
                    0.29
                     
                    USD
                  </div>
                  <p
                    class="sc-LKuAh djatKT"
                    data-testid="ecash-price"
                  >
                    1 
                    XEC
                     = 
                    0.00003000
                     
                    USD
                  </p>
                </div>
              </div>
              <div
                class="sc-lhVmIH gJoLAm"
                data-testid="configure-ctn"
              >
                <div
                  class="sc-gPWkxV fPJZLd"
                >
                  <h2>
                    <span
                      aria-label="copy"
                      class="anticon anticon-copy sc-bwzfXH gJwWNq"
                      role="img"
                    >
                      <svg
                        aria-hidden="true"
                        data-icon="copy"
                        fill="currentColor"
                        focusable="false"
                        height="1em"
                        viewBox="64 64 896 896"
                        width="1em"
                      >
                        <path
                          d="M832 64H296c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h496v688c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V96c0-17.7-14.3-32-32-32zM704 192H192c-17.7 0-32 14.3-32 32v530.7c0 8.5 3.4 16.6 9.4 22.6l173.3 173.3c2.2 2.2 4.7 4 7.4 5.5v1.9h4.2c3.5 1.3 7.2 2 11 2H704c17.7 0 32-14.3 32-32V224c0-17.7-14.3-32-32-32zM350 856.2L263.9 770H350v86.2zM664 888H414V746c0-22.1-17.9-40-40-40H232V264h432v624z"
                        />
     ...
    at Object.getElementError (/work/cashtab/node_modules/@testing-library/dom/dist/config.js:37:19)
    at /work/cashtab/node_modules/@testing-library/dom/dist/query-helpers.js:76:38
    at /work/cashtab/node_modules/@testing-library/dom/dist/query-helpers.js:52:17
    at /work/cashtab/node_modules/@testing-library/dom/dist/query-helpers.js:95:19
    at Object.getByRole (/work/cashtab/src/components/Configure/__tests__/Configure.test.js:489:20)

Each failure log is accessible here:
CashTab Unit Tests: <App /> Clicking "reply" on a Cashtab Msg correctly populates the SendXec to address and amount fields
CashTab Unit Tests: <App /> A new user can import a mnemonic
CashTab Unit Tests: <SendXec /> Pass a valid address and bip21 query string with valid amount param to Send To field
CashTab Unit Tests: <SendXec /> Pass a valid alias and bip21 query string with valid amount param to Send To field
CashTab Unit Tests: <SendXec /> Pass a valid address and bip21 query string with valid amount and op_return_raw params to Send To field
CashTab Unit Tests: <SendXec /> Clicking "Send" will send a valid tx with op_return_raw after entry of a valid address and bip21 query string with valid amount and op_return_raw params to Send To field
CashTab Unit Tests: <SendXec /> We can calculate max send amount with and without a cashtab msg, and send a max sat tx with a cashtab msg
CashTab Unit Tests: <SendXec /> If the user has minFeeSends set to true but no longer has the right token amount, the feature is disabled
CashTab Unit Tests: <Configure /> We can rename the active wallet or a saved wallet, we can add a wallet, we can import a wallet, we can delete a wallet

Handle some bugs discovered in testing

Failed tests logs:

====== CashTab Unit Tests: useWallet hook rendering in different localforage states Cashtab loads wallets, settings, cache, and contactlist from localforage to context if they are present ======
Error: expect(received).toStrictEqual(expected) // deep equality

- Expected  - 8
+ Received  + 0

@@ -5,15 +5,7 @@
        "tokenDocumentHash": "",
        "tokenDocumentUrl": "",
        "tokenName": "Burger",
        "tokenTicker": "  ",
      },
-     "3fee3384150b030490b7bee095a63900f66a45f2d8e3002ae2cf17ce3ef4d109" => Object {
-       "decimals": 0,
-       "success": true,
-       "tokenDocumentHash": "",
-       "tokenDocumentUrl": "https://cashtab.com/",
-       "tokenName": "BearNip",
-       "tokenTicker": "BEAR",
-     },
    },
  }

Ignored nodes: comments, script, style
<html>
  <head />
  <body>
    <div />
  </body>
</html>
    at toStrictEqual (/work/cashtab/src/hooks/__tests__/useWallet.test.js:102:62)
    at runWithExpensiveErrorDiagnosticsDisabled (/work/cashtab/node_modules/@testing-library/dom/dist/config.js:47:12)
    at checkCallback (/work/cashtab/node_modules/@testing-library/dom/dist/wait-for.js:124:77)
    at checkRealTimersCallback (/work/cashtab/node_modules/@testing-library/dom/dist/wait-for.js:118:16)
    at Timeout.task [as _onTimeout] (/work/cashtab/node_modules/jsdom/lib/jsdom/browser/Window.js:520:19)
    at listOnTimeout (node:internal/timers:573:17)
    at processTimers (node:internal/timers:514:7)
====== CashTab Unit Tests: <App /> A new user can import a mnemonic ======
Error: Unable to find an element by: [data-testid="balance-xec"]

Ignored nodes: comments, script, style
<body>
  <div
    class="ant-notification ant-notification-topRight css-dev-only-do-not-override-1rqnfsa ant-notification-stack ant-notification-stack-expanded"
    style="right: 0px; top: 24px;"
  >
    <div
      class="ant-notification-notice-wrapper"
      style="transform: translate3d(0, 16px, 0) scaleX(1); height: 0px;"
    >
      <div
        class="ant-notification-notice ant-notification-notice-success ant-notification-notice-closable"
      >
        <div
          class="ant-notification-notice-content"
        >
          <div
            class="ant-notification-notice-with-icon"
            role="alert"
          >
            <span
              class="ant-notification-notice-icon"
            >
              <div
                class="ant-image css-dev-only-do-not-override-1rqnfsa"
                style="width: 24px; height: 24px;"
              >
                <img
                  class="ant-image-img css-dev-only-do-not-override-1rqnfsa"
                  height="24px"
                  src="test-file-stub"
                  style="height: 24px;"
                  width="24px"
                />
              </div>
            </span>
            <div
              class="ant-notification-notice-message"
            >
              Success
            </div>
            <div
              class="ant-notification-notice-description"
            >
              <a
                href="https://explorer.e.cash/tx/ce727c96439dfe365cb47f780c37ebb2e756051db62375e992419d5db3c81b1e"
                rel="noopener noreferrer"
                target="_blank"
              >
                Transaction successful. Click to view in block explorer.
              </a>
            </div>
          </div>
        </div>
        <a
          class="ant-notification-notice-close"
          tabindex="0"
        >
          <span
            class="ant-notification-notice-close-x"
          >
            <span
              aria-label="close"
              class="anticon anticon-close ant-notification-notice-close-icon"
              role="img"
            >
              <svg
                aria-hidden="true"
                data-icon="close"
                fill="currentColor"
                fill-rule="evenodd"
                focusable="false"
                height="1em"
                viewBox="64 64 896 896"
                width="1em"
              >
                <path
                  d="M799.86 166.31c.02 0 .04.02.08.06l57.69 57.7c.04.03.05.05.06.08a.12.12 0 010 .06c0 .03-.02.05-.06.09L569.93 512l287.7 287.7c.04.04.05.06.06.09a.12.12 0 010 .07c0 .02-.02.04-.06.08l-57.7 57.69c-.03.04-.05.05-.07.06a.12.12 0 01-.07 0c-.03 0-.05-.02-.09-.06L512 569.93l-287.7 287.7c-.04.04-.06.05-.09.06a.12.12 0 01-.07 0c-.02 0-.04-.02-.08-.06l-57.69-57.7c-.04-.03-.05-.05-.06-.07a.12.12 0 010-.07c0-.03.02-.05.06-.09L454.07 512l-287.7-287.7c-.04-.04-.05-.06-.06-.09a.12.12 0 010-.07c0-.02.02-.04.06-.08l57.7-57.69c.03-.04.05-.05.07-.06a.12.12 0 01.07 0c.03 0 .05.02.09.06L512 454.07l287.7-287.7c.04-.04.06-.05.09-.06a.12.12 0 01.07 0z"
                />
              </svg>
            </span>
          </span>
        </a>
      </div>
    </div>
    <div
      class="ant-notification-notice-wrapper"
      style="transform: translate3d(0, 0, 0);"
    >
      <div
        class="ant-notification-notice ant-notification-notice-success ant-notification-notice-closable"
      >
        <div
          class="ant-notification-notice-content"
        >
          <div
            class="ant-notification-notice-with-icon"
            role="alert"
          >
            <span
              class="ant-notification-notice-icon"
            >
              <div
                class="ant-image css-dev-only-do-not-override-1rqnfsa"
                style="width: 24px; height: 24px;"
              >
                <img
                  class="ant-image-img css-dev-only-do-not-override-1rqnfsa"
                  height="24px"
                  src="test-file-stub"
                  style="height: 24px;"
                  width="24px"
                />
              </div>
            </span>
            <div
              class="ant-notification-notice-message"
            >
              eToken transaction received: TBC
            </div>
            <div
              class="ant-notification-notice-description"
            >
              You received 10000000000 tabcash
            </div>
          </div>
        </div>
        <a
          class="ant-notification-notice-close"
          tabindex="0"
        >
          <span
            class="ant-notification-notice-close-x"
          >
            <span
              aria-label="close"
              class="anticon anticon-close ant-notification-notice-close-icon"
              role="img"
            >
              <svg
                aria-hidden="true"
                data-icon="close"
                fill="currentColor"
                fill-...
    at waitForWrapper (/work/cashtab/node_modules/@testing-library/dom/dist/wait-for.js:163:27)
    at /work/cashtab/node_modules/@testing-library/dom/dist/query-helpers.js:86:33
    at Object.findByTestId (/work/cashtab/src/components/__tests__/App.test.js:812:29)

Each failure log is accessible here:
CashTab Unit Tests: useWallet hook rendering in different localforage states Cashtab loads wallets, settings, cache, and contactlist from localforage to context if they are present
CashTab Unit Tests: <App /> A new user can import a mnemonic

Failed tests logs:

====== CashTab Unit Tests: useWallet hook rendering in different localforage states Cashtab loads wallets, settings, cache, and contactlist from localforage to context if they are present ======
Error: expect(received).toStrictEqual(expected) // deep equality

- Expected  - 8
+ Received  + 0

@@ -5,15 +5,7 @@
        "tokenDocumentHash": "",
        "tokenDocumentUrl": "",
        "tokenName": "Burger",
        "tokenTicker": "  ",
      },
-     "3fee3384150b030490b7bee095a63900f66a45f2d8e3002ae2cf17ce3ef4d109" => Object {
-       "decimals": 0,
-       "success": true,
-       "tokenDocumentHash": "",
-       "tokenDocumentUrl": "https://cashtab.com/",
-       "tokenName": "BearNip",
-       "tokenTicker": "BEAR",
-     },
    },
  }

Ignored nodes: comments, script, style
<html>
  <head />
  <body>
    <div />
  </body>
</html>
    at toStrictEqual (/work/cashtab/src/hooks/__tests__/useWallet.test.js:102:62)
    at runWithExpensiveErrorDiagnosticsDisabled (/work/cashtab/node_modules/@testing-library/dom/dist/config.js:47:12)
    at checkCallback (/work/cashtab/node_modules/@testing-library/dom/dist/wait-for.js:124:77)
    at checkRealTimersCallback (/work/cashtab/node_modules/@testing-library/dom/dist/wait-for.js:118:16)
    at Timeout.task [as _onTimeout] (/work/cashtab/node_modules/jsdom/lib/jsdom/browser/Window.js:520:19)
    at listOnTimeout (node:internal/timers:573:17)
    at processTimers (node:internal/timers:514:7)
====== CashTab Unit Tests: <App /> A new user can import a mnemonic ======
Error: Unable to find an element by: [data-testid="balance-xec"]

Ignored nodes: comments, script, style
<body>
  <div
    class="ant-notification ant-notification-topRight css-dev-only-do-not-override-1rqnfsa ant-notification-stack ant-notification-stack-expanded"
    style="right: 0px; top: 24px;"
  >
    <div
      class="ant-notification-notice-wrapper"
      style="transform: translate3d(0, 16px, 0) scaleX(1); height: 0px;"
    >
      <div
        class="ant-notification-notice ant-notification-notice-success ant-notification-notice-closable"
      >
        <div
          class="ant-notification-notice-content"
        >
          <div
            class="ant-notification-notice-with-icon"
            role="alert"
          >
            <span
              class="ant-notification-notice-icon"
            >
              <div
                class="ant-image css-dev-only-do-not-override-1rqnfsa"
                style="width: 24px; height: 24px;"
              >
                <img
                  class="ant-image-img css-dev-only-do-not-override-1rqnfsa"
                  height="24px"
                  src="test-file-stub"
                  style="height: 24px;"
                  width="24px"
                />
              </div>
            </span>
            <div
              class="ant-notification-notice-message"
            >
              Success
            </div>
            <div
              class="ant-notification-notice-description"
            >
              <a
                href="https://explorer.e.cash/tx/ce727c96439dfe365cb47f780c37ebb2e756051db62375e992419d5db3c81b1e"
                rel="noopener noreferrer"
                target="_blank"
              >
                Transaction successful. Click to view in block explorer.
              </a>
            </div>
          </div>
        </div>
        <a
          class="ant-notification-notice-close"
          tabindex="0"
        >
          <span
            class="ant-notification-notice-close-x"
          >
            <span
              aria-label="close"
              class="anticon anticon-close ant-notification-notice-close-icon"
              role="img"
            >
              <svg
                aria-hidden="true"
                data-icon="close"
                fill="currentColor"
                fill-rule="evenodd"
                focusable="false"
                height="1em"
                viewBox="64 64 896 896"
                width="1em"
              >
                <path
                  d="M799.86 166.31c.02 0 .04.02.08.06l57.69 57.7c.04.03.05.05.06.08a.12.12 0 010 .06c0 .03-.02.05-.06.09L569.93 512l287.7 287.7c.04.04.05.06.06.09a.12.12 0 010 .07c0 .02-.02.04-.06.08l-57.7 57.69c-.03.04-.05.05-.07.06a.12.12 0 01-.07 0c-.03 0-.05-.02-.09-.06L512 569.93l-287.7 287.7c-.04.04-.06.05-.09.06a.12.12 0 01-.07 0c-.02 0-.04-.02-.08-.06l-57.69-57.7c-.04-.03-.05-.05-.06-.07a.12.12 0 010-.07c0-.03.02-.05.06-.09L454.07 512l-287.7-287.7c-.04-.04-.05-.06-.06-.09a.12.12 0 010-.07c0-.02.02-.04.06-.08l57.7-57.69c.03-.04.05-.05.07-.06a.12.12 0 01.07 0c.03 0 .05.02.09.06L512 454.07l287.7-287.7c.04-.04.06-.05.09-.06a.12.12 0 01.07 0z"
                />
              </svg>
            </span>
          </span>
        </a>
      </div>
    </div>
    <div
      class="ant-notification-notice-wrapper"
      style="transform: translate3d(0, 0, 0);"
    >
      <div
        class="ant-notification-notice ant-notification-notice-success ant-notification-notice-closable"
      >
        <div
          class="ant-notification-notice-content"
        >
          <div
            class="ant-notification-notice-with-icon"
            role="alert"
          >
            <span
              class="ant-notification-notice-icon"
            >
              <div
                class="ant-image css-dev-only-do-not-override-1rqnfsa"
                style="width: 24px; height: 24px;"
              >
                <img
                  class="ant-image-img css-dev-only-do-not-override-1rqnfsa"
                  height="24px"
                  src="test-file-stub"
                  style="height: 24px;"
                  width="24px"
                />
              </div>
            </span>
            <div
              class="ant-notification-notice-message"
            >
              eToken transaction received: TBC
            </div>
            <div
              class="ant-notification-notice-description"
            >
              You received 10000000000 tabcash
            </div>
          </div>
        </div>
        <a
          class="ant-notification-notice-close"
          tabindex="0"
        >
          <span
            class="ant-notification-notice-close-x"
          >
            <span
              aria-label="close"
              class="anticon anticon-close ant-notification-notice-close-icon"
              role="img"
            >
              <svg
                aria-hidden="true"
                data-icon="close"
                fill="currentColor"
                fill-...
    at waitForWrapper (/work/cashtab/node_modules/@testing-library/dom/dist/wait-for.js:163:27)
    at /work/cashtab/node_modules/@testing-library/dom/dist/query-helpers.js:86:33
    at Object.findByTestId (/work/cashtab/src/components/__tests__/App.test.js:812:29)
    at runNextTicks (node:internal/process/task_queues:60:5)
    at listOnTimeout (node:internal/timers:540:9)
    at processTimers (node:internal/timers:514:7)
====== CashTab Unit Tests: <App /> A user with all valid wallets in savedWallets does not have any savedWallets migrated ======
TypeError: Cannot read properties of null (reading 'slice')
    at Object.slice (/work/cashtab/src/components/__tests__/App.test.js:889:47)

Each failure log is accessible here:
CashTab Unit Tests: useWallet hook rendering in different localforage states Cashtab loads wallets, settings, cache, and contactlist from localforage to context if they are present
CashTab Unit Tests: <App /> A new user can import a mnemonic
CashTab Unit Tests: <App /> A user with all valid wallets in savedWallets does not have any savedWallets migrated

standardize when loading is true and false

Failed tests logs:

====== CashTab Unit Tests: useWallet hook rendering in different localforage states Cashtab loads wallets, settings, cache, and contactlist from localforage to context if they are present ======
Error: expect(received).toStrictEqual(expected) // deep equality

- Expected  - 8
+ Received  + 0

@@ -5,15 +5,7 @@
        "tokenDocumentHash": "",
        "tokenDocumentUrl": "",
        "tokenName": "Burger",
        "tokenTicker": "  ",
      },
-     "3fee3384150b030490b7bee095a63900f66a45f2d8e3002ae2cf17ce3ef4d109" => Object {
-       "decimals": 0,
-       "success": true,
-       "tokenDocumentHash": "",
-       "tokenDocumentUrl": "https://cashtab.com/",
-       "tokenName": "BearNip",
-       "tokenTicker": "BEAR",
-     },
    },
  }

Ignored nodes: comments, script, style
<html>
  <head />
  <body>
    <div />
  </body>
</html>
    at toStrictEqual (/work/cashtab/src/hooks/__tests__/useWallet.test.js:102:62)
    at runWithExpensiveErrorDiagnosticsDisabled (/work/cashtab/node_modules/@testing-library/dom/dist/config.js:47:12)
    at checkCallback (/work/cashtab/node_modules/@testing-library/dom/dist/wait-for.js:124:77)
    at checkRealTimersCallback (/work/cashtab/node_modules/@testing-library/dom/dist/wait-for.js:118:16)
    at Timeout.task [as _onTimeout] (/work/cashtab/node_modules/jsdom/lib/jsdom/browser/Window.js:520:19)
    at listOnTimeout (node:internal/timers:573:17)
    at processTimers (node:internal/timers:514:7)
====== CashTab Unit Tests: <Receive /> Renders the Onboarding screen if user navigates to this route without a wallet ======
Error: Unable to find an element with the text: Welcome to Cashtab!. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.

Ignored nodes: comments, script, style
<body>
  <div>
    <div
      class="ant-spin-nested-loading css-dev-only-do-not-override-1rqnfsa"
    >
      <div>
        <div
          aria-busy="true"
          aria-live="polite"
          class="ant-spin ant-spin-spinning css-dev-only-do-not-override-1rqnfsa"
        >
          <span
            aria-label="loading"
            class="anticon anticon-loading anticon-spin cashLoadingIcon ant-spin-dot"
            role="img"
          >
            <svg
              aria-hidden="true"
              data-icon="loading"
              fill="currentColor"
              focusable="false"
              height="1em"
              viewBox="0 0 1024 1024"
              width="1em"
            >
              <path
                d="M988 548c-19.9 0-36-16.1-36-36 0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 00-94.3-139.9 437.71 437.71 0 00-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.3C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3.1 19.9-16 36-35.9 36z"
              />
            </svg>
          </span>
        </div>
      </div>
      <div
        class="ant-spin-container ant-spin-blur"
      >
        <div
          class="sc-iBEsjs hYXNZl"
        >
          <div
            class="sc-RcBXQ krXqvz"
          >
            <div
              class="sc-iSDuPN cdWENt"
            >
              <div
                class="sc-chbbiW kmKhFr"
              >
                <div
                  class="sc-fZwumE jmbiQP"
                >
                  <img
                    alt="cashtab"
                    class="sc-fQejPQ iclGva"
                    src="test-file-stub"
                  />
                </div>
              </div>
              <div
                class="sc-jwKygS hNrbtP"
                data-testid="rcv-loading"
              />
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</body>
    at waitForWrapper (/work/cashtab/node_modules/@testing-library/dom/dist/wait-for.js:163:27)
    at /work/cashtab/node_modules/@testing-library/dom/dist/query-helpers.js:86:33
    at Object.findByText (/work/cashtab/src/components/Receive/__tests__/Receive.test.js:246:26)
====== CashTab Unit Tests: <Home /> Renders the Home screen with API error ======
Error: Unable to find an element with the text: Error in chronik connection. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.

Ignored nodes: comments, script, style
<body>
  <div>
    <div
      class="ant-spin-nested-loading css-dev-only-do-not-override-1rqnfsa"
    >
      <div>
        <div
          aria-busy="true"
          aria-live="polite"
          class="ant-spin ant-spin-spinning css-dev-only-do-not-override-1rqnfsa"
        >
          <span
            aria-label="loading"
            class="anticon anticon-loading anticon-spin cashLoadingIcon ant-spin-dot"
            role="img"
          >
            <svg
              aria-hidden="true"
              data-icon="loading"
              fill="currentColor"
              focusable="false"
              height="1em"
              viewBox="0 0 1024 1024"
              width="1em"
            >
              <path
                d="M988 548c-19.9 0-36-16.1-36-36 0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 00-94.3-139.9 437.71 437.71 0 00-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.3C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3.1 19.9-16 36-35.9 36z"
              />
            </svg>
          </span>
        </div>
      </div>
      <div
        class="ant-spin-container ant-spin-blur"
      >
        <div
          class="sc-iBEsjs hYXNZl"
        >
          <div
            class="sc-RcBXQ krXqvz"
          >
            <div
              class="sc-iSDuPN cdWENt"
            >
              <div
                class="sc-chbbiW kmKhFr"
              >
                <div
                  class="sc-fZwumE jmbiQP"
                >
                  <img
                    alt="cashtab"
                    class="sc-fQejPQ iclGva"
                    src="test-file-stub"
                  />
                </div>
                <div
                  class="sc-elJkPf geNavj"
                  data-testid="wallet-info-ctn"
                >
                  <div
                    class="sc-jtggT gArshC"
                  >
                    <div
                      class="sc-ebFjAB kYfZhR"
                    >
                      Transaction Fixtures
                    </div>
                    <a
                      href="/configure"
                    >
                      <svg
                        class="sc-jTzLTM bTdWCF"
                      />
                    </a>
                    <div>
                       
                      <button
                        aria-checked="true"
                        class="ant-switch ant-switch-small css-dev-only-do-not-override-1rqnfsa ant-switch-checked"
                        role="switch"
                        type="button"
                      >
                        <div
                          class="ant-switch-handle"
                        />
                        <span
                          class="ant-switch-inner"
                        >
                          <span
                            class="ant-switch-inner-checked"
                          >
                            <svg
                              class="sc-cSHVUG bgwEHu"
                            />
                          </span>
                          <span
                            class="ant-switch-inner-unchecked"
                          >
                            <svg
                              class="sc-kAzzGY fUTkYj"
                            />
                          </span>
                        </span>
                      </button>
                    </div>
                  </div>
                  <div
                    class="sc-jKVCRD gWKHre"
                    data-testid="balance-xec"
                  >
                    9,513.12
                     
                    XEC
                     
                  </div>
                  <div
                    class="sc-kaNhvL HkBfR"
                    data-testid="balance-fiat"
                  >
                    $
                    0.29
                     
                    USD
                  </div>
                  <p
                    class="sc-LKuAh djatKT"
                    data-testid="ecash-price"
                  >
                    1 
                    XEC
                     = 
                    0.00003000
                     
                    USD
                  </p>
                </div>
              </div>
              <div
                class="sc-jwKygS hNrbtP"
                data-testid="loading-ctn"
              />
            </div>
            <div
              class="sc-hzNEM kSokJe"
            >
              <button
                class="sc-kZmsYB dElRnp"
              >
                <svg />
              </button>
              <button
                class="sc-kZmsYB cHYLGA"
                data-testid="nav-btn-send"
              >
                <svg
                  class="sc-kEYyzF dWIuoY"
                  style="margin-top: -9px;"
                />
              </button>
              <button
                class="sc-kZmsYB cHYLGA"
                data-testid="nav-btn-etokens"
              >
                <span
                  aria-label="appstore-add"
                  class="anticon anticon-appstore-add"
                  role="img"
                  style="font-size: 24px;"
                >
                  <svg
                    aria-hidden="true"
                    data-icon="appstore-add"
                    fill="currentColor"
                    focusable="false"
                    height="1em"
                    viewBox="64 64 896 896"
                    width="1em"
                  >
                    <defs />
                    <path
                      d="M464 144H160c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V160c0-8.8-7.2-16-16-16zm-52 268H212V212h200v200zm452-268H560c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V160c0-8.8-7.2-16-16-16zm-52 268H612V212h200v200zm52 132H560c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V560c0-8.8-7.2-16-16-16zm-52 268H612V612h200v200zM424 712H296V584c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8v128H104c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h128v128c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V776h128c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8z"
                    />
                  </svg>
                </span>
              </button>
              <button
                class="sc-kZmsYB cHYLGA"
                data-testid="nav-btn-receive"
              >
                <svg />
              </button>
              <div
                class="sc-kxynE dXtHrl"
                data-testid="hamburger"
              >
                <span
                  class="sc-cooIXK Cjyti"
                />
                <div
                  class="sc-fcdeBU cIfnQb"
                  data-testid="hamburger-menu"
                >
                  <button
                    class="sc-gmeYpB bOTOSp"
                    data-testid="nav-btn-airdrop"
                  >
                     
                    <p>
                      Airdrop
                    </p>
                    <svg
                      height="33px"
                      width="30px"
                    />
                  </button>
                  <button
                    class="sc-gmeYpB bOTOSp"
                    data-testid="nav-btn-swap"
                  >
                     
                    <p>
                      Swap
                    </p>
                    <span
                      aria-label="swap"
                      class="anticon anticon-swap"
                      role="img"
                      style="font-size: 24px;"
                    >
                      <svg
                        aria-hidden="true"
                        data-icon="swap"
                        fill="currentColor"
                        focusable="false"
                        height="1em"
                        viewBox="64 64 896 896"
                        width="1em"
                      >
                        <path
                          d="M847.9 592H152c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h605.2L612.9 851c-4.1 5.2-.4 13 6.3 13h72.5c4.9 0 9.5-2.2 12.6-6.1l168.8-214.1c16.5-21 1.6-51.8-25.2-51.8zM872 356H266.8l144.3-183c4.1-5.2.4-13-6.3-13h-72.5c-4.9 0-9.5 2.2-12.6 6.1L150.9 380.2c-16.5 21-1.6 51.8 25.1 51.8h696c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8z"
                        />
                      </svg>
                    </span>
                  </button>
                  <button
                    class="sc-gmeYpB bOTOSp"
                    data-testid="nav-btn-signverifymsg"
                  >
                    <p>
                      Sign & Verify
                    </p>
                    <svg
                      class="sc-chPdSV kiHcnD"
                    />
                  </button>
                  <button
                    class="sc-gmeYpB bOTOSp"
                    data-testid="nav-btn-configure"
                  >
                    <p>
                      Settings
                    </p>
                    <svg
                      height="33px"
                      width="30px"
                    />
                  </button>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</body>
    at waitForWrapper (/work/cashtab/node_modules/@testing-library/dom/dist/wait-for.js:163:27)
    at /work/cashtab/node_modules/@testing-library/dom/dist/query-helpers.js:86:33
    at Object.findByText (/work/cashtab/src/components/Home/__tests__/Home.test.js:122:26)
====== CashTab Unit Tests: <Home /> Renders the onboarding screen for a new wallet ======
Error: expect(element).not.toBeInTheDocument()

expected document not to contain element, found <div class="sc-jwKygS hNrbtP" data-testid="loading-ctn" /> instead

Ignored nodes: comments, script, style
<html>
  <head />
  <body>
    <div>
      <div
        class="ant-spin-nested-loading css-dev-only-do-not-override-1rqnfsa"
      >
        <div>
          <div
            aria-busy="true"
            aria-live="polite"
            class="ant-spin ant-spin-spinning css-dev-only-do-not-override-1rqnfsa"
          >
            <span
              aria-label="loading"
              class="anticon anticon-loading anticon-spin cashLoadingIcon ant-spin-dot"
              role="img"
            >
              <svg
                aria-hidden="true"
                data-icon="loading"
                fill="currentColor"
                focusable="false"
                height="1em"
                viewBox="0 0 1024 1024"
                width="1em"
              >
                <path
                  d="M988 548c-19.9 0-36-16.1-36-36 0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 00-94.3-139.9 437.71 437.71 0 00-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.3C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3.1 19.9-16 36-35.9 36z"
                />
              </svg>
            </span>
          </div>
        </div>
        <div
          class="ant-spin-container ant-spin-blur"
        >
          <div
            class="sc-iBEsjs hYXNZl"
          >
            <div
              class="sc-RcBXQ krXqvz"
            >
              <div
                class="sc-iSDuPN cdWENt"
              >
                <div
                  class="sc-chbbiW kmKhFr"
                >
                  <div
                    class="sc-fZwumE jmbiQP"
                  >
                    <img
                      alt="cashtab"
                      class="sc-fQejPQ iclGva"
                      src="test-file-stub"
                    />
                  </div>
                </div>
                <div
                  class="sc-jwKygS hNrbtP"
                  data-testid="loading-ctn"
                />
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </body>
</html>...
    at toBeInTheDocument (/work/cashtab/src/components/Home/__tests__/Home.test.js:161:61)
    at runWithExpensiveErrorDiagnosticsDisabled (/work/cashtab/node_modules/@testing-library/dom/dist/config.js:47:12)
    at checkCallback (/work/cashtab/node_modules/@testing-library/dom/dist/wait-for.js:124:77)
    at checkRealTimersCallback (/work/cashtab/node_modules/@testing-library/dom/dist/wait-for.js:118:16)
    at Timeout.task [as _onTimeout] (/work/cashtab/node_modules/jsdom/lib/jsdom/browser/Window.js:520:19)
    at listOnTimeout (node:internal/timers:573:17)
    at processTimers (node:internal/timers:514:7)
====== CashTab Unit Tests: <SignVerifyMsg /> Notification is rendered upon successfully verifying a message ======
Error: Unable to perform pointer interaction as the element inherits `pointer-events: none`:

DIV  <-- This element declared `pointer-events: none`
 DIV
  DIV
   DIV
    DIV
     DIV(testId=signverifymsg-ctn)
      DIV
       DIV
        DIV
         DIV
          DIV
           DIV
            FORM
             DIV
              DIV
               DIV
                DIV
                 DIV
                  DIV
                   DIV
                    DIV
                     DIV
                      DIV
                       DIV
                        SPAN
                         INPUT(testId=destination-address-single-without-qrscan)  <-- Asserted pointer events here
    at Object.assertPointerEvents (/work/cashtab/node_modules/@testing-library/user-event/dist/cjs/utils/pointer/cssPointerEvents.js:47:15)
    at Object.enter (/work/cashtab/node_modules/@testing-library/user-event/dist/cjs/system/pointer/pointer.js:57:34)
    at PointerHost.move (/work/cashtab/node_modules/@testing-library/user-event/dist/cjs/system/pointer/index.js:50:79)
    at pointerAction (/work/cashtab/node_modules/@testing-library/user-event/dist/cjs/pointer/index.js:64:39)
    at Object.pointer (/work/cashtab/node_modules/@testing-library/user-event/dist/cjs/pointer/index.js:32:15)
    at Object.type (/work/cashtab/node_modules/@testing-library/user-event/dist/cjs/utility/type.js:18:9)
    at Object.asyncWrapper (/work/cashtab/node_modules/@testing-library/react/dist/pure.js:88:22)
    at Object.<anonymous> (/work/cashtab/src/components/SignVerifyMsg/__tests__/SignVerifyMsg.test.js:136:9)
====== CashTab Unit Tests: <SignVerifyMsg /> Notification is rendered upon signature verification error ======
Error: Unable to perform pointer interaction as the element inherits `pointer-events: none`:

DIV  <-- This element declared `pointer-events: none`
 DIV
  DIV
   DIV
    DIV
     DIV(testId=signverifymsg-ctn)
      DIV
       DIV
        DIV
         DIV
          DIV
           DIV
            FORM
             DIV
              DIV
               DIV
                DIV
                 DIV
                  DIV
                   DIV
                    DIV
                     DIV
                      DIV
                       DIV
                        SPAN
                         INPUT(testId=destination-address-single-without-qrscan)  <-- Asserted pointer events here
    at Object.assertPointerEvents (/work/cashtab/node_modules/@testing-library/user-event/dist/cjs/utils/pointer/cssPointerEvents.js:47:15)
    at Object.enter (/work/cashtab/node_modules/@testing-library/user-event/dist/cjs/system/pointer/pointer.js:57:34)
    at PointerHost.move (/work/cashtab/node_modules/@testing-library/user-event/dist/cjs/system/pointer/index.js:50:79)
    at pointerAction (/work/cashtab/node_modules/@testing-library/user-event/dist/cjs/pointer/index.js:64:39)
    at Object.pointer (/work/cashtab/node_modules/@testing-library/user-event/dist/cjs/pointer/index.js:32:15)
    at Object.type (/work/cashtab/node_modules/@testing-library/user-event/dist/cjs/utility/type.js:18:9)
    at Object.asyncWrapper (/work/cashtab/node_modules/@testing-library/react/dist/pure.js:88:22)
    at Object.<anonymous> (/work/cashtab/src/components/SignVerifyMsg/__tests__/SignVerifyMsg.test.js:183:9)
====== CashTab Unit Tests: <App /> A new user can import a mnemonic ======
Error: Unable to find role="button" and name `/Import Wallet/`

Ignored nodes: comments, script, style
<body>
  <div
    class="ant-notification ant-notification-topRight css-dev-only-do-not-override-1rqnfsa ant-notification-stack ant-notification-stack-expanded"
    style="right: 0px; top: 24px;"
  >
    <div
      class="ant-notification-notice-wrapper"
      style="transform: translate3d(0, 16px, 0) scaleX(1); height: 0px;"
    >
      <div
        class="ant-notification-notice ant-notification-notice-success ant-notification-notice-closable"
      >
        <div
          class="ant-notification-notice-content"
        >
          <div
            class="ant-notification-notice-with-icon"
            role="alert"
          >
            <span
              class="ant-notification-notice-icon"
            >
              <div
                class="ant-image css-dev-only-do-not-override-1rqnfsa"
                style="width: 24px; height: 24px;"
              >
                <img
                  class="ant-image-img css-dev-only-do-not-override-1rqnfsa"
                  height="24px"
                  src="test-file-stub"
                  style="height: 24px;"
                  width="24px"
                />
              </div>
            </span>
            <div
              class="ant-notification-notice-message"
            >
              Success
            </div>
            <div
              class="ant-notification-notice-description"
            >
              <a
                href="https://explorer.e.cash/tx/ce727c96439dfe365cb47f780c37ebb2e756051db62375e992419d5db3c81b1e"
                rel="noopener noreferrer"
                target="_blank"
              >
                Transaction successful. Click to view in block explorer.
              </a>
            </div>
          </div>
        </div>
        <a
          class="ant-notification-notice-close"
          tabindex="0"
        >
          <span
            class="ant-notification-notice-close-x"
          >
            <span
              aria-label="close"
              class="anticon anticon-close ant-notification-notice-close-icon"
              role="img"
            >
              <svg
                aria-hidden="true"
                data-icon="close"
                fill="currentColor"
                fill-rule="evenodd"
                focusable="false"
                height="1em"
                viewBox="64 64 896 896"
                width="1em"
              >
                <path
                  d="M799.86 166.31c.02 0 .04.02.08.06l57.69 57.7c.04.03.05.05.06.08a.12.12 0 010 .06c0 .03-.02.05-.06.09L569.93 512l287.7 287.7c.04.04.05.06.06.09a.12.12 0 010 .07c0 .02-.02.04-.06.08l-57.7 57.69c-.03.04-.05.05-.07.06a.12.12 0 01-.07 0c-.03 0-.05-.02-.09-.06L512 569.93l-287.7 287.7c-.04.04-.06.05-.09.06a.12.12 0 01-.07 0c-.02 0-.04-.02-.08-.06l-57.69-57.7c-.04-.03-.05-.05-.06-.07a.12.12 0 010-.07c0-.03.02-.05.06-.09L454.07 512l-287.7-287.7c-.04-.04-.05-.06-.06-.09a.12.12 0 010-.07c0-.02.02-.04.06-.08l57.7-57.69c.03-.04.05-.05.07-.06a.12.12 0 01.07 0c.03 0 .05.02.09.06L512 454.07l287.7-287.7c.04-.04.06-.05.09-.06a.12.12 0 01.07 0z"
                />
              </svg>
            </span>
          </span>
        </a>
      </div>
    </div>
    <div
      class="ant-notification-notice-wrapper"
      style="transform: translate3d(0, 0, 0);"
    >
      <div
        class="ant-notification-notice ant-notification-notice-success ant-notification-notice-closable"
      >
        <div
          class="ant-notification-notice-content"
        >
          <div
            class="ant-notification-notice-with-icon"
            role="alert"
          >
            <span
              class="ant-notification-notice-icon"
            >
              <div
                class="ant-image css-dev-only-do-not-override-1rqnfsa"
                style="width: 24px; height: 24px;"
              >
                <img
                  class="ant-image-img css-dev-only-do-not-override-1rqnfsa"
                  height="24px"
                  src="test-file-stub"
                  style="height: 24px;"
                  width="24px"
                />
              </div>
            </span>
            <div
              class="ant-notification-notice-message"
            >
              eToken transaction received: TBC
            </div>
            <div
              class="ant-notification-notice-description"
            >
              You received 10000000000 tabcash
            </div>
          </div>
        </div>
        <a
          class="ant-notification-notice-close"
          tabindex="0"
        >
          <span
            class="ant-notification-notice-close-x"
          >
            <span
              aria-label="close"
              class="anticon anticon-close ant-notification-notice-close-icon"
              role="img"
            >
              <svg
                aria-hidden="true"
                data-icon="close"
                fill="currentColor"
                fill-rule="evenodd"
                focusable="false"
                height="1em"
                viewBox="64 64 896 896"
                width="1em"
              >
                <path
                  d="M799.86 166.31c.02 0 .04.02.08.06l57.69 57.7c.04.03.05.05.06.08a.12.12 0 010 .06c0 .03-.02.05-.06.09L569.93 512l287.7 287.7c.04.04.05.06.06.09a.12.12 0 010 .07c0 .02-.02.04-.06.08l-57.7 57.69c-.03.04-.05.05-.07.06a.12.12 0 01-.07 0c-.03 0-.05-.02-.09-.06L512 569.93l-287.7 287.7c-.04.04-.06.05-.09.06a.12.12 0 01-.07 0c-.02 0-.04-.02-.08-.06l-57.69-57.7c-.04-.03-.05-.05-.06-.07a.12.12 0 010-.07c0-.03.02-.05.06-.09L454.07 512l-287.7-287.7c-.04-.04-.05-.06-.06-.09a.12.12 0 010-.07c0-.02.02-.04.06-.08l57.7-57.69c.03-.04.05-.05.07-.06a.12.12 0 01.07 0c.03 0 .05.02.09.06L512 454.07l287.7-287.7c.04-.04.06-.05.09-.06a.12.12 0 01.07 0z"
                />
              </svg>
            </span>
          </span>
        </a>
      </div>
    </div>
  </div>
  <div>
    <div
      class="ant-spin-nested-loading css-dev-only-do-not-override-1rqnfsa"
    >
      <div>
        <div
          aria-busy="true"
          aria-live="polite"
          class="ant-spin ant-spin-spinning css-dev-only-do-not-override-1rqnfsa"
        >
          <span
            aria-label="loading"
            class="anticon anticon-loading anticon-spin cashLoadingIcon ant-spin-dot"
            role="img"
          >
            <svg
              aria-hidden="true"
              data-icon="loading"
              fill="currentColor"
              focusable="false"
              height="1em"
              viewBox="0 0 1024 1024"
              width="1em"
            >
              <path
                d="M988 548c-19.9 0-36-16.1-36-36 0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 00-94.3-139.9 437.71 437.71 0 00-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.3C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3.1 19.9-16 36-35.9 36z"
              />
            </svg>
          </span>
        </div>
      </div>
      <div
        class="ant-spin-container ant-spin-blur"
      >
        <div
          class="sc-iBEsjs hYXNZl"
        >
          <div
            class="sc-RcBXQ krXqvz"
          >
            <div
              class="sc-iSDuPN cdWENt"
            >
              <div
                class="sc-chbbiW kmKhFr"
              >
                <div
                  class="sc-fZwumE jmbiQP"
                >
                  <img
                    alt="cashtab"
                    class="sc-fQejPQ iclGva"
                    src="test-file-stub"
                  />
                </div>
              </div>
              <div
                class="sc-jwKygS hNrbtP"
                data-testid="loading-ctn"
              />
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</body>
    at waitForWrapper (/work/cashtab/node_modules/@testing-library/dom/dist/wait-for.js:163:27)
    at /work/cashtab/node_modules/@testing-library/dom/dist/query-helpers.js:86:33
    at Object.findByRole (/work/cashtab/src/components/__tests__/App.test.js:771:26)
====== CashTab Unit Tests: <App /> A user with all valid wallets in savedWallets does not have any savedWallets migrated ======
TypeError: Cannot read properties of null (reading 'slice')
    at Object.slice (/work/cashtab/src/components/__tests__/App.test.js:889:47)
    at runNextTicks (node:internal/process/task_queues:60:5)
    at processImmediate (node:internal/timers:449:9)

Each failure log is accessible here:
CashTab Unit Tests: useWallet hook rendering in different localforage states Cashtab loads wallets, settings, cache, and contactlist from localforage to context if they are present
CashTab Unit Tests: <Receive /> Renders the Onboarding screen if user navigates to this route without a wallet
CashTab Unit Tests: <Home /> Renders the Home screen with API error
CashTab Unit Tests: <Home /> Renders the onboarding screen for a new wallet
CashTab Unit Tests: <SignVerifyMsg /> Notification is rendered upon successfully verifying a message
CashTab Unit Tests: <SignVerifyMsg /> Notification is rendered upon signature verification error
CashTab Unit Tests: <App /> A new user can import a mnemonic
CashTab Unit Tests: <App /> A user with all valid wallets in savedWallets does not have any savedWallets migrated

Handle other race conditions caused by update loop being an interval (this will be fixed later)

Failed tests logs:

====== CashTab Unit Tests: useWallet hook rendering in different localforage states Cashtab loads wallets, settings, cache, and contactlist from localforage to context if they are present ======
Error: expect(received).toStrictEqual(expected) // deep equality

- Expected  - 8
+ Received  + 0

@@ -5,15 +5,7 @@
        "tokenDocumentHash": "",
        "tokenDocumentUrl": "",
        "tokenName": "Burger",
        "tokenTicker": "  ",
      },
-     "3fee3384150b030490b7bee095a63900f66a45f2d8e3002ae2cf17ce3ef4d109" => Object {
-       "decimals": 0,
-       "success": true,
-       "tokenDocumentHash": "",
-       "tokenDocumentUrl": "https://cashtab.com/",
-       "tokenName": "BearNip",
-       "tokenTicker": "BEAR",
-     },
    },
  }

Ignored nodes: comments, script, style
<html>
  <head />
  <body>
    <div />
  </body>
</html>
    at toStrictEqual (/work/cashtab/src/hooks/__tests__/useWallet.test.js:120:62)
    at runWithExpensiveErrorDiagnosticsDisabled (/work/cashtab/node_modules/@testing-library/dom/dist/config.js:47:12)
    at checkCallback (/work/cashtab/node_modules/@testing-library/dom/dist/wait-for.js:124:77)
    at checkRealTimersCallback (/work/cashtab/node_modules/@testing-library/dom/dist/wait-for.js:118:16)
    at Timeout.task [as _onTimeout] (/work/cashtab/node_modules/jsdom/lib/jsdom/browser/Window.js:520:19)
    at listOnTimeout (node:internal/timers:573:17)
    at processTimers (node:internal/timers:514:7)

Each failure log is accessible here:
CashTab Unit Tests: useWallet hook rendering in different localforage states Cashtab loads wallets, settings, cache, and contactlist from localforage to context if they are present

Failed tests logs:

====== CashTab Unit Tests: useWallet hook rendering in different localforage states Cashtab loads wallets, settings, cache, and contactlist from localforage to context if they are present ======
Error: expect(received).toStrictEqual(expected) // deep equality

- Expected  - 8
+ Received  + 0

@@ -5,15 +5,7 @@
        "tokenDocumentHash": "",
        "tokenDocumentUrl": "",
        "tokenName": "Burger",
        "tokenTicker": "  ",
      },
-     "3fee3384150b030490b7bee095a63900f66a45f2d8e3002ae2cf17ce3ef4d109" => Object {
-       "decimals": 0,
-       "success": true,
-       "tokenDocumentHash": "",
-       "tokenDocumentUrl": "https://cashtab.com/",
-       "tokenName": "BearNip",
-       "tokenTicker": "BEAR",
-     },
    },
  }

Ignored nodes: comments, script, style
<html>
  <head />
  <body>
    <div />
  </body>
</html>
    at toStrictEqual (/work/cashtab/src/hooks/__tests__/useWallet.test.js:120:62)
    at runWithExpensiveErrorDiagnosticsDisabled (/work/cashtab/node_modules/@testing-library/dom/dist/config.js:47:12)
    at checkCallback (/work/cashtab/node_modules/@testing-library/dom/dist/wait-for.js:124:77)
    at checkRealTimersCallback (/work/cashtab/node_modules/@testing-library/dom/dist/wait-for.js:118:16)
    at Timeout.task [as _onTimeout] (/work/cashtab/node_modules/jsdom/lib/jsdom/browser/Window.js:520:19)
    at listOnTimeout (node:internal/timers:573:17)
    at processTimers (node:internal/timers:514:7)
====== CashTab Unit Tests: <App /> A new user can import a mnemonic ======
Error: Unable to find an element by: [data-testid="balance-xec"]

Ignored nodes: comments, script, style
<body>
  <div
    class="ant-notification ant-notification-topRight css-dev-only-do-not-override-1rqnfsa ant-notification-stack ant-notification-stack-expanded"
    style="right: 0px; top: 24px;"
  >
    <div
      class="ant-notification-notice-wrapper"
      style="transform: translate3d(0, 16px, 0) scaleX(1); height: 0px;"
    >
      <div
        class="ant-notification-notice ant-notification-notice-success ant-notification-notice-closable"
      >
        <div
          class="ant-notification-notice-content"
        >
          <div
            class="ant-notification-notice-with-icon"
            role="alert"
          >
            <span
              class="ant-notification-notice-icon"
            >
              <div
                class="ant-image css-dev-only-do-not-override-1rqnfsa"
                style="width: 24px; height: 24px;"
              >
                <img
                  class="ant-image-img css-dev-only-do-not-override-1rqnfsa"
                  height="24px"
                  src="test-file-stub"
                  style="height: 24px;"
                  width="24px"
                />
              </div>
            </span>
            <div
              class="ant-notification-notice-message"
            >
              Success
            </div>
            <div
              class="ant-notification-notice-description"
            >
              <a
                href="https://explorer.e.cash/tx/ce727c96439dfe365cb47f780c37ebb2e756051db62375e992419d5db3c81b1e"
                rel="noopener noreferrer"
                target="_blank"
              >
                Transaction successful. Click to view in block explorer.
              </a>
            </div>
          </div>
        </div>
        <a
          class="ant-notification-notice-close"
          tabindex="0"
        >
          <span
            class="ant-notification-notice-close-x"
          >
            <span
              aria-label="close"
              class="anticon anticon-close ant-notification-notice-close-icon"
              role="img"
            >
              <svg
                aria-hidden="true"
                data-icon="close"
                fill="currentColor"
                fill-rule="evenodd"
                focusable="false"
                height="1em"
                viewBox="64 64 896 896"
                width="1em"
              >
                <path
                  d="M799.86 166.31c.02 0 .04.02.08.06l57.69 57.7c.04.03.05.05.06.08a.12.12 0 010 .06c0 .03-.02.05-.06.09L569.93 512l287.7 287.7c.04.04.05.06.06.09a.12.12 0 010 .07c0 .02-.02.04-.06.08l-57.7 57.69c-.03.04-.05.05-.07.06a.12.12 0 01-.07 0c-.03 0-.05-.02-.09-.06L512 569.93l-287.7 287.7c-.04.04-.06.05-.09.06a.12.12 0 01-.07 0c-.02 0-.04-.02-.08-.06l-57.69-57.7c-.04-.03-.05-.05-.06-.07a.12.12 0 010-.07c0-.03.02-.05.06-.09L454.07 512l-287.7-287.7c-.04-.04-.05-.06-.06-.09a.12.12 0 010-.07c0-.02.02-.04.06-.08l57.7-57.69c.03-.04.05-.05.07-.06a.12.12 0 01.07 0c.03 0 .05.02.09.06L512 454.07l287.7-287.7c.04-.04.06-.05.09-.06a.12.12 0 01.07 0z"
                />
              </svg>
            </span>
          </span>
        </a>
      </div>
    </div>
    <div
      class="ant-notification-notice-wrapper"
      style="transform: translate3d(0, 0, 0);"
    >
      <div
        class="ant-notification-notice ant-notification-notice-success ant-notification-notice-closable"
      >
        <div
          class="ant-notification-notice-content"
        >
          <div
            class="ant-notification-notice-with-icon"
            role="alert"
          >
            <span
              class="ant-notification-notice-icon"
            >
              <div
                class="ant-image css-dev-only-do-not-override-1rqnfsa"
                style="width: 24px; height: 24px;"
              >
                <img
                  class="ant-image-img css-dev-only-do-not-override-1rqnfsa"
                  height="24px"
                  src="test-file-stub"
                  style="height: 24px;"
                  width="24px"
                />
              </div>
            </span>
            <div
              class="ant-notification-notice-message"
            >
              eToken transaction received: TBC
            </div>
            <div
              class="ant-notification-notice-description"
            >
              You received 10000000000 tabcash
            </div>
          </div>
        </div>
        <a
          class="ant-notification-notice-close"
          tabindex="0"
        >
          <span
            class="ant-notification-notice-close-x"
          >
            <span
              aria-label="close"
              class="anticon anticon-close ant-notification-notice-close-icon"
              role="img"
            >
              <svg
                aria-hidden="true"
                data-icon="close"
                fill="currentColor"
                fill-...
    at waitForWrapper (/work/cashtab/node_modules/@testing-library/dom/dist/wait-for.js:163:27)
    at /work/cashtab/node_modules/@testing-library/dom/dist/query-helpers.js:86:33
    at Object.findByTestId (/work/cashtab/src/components/__tests__/App.test.js:812:29)
    at runNextTicks (node:internal/process/task_queues:60:5)
    at listOnTimeout (node:internal/timers:540:9)
    at processTimers (node:internal/timers:514:7)

Each failure log is accessible here:
CashTab Unit Tests: useWallet hook rendering in different localforage states Cashtab loads wallets, settings, cache, and contactlist from localforage to context if they are present
CashTab Unit Tests: <App /> A new user can import a mnemonic

remove flaky test that is covered by other tests (it passes reliably if i add console.log(update called with...) to beginning of update statement

Failed tests logs:

====== CashTab Unit Tests: <App /> A new user can import a mnemonic ======
Error: Unable to find an element by: [data-testid="balance-xec"]

Ignored nodes: comments, script, style
<body>
  <div
    class="ant-notification ant-notification-topRight css-dev-only-do-not-override-1rqnfsa ant-notification-stack ant-notification-stack-expanded"
    style="right: 0px; top: 24px;"
  >
    <div
      class="ant-notification-notice-wrapper"
      style="transform: translate3d(0, 16px, 0) scaleX(1); height: 0px;"
    >
      <div
        class="ant-notification-notice ant-notification-notice-success ant-notification-notice-closable"
      >
        <div
          class="ant-notification-notice-content"
        >
          <div
            class="ant-notification-notice-with-icon"
            role="alert"
          >
            <span
              class="ant-notification-notice-icon"
            >
              <div
                class="ant-image css-dev-only-do-not-override-1rqnfsa"
                style="width: 24px; height: 24px;"
              >
                <img
                  class="ant-image-img css-dev-only-do-not-override-1rqnfsa"
                  height="24px"
                  src="test-file-stub"
                  style="height: 24px;"
                  width="24px"
                />
              </div>
            </span>
            <div
              class="ant-notification-notice-message"
            >
              Success
            </div>
            <div
              class="ant-notification-notice-description"
            >
              <a
                href="https://explorer.e.cash/tx/ce727c96439dfe365cb47f780c37ebb2e756051db62375e992419d5db3c81b1e"
                rel="noopener noreferrer"
                target="_blank"
              >
                Transaction successful. Click to view in block explorer.
              </a>
            </div>
          </div>
        </div>
        <a
          class="ant-notification-notice-close"
          tabindex="0"
        >
          <span
            class="ant-notification-notice-close-x"
          >
            <span
              aria-label="close"
              class="anticon anticon-close ant-notification-notice-close-icon"
              role="img"
            >
              <svg
                aria-hidden="true"
                data-icon="close"
                fill="currentColor"
                fill-rule="evenodd"
                focusable="false"
                height="1em"
                viewBox="64 64 896 896"
                width="1em"
              >
                <path
                  d="M799.86 166.31c.02 0 .04.02.08.06l57.69 57.7c.04.03.05.05.06.08a.12.12 0 010 .06c0 .03-.02.05-.06.09L569.93 512l287.7 287.7c.04.04.05.06.06.09a.12.12 0 010 .07c0 .02-.02.04-.06.08l-57.7 57.69c-.03.04-.05.05-.07.06a.12.12 0 01-.07 0c-.03 0-.05-.02-.09-.06L512 569.93l-287.7 287.7c-.04.04-.06.05-.09.06a.12.12 0 01-.07 0c-.02 0-.04-.02-.08-.06l-57.69-57.7c-.04-.03-.05-.05-.06-.07a.12.12 0 010-.07c0-.03.02-.05.06-.09L454.07 512l-287.7-287.7c-.04-.04-.05-.06-.06-.09a.12.12 0 010-.07c0-.02.02-.04.06-.08l57.7-57.69c.03-.04.05-.05.07-.06a.12.12 0 01.07 0c.03 0 .05.02.09.06L512 454.07l287.7-287.7c.04-.04.06-.05.09-.06a.12.12 0 01.07 0z"
                />
              </svg>
            </span>
          </span>
        </a>
      </div>
    </div>
    <div
      class="ant-notification-notice-wrapper"
      style="transform: translate3d(0, 0, 0);"
    >
      <div
        class="ant-notification-notice ant-notification-notice-success ant-notification-notice-closable"
      >
        <div
          class="ant-notification-notice-content"
        >
          <div
            class="ant-notification-notice-with-icon"
            role="alert"
          >
            <span
              class="ant-notification-notice-icon"
            >
              <div
                class="ant-image css-dev-only-do-not-override-1rqnfsa"
                style="width: 24px; height: 24px;"
              >
                <img
                  class="ant-image-img css-dev-only-do-not-override-1rqnfsa"
                  height="24px"
                  src="test-file-stub"
                  style="height: 24px;"
                  width="24px"
                />
              </div>
            </span>
            <div
              class="ant-notification-notice-message"
            >
              eToken transaction received: TBC
            </div>
            <div
              class="ant-notification-notice-description"
            >
              You received 10000000000 tabcash
            </div>
          </div>
        </div>
        <a
          class="ant-notification-notice-close"
          tabindex="0"
        >
          <span
            class="ant-notification-notice-close-x"
          >
            <span
              aria-label="close"
              class="anticon anticon-close ant-notification-notice-close-icon"
              role="img"
            >
              <svg
                aria-hidden="true"
                data-icon="close"
                fill="currentColor"
                fill-...
    at waitForWrapper (/work/cashtab/node_modules/@testing-library/dom/dist/wait-for.js:163:27)
    at /work/cashtab/node_modules/@testing-library/dom/dist/query-helpers.js:86:33
    at Object.findByTestId (/work/cashtab/src/components/__tests__/App.test.js:812:29)
    at runNextTicks (node:internal/process/task_queues:60:5)
    at listOnTimeout (node:internal/timers:540:9)
    at processTimers (node:internal/timers:514:7)

Each failure log is accessible here:
CashTab Unit Tests: <App /> A new user can import a mnemonic

Failed tests logs:

====== CashTab Unit Tests: <App /> A new user can import a mnemonic ======
Error: Unable to find an element by: [data-testid="balance-xec"]

Ignored nodes: comments, script, style
<body>
  <div
    class="ant-notification ant-notification-topRight css-dev-only-do-not-override-1rqnfsa ant-notification-stack ant-notification-stack-expanded"
    style="right: 0px; top: 24px;"
  >
    <div
      class="ant-notification-notice-wrapper"
      style="transform: translate3d(0, 16px, 0) scaleX(1); height: 0px;"
    >
      <div
        class="ant-notification-notice ant-notification-notice-success ant-notification-notice-closable"
      >
        <div
          class="ant-notification-notice-content"
        >
          <div
            class="ant-notification-notice-with-icon"
            role="alert"
          >
            <span
              class="ant-notification-notice-icon"
            >
              <div
                class="ant-image css-dev-only-do-not-override-1rqnfsa"
                style="width: 24px; height: 24px;"
              >
                <img
                  class="ant-image-img css-dev-only-do-not-override-1rqnfsa"
                  height="24px"
                  src="test-file-stub"
                  style="height: 24px;"
                  width="24px"
                />
              </div>
            </span>
            <div
              class="ant-notification-notice-message"
            >
              Success
            </div>
            <div
              class="ant-notification-notice-description"
            >
              <a
                href="https://explorer.e.cash/tx/ce727c96439dfe365cb47f780c37ebb2e756051db62375e992419d5db3c81b1e"
                rel="noopener noreferrer"
                target="_blank"
              >
                Transaction successful. Click to view in block explorer.
              </a>
            </div>
          </div>
        </div>
        <a
          class="ant-notification-notice-close"
          tabindex="0"
        >
          <span
            class="ant-notification-notice-close-x"
          >
            <span
              aria-label="close"
              class="anticon anticon-close ant-notification-notice-close-icon"
              role="img"
            >
              <svg
                aria-hidden="true"
                data-icon="close"
                fill="currentColor"
                fill-rule="evenodd"
                focusable="false"
                height="1em"
                viewBox="64 64 896 896"
                width="1em"
              >
                <path
                  d="M799.86 166.31c.02 0 .04.02.08.06l57.69 57.7c.04.03.05.05.06.08a.12.12 0 010 .06c0 .03-.02.05-.06.09L569.93 512l287.7 287.7c.04.04.05.06.06.09a.12.12 0 010 .07c0 .02-.02.04-.06.08l-57.7 57.69c-.03.04-.05.05-.07.06a.12.12 0 01-.07 0c-.03 0-.05-.02-.09-.06L512 569.93l-287.7 287.7c-.04.04-.06.05-.09.06a.12.12 0 01-.07 0c-.02 0-.04-.02-.08-.06l-57.69-57.7c-.04-.03-.05-.05-.06-.07a.12.12 0 010-.07c0-.03.02-.05.06-.09L454.07 512l-287.7-287.7c-.04-.04-.05-.06-.06-.09a.12.12 0 010-.07c0-.02.02-.04.06-.08l57.7-57.69c.03-.04.05-.05.07-.06a.12.12 0 01.07 0c.03 0 .05.02.09.06L512 454.07l287.7-287.7c.04-.04.06-.05.09-.06a.12.12 0 01.07 0z"
                />
              </svg>
            </span>
          </span>
        </a>
      </div>
    </div>
    <div
      class="ant-notification-notice-wrapper"
      style="transform: translate3d(0, 0, 0);"
    >
      <div
        class="ant-notification-notice ant-notification-notice-success ant-notification-notice-closable"
      >
        <div
          class="ant-notification-notice-content"
        >
          <div
            class="ant-notification-notice-with-icon"
            role="alert"
          >
            <span
              class="ant-notification-notice-icon"
            >
              <div
                class="ant-image css-dev-only-do-not-override-1rqnfsa"
                style="width: 24px; height: 24px;"
              >
                <img
                  class="ant-image-img css-dev-only-do-not-override-1rqnfsa"
                  height="24px"
                  src="test-file-stub"
                  style="height: 24px;"
                  width="24px"
                />
              </div>
            </span>
            <div
              class="ant-notification-notice-message"
            >
              eToken transaction received: TBC
            </div>
            <div
              class="ant-notification-notice-description"
            >
              You received 10000000000 tabcash
            </div>
          </div>
        </div>
        <a
          class="ant-notification-notice-close"
          tabindex="0"
        >
          <span
            class="ant-notification-notice-close-x"
          >
            <span
              aria-label="close"
              class="anticon anticon-close ant-notification-notice-close-icon"
              role="img"
            >
              <svg
                aria-hidden="true"
                data-icon="close"
                fill="currentColor"
                fill-...
    at waitForWrapper (/work/cashtab/node_modules/@testing-library/dom/dist/wait-for.js:163:27)
    at /work/cashtab/node_modules/@testing-library/dom/dist/query-helpers.js:86:33
    at Object.findByTestId (/work/cashtab/src/components/__tests__/App.test.js:812:29)
    at runNextTicks (node:internal/process/task_queues:60:5)
    at listOnTimeout (node:internal/timers:540:9)
    at processTimers (node:internal/timers:514:7)

Each failure log is accessible here:
CashTab Unit Tests: <App /> A new user can import a mnemonic

less extreme timeout bump, revert accidental local install of netlify-cli

better documentation, handle wallet add/import edge cases and confirm with test

Failed tests logs:

====== CashTab Unit Tests: <App /> A new user can import a mnemonic ======
Error: Unable to find an element by: [data-testid="balance-xec"]

Ignored nodes: comments, script, style
<body>
  <div
    class="ant-notification ant-notification-topRight css-dev-only-do-not-override-1rqnfsa ant-notification-stack ant-notification-stack-expanded"
    style="right: 0px; top: 24px;"
  >
    <div
      class="ant-notification-notice-wrapper"
      style="transform: translate3d(0, 16px, 0) scaleX(1); height: 0px;"
    >
      <div
        class="ant-notification-notice ant-notification-notice-success ant-notification-notice-closable"
      >
        <div
          class="ant-notification-notice-content"
        >
          <div
            class="ant-notification-notice-with-icon"
            role="alert"
          >
            <span
              class="ant-notification-notice-icon"
            >
              <div
                class="ant-image css-dev-only-do-not-override-1rqnfsa"
                style="width: 24px; height: 24px;"
              >
                <img
                  class="ant-image-img css-dev-only-do-not-override-1rqnfsa"
                  height="24px"
                  src="test-file-stub"
                  style="height: 24px;"
                  width="24px"
                />
              </div>
            </span>
            <div
              class="ant-notification-notice-message"
            >
              Success
            </div>
            <div
              class="ant-notification-notice-description"
            >
              <a
                href="https://explorer.e.cash/tx/ce727c96439dfe365cb47f780c37ebb2e756051db62375e992419d5db3c81b1e"
                rel="noopener noreferrer"
                target="_blank"
              >
                Transaction successful. Click to view in block explorer.
              </a>
            </div>
          </div>
        </div>
        <a
          class="ant-notification-notice-close"
          tabindex="0"
        >
          <span
            class="ant-notification-notice-close-x"
          >
            <span
              aria-label="close"
              class="anticon anticon-close ant-notification-notice-close-icon"
              role="img"
            >
              <svg
                aria-hidden="true"
                data-icon="close"
                fill="currentColor"
                fill-rule="evenodd"
                focusable="false"
                height="1em"
                viewBox="64 64 896 896"
                width="1em"
              >
                <path
                  d="M799.86 166.31c.02 0 .04.02.08.06l57.69 57.7c.04.03.05.05.06.08a.12.12 0 010 .06c0 .03-.02.05-.06.09L569.93 512l287.7 287.7c.04.04.05.06.06.09a.12.12 0 010 .07c0 .02-.02.04-.06.08l-57.7 57.69c-.03.04-.05.05-.07.06a.12.12 0 01-.07 0c-.03 0-.05-.02-.09-.06L512 569.93l-287.7 287.7c-.04.04-.06.05-.09.06a.12.12 0 01-.07 0c-.02 0-.04-.02-.08-.06l-57.69-57.7c-.04-.03-.05-.05-.06-.07a.12.12 0 010-.07c0-.03.02-.05.06-.09L454.07 512l-287.7-287.7c-.04-.04-.05-.06-.06-.09a.12.12 0 010-.07c0-.02.02-.04.06-.08l57.7-57.69c.03-.04.05-.05.07-.06a.12.12 0 01.07 0c.03 0 .05.02.09.06L512 454.07l287.7-287.7c.04-.04.06-.05.09-.06a.12.12 0 01.07 0z"
                />
              </svg>
            </span>
          </span>
        </a>
      </div>
    </div>
    <div
      class="ant-notification-notice-wrapper"
      style="transform: translate3d(0, 0, 0);"
    >
      <div
        class="ant-notification-notice ant-notification-notice-success ant-notification-notice-closable"
      >
        <div
          class="ant-notification-notice-content"
        >
          <div
            class="ant-notification-notice-with-icon"
            role="alert"
          >
            <span
              class="ant-notification-notice-icon"
            >
              <div
                class="ant-image css-dev-only-do-not-override-1rqnfsa"
                style="width: 24px; height: 24px;"
              >
                <img
                  class="ant-image-img css-dev-only-do-not-override-1rqnfsa"
                  height="24px"
                  src="test-file-stub"
                  style="height: 24px;"
                  width="24px"
                />
              </div>
            </span>
            <div
              class="ant-notification-notice-message"
            >
              eToken transaction received: TBC
            </div>
            <div
              class="ant-notification-notice-description"
            >
              You received 10000000000 tabcash
            </div>
          </div>
        </div>
        <a
          class="ant-notification-notice-close"
          tabindex="0"
        >
          <span
            class="ant-notification-notice-close-x"
          >
            <span
              aria-label="close"
              class="anticon anticon-close ant-notification-notice-close-icon"
              role="img"
            >
              <svg
                aria-hidden="true"
                data-icon="close"
                fill="currentColor"
                fill-...
    at waitForWrapper (/work/cashtab/node_modules/@testing-library/dom/dist/wait-for.js:163:27)
    at /work/cashtab/node_modules/@testing-library/dom/dist/query-helpers.js:86:33
    at Object.findByTestId (/work/cashtab/src/components/__tests__/App.test.js:812:29)
    at runNextTicks (node:internal/process/task_queues:60:5)
    at listOnTimeout (node:internal/timers:540:9)
    at processTimers (node:internal/timers:514:7)
====== CashTab Unit Tests: <Configure /> We can rename the active wallet or a saved wallet, we can add a wallet, we can import a wallet, we can delete a wallet ======
TestingLibraryElementError: Found multiple elements with the role "button" and name "OK"

Here are the matching elements:

Ignored nodes: comments, script, style
<button
  class="ant-btn css-dev-only-do-not-override-1rqnfsa ant-btn-primary"
  type="button"
>
  <span>
    OK
  </span>
</button>

Ignored nodes: comments, script, style
<button
  class="ant-btn css-dev-only-do-not-override-1rqnfsa ant-btn-primary"
  type="button"
>
  <span>
    OK
  </span>
</button>

(If this is intentional, then use the `*AllBy*` variant of the query (like `queryAllByText`, `getAllByText`, or `findAllByText`)).

Ignored nodes: comments, script, style
<body>
  <div
    class="ant-notification ant-notification-topRight css-dev-only-do-not-override-1rqnfsa ant-notification-stack ant-notification-stack-expanded"
    style="right: 0px; top: 24px;"
  />
  <div>
    <div
      class="ant-spin-nested-loading css-dev-only-do-not-override-1rqnfsa"
    >
      <div
        class="ant-spin-container"
      >
        <div
          class="sc-iBEsjs hYXNZl"
        >
          <div
            class="sc-RcBXQ krXqvz"
          >
            <div
              class="sc-iSDuPN cdWENt"
            >
              <div
                class="sc-chbbiW kmKhFr"
              >
                <div
                  class="sc-fZwumE jmbiQP"
                >
                  <img
                    alt="cashtab"
                    class="sc-fQejPQ iclGva"
                    src="test-file-stub"
                  />
                  <div
                    class="sc-jXQZqI jNpDSO"
                  >
                    Settings
                    <svg
                      height="33px"
                      width="30px"
                    />
                  </div>
                </div>
                <div
                  class="sc-elJkPf geNavj"
                  data-testid="wallet-info-ctn"
                >
                  <div
                    class="sc-jtggT gArshC"
                  >
                    <div
                      class="sc-ebFjAB kYfZhR"
                    >
                      bravo
                    </div>
                    <a
                      href="/configure"
                    >
                      <svg
                        class="sc-jTzLTM bTdWCF"
                      />
                    </a>
                    <div>
                       
                      <button
                        aria-checked="true"
                        class="ant-switch ant-switch-small css-dev-only-do-not-override-1rqnfsa ant-switch-checked"
                        role="switch"
                        type="button"
                      >
                        <div
                          class="ant-switch-handle"
                        />
                        <span
                          class="ant-switch-inner"
                        >
                          <span
                            class="ant-switch-inner-checked"
                          >
                            <svg
                              class="sc-cSHVUG bgwEHu"
                            />
                          </span>
                          <span
                            class="ant-switch-inner-unchecked"
                          >
                            <svg
                              class="sc-kAzzGY fUTkYj"
                            />
                          </span>
                        </span>
                      </button>
                    </div>
                  </div>
                  <div
                    class="sc-hMqMXs dRhoAj"
                  >
                    <span
                      aria-label="loading"
                      class="anticon anticon-loading anticon-spin"
                      data-testid="cash-loader"
                      role="img"
                    >
                      <svg
                        aria-hidden="true"
                        data-icon="loading"
                        fill="currentColor"
                        focusable="false"
                        height="1em"
                        viewBox="0 0 1024 1024"
                        width="1em"
                      >
                        <path
                          d="M988 548c-19.9 0-36-16.1-36-36 0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 00-94.3-139.9 437.71 437.71 0 00-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.3C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3.1 19.9-16 36-35.9 36z"
                        />
                      </svg>
                    </span>
                  </div>
                </div>
              </div>
              <div
                class="sc-lhVmIH gJoLAm"
                data-testid="configure-ctn"
              >
                <div
                  class="sc-gPWkxV fPJZLd"
                >
                  <h2>
                    <span
                      aria-label="copy"
                      class="anticon anticon-copy sc-bwzfXH gJwWNq"
                      role="img"
                    >
                      <svg
                        aria-hidden="true"
                        data-icon="copy"
                        fill="currentColor"
                        focusable="false"
                        height="1em"
                        viewBox="64 64 896 896"
                        width="1em"
                      >
                        <path
                          d="M832 64H296c-4.4 0-8 3.6-8 8v56c0 4.4...
    at Object.getElementError (/work/cashtab/node_modules/@testing-library/dom/dist/config.js:37:19)
    at getElementError (/work/cashtab/node_modules/@testing-library/dom/dist/query-helpers.js:20:35)
    at getMultipleElementsFoundError (/work/cashtab/node_modules/@testing-library/dom/dist/query-helpers.js:23:10)
    at /work/cashtab/node_modules/@testing-library/dom/dist/query-helpers.js:55:13
    at /work/cashtab/node_modules/@testing-library/dom/dist/query-helpers.js:95:19
    at Object.getByRole (/work/cashtab/src/components/Configure/__tests__/Configure.test.js:645:33)
    at runNextTicks (node:internal/process/task_queues:60:5)
    at listOnTimeout (node:internal/timers:540:9)
    at processTimers (node:internal/timers:514:7)

Each failure log is accessible here:
CashTab Unit Tests: <App /> A new user can import a mnemonic
CashTab Unit Tests: <Configure /> We can rename the active wallet or a saved wallet, we can add a wallet, we can import a wallet, we can delete a wallet

Tail of the build log:

Installing mock-chronik-client dependencies...
/work/apps/mock-chronik-client /work/abc-ci-builds/cashtab-tests

added 236 packages, and audited 237 packages in 1s

35 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities
/work/cashtab /work/apps/mock-chronik-client /work/abc-ci-builds/cashtab-tests
npm WARN deprecated @babel/plugin-proposal-private-methods@7.18.6: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-methods instead.
npm WARN deprecated @babel/plugin-proposal-numeric-separator@7.18.6: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-numeric-separator instead.
npm WARN deprecated @babel/plugin-proposal-nullish-coalescing-operator@7.18.6: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-nullish-coalescing-operator instead.
npm WARN deprecated rollup-plugin-terser@7.0.2: This package has been deprecated and is no longer maintained. Please use @rollup/plugin-terser
npm WARN deprecated @babel/plugin-proposal-class-properties@7.18.6: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead.
npm WARN deprecated @babel/plugin-proposal-private-property-in-object@7.21.11: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-property-in-object instead.
npm WARN deprecated domexception@4.0.0: Use your platform's native DOMException instead
npm WARN deprecated abab@2.0.6: Use your platform's native atob() and btoa() methods instead
npm WARN deprecated stable@0.1.8: Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility
npm WARN deprecated sourcemap-codec@1.4.8: Please use @jridgewell/sourcemap-codec instead
npm WARN deprecated @babel/plugin-proposal-optional-chaining@7.21.0: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-chaining instead.
npm WARN deprecated workbox-cacheable-response@6.6.0: workbox-background-sync@6.6.0

added 1722 packages, and audited 1723 packages in 22s

263 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

> cashtab@1.7.0 build
> node scripts/build.js

Creating an optimized production build...
Failed to compile.

[eslint] 
src/hooks/useWallet.js
  Line 7:8:    'useInterval' is defined but never used             no-unused-vars
  Line 64:24:  'setHasUpdated' is assigned a value but never used  no-unused-vars

Search for the keywords to learn more about each error.


Build cashtab-tests failed with exit code 1

Clean up tests, add migration tests, test everything else from new way of loading, stop using useInterval to prevent re-entrancy in update

move Cashtab off the 'always update at interval' basis, update when you are supposed to update

bytesofman edited the test plan for this revision. (Show Details)
bytesofman retitled this revision from [Cashtab] Migrate from 'wallet' and 'savedWallets' storage keys to 'wallets' to [Cashtab] Cashtab 2.0.0 - Migrate to new wallet management API.Mar 7 2024, 19:46
bytesofman edited the summary of this revision. (Show Details)
bytesofman published this revision for review.Mar 7 2024, 19:55
bytesofman added inline comments.
cashtab/jest.config.js
19 ↗(On Diff #46016)

45s is not long enough for some integration tests to run locally

56 ↗(On Diff #46016)

this was a default setting from create-react-app days. Needs to be disabled so that we can mock the bip39 module.

cashtab/package-lock.json
9 ↗(On Diff #46016)

This isn't really a breaking change as we do support migration. However, this diff is a significant enough refactor to Cashtab that I think a major version bump is warranted.

cashtab/src/components/Alias/Alias.js
94 ↗(On Diff #46016)

we now handle this in useWallet.js so we do not have to handle it in every single context-using component

94 ↗(On Diff #46003)

We don't need to do this type check everywhere now that cashtabState is initialized as a new CashtabState() on app load. Now its keys will always be defined.

emack requested changes to this revision.EditedMar 8 2024, 13:17
emack added a subscriber: emack.

Manual testing observations:

  • When switched to another wallet, the wallets[0] is updated but the separate wallet object is still on the previous wallet. Deleting this stale wallet in the wallet object does not update it.
  • Even with this mammoth refactor, wallet cache did not nuke.
  • No longer seeing that edge case where clicking Activate on another wallet loops back to the same wallet, until you click Activate a 2nd time
  • Wallet switching is soooo much faster now
cashtab/src/components/Configure/Configure.js
771 ↗(On Diff #46018)

Should throw something or put up the modal.error otherwise this is happening silently unbeknownst to us

cashtab/src/components/Configure/__tests__/Configure.test.js
283 ↗(On Diff #46018)
353 ↗(On Diff #46018)
433 ↗(On Diff #46018)

Why are there two active wallet labels?

516 ↗(On Diff #46018)

No action here, but let's start tagging these antd shims with something like // antd shim so when we get around to fully deprecating antd we can just grep for this tag without going through the codebase.

cashtab/src/components/fixtures/helpers.js
40 ↗(On Diff #46018)
54 ↗(On Diff #46018)

Side note: the extension being 3.0.0 and web being on 2.0.0 might get a bit confusing when we're trying to debut post 2.0 but pre 3.0 bugs.

cashtab/src/hooks/useWallet.js
514 ↗(On Diff #46018)

there's no other way of just subscribing the specific payload without unsubscribing all hashes for this wallet beforehand? Or is this more of a cautious approach to avoid the subscriptions being out of sync?

932 ↗(On Diff #46018)

// We can call with fiatPrice of null, we will not always have fiatPrice

Doesn't the fiatPrice dependency here suggest it triggers the useEffect loop when fiatPrice is not null?

This revision now requires changes to proceed.Mar 8 2024, 13:17

Manual testing observations:

When switched to another wallet, the wallets[0] is updated but the separate wallet object is still on the previous wallet. Deleting this stale wallet in the wallet object does not update it.

Are you referring to the legacy wallet key in localforage? We do not expect this to change. We leave wallet and savedWallets keys untouched in this diff. Cashtab no longer writes to them, and only reads them on load (if there is no wallets key) for the purposes of migrating to the new storage structure.

Could be cleaner to delete them, but something about deleting wallets from localforage feels a little too final. I can see some kind of situation where the user wants to recover them somehow. Or, if there is some issue with the migration -- it's possible for us to revert this diff as long as we didn't mess with the legacy storage keys.

They will only exist for legacy users. But there's always a chance that some legacy cashtab user visits cashtab.com after this version, so we do always need to support migrating them.

cashtab/src/components/Configure/__tests__/Configure.test.js
433 ↗(On Diff #46018)

The active wallet also appears in the wallet info banner

image.png (227×317 px, 21 KB)

image.png (145×443 px, 8 KB)

cashtab/src/components/fixtures/helpers.js
54 ↗(On Diff #46018)

Yes. It would be nice to keep them on the same version, but imo keeping them one major apart is better than what we have right now. The extension will still display the same version as the webapp. Its 3.x.x version is only used in the app store.

I don't think it's worth bumping web cashtab from 1 to 3 just to get them 'the same', but we could do this later if it ends up being important.

cashtab/src/hooks/useWallet.js
514 ↗(On Diff #46018)

In this diff I'm not looking to change this behavior. In the future, we may want to simply subscribe to every address of every wallet. This would allow us to, for example, get notifications of incoming txs in an inactive wallet. Would be relatively easy to implement now that wallets are better organized.

However if we do this, we need some sort of filter on incoming msgs so that we take the correct action on the active wallet msgs vs the inactive wallet msgs. We don't have this right now.

So, right now -- the filter is this. We only subscribe to active wallet scripts, so all messages from the websocket relate only to the active wallet. When we switch wallets, we unsubscribe from the old scripts and subscribe to new ones.

I'm not sure it's the best way, but if we change it, should be its own diff.

932 ↗(On Diff #46018)

Yes -- updated above comments.

We want the onMessage routine to update when fiatPrice changes, otherwise the user will see the 'old' fiatPrice on incoming tx notifications. Not the end of the world, but would be weird if the user just switched currencies from, say, rupiah to gbp.

Can test this manually by

  • Set currency to something cheap, say naira
  • receive a tx, see forex rate
  • change to gpb or eur
  • receive a tx, see forex rate is correct
bytesofman marked 3 inline comments as done.

clean up comments, handle edge case rename error with modal

This revision is now accepted and ready to land.Mar 8 2024, 13:59