gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-wallet-core] branch master updated (2bf9461f -> a35604fd)


From: gnunet
Subject: [taler-wallet-core] branch master updated (2bf9461f -> a35604fd)
Date: Fri, 19 Nov 2021 18:51:47 +0100

This is an automated email from the git hooks/post-receive script.

sebasjm pushed a change to branch master
in repository wallet-core.

    from 2bf9461f do not ask for confirmation during recovery
     new 49415468 version 10
     new 60cfb0e7 add reservePub to the withdrawal transaction info
     new a35604fd some changes:

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 packages/taler-util/src/transactionsTypes.ts       |   6 +
 .../taler-wallet-core/src/operations/reserves.ts   |  67 ++---
 .../src/operations/transactions.ts                 |  41 ++-
 packages/taler-wallet-core/src/versions.ts         |   2 +-
 .../.storybook/preview.js                          |  12 +-
 .../src/components/BalanceTable.tsx                |  48 ++++
 .../src/components/BankDetailsByPaytoType.tsx      | 109 ++++++++
 .../src/components/styled/index.tsx                | 167 ++++++++++++-
 packages/taler-wallet-webextension/src/cta/Pay.tsx |   4 +-
 .../src/hooks/useAsyncAsHook.ts                    |  10 +-
 .../src/hooks/useBalances.ts                       |  53 ----
 .../src/popup/Backup.stories.tsx                   | 198 ---------------
 .../src/popup/BackupPage.tsx                       | 197 ---------------
 .../src/popup/Balance.stories.tsx                  |   2 +-
 .../src/popup/BalancePage.tsx                      | 195 +++------------
 .../taler-wallet-webextension/src/popup/Debug.tsx  |   2 +-
 .../src/popup/History.stories.tsx                  |   1 +
 .../src/popup/History.tsx                          |  22 +-
 .../popup/ProviderAddConfirmProvider.stories.tsx   |  51 ----
 .../src/popup/ProviderAddPage.tsx                  | 244 ------------------
 .../src/popup/ProviderAddSetUrl.stories.tsx        |  51 ----
 .../src/popup/ProviderDetail.stories.tsx           | 235 -----------------
 .../src/popup/ProviderDetailPage.tsx               | 278 ---------------------
 .../src/popup/Settings.tsx                         |  83 ++++--
 .../src/popup/TalerActionFound.tsx                 |  12 +-
 .../src/popupEntryPoint.tsx                        |  13 +-
 .../taler-wallet-webextension/src/svg/index.tsx    |  40 +++
 .../src/wallet/BackupPage.tsx                      |  13 +-
 .../src/wallet/BalancePage.tsx                     | 110 ++------
 .../src/wallet/CreateManualWithdraw.stories.tsx    |   4 +
 .../src/wallet/CreateManualWithdraw.tsx            |  26 +-
 .../src/wallet/History.stories.tsx                 |   1 +
 .../src/wallet/History.tsx                         |  33 ++-
 .../src/wallet/ManualWithdrawPage.tsx              |  15 +-
 .../src/wallet/ProviderAddPage.tsx                 |  13 +-
 .../src/wallet/ProviderDetailPage.tsx              |  94 +++----
 .../src/wallet/ReserveCreated.tsx                  | 119 +--------
 .../src/wallet/Settings.tsx                        |  73 +++---
 .../src/wallet/Transaction.stories.tsx             |  44 +++-
 .../src/wallet/Transaction.tsx                     | 226 +++++++++++++----
 .../src/wallet/Welcome.tsx                         |  11 +-
 .../src/walletEntryPoint.tsx                       |  46 +++-
 packages/taler-wallet-webextension/src/wxApi.ts    |  49 ++--
 .../taler-wallet-webextension/src/wxBackend.ts     |  10 +-
 44 files changed, 998 insertions(+), 2032 deletions(-)
 create mode 100644 
packages/taler-wallet-webextension/src/components/BalanceTable.tsx
 create mode 100644 
packages/taler-wallet-webextension/src/components/BankDetailsByPaytoType.tsx
 delete mode 100644 packages/taler-wallet-webextension/src/hooks/useBalances.ts
 delete mode 100644 
packages/taler-wallet-webextension/src/popup/Backup.stories.tsx
 delete mode 100644 packages/taler-wallet-webextension/src/popup/BackupPage.tsx
 delete mode 100644 
packages/taler-wallet-webextension/src/popup/ProviderAddConfirmProvider.stories.tsx
 delete mode 100644 
packages/taler-wallet-webextension/src/popup/ProviderAddPage.tsx
 delete mode 100644 
packages/taler-wallet-webextension/src/popup/ProviderAddSetUrl.stories.tsx
 delete mode 100644 
packages/taler-wallet-webextension/src/popup/ProviderDetail.stories.tsx
 delete mode 100644 
packages/taler-wallet-webextension/src/popup/ProviderDetailPage.tsx
 create mode 100644 packages/taler-wallet-webextension/src/svg/index.tsx

diff --git a/packages/taler-util/src/transactionsTypes.ts 
b/packages/taler-util/src/transactionsTypes.ts
index 2ee34022..e780ca41 100644
--- a/packages/taler-util/src/transactionsTypes.ts
+++ b/packages/taler-util/src/transactionsTypes.ts
@@ -130,6 +130,9 @@ interface WithdrawalDetailsForManualTransfer {
    * Already contains the amount and message.
    */
   exchangePaytoUris: string[];
+
+  // Public key of the reserve
+  reservePub: string;
 }
 
 interface WithdrawalDetailsForTalerBankIntegrationApi {
@@ -147,6 +150,9 @@ interface WithdrawalDetailsForTalerBankIntegrationApi {
    * initiated confirmation.
    */
   bankConfirmationUrl?: string;
+
+  // Public key of the reserve
+  reservePub: string;
 }
 
 // This should only be used for actual withdrawals
diff --git a/packages/taler-wallet-core/src/operations/reserves.ts 
b/packages/taler-wallet-core/src/operations/reserves.ts
index 4b5862be..43e0d7d3 100644
--- a/packages/taler-wallet-core/src/operations/reserves.ts
+++ b/packages/taler-wallet-core/src/operations/reserves.ts
@@ -15,62 +15,36 @@
  */
 
 import {
-  CreateReserveRequest,
-  CreateReserveResponse,
-  TalerErrorDetails,
-  AcceptWithdrawalResponse,
-  Amounts,
-  codecForBankWithdrawalOperationPostResponse,
+  AcceptWithdrawalResponse, addPaytoQueryParams, Amounts, canonicalizeBaseUrl, 
codecForBankWithdrawalOperationPostResponse,
   codecForReserveStatus,
-  codecForWithdrawOperationStatusResponse,
-  Duration,
+  codecForWithdrawOperationStatusResponse, CreateReserveRequest,
+  CreateReserveResponse, Duration,
   durationMax,
-  durationMin,
-  getTimestampNow,
-  NotificationType,
-  ReserveTransactionType,
-  TalerErrorCode,
-  addPaytoQueryParams,
+  durationMin, encodeCrock, getRandomBytes, getTimestampNow, Logger, 
NotificationType, randomBytes, ReserveTransactionType,
+  TalerErrorCode, TalerErrorDetails, URL
 } from "@gnu-taler/taler-util";
-import { randomBytes } from "@gnu-taler/taler-util";
+import { InternalWalletState } from "../common.js";
 import {
-  ReserveRecordStatus,
   ReserveBankInfo,
-  ReserveRecord,
-  WithdrawalGroupRecord,
-  WalletStoresV1,
+  ReserveRecord, ReserveRecordStatus, WalletStoresV1, WithdrawalGroupRecord
 } from "../db.js";
+import { guardOperationException, OperationFailedError } from "../errors.js";
 import { assertUnreachable } from "../util/assertUnreachable.js";
-import { canonicalizeBaseUrl } from "@gnu-taler/taler-util";
 import {
-  initRetryInfo,
-  getRetryDuration,
-  updateRetryInfoTimeout,
+  readSuccessResponseJsonOrErrorCode,
+  readSuccessResponseJsonOrThrow,
+  throwUnexpectedRequestError
+} from "../util/http.js";
+import { GetReadOnlyAccess } from "../util/query.js";
+import {
+  getRetryDuration, initRetryInfo, updateRetryInfoTimeout
 } from "../util/retries.js";
-import { guardOperationException, OperationFailedError } from "../errors.js";
 import {
-  updateExchangeFromUrl,
-  getExchangePaytoUri,
-  getExchangeDetails,
-  getExchangeTrust,
+  getExchangeDetails, getExchangePaytoUri, getExchangeTrust, 
updateExchangeFromUrl
 } from "./exchanges.js";
-import { InternalWalletState } from "../common.js";
 import {
-  updateWithdrawalDenoms,
-  getCandidateWithdrawalDenoms,
-  selectWithdrawalDenominations,
-  denomSelectionInfoToState,
-  processWithdrawGroup,
-  getBankWithdrawalInfo,
+  denomSelectionInfoToState, getBankWithdrawalInfo, 
getCandidateWithdrawalDenoms, processWithdrawGroup, 
selectWithdrawalDenominations, updateWithdrawalDenoms
 } from "./withdraw.js";
-import { encodeCrock, getRandomBytes } from "@gnu-taler/taler-util";
-import { Logger, URL } from "@gnu-taler/taler-util";
-import {
-  readSuccessResponseJsonOrErrorCode,
-  readSuccessResponseJsonOrThrow,
-  throwUnexpectedRequestError,
-} from "../util/http.js";
-import { GetReadOnlyAccess } from "../util/query.js";
 
 const logger = new Logger("reserves.ts");
 
@@ -540,7 +514,7 @@ async function updateReserve(
     if (
       resp.status === 404 &&
       result.talerErrorResponse.code ===
-        TalerErrorCode.EXCHANGE_RESERVES_GET_STATUS_UNKNOWN
+      TalerErrorCode.EXCHANGE_RESERVES_GET_STATUS_UNKNOWN
     ) {
       ws.notify({
         type: NotificationType.ReserveNotYetFound,
@@ -643,8 +617,7 @@ async function updateReserve(
       logger.trace(
         `Remaining unclaimed amount in reseve is ${Amounts.stringify(
           remainingAmount,
-        )} and can be withdrawn with ${
-          denomSelInfo.selectedDenoms.length
+        )} and can be withdrawn with ${denomSelInfo.selectedDenoms.length
         } coins`,
       );
 
@@ -731,7 +704,7 @@ async function processReserveImpl(
     case ReserveRecordStatus.REGISTERING_BANK:
       await processReserveBankStatus(ws, reservePub);
       return await processReserveImpl(ws, reservePub, true);
-    case ReserveRecordStatus.QUERYING_STATUS: 
+    case ReserveRecordStatus.QUERYING_STATUS:
       const res = await updateReserve(ws, reservePub);
       if (res.ready) {
         return await processReserveImpl(ws, reservePub, true);
diff --git a/packages/taler-wallet-core/src/operations/transactions.ts 
b/packages/taler-wallet-core/src/operations/transactions.ts
index dc738b77..b00779fa 100644
--- a/packages/taler-wallet-core/src/operations/transactions.ts
+++ b/packages/taler-wallet-core/src/operations/transactions.ts
@@ -17,32 +17,22 @@
 /**
  * Imports.
  */
+import {
+  AmountJson,
+  Amounts, OrderShortInfo, PaymentStatus, timestampCmp, Transaction, 
TransactionsRequest,
+  TransactionsResponse, TransactionType, WithdrawalDetails, WithdrawalType
+} from "@gnu-taler/taler-util";
 import { InternalWalletState } from "../common.js";
 import {
-  WalletRefundItem,
-  RefundState,
-  ReserveRecordStatus,
-  AbortStatus,
-  ReserveRecord,
+  AbortStatus, RefundState, ReserveRecord, ReserveRecordStatus, 
WalletRefundItem
 } from "../db.js";
-import { AmountJson, Amounts, timestampCmp } from "@gnu-taler/taler-util";
-import {
-  TransactionsRequest,
-  TransactionsResponse,
-  Transaction,
-  TransactionType,
-  PaymentStatus,
-  WithdrawalType,
-  WithdrawalDetails,
-  OrderShortInfo,
-} from "@gnu-taler/taler-util";
-import { getFundingPaytoUris } from "./reserves.js";
+import { processDepositGroup } from "./deposits.js";
 import { getExchangeDetails } from "./exchanges.js";
-import { processWithdrawGroup } from "./withdraw.js";
 import { processPurchasePay } from "./pay.js";
-import { processDepositGroup } from "./deposits.js";
-import { processTip } from "./tip.js";
 import { processRefreshGroup } from "./refresh.js";
+import { getFundingPaytoUris } from "./reserves.js";
+import { processTip } from "./tip.js";
+import { processWithdrawGroup } from "./withdraw.js";
 
 /**
  * Create an event ID from the type and the primary key for the event.
@@ -138,6 +128,7 @@ export async function getTransactions(
             withdrawalDetails = {
               type: WithdrawalType.TalerBankIntegrationApi,
               confirmed: true,
+              reservePub: wsr.reservePub,
               bankConfirmationUrl: r.bankInfo.confirmUrl,
             };
           } else {
@@ -151,11 +142,13 @@ export async function getTransactions(
             }
             withdrawalDetails = {
               type: WithdrawalType.ManualTransfer,
+              reservePub: wsr.reservePub,
               exchangePaytoUris:
                 exchangeDetails.wireInfo?.accounts.map((x) => x.payto_uri) ??
                 [],
             };
           }
+
           transactions.push({
             type: TransactionType.Withdrawal,
             amountEffective: Amounts.stringify(wsr.denomsSel.totalCoinValue),
@@ -194,11 +187,13 @@ export async function getTransactions(
             withdrawalDetails = {
               type: WithdrawalType.TalerBankIntegrationApi,
               confirmed: false,
+              reservePub: r.reservePub,
               bankConfirmationUrl: r.bankInfo.confirmUrl,
             };
           } else {
             withdrawalDetails = {
               type: WithdrawalType.ManualTransfer,
+              reservePub: r.reservePub,
               exchangePaytoUris: await getFundingPaytoUris(tx, r.reservePub),
             };
           }
@@ -439,7 +434,7 @@ export async function retryTransaction(
       const proposalId = rest[0];
       await processPurchasePay(ws, proposalId, true);
       break;
-    case TransactionType.Tip: 
+    case TransactionType.Tip:
       const walletTipId = rest[0];
       await processTip(ws, walletTipId, true);
       break;
@@ -483,8 +478,8 @@ export async function deleteTransaction(
         const reserveRecord:
           | ReserveRecord
           | undefined = await 
tx.reserves.indexes.byInitialWithdrawalGroupId.get(
-          withdrawalGroupId,
-        );
+            withdrawalGroupId,
+          );
         if (reserveRecord && !reserveRecord.initialWithdrawalStarted) {
           const reservePub = reserveRecord.reservePub;
           await tx.reserves.delete(reservePub);
diff --git a/packages/taler-wallet-core/src/versions.ts 
b/packages/taler-wallet-core/src/versions.ts
index b798871c..7383355b 100644
--- a/packages/taler-wallet-core/src/versions.ts
+++ b/packages/taler-wallet-core/src/versions.ts
@@ -19,7 +19,7 @@
  *
  * Uses libtool's current:revision:age versioning.
  */
-export const WALLET_EXCHANGE_PROTOCOL_VERSION = "9:0:0";
+export const WALLET_EXCHANGE_PROTOCOL_VERSION = "10:0:0";
 
 /**
  * Protocol version spoken with the merchant.
diff --git a/packages/taler-wallet-webextension/.storybook/preview.js 
b/packages/taler-wallet-webextension/.storybook/preview.js
index 0efb9630..25f9f46b 100644
--- a/packages/taler-wallet-webextension/.storybook/preview.js
+++ b/packages/taler-wallet-webextension/.storybook/preview.js
@@ -18,7 +18,7 @@ import { h, Fragment } from "preact"
 import { NavBar } from '../src/NavigationBar'
 import { LogoHeader } from '../src/components/LogoHeader'
 import { TranslationProvider } from '../src/context/translation'
-
+import { PopupBox, WalletBox } from '../src/components/styled'
 export const parameters = {
   controls: { expanded: true },
   actions: { argTypesRegex: "^on[A-Z].*" },
@@ -58,9 +58,9 @@ export const decorators = [
         // add a fake header so it looks similar
         return <Fragment>
           <NavBar path={path} devMode={path === '/dev'} />
-          <div style={{ width: 400, height: 290 }}>
+          <PopupBox>
             <Story />
-          </div>
+          </PopupBox>
         </Fragment>
       }
 
@@ -125,7 +125,7 @@ export const decorators = [
         <link key="1" rel="stylesheet" type="text/css" 
href="/static/style/pure.css" />
         <link key="2" rel="stylesheet" type="text/css" 
href="/static/style/wallet.css" />
         <Story />
-      </div>      
+      </div>
     }
     if (kind.startsWith('wallet')) {
       const path = /wallet(\/.*).*/.exec(kind)[1];
@@ -157,7 +157,9 @@ export const decorators = [
         </style>
         <LogoHeader />
         <NavBar path={path} devMode={path === '/dev'} />
-        <Story />
+        <WalletBox>
+          <Story />
+        </WalletBox>
       </div>
     }
     return <div>
diff --git a/packages/taler-wallet-webextension/src/components/BalanceTable.tsx 
b/packages/taler-wallet-webextension/src/components/BalanceTable.tsx
new file mode 100644
index 00000000..e1c19cc2
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/components/BalanceTable.tsx
@@ -0,0 +1,48 @@
+/*
+ This file is part of TALER
+ (C) 2016 GNUnet e.V.
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+import { amountFractionalBase, Amounts, Balance } from "@gnu-taler/taler-util";
+import { h, VNode } from "preact";
+import { TableWithRoundRows as TableWithRoundedRows } from "./styled/index";
+
+export function BalanceTable({ balances }: { balances: Balance[] }): VNode {
+  const currencyFormatter = new Intl.NumberFormat("en-US");
+  return (
+    <TableWithRoundedRows>
+      {balances.map((entry, idx) => {
+        const av = Amounts.parseOrThrow(entry.available);
+
+        const v = currencyFormatter.format(
+          av.value + av.fraction / amountFractionalBase,
+        );
+        return (
+          <tr key={idx}>
+            <td>{av.currency}</td>
+            <td
+              style={{
+                fontSize: "2em",
+                textAlign: "right",
+                width: "100%",
+              }}
+            >
+              {v}
+            </td>
+          </tr>
+        );
+      })}
+    </TableWithRoundedRows>
+  );
+}
diff --git 
a/packages/taler-wallet-webextension/src/components/BankDetailsByPaytoType.tsx 
b/packages/taler-wallet-webextension/src/components/BankDetailsByPaytoType.tsx
new file mode 100644
index 00000000..71365e08
--- /dev/null
+++ 
b/packages/taler-wallet-webextension/src/components/BankDetailsByPaytoType.tsx
@@ -0,0 +1,109 @@
+/*
+ This file is part of GNU Taler
+ (C) 2019 Taler Systems SA
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+import { PaytoUri } from "@gnu-taler/taler-util";
+import { Fragment, h, VNode } from "preact";
+import { useEffect, useState } from "preact/hooks";
+import { CopiedIcon, CopyIcon } from "../svg";
+import { ButtonBox, TooltipRight } from "./styled";
+
+export interface BankDetailsProps {
+  payto: PaytoUri | undefined;
+  exchangeBaseUrl: string;
+  subject: string;
+  amount: string;
+}
+
+export function BankDetailsByPaytoType({
+  payto,
+  subject,
+  exchangeBaseUrl,
+  amount,
+}: BankDetailsProps): VNode {
+  const firstPart = !payto ? undefined : !payto.isKnown ? (
+    <Row name="Account" value={payto.targetPath} />
+  ) : payto.targetType === "x-taler-bank" ? (
+    <Fragment>
+      <Row name="Bank host" value={payto.host} />
+      <Row name="Bank account" value={payto.account} />
+    </Fragment>
+  ) : payto.targetType === "iban" ? (
+    <Row name="IBAN" value={payto.iban} />
+  ) : undefined;
+  return (
+    <div style={{ textAlign: "left" }}>
+      <p>Bank transfer details</p>
+      <table>
+        {firstPart}
+        <Row name="Exchange" value={exchangeBaseUrl} />
+        <Row name="Chosen amount" value={amount} />
+        <Row name="Subject" value={subject} literal />
+      </table>
+    </div>
+  );
+}
+
+function Row({
+  name,
+  value,
+  literal,
+}: {
+  name: string;
+  value: string;
+  literal?: boolean;
+}): VNode {
+  const [copied, setCopied] = useState(false);
+  function copyText(): void {
+    navigator.clipboard.writeText(value);
+    setCopied(true);
+  }
+  useEffect(() => {
+    if (copied) {
+      setTimeout(() => {
+        setCopied(false);
+      }, 1000);
+    }
+  }, [copied]);
+  return (
+    <tr>
+      <td>
+        {!copied ? (
+          <ButtonBox onClick={copyText}>
+            <CopyIcon />
+          </ButtonBox>
+        ) : (
+          <TooltipRight content="Copied">
+            <ButtonBox disabled>
+              <CopiedIcon />
+            </ButtonBox>
+          </TooltipRight>
+        )}
+      </td>
+      <td>
+        <b>{name}</b>
+      </td>
+      {literal ? (
+        <td>
+          <pre style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }}>
+            {value}
+          </pre>
+        </td>
+      ) : (
+        <td>{value}</td>
+      )}
+    </tr>
+  );
+}
diff --git a/packages/taler-wallet-webextension/src/components/styled/index.tsx 
b/packages/taler-wallet-webextension/src/components/styled/index.tsx
index 2db7c61f..b2ca1380 100644
--- a/packages/taler-wallet-webextension/src/components/styled/index.tsx
+++ b/packages/taler-wallet-webextension/src/components/styled/index.tsx
@@ -15,6 +15,8 @@
  */
 
 // need to import linaria types, otherwise compiler will complain
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+// eslint-disable-next-line no-unused-vars
 import type * as Linaria from "@linaria/core";
 
 import { styled } from "@linaria/react";
@@ -78,9 +80,8 @@ export const WalletBox = styled.div<{ noPadding?: boolean }>`
     width: 400px;
   }
   & > section {
-    padding-left: ${({ noPadding }) => (noPadding ? "0px" : "8px")};
-    padding-right: ${({ noPadding }) => (noPadding ? "0px" : "8px")};
-    // this margin will send the section up when used with a header
+    padding: ${({ noPadding }) => (noPadding ? "0px" : "8px")};
+
     margin-bottom: auto;
     overflow: auto;
 
@@ -202,6 +203,152 @@ export const PopupBox = styled.div<{ noPadding?: boolean 
}>`
   }
 `;
 
+export const TableWithRoundRows = styled.table`
+  border-collapse: separate;
+  border-spacing: 0px 10px;
+  margin-top: -10px;
+
+  td {
+    border: solid 1px #000;
+    border-style: solid none;
+    padding: 10px;
+  }
+  td:first-child {
+    border-left-style: solid;
+    border-top-left-radius: 5px;
+    border-bottom-left-radius: 5px;
+  }
+  td:last-child {
+    border-right-style: solid;
+    border-bottom-right-radius: 5px;
+    border-top-right-radius: 5px;
+  }
+`;
+
+const Tooltip = styled.div<{ content: string }>`
+  display: block;
+  position: relative;
+
+  ::before {
+    position: absolute;
+    z-index: 1000001;
+    width: 0;
+    height: 0;
+    color: darkgray;
+    pointer-events: none;
+    content: "";
+    border: 6px solid transparent;
+
+    border-bottom-color: darkgray;
+  }
+
+  ::after {
+    position: absolute;
+    z-index: 1000001;
+    padding: 0.5em 0.75em;
+    font: normal normal 11px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI",
+      Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
+    -webkit-font-smoothing: subpixel-antialiased;
+    color: white;
+    text-align: center;
+    text-decoration: none;
+    text-shadow: none;
+    text-transform: none;
+    letter-spacing: normal;
+    word-wrap: break-word;
+    white-space: pre;
+    pointer-events: none;
+    content: attr(content);
+    background: darkgray;
+    border-radius: 6px;
+  }
+`;
+
+export const TooltipBottom = styled(Tooltip)`
+  ::before {
+    top: auto;
+    right: 50%;
+    bottom: -7px;
+    margin-right: -6px;
+  }
+  ::after {
+    top: 100%;
+    right: -50%;
+    margin-top: 6px;
+  }
+`;
+
+export const TooltipRight = styled(Tooltip)`
+  ::before {
+    top: 0px;
+    left: 16px;
+    transform: rotate(-90deg);
+  }
+  ::after {
+    top: -50%;
+    left: 28px;
+    margin-top: 6px;
+  }
+`;
+
+export const Overlay = styled.div`
+  position: fixed;
+  width: 100%;
+  height: 100%;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background-color: rgba(0, 0, 0, 0.5);
+  z-index: 2;
+  cursor: pointer;
+`;
+
+export const CenteredDialog = styled.div`
+  position: absolute;
+  text-align: left;
+
+  display: flex;
+  flex-direction: column;
+  justify-content: space-between;
+
+  top: 50%;
+  left: 50%;
+  /* font-size: 50px; */
+  color: black;
+  transform: translate(-50%, -50%);
+  -ms-transform: translate(-50%, -50%);
+  cursor: initial;
+  background-color: white;
+  border-radius: 10px;
+
+  max-height: 70%;
+
+  & > header {
+    border-top-right-radius: 6px;
+    border-top-left-radius: 6px;
+    padding: 10px;
+    background-color: #f5f5f5;
+    border-bottom: 1px solid #dbdbdb;
+    font-weight: bold;
+  }
+  & > section {
+    padding: 10px;
+    flex-grow: 1;
+    flex-shrink: 1;
+    overflow: auto;
+  }
+  & > footer {
+    border-top: 1px solid #dbdbdb;
+    border-bottom-right-radius: 6px;
+    border-bottom-left-radius: 6px;
+    padding: 10px;
+    background-color: #f5f5f5;
+    display: flex;
+    justify-content: space-between;
+  }
+`;
+
 export const Button = styled.button<{ upperCased?: boolean }>`
   display: inline-block;
   zoom: 1;
@@ -217,7 +364,7 @@ export const Button = styled.button<{ upperCased?: boolean 
}>`
   font-family: inherit;
   font-size: 100%;
   padding: 0.5em 1em;
-  color: #444; /* rgba not supported (IE 8) */
+  /* color: #444; rgba not supported (IE 8) */
   color: rgba(0, 0, 0, 0.8); /* rgba supported */
   border: 1px solid #999; /*IE 6/7/8*/
   border: none rgba(0, 0, 0, 0); /*IE9 + everything else*/
@@ -305,8 +452,7 @@ export const FontIcon = styled.div`
 `;
 export const ButtonBox = styled(Button)`
   padding: 0.5em;
-  width: fit-content;
-  height: 2em;
+  font-size: x-small;
 
   & > ${FontIcon} {
     width: 1em;
@@ -320,6 +466,8 @@ export const ButtonBox = styled(Button)`
   border-radius: 4px;
   border-color: black;
   color: black;
+  /* -webkit-border-horizontal-spacing: 0px;
+  -webkit-border-vertical-spacing: 0px; */
 `;
 
 const ButtonVariant = styled(Button)`
@@ -377,6 +525,7 @@ export const Centered = styled.div`
     margin-top: 15px;
   }
 `;
+
 export const Row = styled.div`
   display: flex;
   margin: 0.5em 0;
@@ -566,6 +715,12 @@ export const ErrorBox = styled.div`
   }
 `;
 
+export const InfoBox = styled(ErrorBox)`
+  color: black;
+  background-color: #d1e7dd;
+  border-color: #badbcc;
+`;
+
 export const SuccessBox = styled(ErrorBox)`
   color: #0f5132;
   background-color: #d1e7dd;
diff --git a/packages/taler-wallet-webextension/src/cta/Pay.tsx 
b/packages/taler-wallet-webextension/src/cta/Pay.tsx
index d5861c47..30b571f0 100644
--- a/packages/taler-wallet-webextension/src/cta/Pay.tsx
+++ b/packages/taler-wallet-webextension/src/cta/Pay.tsx
@@ -49,7 +49,7 @@ import {
   WalletAction,
   WarningBox,
 } from "../components/styled";
-import { useBalances } from "../hooks/useBalances";
+import { useAsyncAsHook } from "../hooks/useAsyncAsHook";
 import * as wxApi from "../wxApi";
 
 interface Props {
@@ -109,7 +109,7 @@ export function PayPage({ talerPayUri }: Props): VNode {
   );
   const [payErrMsg, setPayErrMsg] = useState<string | undefined>(undefined);
 
-  const balance = useBalances();
+  const balance = useAsyncAsHook(wxApi.getBalance);
   const balanceWithoutError = balance?.hasError
     ? []
     : balance?.response.balances || [];
diff --git a/packages/taler-wallet-webextension/src/hooks/useAsyncAsHook.ts 
b/packages/taler-wallet-webextension/src/hooks/useAsyncAsHook.ts
index aa6695c3..38ec5bf7 100644
--- a/packages/taler-wallet-webextension/src/hooks/useAsyncAsHook.ts
+++ b/packages/taler-wallet-webextension/src/hooks/useAsyncAsHook.ts
@@ -13,7 +13,7 @@
  You should have received a copy of the GNU General Public License along with
  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
-import { ExchangesListRespose } from "@gnu-taler/taler-util";
+import { ExchangesListRespose, NotificationType } from "@gnu-taler/taler-util";
 import { useEffect, useState } from "preact/hooks";
 import * as wxApi from "../wxApi";
 
@@ -29,7 +29,8 @@ interface HookError {
 
 export type HookResponse<T> = HookOk<T> | HookError | undefined;
 
-export function useAsyncAsHook<T>(fn: () => Promise<T>): HookResponse<T> {
+//"withdraw-group-finished"
+export function useAsyncAsHook<T>(fn: () => Promise<T>, updateOnNotification?: 
Array<NotificationType>): HookResponse<T> {
   const [result, setHookResponse] = useState<HookResponse<T>>(undefined);
   useEffect(() => {
     async function doAsync() {
@@ -43,6 +44,11 @@ export function useAsyncAsHook<T>(fn: () => Promise<T>): 
HookResponse<T> {
       }
     }
     doAsync();
+    if (updateOnNotification && updateOnNotification.length > 0) {
+      return wxApi.onUpdateNotification(updateOnNotification, () => {
+        doAsync()
+      });
+    }
   }, []);
   return result;
 }
diff --git a/packages/taler-wallet-webextension/src/hooks/useBalances.ts 
b/packages/taler-wallet-webextension/src/hooks/useBalances.ts
deleted file mode 100644
index 403ce7b8..00000000
--- a/packages/taler-wallet-webextension/src/hooks/useBalances.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- This file is part of TALER
- (C) 2016 GNUnet e.V.
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-
-import { BalancesResponse } from "@gnu-taler/taler-util";
-import { useEffect, useState } from "preact/hooks";
-import * as wxApi from "../wxApi";
-
-interface BalancesHookOk {
-  hasError: false;
-  response: BalancesResponse;
-}
-
-interface BalancesHookError {
-  hasError: true;
-  message: string;
-}
-
-export type BalancesHook = BalancesHookOk | BalancesHookError | undefined;
-
-export function useBalances(): BalancesHook {
-  const [balance, setBalance] = useState<BalancesHook>(undefined);
-  useEffect(() => {
-    async function checkBalance() {
-      try {
-        const response = await wxApi.getBalance();
-        console.log("got balance", balance);
-        setBalance({ hasError: false, response });
-      } catch (e) {
-        console.error("could not retrieve balances", e);
-        if (e instanceof Error) {
-          setBalance({ hasError: true, message: e.message });
-        }
-      }
-    }
-    checkBalance();
-    return wxApi.onUpdateNotification(checkBalance);
-  }, []);
-
-  return balance;
-}
diff --git a/packages/taler-wallet-webextension/src/popup/Backup.stories.tsx 
b/packages/taler-wallet-webextension/src/popup/Backup.stories.tsx
deleted file mode 100644
index 232b0da7..00000000
--- a/packages/taler-wallet-webextension/src/popup/Backup.stories.tsx
+++ /dev/null
@@ -1,198 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021 Taler Systems S.A.
-
- GNU Taler is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-import { ProviderPaymentType } from "@gnu-taler/taler-wallet-core";
-import { addDays } from "date-fns";
-import { BackupView as TestedComponent } from "./BackupPage";
-import { createExample } from "../test-utils";
-
-export default {
-  title: "popup/backup/list",
-  component: TestedComponent,
-  argTypes: {
-    onRetry: { action: "onRetry" },
-    onDelete: { action: "onDelete" },
-    onBack: { action: "onBack" },
-  },
-};
-
-export const LotOfProviders = createExample(TestedComponent, {
-  providers: [
-    {
-      active: true,
-      name: "sync.demo",
-      syncProviderBaseUrl: "http://sync.taler:9967/";,
-      lastSuccessfulBackupTimestamp: {
-        t_ms: 1625063925078,
-      },
-      paymentProposalIds: [
-        "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG",
-      ],
-      paymentStatus: {
-        type: ProviderPaymentType.Paid,
-        paidUntil: {
-          t_ms: 1656599921000,
-        },
-      },
-      terms: {
-        annualFee: "ARS:1",
-        storageLimitInMegabytes: 16,
-        supportedProtocolVersion: "0.0",
-      },
-    },
-    {
-      active: true,
-      name: "sync.demo",
-      syncProviderBaseUrl: "http://sync.taler:9967/";,
-      lastSuccessfulBackupTimestamp: {
-        t_ms: 1625063925078,
-      },
-      paymentProposalIds: [
-        "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG",
-      ],
-      paymentStatus: {
-        type: ProviderPaymentType.Paid,
-        paidUntil: {
-          t_ms: addDays(new Date(), 13).getTime(),
-        },
-      },
-      terms: {
-        annualFee: "ARS:1",
-        storageLimitInMegabytes: 16,
-        supportedProtocolVersion: "0.0",
-      },
-    },
-    {
-      active: false,
-      name: "sync.demo",
-      syncProviderBaseUrl: "http://sync.demo.taler.net/";,
-      paymentProposalIds: [],
-      paymentStatus: {
-        type: ProviderPaymentType.Pending,
-      },
-      terms: {
-        annualFee: "KUDOS:0.1",
-        storageLimitInMegabytes: 16,
-        supportedProtocolVersion: "0.0",
-      },
-    },
-    {
-      active: false,
-      name: "sync.demo",
-      syncProviderBaseUrl: "http://sync.demo.taler.net/";,
-      paymentProposalIds: [],
-      paymentStatus: {
-        type: ProviderPaymentType.InsufficientBalance,
-      },
-      terms: {
-        annualFee: "KUDOS:0.1",
-        storageLimitInMegabytes: 16,
-        supportedProtocolVersion: "0.0",
-      },
-    },
-    {
-      active: false,
-      name: "sync.demo",
-      syncProviderBaseUrl: "http://sync.demo.taler.net/";,
-      paymentProposalIds: [],
-      paymentStatus: {
-        type: ProviderPaymentType.TermsChanged,
-        newTerms: {
-          annualFee: "USD:2",
-          storageLimitInMegabytes: 8,
-          supportedProtocolVersion: "2",
-        },
-        oldTerms: {
-          annualFee: "USD:1",
-          storageLimitInMegabytes: 16,
-          supportedProtocolVersion: "1",
-        },
-        paidUntil: {
-          t_ms: "never",
-        },
-      },
-      terms: {
-        annualFee: "KUDOS:0.1",
-        storageLimitInMegabytes: 16,
-        supportedProtocolVersion: "0.0",
-      },
-    },
-    {
-      active: false,
-      name: "sync.demo",
-      syncProviderBaseUrl: "http://sync.demo.taler.net/";,
-      paymentProposalIds: [],
-      paymentStatus: {
-        type: ProviderPaymentType.Unpaid,
-      },
-      terms: {
-        annualFee: "KUDOS:0.1",
-        storageLimitInMegabytes: 16,
-        supportedProtocolVersion: "0.0",
-      },
-    },
-    {
-      active: false,
-      name: "sync.demo",
-      syncProviderBaseUrl: "http://sync.demo.taler.net/";,
-      paymentProposalIds: [],
-      paymentStatus: {
-        type: ProviderPaymentType.Unpaid,
-      },
-      terms: {
-        annualFee: "KUDOS:0.1",
-        storageLimitInMegabytes: 16,
-        supportedProtocolVersion: "0.0",
-      },
-    },
-  ],
-});
-
-export const OneProvider = createExample(TestedComponent, {
-  providers: [
-    {
-      active: true,
-      name: "sync.demo",
-      syncProviderBaseUrl: "http://sync.taler:9967/";,
-      lastSuccessfulBackupTimestamp: {
-        t_ms: 1625063925078,
-      },
-      paymentProposalIds: [
-        "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG",
-      ],
-      paymentStatus: {
-        type: ProviderPaymentType.Paid,
-        paidUntil: {
-          t_ms: 1656599921000,
-        },
-      },
-      terms: {
-        annualFee: "ARS:1",
-        storageLimitInMegabytes: 16,
-        supportedProtocolVersion: "0.0",
-      },
-    },
-  ],
-});
-
-export const Empty = createExample(TestedComponent, {
-  providers: [],
-});
diff --git a/packages/taler-wallet-webextension/src/popup/BackupPage.tsx 
b/packages/taler-wallet-webextension/src/popup/BackupPage.tsx
deleted file mode 100644
index ae93f8a4..00000000
--- a/packages/taler-wallet-webextension/src/popup/BackupPage.tsx
+++ /dev/null
@@ -1,197 +0,0 @@
-/*
- This file is part of TALER
- (C) 2016 GNUnet e.V.
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
-*/
-
-import { i18n, Timestamp } from "@gnu-taler/taler-util";
-import {
-  ProviderInfo,
-  ProviderPaymentStatus,
-} from "@gnu-taler/taler-wallet-core";
-import {
-  differenceInMonths,
-  formatDuration,
-  intervalToDuration,
-} from "date-fns";
-import { Fragment, h, VNode } from "preact";
-import {
-  BoldLight,
-  ButtonPrimary,
-  ButtonSuccess,
-  Centered,
-  CenteredBoldText,
-  CenteredText,
-  PopupBox,
-  RowBorderGray,
-  SmallLightText,
-  SmallText,
-} from "../components/styled";
-import { useBackupStatus } from "../hooks/useBackupStatus";
-import { Pages } from "../NavigationBar";
-
-interface Props {
-  onAddProvider: () => void;
-}
-
-export function BackupPage({ onAddProvider }: Props): VNode {
-  const status = useBackupStatus();
-  if (!status) {
-    return <div>Loading...</div>;
-  }
-  return (
-    <BackupView
-      providers={status.providers}
-      onAddProvider={onAddProvider}
-      onSyncAll={status.sync}
-    />
-  );
-}
-
-export interface ViewProps {
-  providers: ProviderInfo[];
-  onAddProvider: () => void;
-  onSyncAll: () => Promise<void>;
-}
-
-export function BackupView({
-  providers,
-  onAddProvider,
-  onSyncAll,
-}: ViewProps): VNode {
-  return (
-    <PopupBox>
-      <section>
-        {providers.map((provider, idx) => (
-          <BackupLayout
-            key={idx}
-            status={provider.paymentStatus}
-            timestamp={provider.lastSuccessfulBackupTimestamp}
-            id={provider.syncProviderBaseUrl}
-            active={provider.active}
-            title={provider.name}
-          />
-        ))}
-        {!providers.length && (
-          <Centered style={{ marginTop: 100 }}>
-            <BoldLight>No backup providers configured</BoldLight>
-            <ButtonSuccess onClick={onAddProvider}>
-              <i18n.Translate>Add provider</i18n.Translate>
-            </ButtonSuccess>
-          </Centered>
-        )}
-      </section>
-      {!!providers.length && (
-        <footer>
-          <div />
-          <div>
-            <ButtonPrimary onClick={onSyncAll}>
-              {providers.length > 1 ? (
-                <i18n.Translate>Sync all backups</i18n.Translate>
-              ) : (
-                <i18n.Translate>Sync now</i18n.Translate>
-              )}
-            </ButtonPrimary>
-            <ButtonSuccess onClick={onAddProvider}>Add provider</ButtonSuccess>
-          </div>
-        </footer>
-      )}
-    </PopupBox>
-  );
-}
-
-interface TransactionLayoutProps {
-  status: ProviderPaymentStatus;
-  timestamp?: Timestamp;
-  title: string;
-  id: string;
-  active: boolean;
-}
-
-function BackupLayout(props: TransactionLayoutProps): VNode {
-  const date = !props.timestamp ? undefined : new Date(props.timestamp.t_ms);
-  const dateStr = date?.toLocaleString([], {
-    dateStyle: "medium",
-    timeStyle: "short",
-  } as any);
-
-  return (
-    <RowBorderGray>
-      <div style={{ color: !props.active ? "grey" : undefined }}>
-        <a
-          href={Pages.provider_detail.replace(
-            ":pid",
-            encodeURIComponent(props.id),
-          )}
-        >
-          <span>{props.title}</span>
-        </a>
-
-        {dateStr && (
-          <SmallText style={{ marginTop: 5 }}>Last synced: 
{dateStr}</SmallText>
-        )}
-        {!dateStr && (
-          <SmallLightText style={{ marginTop: 5 }}>Not synced</SmallLightText>
-        )}
-      </div>
-      <div>
-        {props.status?.type === "paid" ? (
-          <ExpirationText until={props.status.paidUntil} />
-        ) : (
-          <div>{props.status.type}</div>
-        )}
-      </div>
-    </RowBorderGray>
-  );
-}
-
-function ExpirationText({ until }: { until: Timestamp }) {
-  return (
-    <Fragment>
-      <CenteredText> Expires in </CenteredText>
-      <CenteredBoldText {...{ color: colorByTimeToExpire(until) }}>
-        {" "}
-        {daysUntil(until)}{" "}
-      </CenteredBoldText>
-    </Fragment>
-  );
-}
-
-function colorByTimeToExpire(d: Timestamp) {
-  if (d.t_ms === "never") return "rgb(28, 184, 65)";
-  const months = differenceInMonths(d.t_ms, new Date());
-  return months > 1 ? "rgb(28, 184, 65)" : "rgb(223, 117, 20)";
-}
-
-function daysUntil(d: Timestamp) {
-  if (d.t_ms === "never") return undefined;
-  const duration = intervalToDuration({
-    start: d.t_ms,
-    end: new Date(),
-  });
-  const str = formatDuration(duration, {
-    delimiter: ", ",
-    format: [
-      duration?.years
-        ? "years"
-        : duration?.months
-        ? "months"
-        : duration?.days
-        ? "days"
-        : duration.hours
-        ? "hours"
-        : "minutes",
-    ],
-  });
-  return `${str}`;
-}
diff --git a/packages/taler-wallet-webextension/src/popup/Balance.stories.tsx 
b/packages/taler-wallet-webextension/src/popup/Balance.stories.tsx
index 80203f6d..a4988cf2 100644
--- a/packages/taler-wallet-webextension/src/popup/Balance.stories.tsx
+++ b/packages/taler-wallet-webextension/src/popup/Balance.stories.tsx
@@ -158,7 +158,7 @@ export const SomeCoinsInTreeCurrencies = 
createExample(TestedComponent, {
           requiresUserInput: false,
         },
         {
-          available: "COL:2000",
+          available: "TESTKUDOS:2000",
           hasPendingTransactions: false,
           pendingIncoming: "USD:0",
           pendingOutgoing: "USD:0",
diff --git a/packages/taler-wallet-webextension/src/popup/BalancePage.tsx 
b/packages/taler-wallet-webextension/src/popup/BalancePage.tsx
index a23c81cd..008f30cb 100644
--- a/packages/taler-wallet-webextension/src/popup/BalancePage.tsx
+++ b/packages/taler-wallet-webextension/src/popup/BalancePage.tsx
@@ -14,194 +14,77 @@
  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-import {
-  amountFractionalBase,
-  Amounts,
-  Balance,
-  i18n,
-} from "@gnu-taler/taler-util";
-import { h, VNode } from "preact";
-import {
-  ButtonPrimary,
-  ErrorBox,
-  Middle,
-  PopupBox,
-} from "../components/styled/index";
-import { BalancesHook, useBalances } from "../hooks/useBalances";
-import { PageLink, renderAmount } from "../renderHtml";
+import { BalancesResponse, i18n } from "@gnu-taler/taler-util";
+import { Fragment, h, VNode } from "preact";
+import { BalanceTable } from "../components/BalanceTable";
+import { ButtonPrimary, ErrorBox } from "../components/styled/index";
+import { HookResponse, useAsyncAsHook } from "../hooks/useAsyncAsHook";
+import { PageLink } from "../renderHtml";
+import * as wxApi from "../wxApi";
 
 export function BalancePage({
   goToWalletManualWithdraw,
 }: {
   goToWalletManualWithdraw: () => void;
 }): VNode {
-  const balance = useBalances();
+  const state = useAsyncAsHook(wxApi.getBalance);
   return (
     <BalanceView
-      balance={balance}
+      balance={state}
       Linker={PageLink}
       goToWalletManualWithdraw={goToWalletManualWithdraw}
     />
   );
 }
 export interface BalanceViewProps {
-  balance: BalancesHook;
+  balance: HookResponse<BalancesResponse>;
   Linker: typeof PageLink;
   goToWalletManualWithdraw: () => void;
 }
 
-function formatPending(entry: Balance): VNode {
-  let incoming: VNode | undefined;
-  let payment: VNode | undefined;
-
-  // const available = Amounts.parseOrThrow(entry.available);
-  const pendingIncoming = Amounts.parseOrThrow(entry.pendingIncoming);
-  const pendingOutgoing = Amounts.parseOrThrow(entry.pendingOutgoing);
-
-  if (!Amounts.isZero(pendingIncoming)) {
-    incoming = (
-      <span>
-        <i18n.Translate>
-          <span style={{ color: "darkgreen" }} title="incoming amount">
-            {"+"}
-            {renderAmount(entry.pendingIncoming)}
-          </span>{" "}
-        </i18n.Translate>
-      </span>
-    );
-  }
-  if (!Amounts.isZero(pendingOutgoing)) {
-    payment = (
-      <span>
-        <i18n.Translate>
-          <span style={{ color: "darkred" }} title="outgoing amount">
-            {"-"}
-            {renderAmount(entry.pendingOutgoing)}
-          </span>{" "}
-        </i18n.Translate>
-      </span>
-    );
-  }
-
-  const l = [incoming, payment].filter((x) => x !== undefined);
-  if (l.length === 0) {
-    return <span />;
-  }
-
-  if (l.length === 1) {
-    return <span>{l}</span>;
-  }
-  return (
-    <span>
-      {l[0]}, {l[1]}
-    </span>
-  );
-}
-
 export function BalanceView({
   balance,
   Linker,
   goToWalletManualWithdraw,
 }: BalanceViewProps): VNode {
-  function Content(): VNode {
-    if (!balance) {
-      return <span />;
-    }
+  if (!balance) {
+    return <div>Loading...</div>;
+  }
 
-    if (balance.hasError) {
-      return (
-        <section>
-          <ErrorBox>{balance.message}</ErrorBox>
-          <p>
-            Click <Linker pageName="welcome">here</Linker> for help and
-            diagnostics.
-          </p>
-        </section>
-      );
-    }
-    if (balance.response.balances.length === 0) {
-      return (
-        <section data-expanded>
-          <Middle>
-            <p>
-              <i18n.Translate>
-                You have no balance to show. Need some{" "}
-                <Linker pageName="/welcome">help</Linker> getting started?
-              </i18n.Translate>
-            </p>
-          </Middle>
-        </section>
-      );
-    }
+  if (balance.hasError) {
     return (
-      <section data-expanded data-centered>
-        <table style={{ width: "100%" }}>
-          {balance.response.balances.map((entry, idx) => {
-            const av = Amounts.parseOrThrow(entry.available);
-            // Create our number formatter.
-            let formatter;
-            try {
-              formatter = new Intl.NumberFormat("en-US", {
-                style: "currency",
-                currency: av.currency,
-                currencyDisplay: "symbol",
-                // These options are needed to round to whole numbers if 
that's what you want.
-                //minimumFractionDigits: 0, // (this suffices for whole 
numbers, but will print 2500.10 as $2,500.1)
-                //maximumFractionDigits: 0, // (causes 2500.99 to be printed 
as $2,501)
-              });
-            } catch {
-              formatter = new Intl.NumberFormat("en-US", {
-                // style: 'currency',
-                // currency: av.currency,
-                // These options are needed to round to whole numbers if 
that's what you want.
-                //minimumFractionDigits: 0, // (this suffices for whole 
numbers, but will print 2500.10 as $2,500.1)
-                //maximumFractionDigits: 0, // (causes 2500.99 to be printed 
as $2,501)
-              });
-            }
-
-            const v = formatter.format(
-              av.value + av.fraction / amountFractionalBase,
-            );
-            const fontSize =
-              v.length < 8 ? "3em" : v.length < 13 ? "2em" : "1em";
-            return (
-              <tr key={idx}>
-                <td
-                  style={{
-                    height: 50,
-                    fontSize,
-                    width: "60%",
-                    textAlign: "right",
-                    padding: 0,
-                  }}
-                >
-                  {v}
-                </td>
-                <td style={{ maxWidth: "2em", overflowX: "hidden" }}>
-                  {av.currency}
-                </td>
-                <td style={{ fontSize: "small", color: "gray" }}>
-                  {formatPending(entry)}
-                </td>
-              </tr>
-            );
-          })}
-        </table>
-      </section>
+      <Fragment>
+        <ErrorBox>{balance.message}</ErrorBox>
+        <p>
+          Click <Linker pageName="welcome">here</Linker> for help and
+          diagnostics.
+        </p>
+      </Fragment>
+    );
+  }
+  if (balance.response.balances.length === 0) {
+    return (
+      <Fragment>
+        <p>
+          <i18n.Translate>
+            You have no balance to show. Need some{" "}
+            <Linker pageName="/welcome">help</Linker> getting started?
+          </i18n.Translate>
+        </p>
+      </Fragment>
     );
   }
 
   return (
-    <PopupBox>
-      {/* <section> */}
-      <Content />
-      {/* </section> */}
-      <footer>
-        <div />
+    <Fragment>
+      <section>
+        <BalanceTable balances={balance.response.balances} />
+      </section>
+      <footer style={{ justifyContent: "space-around" }}>
         <ButtonPrimary onClick={goToWalletManualWithdraw}>
           Withdraw
         </ButtonPrimary>
       </footer>
-    </PopupBox>
+    </Fragment>
   );
 }
diff --git a/packages/taler-wallet-webextension/src/popup/Debug.tsx 
b/packages/taler-wallet-webextension/src/popup/Debug.tsx
index b0e8543f..8b5d4165 100644
--- a/packages/taler-wallet-webextension/src/popup/Debug.tsx
+++ b/packages/taler-wallet-webextension/src/popup/Debug.tsx
@@ -16,7 +16,7 @@
 
 import { h, VNode } from "preact";
 import { Diagnostics } from "../components/Diagnostics";
-import { useDiagnostics } from "../hooks/useDiagnostics.js";
+import { useDiagnostics } from "../hooks/useDiagnostics";
 import * as wxApi from "../wxApi";
 
 export function DeveloperPage(): VNode {
diff --git a/packages/taler-wallet-webextension/src/popup/History.stories.tsx 
b/packages/taler-wallet-webextension/src/popup/History.stories.tsx
index 95f4a547..43d39da8 100644
--- a/packages/taler-wallet-webextension/src/popup/History.stories.tsx
+++ b/packages/taler-wallet-webextension/src/popup/History.stories.tsx
@@ -55,6 +55,7 @@ const exampleData = {
     type: TransactionType.Withdrawal,
     exchangeBaseUrl: "http://exchange.demo.taler.net";,
     withdrawalDetails: {
+      reservePub: "A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG",
       confirmed: false,
       exchangePaytoUris: ["payto://x-taler-bank/bank/account"],
       type: WithdrawalType.ManualTransfer,
diff --git a/packages/taler-wallet-webextension/src/popup/History.tsx 
b/packages/taler-wallet-webextension/src/popup/History.tsx
index 2228271d..b23b4781 100644
--- a/packages/taler-wallet-webextension/src/popup/History.tsx
+++ b/packages/taler-wallet-webextension/src/popup/History.tsx
@@ -21,18 +21,18 @@ import {
   Transaction,
   TransactionsResponse,
 } from "@gnu-taler/taler-util";
-import { h, VNode } from "preact";
+import { Fragment, h, VNode } from "preact";
 import { useEffect, useState } from "preact/hooks";
 import { PopupBox } from "../components/styled";
 import { TransactionItem } from "../components/TransactionItem";
-import { useBalances } from "../hooks/useBalances";
+import { useAsyncAsHook } from "../hooks/useAsyncAsHook";
 import * as wxApi from "../wxApi";
 
 export function HistoryPage(): VNode {
   const [transactions, setTransactions] = useState<
     TransactionsResponse | undefined
   >(undefined);
-  const balance = useBalances();
+  const balance = useAsyncAsHook(wxApi.getBalance);
   const balanceWithoutError = balance?.hasError
     ? []
     : balance?.response.balances || [];
@@ -57,7 +57,7 @@ export function HistoryPage(): VNode {
   );
 }
 
-function amountToString(c: AmountString) {
+function amountToString(c: AmountString): string {
   const idx = c.indexOf(":");
   return `${c.substring(idx + 1)} ${c.substring(0, idx)}`;
 }
@@ -68,18 +68,18 @@ export function HistoryView({
 }: {
   list: Transaction[];
   balances: Balance[];
-}) {
+}): VNode {
   const multiCurrency = balances.length > 1;
   return (
-    <PopupBox noPadding>
+    <Fragment>
       {balances.length > 0 && (
         <header>
           {multiCurrency ? (
             <div class="title">
               Balance:{" "}
               <ul style={{ margin: 0 }}>
-                {balances.map((b) => (
-                  <li>{b.available}</li>
+                {balances.map((b, i) => (
+                  <li key={i}>{b.available}</li>
                 ))}
               </ul>
             </div>
@@ -113,8 +113,10 @@ export function HistoryView({
             rel="noopener noreferrer"
             style={{ color: "darkgreen", textDecoration: "none" }}
             href={
+              // eslint-disable-next-line no-undef
               chrome.extension
-                ? chrome.extension.getURL(`/static/wallet.html#/history`)
+                ? // eslint-disable-next-line no-undef
+                  chrome.extension.getURL(`/static/wallet.html#/history`)
                 : "#"
             }
           >
@@ -122,6 +124,6 @@ export function HistoryView({
           </a>
         )}
       </footer>
-    </PopupBox>
+    </Fragment>
   );
 }
diff --git 
a/packages/taler-wallet-webextension/src/popup/ProviderAddConfirmProvider.stories.tsx
 
b/packages/taler-wallet-webextension/src/popup/ProviderAddConfirmProvider.stories.tsx
deleted file mode 100644
index 0cff7f75..00000000
--- 
a/packages/taler-wallet-webextension/src/popup/ProviderAddConfirmProvider.stories.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021 Taler Systems S.A.
-
- GNU Taler is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-import { createExample } from "../test-utils";
-import { ConfirmProviderView as TestedComponent } from "./ProviderAddPage";
-
-export default {
-  title: "popup/backup/confirm",
-  component: TestedComponent,
-  argTypes: {
-    onRetry: { action: "onRetry" },
-    onDelete: { action: "onDelete" },
-    onBack: { action: "onBack" },
-  },
-};
-
-export const DemoService = createExample(TestedComponent, {
-  url: "https://sync.demo.taler.net/";,
-  provider: {
-    annual_fee: "KUDOS:0.1",
-    storage_limit_in_megabytes: 20,
-    supported_protocol_version: "1",
-  },
-});
-
-export const FreeService = createExample(TestedComponent, {
-  url: "https://sync.taler:9667/";,
-  provider: {
-    annual_fee: "ARS:0",
-    storage_limit_in_megabytes: 20,
-    supported_protocol_version: "1",
-  },
-});
diff --git a/packages/taler-wallet-webextension/src/popup/ProviderAddPage.tsx 
b/packages/taler-wallet-webextension/src/popup/ProviderAddPage.tsx
deleted file mode 100644
index 55686ee9..00000000
--- a/packages/taler-wallet-webextension/src/popup/ProviderAddPage.tsx
+++ /dev/null
@@ -1,244 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021 Taler Systems S.A.
-
- GNU Taler is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-
-import {
-  Amounts,
-  BackupBackupProviderTerms,
-  canonicalizeBaseUrl,
-  i18n,
-} from "@gnu-taler/taler-util";
-import { VNode, h } from "preact";
-import { useEffect, useState } from "preact/hooks";
-import { Checkbox } from "../components/Checkbox";
-import { ErrorMessage } from "../components/ErrorMessage";
-import {
-  Button,
-  ButtonPrimary,
-  Input,
-  LightText,
-  PopupBox,
-  SmallLightText,
-} from "../components/styled/index";
-import * as wxApi from "../wxApi";
-
-interface Props {
-  currency: string;
-  onBack: () => void;
-}
-
-function getJsonIfOk(r: Response) {
-  if (r.ok) {
-    return r.json();
-  } else {
-    if (r.status >= 400 && r.status < 500) {
-      throw new Error(`URL may not be right: (${r.status}) ${r.statusText}`);
-    } else {
-      throw new Error(
-        `Try another server: (${r.status}) ${
-          r.statusText || "internal server error"
-        }`,
-      );
-    }
-  }
-}
-
-export function ProviderAddPage({ onBack }: Props): VNode {
-  const [verifying, setVerifying] = useState<
-    | { url: string; name: string; provider: BackupBackupProviderTerms }
-    | undefined
-  >(undefined);
-
-  async function getProviderInfo(
-    url: string,
-  ): Promise<BackupBackupProviderTerms> {
-    return fetch(`${url}config`)
-      .catch((e) => {
-        throw new Error(`Network error`);
-      })
-      .then(getJsonIfOk);
-  }
-
-  if (!verifying) {
-    return (
-      <SetUrlView
-        onCancel={onBack}
-        onVerify={(url) => getProviderInfo(url)}
-        onConfirm={(url, name) =>
-          getProviderInfo(url)
-            .then((provider) => {
-              setVerifying({ url, name, provider });
-            })
-            .catch((e) => e.message)
-        }
-      />
-    );
-  }
-  return (
-    <ConfirmProviderView
-      provider={verifying.provider}
-      url={verifying.url}
-      onCancel={() => {
-        setVerifying(undefined);
-      }}
-      onConfirm={() => {
-        wxApi.addBackupProvider(verifying.url, verifying.name).then(onBack);
-      }}
-    />
-  );
-}
-
-export interface SetUrlViewProps {
-  initialValue?: string;
-  onCancel: () => void;
-  onVerify: (s: string) => Promise<BackupBackupProviderTerms | undefined>;
-  onConfirm: (url: string, name: string) => Promise<string | undefined>;
-  withError?: string;
-}
-
-export function SetUrlView({
-  initialValue,
-  onCancel,
-  onVerify,
-  onConfirm,
-  withError,
-}: SetUrlViewProps) {
-  const [value, setValue] = useState<string>(initialValue || "");
-  const [urlError, setUrlError] = useState(false);
-  const [name, setName] = useState<string | undefined>(undefined);
-  const [error, setError] = useState<string | undefined>(withError);
-  useEffect(() => {
-    try {
-      const url = canonicalizeBaseUrl(value);
-      onVerify(url)
-        .then((r) => {
-          setUrlError(false);
-          setName(new URL(url).hostname);
-        })
-        .catch(() => {
-          setUrlError(true);
-          setName(undefined);
-        });
-    } catch {
-      setUrlError(true);
-      setName(undefined);
-    }
-  }, [value]);
-  return (
-    <PopupBox>
-      <section>
-        <h1> Add backup provider</h1>
-        <ErrorMessage
-          title={error && "Could not get provider information"}
-          description={error}
-        />
-        <LightText> Backup providers may charge for their service</LightText>
-        <p>
-          <Input invalid={urlError}>
-            <label>URL</label>
-            <input
-              type="text"
-              placeholder="https://";
-              value={value}
-              onChange={(e) => setValue(e.currentTarget.value)}
-            />
-          </Input>
-          <Input>
-            <label>Name</label>
-            <input
-              type="text"
-              disabled={name === undefined}
-              value={name}
-              onChange={(e) => setName(e.currentTarget.value)}
-            />
-          </Input>
-        </p>
-      </section>
-      <footer>
-        <Button onClick={onCancel}>
-          <i18n.Translate> &lt; Back</i18n.Translate>
-        </Button>
-        <ButtonPrimary
-          disabled={!value && !urlError}
-          onClick={() => {
-            const url = canonicalizeBaseUrl(value);
-            return onConfirm(url, name!).then((r) =>
-              r ? setError(r) : undefined,
-            );
-          }}
-        >
-          <i18n.Translate>Next</i18n.Translate>
-        </ButtonPrimary>
-      </footer>
-    </PopupBox>
-  );
-}
-
-export interface ConfirmProviderViewProps {
-  provider: BackupBackupProviderTerms;
-  url: string;
-  onCancel: () => void;
-  onConfirm: () => void;
-}
-export function ConfirmProviderView({
-  url,
-  provider,
-  onCancel,
-  onConfirm,
-}: ConfirmProviderViewProps) {
-  const [accepted, setAccepted] = useState(false);
-
-  return (
-    <PopupBox>
-      <section>
-        <h1>Review terms of service</h1>
-        <div>
-          Provider URL:{" "}
-          <a href={url} target="_blank">
-            {url}
-          </a>
-        </div>
-        <SmallLightText>
-          Please review and accept this provider's terms of service
-        </SmallLightText>
-        <h2>1. Pricing</h2>
-        <p>
-          {Amounts.isZero(provider.annual_fee)
-            ? "free of charge"
-            : `${provider.annual_fee} per year of service`}
-        </p>
-        <h2>2. Storage</h2>
-        <p>
-          {provider.storage_limit_in_megabytes} megabytes of storage per year 
of
-          service
-        </p>
-        <Checkbox
-          label="Accept terms of service"
-          name="terms"
-          onToggle={() => setAccepted((old) => !old)}
-          enabled={accepted}
-        />
-      </section>
-      <footer>
-        <Button onClick={onCancel}>
-          <i18n.Translate> &lt; Back</i18n.Translate>
-        </Button>
-        <ButtonPrimary disabled={!accepted} onClick={onConfirm}>
-          <i18n.Translate>Add provider</i18n.Translate>
-        </ButtonPrimary>
-      </footer>
-    </PopupBox>
-  );
-}
diff --git 
a/packages/taler-wallet-webextension/src/popup/ProviderAddSetUrl.stories.tsx 
b/packages/taler-wallet-webextension/src/popup/ProviderAddSetUrl.stories.tsx
deleted file mode 100644
index 9a2f9705..00000000
--- a/packages/taler-wallet-webextension/src/popup/ProviderAddSetUrl.stories.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021 Taler Systems S.A.
-
- GNU Taler is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-import { createExample } from "../test-utils";
-import { SetUrlView as TestedComponent } from "./ProviderAddPage";
-
-export default {
-  title: "popup/backup/add",
-  component: TestedComponent,
-  argTypes: {
-    onRetry: { action: "onRetry" },
-    onDelete: { action: "onDelete" },
-    onBack: { action: "onBack" },
-  },
-};
-
-export const Initial = createExample(TestedComponent, {});
-
-export const WithValue = createExample(TestedComponent, {
-  initialValue: "sync.demo.taler.net",
-});
-
-export const WithConnectionError = createExample(TestedComponent, {
-  withError: "Network error",
-});
-
-export const WithClientError = createExample(TestedComponent, {
-  withError: "URL may not be right: (404) Not Found",
-});
-
-export const WithServerError = createExample(TestedComponent, {
-  withError: "Try another server: (500) Internal Server Error",
-});
diff --git 
a/packages/taler-wallet-webextension/src/popup/ProviderDetail.stories.tsx 
b/packages/taler-wallet-webextension/src/popup/ProviderDetail.stories.tsx
deleted file mode 100644
index fab21398..00000000
--- a/packages/taler-wallet-webextension/src/popup/ProviderDetail.stories.tsx
+++ /dev/null
@@ -1,235 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021 Taler Systems S.A.
-
- GNU Taler is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-import { ProviderPaymentType } from "@gnu-taler/taler-wallet-core";
-import { createExample } from "../test-utils";
-import { ProviderView as TestedComponent } from "./ProviderDetailPage";
-
-export default {
-  title: "popup/backup/details",
-  component: TestedComponent,
-  argTypes: {
-    onRetry: { action: "onRetry" },
-    onDelete: { action: "onDelete" },
-    onBack: { action: "onBack" },
-  },
-};
-
-export const Active = createExample(TestedComponent, {
-  info: {
-    active: true,
-    name: "sync.demo",
-    syncProviderBaseUrl: "http://sync.taler:9967/";,
-    lastSuccessfulBackupTimestamp: {
-      t_ms: 1625063925078,
-    },
-    paymentProposalIds: [
-      "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG",
-    ],
-    paymentStatus: {
-      type: ProviderPaymentType.Paid,
-      paidUntil: {
-        t_ms: 1656599921000,
-      },
-    },
-    terms: {
-      annualFee: "EUR:1",
-      storageLimitInMegabytes: 16,
-      supportedProtocolVersion: "0.0",
-    },
-  },
-});
-
-export const ActiveErrorSync = createExample(TestedComponent, {
-  info: {
-    active: true,
-    name: "sync.demo",
-    syncProviderBaseUrl: "http://sync.taler:9967/";,
-    lastSuccessfulBackupTimestamp: {
-      t_ms: 1625063925078,
-    },
-    lastAttemptedBackupTimestamp: {
-      t_ms: 1625063925078,
-    },
-    paymentProposalIds: [
-      "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG",
-    ],
-    paymentStatus: {
-      type: ProviderPaymentType.Paid,
-      paidUntil: {
-        t_ms: 1656599921000,
-      },
-    },
-    lastError: {
-      code: 2002,
-      details: "details",
-      hint: "error hint from the server",
-      message: "message",
-    },
-    terms: {
-      annualFee: "EUR:1",
-      storageLimitInMegabytes: 16,
-      supportedProtocolVersion: "0.0",
-    },
-  },
-});
-
-export const ActiveBackupProblemUnreadable = createExample(TestedComponent, {
-  info: {
-    active: true,
-    name: "sync.demo",
-    syncProviderBaseUrl: "http://sync.taler:9967/";,
-    lastSuccessfulBackupTimestamp: {
-      t_ms: 1625063925078,
-    },
-    paymentProposalIds: [
-      "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG",
-    ],
-    paymentStatus: {
-      type: ProviderPaymentType.Paid,
-      paidUntil: {
-        t_ms: 1656599921000,
-      },
-    },
-    backupProblem: {
-      type: "backup-unreadable",
-    },
-    terms: {
-      annualFee: "EUR:1",
-      storageLimitInMegabytes: 16,
-      supportedProtocolVersion: "0.0",
-    },
-  },
-});
-
-export const ActiveBackupProblemDevice = createExample(TestedComponent, {
-  info: {
-    active: true,
-    name: "sync.demo",
-    syncProviderBaseUrl: "http://sync.taler:9967/";,
-    lastSuccessfulBackupTimestamp: {
-      t_ms: 1625063925078,
-    },
-    paymentProposalIds: [
-      "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG",
-    ],
-    paymentStatus: {
-      type: ProviderPaymentType.Paid,
-      paidUntil: {
-        t_ms: 1656599921000,
-      },
-    },
-    backupProblem: {
-      type: "backup-conflicting-device",
-      myDeviceId: "my-device-id",
-      otherDeviceId: "other-device-id",
-      backupTimestamp: {
-        t_ms: 1656599921000,
-      },
-    },
-    terms: {
-      annualFee: "EUR:1",
-      storageLimitInMegabytes: 16,
-      supportedProtocolVersion: "0.0",
-    },
-  },
-});
-
-export const InactiveUnpaid = createExample(TestedComponent, {
-  info: {
-    active: false,
-    name: "sync.demo",
-    syncProviderBaseUrl: "http://sync.demo.taler.net/";,
-    paymentProposalIds: [],
-    paymentStatus: {
-      type: ProviderPaymentType.Unpaid,
-    },
-    terms: {
-      annualFee: "EUR:0.1",
-      storageLimitInMegabytes: 16,
-      supportedProtocolVersion: "0.0",
-    },
-  },
-});
-
-export const InactiveInsufficientBalance = createExample(TestedComponent, {
-  info: {
-    active: false,
-    name: "sync.demo",
-    syncProviderBaseUrl: "http://sync.demo.taler.net/";,
-    paymentProposalIds: [],
-    paymentStatus: {
-      type: ProviderPaymentType.InsufficientBalance,
-    },
-    terms: {
-      annualFee: "EUR:0.1",
-      storageLimitInMegabytes: 16,
-      supportedProtocolVersion: "0.0",
-    },
-  },
-});
-
-export const InactivePending = createExample(TestedComponent, {
-  info: {
-    active: false,
-    name: "sync.demo",
-    syncProviderBaseUrl: "http://sync.demo.taler.net/";,
-    paymentProposalIds: [],
-    paymentStatus: {
-      type: ProviderPaymentType.Pending,
-    },
-    terms: {
-      annualFee: "EUR:0.1",
-      storageLimitInMegabytes: 16,
-      supportedProtocolVersion: "0.0",
-    },
-  },
-});
-
-export const ActiveTermsChanged = createExample(TestedComponent, {
-  info: {
-    active: true,
-    name: "sync.demo",
-    syncProviderBaseUrl: "http://sync.demo.taler.net/";,
-    paymentProposalIds: [],
-    paymentStatus: {
-      type: ProviderPaymentType.TermsChanged,
-      paidUntil: {
-        t_ms: 1656599921000,
-      },
-      newTerms: {
-        annualFee: "EUR:10",
-        storageLimitInMegabytes: 8,
-        supportedProtocolVersion: "0.0",
-      },
-      oldTerms: {
-        annualFee: "EUR:0.1",
-        storageLimitInMegabytes: 16,
-        supportedProtocolVersion: "0.0",
-      },
-    },
-    terms: {
-      annualFee: "EUR:0.1",
-      storageLimitInMegabytes: 16,
-      supportedProtocolVersion: "0.0",
-    },
-  },
-});
diff --git 
a/packages/taler-wallet-webextension/src/popup/ProviderDetailPage.tsx 
b/packages/taler-wallet-webextension/src/popup/ProviderDetailPage.tsx
deleted file mode 100644
index 9617c9a4..00000000
--- a/packages/taler-wallet-webextension/src/popup/ProviderDetailPage.tsx
+++ /dev/null
@@ -1,278 +0,0 @@
-/*
- This file is part of TALER
- (C) 2016 GNUnet e.V.
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
-*/
-
-import { i18n, Timestamp } from "@gnu-taler/taler-util";
-import {
-  ProviderInfo,
-  ProviderPaymentStatus,
-  ProviderPaymentType,
-} from "@gnu-taler/taler-wallet-core";
-import { format, formatDuration, intervalToDuration } from "date-fns";
-import { Fragment, VNode, h } from "preact";
-import { ErrorMessage } from "../components/ErrorMessage";
-import {
-  Button,
-  ButtonDestructive,
-  ButtonPrimary,
-  PaymentStatus,
-  PopupBox,
-  SmallLightText,
-} from "../components/styled";
-import { useProviderStatus } from "../hooks/useProviderStatus";
-
-interface Props {
-  pid: string;
-  onBack: () => void;
-}
-
-export function ProviderDetailPage({ pid, onBack }: Props): VNode {
-  const status = useProviderStatus(pid);
-  if (!status) {
-    return (
-      <div>
-        <i18n.Translate>Loading...</i18n.Translate>
-      </div>
-    );
-  }
-  if (!status.info) {
-    onBack();
-    return <div />;
-  }
-  return (
-    <ProviderView
-      info={status.info}
-      onSync={status.sync}
-      onDelete={() => status.remove().then(onBack)}
-      onBack={onBack}
-      onExtend={() => {
-        null;
-      }}
-    />
-  );
-}
-
-export interface ViewProps {
-  info: ProviderInfo;
-  onDelete: () => void;
-  onSync: () => void;
-  onBack: () => void;
-  onExtend: () => void;
-}
-
-export function ProviderView({
-  info,
-  onDelete,
-  onSync,
-  onBack,
-  onExtend,
-}: ViewProps): VNode {
-  const lb = info?.lastSuccessfulBackupTimestamp;
-  const isPaid =
-    info.paymentStatus.type === ProviderPaymentType.Paid ||
-    info.paymentStatus.type === ProviderPaymentType.TermsChanged;
-  return (
-    <PopupBox>
-      <Error info={info} />
-      <header>
-        <h3>
-          {info.name}{" "}
-          <SmallLightText>{info.syncProviderBaseUrl}</SmallLightText>
-        </h3>
-        <PaymentStatus color={isPaid ? "rgb(28, 184, 65)" : "rgb(202, 60, 
60)"}>
-          {isPaid ? "Paid" : "Unpaid"}
-        </PaymentStatus>
-      </header>
-      <section>
-        <p>
-          <b>Last backup:</b>{" "}
-          {lb == null || lb.t_ms == "never"
-            ? "never"
-            : format(lb.t_ms, "dd MMM yyyy")}{" "}
-        </p>
-        <ButtonPrimary onClick={onSync}>
-          <i18n.Translate>Back up</i18n.Translate>
-        </ButtonPrimary>
-        {info.terms && (
-          <Fragment>
-            <p>
-              <b>Provider fee:</b> {info.terms && info.terms.annualFee} per 
year
-            </p>
-          </Fragment>
-        )}
-        <p>{descriptionByStatus(info.paymentStatus)}</p>
-        <ButtonPrimary disabled onClick={onExtend}>
-          <i18n.Translate>Extend</i18n.Translate>
-        </ButtonPrimary>
-
-        {info.paymentStatus.type === ProviderPaymentType.TermsChanged && (
-          <div>
-            <p>
-              <i18n.Translate>
-                terms has changed, extending the service will imply accepting
-                the new terms of service
-              </i18n.Translate>
-            </p>
-            <table>
-              <thead>
-                <tr>
-                  <td></td>
-                  <td>
-                    <i18n.Translate>old</i18n.Translate>
-                  </td>
-                  <td> -&gt;</td>
-                  <td>
-                    <i18n.Translate>new</i18n.Translate>
-                  </td>
-                </tr>
-              </thead>
-              <tbody>
-                <tr>
-                  <td>
-                    <i18n.Translate>fee</i18n.Translate>
-                  </td>
-                  <td>{info.paymentStatus.oldTerms.annualFee}</td>
-                  <td>-&gt;</td>
-                  <td>{info.paymentStatus.newTerms.annualFee}</td>
-                </tr>
-                <tr>
-                  <td>
-                    <i18n.Translate>storage</i18n.Translate>
-                  </td>
-                  
<td>{info.paymentStatus.oldTerms.storageLimitInMegabytes}</td>
-                  <td>-&gt;</td>
-                  
<td>{info.paymentStatus.newTerms.storageLimitInMegabytes}</td>
-                </tr>
-              </tbody>
-            </table>
-          </div>
-        )}
-      </section>
-      <footer>
-        <Button onClick={onBack}>
-          <i18n.Translate> &lt; back</i18n.Translate>
-        </Button>
-        <div>
-          <ButtonDestructive onClick={onDelete}>
-            <i18n.Translate>remove provider</i18n.Translate>
-          </ButtonDestructive>
-        </div>
-      </footer>
-    </PopupBox>
-  );
-}
-
-function daysSince(d?: Timestamp) {
-  if (!d || d.t_ms === "never") return "never synced";
-  const duration = intervalToDuration({
-    start: d.t_ms,
-    end: new Date(),
-  });
-  const str = formatDuration(duration, {
-    delimiter: ", ",
-    format: [
-      duration?.years
-        ? i18n.str`years`
-        : duration?.months
-        ? i18n.str`months`
-        : duration?.days
-        ? i18n.str`days`
-        : duration?.hours
-        ? i18n.str`hours`
-        : duration?.minutes
-        ? i18n.str`minutes`
-        : i18n.str`seconds`,
-    ],
-  });
-  return `synced ${str} ago`;
-}
-
-function Error({ info }: { info: ProviderInfo }) {
-  if (info.lastError) {
-    return <ErrorMessage title={info.lastError.hint} />;
-  }
-  if (info.backupProblem) {
-    switch (info.backupProblem.type) {
-      case "backup-conflicting-device":
-        return (
-          <ErrorMessage
-            title={
-              <Fragment>
-                <i18n.Translate>
-                  There is conflict with another backup from{" "}
-                  <b>{info.backupProblem.otherDeviceId}</b>
-                </i18n.Translate>
-              </Fragment>
-            }
-          />
-        );
-      case "backup-unreadable":
-        return <ErrorMessage title="Backup is not readable" />;
-      default:
-        return (
-          <ErrorMessage
-            title={
-              <Fragment>
-                <i18n.Translate>
-                  Unknown backup problem: {JSON.stringify(info.backupProblem)}
-                </i18n.Translate>
-              </Fragment>
-            }
-          />
-        );
-    }
-  }
-  return null;
-}
-
-function colorByStatus(status: ProviderPaymentType) {
-  switch (status) {
-    case ProviderPaymentType.InsufficientBalance:
-      return "rgb(223, 117, 20)";
-    case ProviderPaymentType.Unpaid:
-      return "rgb(202, 60, 60)";
-    case ProviderPaymentType.Paid:
-      return "rgb(28, 184, 65)";
-    case ProviderPaymentType.Pending:
-      return "gray";
-    case ProviderPaymentType.InsufficientBalance:
-      return "rgb(202, 60, 60)";
-    case ProviderPaymentType.TermsChanged:
-      return "rgb(202, 60, 60)";
-  }
-}
-
-function descriptionByStatus(status: ProviderPaymentStatus) {
-  switch (status.type) {
-    // return i18n.str`no enough balance to make the payment`
-    // return i18n.str`not paid yet`
-    case ProviderPaymentType.Paid:
-    case ProviderPaymentType.TermsChanged:
-      if (status.paidUntil.t_ms === "never") {
-        return i18n.str`service paid`;
-      } else {
-        return (
-          <Fragment>
-            <b>Backup valid until:</b>{" "}
-            {format(status.paidUntil.t_ms, "dd MMM yyyy")}
-          </Fragment>
-        );
-      }
-    case ProviderPaymentType.Unpaid:
-    case ProviderPaymentType.InsufficientBalance:
-    case ProviderPaymentType.Pending:
-      return "";
-  }
-}
diff --git a/packages/taler-wallet-webextension/src/popup/Settings.tsx 
b/packages/taler-wallet-webextension/src/popup/Settings.tsx
index 3b83f076..0a3f777d 100644
--- a/packages/taler-wallet-webextension/src/popup/Settings.tsx
+++ b/packages/taler-wallet-webextension/src/popup/Settings.tsx
@@ -14,26 +14,34 @@
  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
 
-import { i18n } from "@gnu-taler/taler-util";
-import { VNode, h } from "preact";
+import { ExchangeListItem, i18n } from "@gnu-taler/taler-util";
+import { Fragment, h, VNode } from "preact";
 import { Checkbox } from "../components/Checkbox";
-import { EditableText } from "../components/EditableText";
-import { SelectList } from "../components/SelectList";
-import { PopupBox } from "../components/styled";
+import { ButtonPrimary } from "../components/styled";
 import { useDevContext } from "../context/devContext";
+import { useAsyncAsHook } from "../hooks/useAsyncAsHook";
 import { useBackupDeviceName } from "../hooks/useBackupDeviceName";
 import { useExtendedPermissions } from "../hooks/useExtendedPermissions";
 import { useLang } from "../hooks/useLang";
+// import { strings as messages } from "../i18n/strings";
+import * as wxApi from "../wxApi";
 
 export function SettingsPage(): VNode {
   const [permissionsEnabled, togglePermissions] = useExtendedPermissions();
   const { devMode, toggleDevMode } = useDevContext();
   const { name, update } = useBackupDeviceName();
   const [lang, changeLang] = useLang();
+  const exchangesHook = useAsyncAsHook(wxApi.listExchanges);
+
   return (
     <SettingsView
       lang={lang}
       changeLang={changeLang}
+      knownExchanges={
+        !exchangesHook || exchangesHook.hasError
+          ? []
+          : exchangesHook.response.exchanges
+      }
       deviceName={name}
       setDeviceName={update}
       permissionsEnabled={permissionsEnabled}
@@ -53,36 +61,59 @@ export interface ViewProps {
   togglePermissions: () => void;
   developerMode: boolean;
   toggleDeveloperMode: () => void;
+  knownExchanges: Array<ExchangeListItem>;
 }
 
-import { strings as messages } from "../i18n/strings";
-
-type LangsNames = {
-  [P in keyof typeof messages]: string;
-};
+// type LangsNames = {
+//   [P in keyof typeof messages]: string;
+// };
 
-const names: LangsNames = {
-  es: "Español [es]",
-  en: "English [en]",
-  fr: "Français [fr]",
-  de: "Deutsch [de]",
-  sv: "Svenska [sv]",
-  it: "Italiano [it]",
-};
+// const names: LangsNames = {
+//   es: "Español [es]",
+//   en: "English [en]",
+//   fr: "Français [fr]",
+//   de: "Deutsch [de]",
+//   sv: "Svenska [sv]",
+//   it: "Italiano [it]",
+// };
 
 export function SettingsView({
-  lang,
-  changeLang,
-  deviceName,
-  setDeviceName,
+  knownExchanges,
+  // lang,
+  // changeLang,
+  // deviceName,
+  // setDeviceName,
   permissionsEnabled,
   togglePermissions,
   developerMode,
   toggleDeveloperMode,
 }: ViewProps): VNode {
   return (
-    <PopupBox>
+    <Fragment>
       <section>
+        <h2>
+          <i18n.Translate>Known exchanges</i18n.Translate>
+        </h2>
+        {!knownExchanges || !knownExchanges.length ? (
+          <div>No exchange yet!</div>
+        ) : (
+          <Fragment>
+            <table>
+              {knownExchanges.map((e, idx) => (
+                <tr key={idx}>
+                  <td>{e.currency}</td>
+                  <td>
+                    <a href={e.exchangeBaseUrl}>{e.exchangeBaseUrl}</a>
+                  </td>
+                </tr>
+              ))}
+            </table>
+          </Fragment>
+        )}
+        <div style={{ display: "flex", justifyContent: "space-between" }}>
+          <div />
+          <ButtonPrimary>Manage exchange</ButtonPrimary>
+        </div>
         {/* <h2><i18n.Translate>Wallet</i18n.Translate></h2> */}
         {/* <SelectList
           value={lang}
@@ -124,14 +155,16 @@ export function SettingsView({
           rel="noopener noreferrer"
           style={{ color: "darkgreen", textDecoration: "none" }}
           href={
+            // eslint-disable-next-line no-undef
             chrome.extension
-              ? chrome.extension.getURL(`/static/wallet.html#/settings`)
+              ? // eslint-disable-next-line no-undef
+                chrome.extension.getURL(`/static/wallet.html#/settings`)
               : "#"
           }
         >
           VIEW MORE SETTINGS
         </a>
       </footer>
-    </PopupBox>
+    </Fragment>
   );
 }
diff --git a/packages/taler-wallet-webextension/src/popup/TalerActionFound.tsx 
b/packages/taler-wallet-webextension/src/popup/TalerActionFound.tsx
index cbdcbeb1..b2220e37 100644
--- a/packages/taler-wallet-webextension/src/popup/TalerActionFound.tsx
+++ b/packages/taler-wallet-webextension/src/popup/TalerActionFound.tsx
@@ -20,12 +20,8 @@
  */
 
 import { classifyTalerUri, TalerUriType } from "@gnu-taler/taler-util";
-import {
-  ButtonPrimary,
-  ButtonSuccess,
-  PopupBox,
-} from "../components/styled/index";
-import { h } from "preact";
+import { Fragment, h } from "preact";
+import { ButtonPrimary, ButtonSuccess } from "../components/styled/index";
 
 export interface Props {
   url: string;
@@ -35,7 +31,7 @@ export interface Props {
 export function TalerActionFound({ url, onDismiss }: Props) {
   const uriType = classifyTalerUri(url);
   return (
-    <PopupBox>
+    <Fragment>
       <section>
         <h1>Taler Action </h1>
         {uriType === TalerUriType.TalerPay && (
@@ -109,7 +105,7 @@ export function TalerActionFound({ url, onDismiss }: Props) 
{
         <div />
         <ButtonPrimary onClick={() => onDismiss()}> Dismiss </ButtonPrimary>
       </footer>
-    </PopupBox>
+    </Fragment>
   );
 }
 
diff --git a/packages/taler-wallet-webextension/src/popupEntryPoint.tsx 
b/packages/taler-wallet-webextension/src/popupEntryPoint.tsx
index a5723ccb..d0c79f6d 100644
--- a/packages/taler-wallet-webextension/src/popupEntryPoint.tsx
+++ b/packages/taler-wallet-webextension/src/popupEntryPoint.tsx
@@ -25,16 +25,17 @@ import { createHashHistory } from "history";
 import { render, h } from "preact";
 import Router, { route, Route } from "preact-router";
 import { useEffect } from "preact/hooks";
+import { PopupBox } from "./components/styled";
 import { DevContextProvider } from "./context/devContext";
 import { useTalerActionURL } from "./hooks/useTalerActionURL";
 import { strings } from "./i18n/strings";
 import { Pages, WalletNavBar } from "./NavigationBar";
-import { BackupPage } from "./popup/BackupPage";
+import { BackupPage } from "./wallet/BackupPage";
 import { BalancePage } from "./popup/BalancePage";
 import { DeveloperPage } from "./popup/Debug";
 import { HistoryPage } from "./popup/History";
-import { ProviderAddPage } from "./popup/ProviderAddPage";
-import { ProviderDetailPage } from "./popup/ProviderDetailPage";
+import { ProviderAddPage } from "./wallet/ProviderAddPage";
+import { ProviderDetailPage } from "./wallet/ProviderDetailPage";
 import { SettingsPage } from "./popup/Settings";
 import { TalerActionFound } from "./popup/TalerActionFound";
 
@@ -72,7 +73,7 @@ function Application() {
     <div>
       <DevContextProvider>
         <WalletNavBar />
-        <div style={{ width: 400, height: 290 }}>
+        <PopupBox>
           <Router history={createHashHistory()}>
             <Route path={Pages.dev} component={DeveloperPage} />
 
@@ -128,15 +129,17 @@ function Application() {
             />
             <Route default component={Redirect} to={Pages.balance} />
           </Router>
-        </div>
+        </PopupBox>
       </DevContextProvider>
     </div>
   );
 }
 
 function goToWalletPage(page: Pages | string): null {
+  // eslint-disable-next-line no-undef
   chrome.tabs.create({
     active: true,
+    // eslint-disable-next-line no-undef
     url: chrome.extension.getURL(`/static/wallet.html#${page}`),
   });
   return null;
diff --git a/packages/taler-wallet-webextension/src/svg/index.tsx 
b/packages/taler-wallet-webextension/src/svg/index.tsx
new file mode 100644
index 00000000..34ff7767
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/svg/index.tsx
@@ -0,0 +1,40 @@
+import { h, VNode } from "preact";
+
+export const CopyIcon = (): VNode => (
+  <svg
+    aria-hidden="true"
+    height="10"
+    viewBox="0 0 16 16"
+    version="1.1"
+    width="10"
+    data-view-component="true"
+    class="octicon octicon-copy"
+    style="display: inline-block;"
+  >
+    <path
+      fill-rule="evenodd"
+      d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 010 1.5h-1.5a.25.25 0 
00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-1.5a.75.75 0 011.5 
0v1.5A1.75 1.75 0 019.25 16h-7.5A1.75 1.75 0 010 14.25v-7.5z"
+    />
+    <path
+      fill-rule="evenodd"
+      d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 
1.75 0 0114.25 11h-7.5A1.75 1.75 0 015 9.25v-7.5zm1.75-.25a.25.25 0 
00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-7.5a.25.25 0 
00-.25-.25h-7.5z"
+    />
+  </svg>
+);
+
+export const CopiedIcon = (): VNode => (
+  <svg
+    aria-hidden="true"
+    height="8"
+    viewBox="0 0 16 16"
+    version="1.1"
+    width="8"
+    data-view-component="true"
+    class="octicon octicon-check color-fg-success"
+  >
+    <path
+      fill-rule="evenodd"
+      d="M13.78 4.22a.75.75 0 010 1.06l-7.25 7.25a.75.75 0 01-1.06 0L2.22 
9.28a.75.75 0 011.06-1.06L6 10.94l6.72-6.72a.75.75 0 011.06 0z"
+    />
+  </svg>
+);
diff --git a/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx 
b/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx
index f0ae38e0..0b0af25a 100644
--- a/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx
@@ -35,7 +35,6 @@ import {
   RowBorderGray,
   SmallLightText,
   SmallText,
-  WalletBox,
 } from "../components/styled";
 import { useBackupStatus } from "../hooks/useBackupStatus";
 import { Pages } from "../NavigationBar";
@@ -70,7 +69,7 @@ export function BackupView({
   onSyncAll,
 }: ViewProps): VNode {
   return (
-    <WalletBox>
+    <Fragment>
       <section>
         {providers.map((provider, idx) => (
           <BackupLayout
@@ -106,7 +105,7 @@ export function BackupView({
           </div>
         </footer>
       )}
-    </WalletBox>
+    </Fragment>
   );
 }
 
@@ -155,7 +154,7 @@ function BackupLayout(props: TransactionLayoutProps): VNode 
{
   );
 }
 
-function ExpirationText({ until }: { until: Timestamp }) {
+function ExpirationText({ until }: { until: Timestamp }): VNode {
   return (
     <Fragment>
       <CenteredText> Expires in </CenteredText>
@@ -167,14 +166,14 @@ function ExpirationText({ until }: { until: Timestamp }) {
   );
 }
 
-function colorByTimeToExpire(d: Timestamp) {
+function colorByTimeToExpire(d: Timestamp): string {
   if (d.t_ms === "never") return "rgb(28, 184, 65)";
   const months = differenceInMonths(d.t_ms, new Date());
   return months > 1 ? "rgb(28, 184, 65)" : "rgb(223, 117, 20)";
 }
 
-function daysUntil(d: Timestamp) {
-  if (d.t_ms === "never") return undefined;
+function daysUntil(d: Timestamp): string {
+  if (d.t_ms === "never") return "";
   const duration = intervalToDuration({
     start: d.t_ms,
     end: new Date(),
diff --git a/packages/taler-wallet-webextension/src/wallet/BalancePage.tsx 
b/packages/taler-wallet-webextension/src/wallet/BalancePage.tsx
index 9a284767..04d79a5e 100644
--- a/packages/taler-wallet-webextension/src/wallet/BalancePage.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/BalancePage.tsx
@@ -14,27 +14,23 @@
  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-import {
-  amountFractionalBase,
-  Amounts,
-  Balance,
-  BalancesResponse,
-  i18n,
-} from "@gnu-taler/taler-util";
-import { h, VNode } from "preact";
-import { ButtonPrimary, Centered, WalletBox } from 
"../components/styled/index";
-import { BalancesHook, useBalances } from "../hooks/useBalances";
-import { PageLink, renderAmount } from "../renderHtml";
+import { BalancesResponse, i18n } from "@gnu-taler/taler-util";
+import { Fragment, h, VNode } from "preact";
+import { BalanceTable } from "../components/BalanceTable";
+import { ButtonPrimary, ErrorBox } from "../components/styled/index";
+import { HookResponse, useAsyncAsHook } from "../hooks/useAsyncAsHook";
+import { PageLink } from "../renderHtml";
+import * as wxApi from "../wxApi";
 
 export function BalancePage({
   goToWalletManualWithdraw,
 }: {
   goToWalletManualWithdraw: () => void;
 }): VNode {
-  const balance = useBalances();
+  const state = useAsyncAsHook(wxApi.getBalance);
   return (
     <BalanceView
-      balance={balance}
+      balance={state}
       Linker={PageLink}
       goToWalletManualWithdraw={goToWalletManualWithdraw}
     />
@@ -42,7 +38,7 @@ export function BalancePage({
 }
 
 export interface BalanceViewProps {
-  balance: BalancesHook;
+  balance: HookResponse<BalancesResponse>;
   Linker: typeof PageLink;
   goToWalletManualWithdraw: () => void;
 }
@@ -53,18 +49,18 @@ export function BalanceView({
   goToWalletManualWithdraw,
 }: BalanceViewProps): VNode {
   if (!balance) {
-    return <span />;
+    return <div>Loading...</div>;
   }
 
   if (balance.hasError) {
     return (
-      <div>
-        <p>{i18n.str`Error: could not retrieve balance information.`}</p>
+      <Fragment>
+        <ErrorBox>{balance.message}</ErrorBox>
         <p>
           Click <Linker pageName="welcome">here</Linker> for help and
           diagnostics.
         </p>
-      </div>
+      </Fragment>
     );
   }
   if (balance.response.balances.length === 0) {
@@ -77,81 +73,17 @@ export function BalanceView({
       </p>
     );
   }
-  return (
-    <ShowBalances
-      wallet={balance.response}
-      onWithdraw={goToWalletManualWithdraw}
-    />
-  );
-}
-
-function formatPending(entry: Balance): VNode {
-  let incoming: VNode | undefined;
-  let payment: VNode | undefined;
-
-  // const available = Amounts.parseOrThrow(entry.available);
-  const pendingIncoming = Amounts.parseOrThrow(entry.pendingIncoming);
-  // const pendingOutgoing = Amounts.parseOrThrow(entry.pendingOutgoing);
-
-  if (!Amounts.isZero(pendingIncoming)) {
-    incoming = (
-      <span>
-        <i18n.Translate>
-          <span style={{ color: "darkgreen" }}>
-            {"+"}
-            {renderAmount(entry.pendingIncoming)}
-          </span>{" "}
-          incoming
-        </i18n.Translate>
-      </span>
-    );
-  }
-
-  const l = [incoming, payment].filter((x) => x !== undefined);
-  if (l.length === 0) {
-    return <span />;
-  }
-
-  if (l.length === 1) {
-    return <span>({l})</span>;
-  }
-  return (
-    <span>
-      ({l[0]}, {l[1]})
-    </span>
-  );
-}
 
-function ShowBalances({
-  wallet,
-  onWithdraw,
-}: {
-  wallet: BalancesResponse;
-  onWithdraw: () => void;
-}): VNode {
   return (
-    <WalletBox>
+    <Fragment>
       <section>
-        <Centered>
-          {wallet.balances.map((entry) => {
-            const av = Amounts.parseOrThrow(entry.available);
-            const v = av.value + av.fraction / amountFractionalBase;
-            return (
-              <p key={av.currency}>
-                <span>
-                  <span style={{ fontSize: "5em", display: "block" 
}}>{v}</span>{" "}
-                  <span>{av.currency}</span>
-                </span>
-                {formatPending(entry)}
-              </p>
-            );
-          })}
-        </Centered>
+        <BalanceTable balances={balance.response.balances} />
       </section>
-      <footer>
-        <div />
-        <ButtonPrimary onClick={onWithdraw}>Withdraw</ButtonPrimary>
+      <footer style={{ justifyContent: "space-around" }}>
+        <ButtonPrimary onClick={goToWalletManualWithdraw}>
+          Withdraw
+        </ButtonPrimary>
       </footer>
-    </WalletBox>
+    </Fragment>
   );
 }
diff --git 
a/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.stories.tsx
 
b/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.stories.tsx
index 300e9cd5..e4955e37 100644
--- 
a/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.stories.tsx
+++ 
b/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.stories.tsx
@@ -34,6 +34,10 @@ const exchangeList = {
   "http://exchange.tal": "EUR",
 };
 
+export const WithoutAnyExchangeKnown = createExample(TestedComponent, {
+  exchangeList: {},
+});
+
 export const InitialState = createExample(TestedComponent, {
   exchangeList,
 });
diff --git 
a/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx 
b/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx
index 140ac2d4..1bceabd2 100644
--- a/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx
@@ -19,17 +19,19 @@
  * @author Sebastian Javier Marchano (sebasjm)
  */
 
-import { AmountJson, Amounts } from "@gnu-taler/taler-util";
-import { h, VNode } from "preact";
+import { AmountJson, Amounts, i18n } from "@gnu-taler/taler-util";
+import { Fragment, h, VNode } from "preact";
 import { useState } from "preact/hooks";
 import { ErrorMessage } from "../components/ErrorMessage";
 import { SelectList } from "../components/SelectList";
 import {
+  BoldLight,
   ButtonPrimary,
+  ButtonSuccess,
+  Centered,
   Input,
   InputWithLabel,
   LightText,
-  WalletBox,
 } from "../components/styled";
 
 export interface Props {
@@ -82,11 +84,23 @@ export function CreateManualWithdraw({
   }
 
   if (!initialExchange) {
-    return <div>There is no known exchange where to withdraw, add one</div>;
+    return (
+      <Centered style={{ marginTop: 100 }}>
+        <BoldLight>No exchange configured</BoldLight>
+        <ButtonSuccess
+          //FIXME: add exchange feature
+          onClick={() => {
+            null;
+          }}
+        >
+          <i18n.Translate>Add exchange</i18n.Translate>
+        </ButtonSuccess>
+      </Centered>
+    );
   }
 
   return (
-    <WalletBox>
+    <Fragment>
       <section>
         <ErrorMessage
           title={error && "Can't create the reserve"}
@@ -145,6 +159,6 @@ export function CreateManualWithdraw({
           Start withdrawal
         </ButtonPrimary>
       </footer>
-    </WalletBox>
+    </Fragment>
   );
 }
diff --git a/packages/taler-wallet-webextension/src/wallet/History.stories.tsx 
b/packages/taler-wallet-webextension/src/wallet/History.stories.tsx
index 9ae3ac3b..0f471ac3 100644
--- a/packages/taler-wallet-webextension/src/wallet/History.stories.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/History.stories.tsx
@@ -57,6 +57,7 @@ const exampleData = {
     type: TransactionType.Withdrawal,
     exchangeBaseUrl: "http://exchange.demo.taler.net";,
     withdrawalDetails: {
+      reservePub: "A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG",
       confirmed: false,
       exchangePaytoUris: ["payto://x-taler-bank/bank/account"],
       type: WithdrawalType.ManualTransfer,
diff --git a/packages/taler-wallet-webextension/src/wallet/History.tsx 
b/packages/taler-wallet-webextension/src/wallet/History.tsx
index 6b1a2185..bc8ef734 100644
--- a/packages/taler-wallet-webextension/src/wallet/History.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/History.tsx
@@ -17,42 +17,37 @@
 import {
   AmountString,
   Balance,
+  NotificationType,
   Transaction,
-  TransactionsResponse,
 } from "@gnu-taler/taler-util";
 import { Fragment, h, VNode } from "preact";
-import { useEffect, useState } from "preact/hooks";
-import { DateSeparator, WalletBox } from "../components/styled";
+import { DateSeparator } from "../components/styled";
 import { Time } from "../components/Time";
 import { TransactionItem } from "../components/TransactionItem";
-import { useBalances } from "../hooks/useBalances";
+import { useAsyncAsHook } from "../hooks/useAsyncAsHook";
 import * as wxApi from "../wxApi";
 
 export function HistoryPage(): VNode {
-  const [transactions, setTransactions] = useState<
-    TransactionsResponse | undefined
-  >(undefined);
-  const balance = useBalances();
+  const balance = useAsyncAsHook(wxApi.getBalance);
   const balanceWithoutError = balance?.hasError
     ? []
     : balance?.response.balances || [];
 
-  useEffect(() => {
-    const fetchData = async (): Promise<void> => {
-      const res = await wxApi.getTransactions();
-      setTransactions(res);
-    };
-    fetchData();
-  }, []);
+  const transactionQuery = useAsyncAsHook(wxApi.getTransactions, [
+    NotificationType.WithdrawGroupFinished,
+  ]);
 
-  if (!transactions) {
+  if (!transactionQuery) {
     return <div>Loading ...</div>;
   }
+  if (transactionQuery.hasError) {
+    return <div>There was an error loading the transactions.</div>;
+  }
 
   return (
     <HistoryView
       balances={balanceWithoutError}
-      list={[...transactions.transactions].reverse()}
+      list={[...transactionQuery.response.transactions].reverse()}
     />
   );
 }
@@ -87,7 +82,7 @@ export function HistoryView({
   const multiCurrency = balances.length > 1;
 
   return (
-    <WalletBox noPadding>
+    <Fragment>
       {balances.length > 0 && (
         <header>
           {balances.length === 1 && (
@@ -128,6 +123,6 @@ export function HistoryView({
           );
         })}
       </section>
-    </WalletBox>
+    </Fragment>
   );
 }
diff --git 
a/packages/taler-wallet-webextension/src/wallet/ManualWithdrawPage.tsx 
b/packages/taler-wallet-webextension/src/wallet/ManualWithdrawPage.tsx
index 1af4e8d8..88d5f172 100644
--- a/packages/taler-wallet-webextension/src/wallet/ManualWithdrawPage.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/ManualWithdrawPage.tsx
@@ -23,9 +23,9 @@ import {
   AmountJson,
   Amounts,
 } from "@gnu-taler/taler-util";
-import { ReserveCreated } from "./ReserveCreated.js";
+import { ReserveCreated } from "./ReserveCreated";
 import { route } from "preact-router";
-import { Pages } from "../NavigationBar.js";
+import { Pages } from "../NavigationBar";
 import { useAsyncAsHook } from "../hooks/useAsyncAsHook";
 
 export function ManualWithdrawPage(): VNode {
@@ -39,7 +39,7 @@ export function ManualWithdrawPage(): VNode {
   >(undefined);
   const [error, setError] = useState<string | undefined>(undefined);
 
-  const knownExchangesHook = useAsyncAsHook(() => wxApi.listExchanges());
+  const state = useAsyncAsHook(() => wxApi.listExchanges());
 
   async function doCreate(
     exchangeBaseUrl: string,
@@ -75,10 +75,13 @@ export function ManualWithdrawPage(): VNode {
     );
   }
 
-  if (!knownExchangesHook || knownExchangesHook.hasError) {
-    return <div>No Known exchanges</div>;
+  if (!state) {
+    return <div>loading...</div>;
   }
-  const exchangeList = knownExchangesHook.response.exchanges.reduce(
+  if (state.hasError) {
+    return <div>There was an error getting the known exchanges</div>;
+  }
+  const exchangeList = state.response.exchanges.reduce(
     (p, c) => ({
       ...p,
       [c.exchangeBaseUrl]: c.currency,
diff --git a/packages/taler-wallet-webextension/src/wallet/ProviderAddPage.tsx 
b/packages/taler-wallet-webextension/src/wallet/ProviderAddPage.tsx
index 1c7fdc82..41852e38 100644
--- a/packages/taler-wallet-webextension/src/wallet/ProviderAddPage.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/ProviderAddPage.tsx
@@ -20,7 +20,7 @@ import {
   canonicalizeBaseUrl,
   i18n,
 } from "@gnu-taler/taler-util";
-import { VNode, h } from "preact";
+import { Fragment, h, VNode } from "preact";
 import { useEffect, useState } from "preact/hooks";
 import { Checkbox } from "../components/Checkbox";
 import { ErrorMessage } from "../components/ErrorMessage";
@@ -29,7 +29,6 @@ import {
   ButtonPrimary,
   Input,
   LightText,
-  WalletBox,
   SmallLightText,
 } from "../components/styled/index";
 import * as wxApi from "../wxApi";
@@ -64,7 +63,7 @@ export function ProviderAddPage({ onBack }: Props): VNode {
   async function getProviderInfo(
     url: string,
   ): Promise<BackupBackupProviderTerms> {
-    return fetch(`${url}config`)
+    return fetch(new URL("config", url).href)
       .catch((e) => {
         throw new Error(`Network error`);
       })
@@ -137,7 +136,7 @@ export function SetUrlView({
     }
   }, [value]);
   return (
-    <WalletBox>
+    <Fragment>
       <section>
         <h1> Add backup provider</h1>
         <ErrorMessage
@@ -182,7 +181,7 @@ export function SetUrlView({
           <i18n.Translate>Next</i18n.Translate>
         </ButtonPrimary>
       </footer>
-    </WalletBox>
+    </Fragment>
   );
 }
 
@@ -201,7 +200,7 @@ export function ConfirmProviderView({
   const [accepted, setAccepted] = useState(false);
 
   return (
-    <WalletBox>
+    <Fragment>
       <section>
         <h1>Review terms of service</h1>
         <div>
@@ -239,6 +238,6 @@ export function ConfirmProviderView({
           <i18n.Translate>Add provider</i18n.Translate>
         </ButtonPrimary>
       </footer>
-    </WalletBox>
+    </Fragment>
   );
 }
diff --git 
a/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx 
b/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx
index 1c14c6e0..d14429ee 100644
--- a/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx
@@ -28,34 +28,62 @@ import {
   ButtonPrimary,
   PaymentStatus,
   SmallLightText,
-  WalletBox,
 } from "../components/styled";
 import { Time } from "../components/Time";
-import { useProviderStatus } from "../hooks/useProviderStatus";
+import { useAsyncAsHook } from "../hooks/useAsyncAsHook";
+import * as wxApi from "../wxApi";
 
 interface Props {
   pid: string;
   onBack: () => void;
 }
 
-export function ProviderDetailPage({ pid, onBack }: Props): VNode {
-  const status = useProviderStatus(pid);
-  if (!status) {
+export function ProviderDetailPage({ pid: providerURL, onBack }: Props): VNode 
{
+  async function getProviderInfo(): Promise<ProviderInfo | null> {
+    //create a first list of backup info by currency
+    const status = await wxApi.getBackupInfo();
+
+    const providers = status.providers.filter(
+      (p) => p.syncProviderBaseUrl === providerURL,
+    );
+    return providers.length ? providers[0] : null;
+  }
+
+  const state = useAsyncAsHook(getProviderInfo);
+
+  if (!state) {
     return (
       <div>
         <i18n.Translate>Loading...</i18n.Translate>
       </div>
     );
   }
-  if (!status.info) {
+  if (state.hasError) {
+    return (
+      <div>
+        <i18n.Translate>
+          There was an error loading the provider detail for "{providerURL}"
+        </i18n.Translate>
+      </div>
+    );
+  }
+
+  if (state.response === null) {
     onBack();
-    return <div />;
+    return (
+      <div>
+        <i18n.Translate>
+          There is not known provider with url "{providerURL}". Redirecting
+          back...
+        </i18n.Translate>
+      </div>
+    );
   }
   return (
     <ProviderView
-      info={status.info}
-      onSync={status.sync}
-      onDelete={() => status.remove().then(onBack)}
+      info={state.response}
+      onSync={async () => wxApi.syncOneProvider(providerURL)}
+      onDelete={async () => wxApi.syncOneProvider(providerURL).then(onBack)}
       onBack={onBack}
       onExtend={() => {
         null;
@@ -84,7 +112,7 @@ export function ProviderView({
     info.paymentStatus.type === ProviderPaymentType.Paid ||
     info.paymentStatus.type === ProviderPaymentType.TermsChanged;
   return (
-    <WalletBox>
+    <Fragment>
       <Error info={info} />
       <header>
         <h3>
@@ -167,35 +195,10 @@ export function ProviderView({
           </ButtonDestructive>
         </div>
       </footer>
-    </WalletBox>
+    </Fragment>
   );
 }
 
-// function daysSince(d?: Timestamp): string {
-//   if (!d || d.t_ms === "never") return "never synced";
-//   const duration = intervalToDuration({
-//     start: d.t_ms,
-//     end: new Date(),
-//   });
-//   const str = formatDuration(duration, {
-//     delimiter: ", ",
-//     format: [
-//       duration?.years
-//         ? i18n.str`years`
-//         : duration?.months
-//         ? i18n.str`months`
-//         : duration?.days
-//         ? i18n.str`days`
-//         : duration?.hours
-//         ? i18n.str`hours`
-//         : duration?.minutes
-//         ? i18n.str`minutes`
-//         : i18n.str`seconds`,
-//     ],
-//   });
-//   return `synced ${str} ago`;
-// }
-
 function Error({ info }: { info: ProviderInfo }): VNode {
   if (info.lastError) {
     return <ErrorMessage title={info.lastError.hint} />;
@@ -234,23 +237,6 @@ function Error({ info }: { info: ProviderInfo }): VNode {
   return <Fragment />;
 }
 
-// function colorByStatus(status: ProviderPaymentType): string {
-//   switch (status) {
-//     case ProviderPaymentType.InsufficientBalance:
-//       return "rgb(223, 117, 20)";
-//     case ProviderPaymentType.Unpaid:
-//       return "rgb(202, 60, 60)";
-//     case ProviderPaymentType.Paid:
-//       return "rgb(28, 184, 65)";
-//     case ProviderPaymentType.Pending:
-//       return "gray";
-//     // case ProviderPaymentType.InsufficientBalance:
-//     //   return "rgb(202, 60, 60)";
-//     case ProviderPaymentType.TermsChanged:
-//       return "rgb(202, 60, 60)";
-//   }
-// }
-
 function descriptionByStatus(status: ProviderPaymentStatus): VNode {
   switch (status.type) {
     // return i18n.str`no enough balance to make the payment`
diff --git a/packages/taler-wallet-webextension/src/wallet/ReserveCreated.tsx 
b/packages/taler-wallet-webextension/src/wallet/ReserveCreated.tsx
index a72026ab..075126dc 100644
--- a/packages/taler-wallet-webextension/src/wallet/ReserveCreated.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/ReserveCreated.tsx
@@ -1,18 +1,8 @@
-import {
-  AmountJson,
-  Amounts,
-  parsePaytoUri,
-  PaytoUri,
-} from "@gnu-taler/taler-util";
+import { AmountJson, Amounts, parsePaytoUri } from "@gnu-taler/taler-util";
 import { Fragment, h, VNode } from "preact";
-import { useEffect, useState } from "preact/hooks";
+import { BankDetailsByPaytoType } from "../components/BankDetailsByPaytoType";
 import { QR } from "../components/QR";
-import {
-  ButtonDestructive,
-  ButtonPrimary,
-  WalletBox,
-  WarningBox,
-} from "../components/styled";
+import { ButtonDestructive, WarningBox } from "../components/styled";
 export interface Props {
   reservePub: string;
   payto: string;
@@ -21,92 +11,6 @@ export interface Props {
   onBack: () => void;
 }
 
-interface BankDetailsProps {
-  payto: PaytoUri;
-  exchangeBaseUrl: string;
-  subject: string;
-  amount: string;
-}
-
-function Row({
-  name,
-  value,
-  literal,
-}: {
-  name: string;
-  value: string;
-  literal?: boolean;
-}): VNode {
-  const [copied, setCopied] = useState(false);
-  function copyText(): void {
-    navigator.clipboard.writeText(value);
-    setCopied(true);
-  }
-  useEffect(() => {
-    setTimeout(() => {
-      setCopied(false);
-    }, 1000);
-  }, [copied]);
-  return (
-    <tr>
-      <td>
-        {!copied ? (
-          <ButtonPrimary small onClick={copyText}>
-            &nbsp; Copy &nbsp;
-          </ButtonPrimary>
-        ) : (
-          <ButtonPrimary small disabled>
-            Copied
-          </ButtonPrimary>
-        )}
-      </td>
-      <td>
-        <b>{name}</b>
-      </td>
-      {literal ? (
-        <td>
-          <pre style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }}>
-            {value}
-          </pre>
-        </td>
-      ) : (
-        <td>{value}</td>
-      )}
-    </tr>
-  );
-}
-
-function BankDetailsByPaytoType({
-  payto,
-  subject,
-  exchangeBaseUrl,
-  amount,
-}: BankDetailsProps): VNode {
-  const firstPart = !payto.isKnown ? (
-    <Fragment>
-      <Row name="Account" value={payto.targetPath} />
-      <Row name="Exchange" value={exchangeBaseUrl} />
-    </Fragment>
-  ) : payto.targetType === "x-taler-bank" ? (
-    <Fragment>
-      <Row name="Bank host" value={payto.host} />
-      <Row name="Bank account" value={payto.account} />
-      <Row name="Exchange" value={exchangeBaseUrl} />
-    </Fragment>
-  ) : payto.targetType === "iban" ? (
-    <Fragment>
-      <Row name="IBAN" value={payto.iban} />
-      <Row name="Exchange" value={exchangeBaseUrl} />
-    </Fragment>
-  ) : undefined;
-  return (
-    <table>
-      {firstPart}
-      <Row name="Amount" value={amount} />
-      <Row name="Subject" value={subject} literal />
-    </table>
-  );
-}
 export function ReserveCreated({
   reservePub,
   payto,
@@ -120,11 +24,12 @@ export function ReserveCreated({
     return <div>could not parse payto uri from exchange {payto}</div>;
   }
   return (
-    <WalletBox>
+    <Fragment>
       <section>
-        <h1>Bank transfer details</h1>
+        <h1>Exchange is ready for withdrawal!</h1>
         <p>
-          Please wire <b>{Amounts.stringify(amount)}</b> to:
+          To complete the process you need to wire{" "}
+          <b>{Amounts.stringify(amount)}</b> to the exchange bank account
         </p>
         <BankDetailsByPaytoType
           amount={Amounts.stringify(amount)}
@@ -132,14 +37,14 @@ export function ReserveCreated({
           payto={paytoURI}
           subject={reservePub}
         />
-      </section>
-      <section>
         <p>
           <WarningBox>
             Make sure to use the correct subject, otherwise the money will not
             arrive in this wallet.
           </WarningBox>
         </p>
+      </section>
+      <section>
         <p>
           Alternative, you can also scan this QR code or open{" "}
           <a href={payto}>this link</a> if you have a banking app installed 
that
@@ -149,8 +54,10 @@ export function ReserveCreated({
       </section>
       <footer>
         <div />
-        <ButtonDestructive onClick={onBack}>Cancel withdraw</ButtonDestructive>
+        <ButtonDestructive onClick={onBack}>
+          Cancel withdrawal
+        </ButtonDestructive>
       </footer>
-    </WalletBox>
+    </Fragment>
   );
 }
diff --git a/packages/taler-wallet-webextension/src/wallet/Settings.tsx 
b/packages/taler-wallet-webextension/src/wallet/Settings.tsx
index 8d8f3cdb..586d7b53 100644
--- a/packages/taler-wallet-webextension/src/wallet/Settings.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Settings.tsx
@@ -15,16 +15,15 @@
 */
 
 import { ExchangeListItem, i18n } from "@gnu-taler/taler-util";
-import { VNode, h, Fragment } from "preact";
+import { Fragment, h, VNode } from "preact";
 import { Checkbox } from "../components/Checkbox";
-import { EditableText } from "../components/EditableText";
-import { SelectList } from "../components/SelectList";
-import { ButtonPrimary, ButtonSuccess, WalletBox } from "../components/styled";
+import { ButtonPrimary } from "../components/styled";
 import { useDevContext } from "../context/devContext";
+import { useAsyncAsHook } from "../hooks/useAsyncAsHook";
 import { useBackupDeviceName } from "../hooks/useBackupDeviceName";
 import { useExtendedPermissions } from "../hooks/useExtendedPermissions";
-import { useAsyncAsHook } from "../hooks/useAsyncAsHook";
 import { useLang } from "../hooks/useLang";
+// import { strings as messages } from "../i18n/strings";
 import * as wxApi from "../wxApi";
 
 export function SettingsPage(): VNode {
@@ -32,7 +31,7 @@ export function SettingsPage(): VNode {
   const { devMode, toggleDevMode } = useDevContext();
   const { name, update } = useBackupDeviceName();
   const [lang, changeLang] = useLang();
-  const exchangesHook = useAsyncAsHook(() => wxApi.listExchanges());
+  const exchangesHook = useAsyncAsHook(wxApi.listExchanges);
 
   return (
     <SettingsView
@@ -65,34 +64,32 @@ export interface ViewProps {
   knownExchanges: Array<ExchangeListItem>;
 }
 
-import { strings as messages } from "../i18n/strings";
-
-type LangsNames = {
-  [P in keyof typeof messages]: string;
-};
+// type LangsNames = {
+//   [P in keyof typeof messages]: string;
+// };
 
-const names: LangsNames = {
-  es: "Español [es]",
-  en: "English [en]",
-  fr: "Français [fr]",
-  de: "Deutsch [de]",
-  sv: "Svenska [sv]",
-  it: "Italiano [it]",
-};
+// const names: LangsNames = {
+//   es: "Español [es]",
+//   en: "English [en]",
+//   fr: "Français [fr]",
+//   de: "Deutsch [de]",
+//   sv: "Svenska [sv]",
+//   it: "Italiano [it]",
+// };
 
 export function SettingsView({
   knownExchanges,
-  lang,
-  changeLang,
-  deviceName,
-  setDeviceName,
+  // lang,
+  // changeLang,
+  // deviceName,
+  // setDeviceName,
   permissionsEnabled,
   togglePermissions,
   developerMode,
   toggleDeveloperMode,
 }: ViewProps): VNode {
   return (
-    <WalletBox>
+    <Fragment>
       <section>
         <h2>
           <i18n.Translate>Known exchanges</i18n.Translate>
@@ -100,17 +97,23 @@ export function SettingsView({
         {!knownExchanges || !knownExchanges.length ? (
           <div>No exchange yet!</div>
         ) : (
-          <table>
-            {knownExchanges.map((e) => (
-              <tr>
-                <td>{e.currency}</td>
-                <td>
-                  <a href={e.exchangeBaseUrl}>{e.exchangeBaseUrl}</a>
-                </td>
-              </tr>
-            ))}
-          </table>
+          <Fragment>
+            <table>
+              {knownExchanges.map((e, idx) => (
+                <tr key={idx}>
+                  <td>{e.currency}</td>
+                  <td>
+                    <a href={e.exchangeBaseUrl}>{e.exchangeBaseUrl}</a>
+                  </td>
+                </tr>
+              ))}
+            </table>
+          </Fragment>
         )}
+        <div style={{ display: "flex", justifyContent: "space-between" }}>
+          <div />
+          <ButtonPrimary>Manage exchange</ButtonPrimary>
+        </div>
 
         <h2>
           <i18n.Translate>Permissions</i18n.Translate>
@@ -131,6 +134,6 @@ export function SettingsView({
           onToggle={toggleDeveloperMode}
         />
       </section>
-    </WalletBox>
+    </Fragment>
   );
 }
diff --git 
a/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx 
b/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx
index c9a3f47c..a25e2ca8 100644
--- a/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx
@@ -61,6 +61,7 @@ const exampleData = {
     exchangeBaseUrl: "http://exchange.taler";,
     withdrawalDetails: {
       confirmed: false,
+      reservePub: "A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG",
       exchangePaytoUris: ["payto://x-taler-bank/bank/account"],
       type: WithdrawalType.ManualTransfer,
     },
@@ -134,10 +135,49 @@ export const WithdrawError = 
createExample(TestedComponent, {
   },
 });
 
-export const WithdrawPending = createExample(TestedComponent, {
-  transaction: { ...exampleData.withdraw, pending: true },
+export const WithdrawPendingManual = createExample(TestedComponent, {
+  transaction: {
+    ...exampleData.withdraw,
+    withdrawalDetails: {
+      type: WithdrawalType.ManualTransfer,
+      exchangePaytoUris: ["payto://iban/asdasdasd"],
+      reservePub: "A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG",
+    },
+    pending: true,
+  },
 });
 
+export const WithdrawPendingTalerBankUnconfirmed = createExample(
+  TestedComponent,
+  {
+    transaction: {
+      ...exampleData.withdraw,
+      withdrawalDetails: {
+        type: WithdrawalType.TalerBankIntegrationApi,
+        confirmed: false,
+        reservePub: "A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG",
+        bankConfirmationUrl: "http://bank.demo.taler.net";,
+      },
+      pending: true,
+    },
+  },
+);
+
+export const WithdrawPendingTalerBankConfirmed = createExample(
+  TestedComponent,
+  {
+    transaction: {
+      ...exampleData.withdraw,
+      withdrawalDetails: {
+        type: WithdrawalType.TalerBankIntegrationApi,
+        confirmed: true,
+        reservePub: "A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG",
+      },
+      pending: true,
+    },
+  },
+);
+
 export const Payment = createExample(TestedComponent, {
   transaction: exampleData.payment,
 });
diff --git a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx 
b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx
index 1472efb4..02c78320 100644
--- a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx
@@ -18,62 +18,80 @@ import {
   AmountLike,
   Amounts,
   i18n,
+  NotificationType,
+  parsePaytoUri,
   Transaction,
   TransactionType,
+  WithdrawalType,
 } from "@gnu-taler/taler-util";
-import { h, VNode } from "preact";
+import { ComponentChildren, Fragment, h, VNode } from "preact";
 import { route } from "preact-router";
-import { useEffect, useState } from "preact/hooks";
+import { useState } from "preact/hooks";
 import emptyImg from "../../static/img/empty.png";
+import { BankDetailsByPaytoType } from "../components/BankDetailsByPaytoType";
 import { ErrorMessage } from "../components/ErrorMessage";
 import { Part } from "../components/Part";
 import {
   Button,
   ButtonDestructive,
   ButtonPrimary,
+  CenteredDialog,
+  InfoBox,
   ListOfProducts,
+  Overlay,
   RowBorderGray,
   SmallLightText,
-  WalletBox,
   WarningBox,
 } from "../components/styled";
 import { Time } from "../components/Time";
+import { useAsyncAsHook } from "../hooks/useAsyncAsHook";
 import { Pages } from "../NavigationBar";
 import * as wxApi from "../wxApi";
 
 export function TransactionPage({ tid }: { tid: string }): VNode {
-  const [transaction, setTransaction] = useState<Transaction | undefined>(
-    undefined,
-  );
+  async function getTransaction(): Promise<Transaction> {
+    const res = await wxApi.getTransactions();
+    const ts = res.transactions.filter((t) => t.transactionId === tid);
+    if (ts.length > 1) throw Error("more than one transaction with this id");
+    if (ts.length === 1) {
+      return ts[0];
+    }
+    throw Error("no transaction found");
+  }
 
-  useEffect(() => {
-    const fetchData = async (): Promise<void> => {
-      const res = await wxApi.getTransactions();
-      const ts = res.transactions.filter((t) => t.transactionId === tid);
-      if (ts.length === 1) {
-        setTransaction(ts[0]);
-      } else {
-        route(Pages.history);
-      }
-    };
-    fetchData();
-  }, [tid]);
+  const state = useAsyncAsHook(getTransaction, [
+    NotificationType.WithdrawGroupFinished,
+  ]);
 
-  if (!transaction) {
+  if (!state) {
     return (
       <div>
         <i18n.Translate>Loading ...</i18n.Translate>
       </div>
     );
   }
+
+  if (state.hasError) {
+    route(Pages.history);
+    return (
+      <div>
+        <i18n.Translate>
+          There was an error. Redirecting into the history page
+        </i18n.Translate>
+      </div>
+    );
+  }
+
+  function goToHistory(): void {
+    route(Pages.history);
+  }
+
   return (
     <TransactionView
-      transaction={transaction}
-      onDelete={() => wxApi.deleteTransaction(tid).then(() => history.go(-1))}
-      onRetry={() => wxApi.retryTransaction(tid).then(() => history.go(-1))}
-      onBack={() => {
-        route(Pages.history);
-      }}
+      transaction={state.response}
+      onDelete={() => wxApi.deleteTransaction(tid).then(goToHistory)}
+      onRetry={() => wxApi.retryTransaction(tid).then(goToHistory)}
+      onBack={goToHistory}
     />
   );
 }
@@ -91,16 +109,28 @@ export function TransactionView({
   onRetry,
   onBack,
 }: WalletTransactionProps): VNode {
-  function TransactionTemplate({ children }: { children: VNode[] }): VNode {
+  const [confirmBeforeForget, setConfirmBeforeForget] = useState(false);
+  function doCheckBeforeForget(): void {
+    if (
+      transaction.pending &&
+      transaction.type === TransactionType.Withdrawal
+    ) {
+      setConfirmBeforeForget(true);
+    } else {
+      onDelete();
+    }
+  }
+  function TransactionTemplate({
+    children,
+  }: {
+    children: ComponentChildren;
+  }): VNode {
     return (
-      <WalletBox>
+      <Fragment>
         <section style={{ padding: 8, textAlign: "center" }}>
           <ErrorMessage title={transaction?.error?.hint} />
           {transaction.pending && (
-            <WarningBox>
-              This transaction is not completed
-              <a href="">more info...</a>
-            </WarningBox>
+            <WarningBox>This transaction is not completed</WarningBox>
           )}
         </section>
         <section>
@@ -116,12 +146,12 @@ export function TransactionView({
                 <i18n.Translate>retry</i18n.Translate>
               </ButtonPrimary>
             ) : null}
-            <ButtonDestructive onClick={onDelete}>
+            <ButtonDestructive onClick={doCheckBeforeForget}>
               <i18n.Translate> Forget </i18n.Translate>
             </ButtonDestructive>
           </div>
         </footer>
-      </WalletBox>
+      </Fragment>
     );
   }
 
@@ -138,27 +168,119 @@ export function TransactionView({
     ).amount;
     return (
       <TransactionTemplate>
+        {confirmBeforeForget ? (
+          <Overlay>
+            <CenteredDialog>
+              <header>Caution!</header>
+              <section>
+                If you have already wired money to the exchange you will loose
+                the chance to get the coins form it.
+              </section>
+              <footer>
+                <Button onClick={() => setConfirmBeforeForget(false)}>
+                  <i18n.Translate> Cancel </i18n.Translate>
+                </Button>
+
+                <ButtonDestructive onClick={onDelete}>
+                  <i18n.Translate> Confirm </i18n.Translate>
+                </ButtonDestructive>
+              </footer>
+            </CenteredDialog>
+          </Overlay>
+        ) : undefined}
         <h2>Withdrawal</h2>
         <Time timestamp={transaction.timestamp} format="dd MMMM yyyy, HH:mm" />
-        <br />
-        <Part
-          big
-          title="Total withdrawn"
-          text={amountToString(transaction.amountEffective)}
-          kind="positive"
-        />
-        <Part
-          big
-          title="Chosen amount"
-          text={amountToString(transaction.amountRaw)}
-          kind="neutral"
-        />
-        <Part
-          big
-          title="Exchange fee"
-          text={amountToString(fee)}
-          kind="negative"
-        />
+        {transaction.pending ? (
+          transaction.withdrawalDetails.type ===
+          WithdrawalType.ManualTransfer ? (
+            <Fragment>
+              <BankDetailsByPaytoType
+                amount={amountToString(transaction.amountRaw)}
+                exchangeBaseUrl={transaction.exchangeBaseUrl}
+                payto={parsePaytoUri(
+                  transaction.withdrawalDetails.exchangePaytoUris[0],
+                )}
+                subject={transaction.withdrawalDetails.reservePub}
+              />
+              <p>
+                <WarningBox>
+                  Make sure to use the correct subject, otherwise the money 
will
+                  not arrive in this wallet.
+                </WarningBox>
+              </p>
+              <Part
+                big
+                title="Total withdrawn"
+                text={amountToString(transaction.amountEffective)}
+                kind="positive"
+              />
+              <Part
+                big
+                title="Exchange fee"
+                text={amountToString(fee)}
+                kind="negative"
+              />
+            </Fragment>
+          ) : (
+            <Fragment>
+              {!transaction.withdrawalDetails.confirmed &&
+              transaction.withdrawalDetails.bankConfirmationUrl ? (
+                <InfoBox>
+                  The bank is waiting for confirmation. Go to the
+                  <a
+                    href={transaction.withdrawalDetails.bankConfirmationUrl}
+                    target="_blank"
+                    rel="noreferrer"
+                  >
+                    bank site
+                  </a>
+                </InfoBox>
+              ) : undefined}
+              {transaction.withdrawalDetails.confirmed && (
+                <InfoBox>Waiting for the coins to arrive</InfoBox>
+              )}
+              <Part
+                big
+                title="Total withdrawn"
+                text={amountToString(transaction.amountEffective)}
+                kind="positive"
+              />
+              <Part
+                big
+                title="Chosen amount"
+                text={amountToString(transaction.amountRaw)}
+                kind="neutral"
+              />
+              <Part
+                big
+                title="Exchange fee"
+                text={amountToString(fee)}
+                kind="negative"
+              />
+            </Fragment>
+          )
+        ) : (
+          <Fragment>
+            <Part
+              big
+              title="Total withdrawn"
+              text={amountToString(transaction.amountEffective)}
+              kind="positive"
+            />
+            <Part
+              big
+              title="Chosen amount"
+              text={amountToString(transaction.amountRaw)}
+              kind="neutral"
+            />
+            <Part
+              big
+              title="Exchange fee"
+              text={amountToString(fee)}
+              kind="negative"
+            />
+          </Fragment>
+        )}
         <Part
           title="Exchange"
           text={new URL(transaction.exchangeBaseUrl).hostname}
diff --git a/packages/taler-wallet-webextension/src/wallet/Welcome.tsx 
b/packages/taler-wallet-webextension/src/wallet/Welcome.tsx
index a6dd040e..b180fdd0 100644
--- a/packages/taler-wallet-webextension/src/wallet/Welcome.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Welcome.tsx
@@ -20,13 +20,12 @@
  * @author Florian Dold
  */
 
+import { WalletDiagnostics } from "@gnu-taler/taler-util";
+import { Fragment, h, VNode } from "preact";
 import { Checkbox } from "../components/Checkbox";
-import { useExtendedPermissions } from "../hooks/useExtendedPermissions";
 import { Diagnostics } from "../components/Diagnostics";
-import { WalletBox } from "../components/styled";
 import { useDiagnostics } from "../hooks/useDiagnostics";
-import { WalletDiagnostics } from "@gnu-taler/taler-util";
-import { h, VNode } from "preact";
+import { useExtendedPermissions } from "../hooks/useExtendedPermissions";
 
 export function WelcomePage(): VNode {
   const [permissionsEnabled, togglePermissions] = useExtendedPermissions();
@@ -54,7 +53,7 @@ export function View({
   timedOut,
 }: ViewProps): VNode {
   return (
-    <WalletBox>
+    <Fragment>
       <h1>Browser Extension Installed!</h1>
       <div>
         <p>Thank you for installing the wallet.</p>
@@ -75,6 +74,6 @@ export function View({
           Learn how to top up your wallet balance »
         </a>
       </div>
-    </WalletBox>
+    </Fragment>
   );
 }
diff --git a/packages/taler-wallet-webextension/src/walletEntryPoint.tsx 
b/packages/taler-wallet-webextension/src/walletEntryPoint.tsx
index f097d58b..a17550ff 100644
--- a/packages/taler-wallet-webextension/src/walletEntryPoint.tsx
+++ b/packages/taler-wallet-webextension/src/walletEntryPoint.tsx
@@ -22,7 +22,7 @@
 
 import { setupI18n } from "@gnu-taler/taler-util";
 import { createHashHistory } from "history";
-import { Fragment, h, render } from "preact";
+import { Fragment, h, render, VNode } from "preact";
 import Router, { route, Route } from "preact-router";
 import { useEffect } from "preact/hooks";
 import { LogoHeader } from "./components/LogoHeader";
@@ -39,8 +39,11 @@ import { SettingsPage } from "./wallet/Settings";
 import { TransactionPage } from "./wallet/Transaction";
 import { WelcomePage } from "./wallet/Welcome";
 import { BackupPage } from "./wallet/BackupPage";
-import { DeveloperPage } from "./popup/Debug.js";
-import { ManualWithdrawPage } from "./wallet/ManualWithdrawPage.js";
+import { DeveloperPage } from "./popup/Debug";
+import { ManualWithdrawPage } from "./wallet/ManualWithdrawPage";
+import { WalletBox } from "./components/styled";
+import { ProviderDetailPage } from "./wallet/ProviderDetailPage";
+import { ProviderAddPage } from "./wallet/ProviderAddPage";
 
 function main(): void {
   try {
@@ -66,16 +69,20 @@ if (document.readyState === "loading") {
 }
 
 function withLogoAndNavBar(Component: any) {
-  return (props: any) => (
-    <Fragment>
-      <LogoHeader />
-      <WalletNavBar />
-      <Component {...props} />
-    </Fragment>
-  );
+  return function withLogoAndNavBarComponent(props: any): VNode {
+    return (
+      <Fragment>
+        <LogoHeader />
+        <WalletNavBar />
+        <WalletBox>
+          <Component {...props} />
+        </WalletBox>
+      </Fragment>
+    );
+  };
 }
 
-function Application() {
+function Application(): VNode {
   return (
     <div>
       <DevContextProvider>
@@ -105,6 +112,23 @@ function Application() {
           <Route
             path={Pages.backup}
             component={withLogoAndNavBar(BackupPage)}
+            onAddProvider={() => {
+              route(Pages.provider_add);
+            }}
+          />
+          <Route
+            path={Pages.provider_detail}
+            component={withLogoAndNavBar(ProviderDetailPage)}
+            onBack={() => {
+              route(Pages.backup);
+            }}
+          />
+          <Route
+            path={Pages.provider_add}
+            component={withLogoAndNavBar(ProviderAddPage)}
+            onBack={() => {
+              route(Pages.backup);
+            }}
           />
 
           <Route
diff --git a/packages/taler-wallet-webextension/src/wxApi.ts 
b/packages/taler-wallet-webextension/src/wxApi.ts
index 90cfd3ed..be0fed88 100644
--- a/packages/taler-wallet-webextension/src/wxApi.ts
+++ b/packages/taler-wallet-webextension/src/wxApi.ts
@@ -22,39 +22,21 @@
  * Imports.
  */
 import {
-  CoreApiResponse,
-  ConfirmPayResult,
-  BalancesResponse,
-  TransactionsResponse,
-  ApplyRefundResponse,
-  PreparePayResult,
-  AcceptWithdrawalResponse,
-  WalletDiagnostics,
-  GetWithdrawalDetailsForUriRequest,
-  WithdrawUriInfoResponse,
-  PrepareTipRequest,
-  PrepareTipResult,
-  AcceptTipRequest,
-  DeleteTransactionRequest,
-  RetryTransactionRequest,
-  SetWalletDeviceIdRequest,
-  GetExchangeWithdrawalInfo,
   AcceptExchangeTosRequest,
-  AcceptManualWithdrawalResult,
-  AcceptManualWithdrawalRequest,
-  AmountJson,
-  ExchangesListRespose,
-  AddExchangeRequest,
-  GetExchangeTosResult,
+  AcceptManualWithdrawalResult, AcceptTipRequest, AcceptWithdrawalResponse,
+  AddExchangeRequest, ApplyRefundResponse, BalancesResponse, ConfirmPayResult,
+  CoreApiResponse, DeleteTransactionRequest, ExchangesListRespose,
+  GetExchangeTosResult, GetExchangeWithdrawalInfo,
+  GetWithdrawalDetailsForUriRequest, NotificationType, PreparePayResult, 
PrepareTipRequest,
+  PrepareTipResult, RetryTransactionRequest,
+  SetWalletDeviceIdRequest, TransactionsResponse, WalletDiagnostics, 
WithdrawUriInfoResponse
 } from "@gnu-taler/taler-util";
 import {
-  AddBackupProviderRequest,
-  BackupProviderState,
-  OperationFailedError,
-  RemoveBackupProviderRequest,
+  AddBackupProviderRequest, BackupInfo, OperationFailedError,
+  RemoveBackupProviderRequest
 } from "@gnu-taler/taler-wallet-core";
-import { BackupInfo } from "@gnu-taler/taler-wallet-core";
 import { ExchangeWithdrawDetails } from 
"@gnu-taler/taler-wallet-core/src/operations/withdraw";
+import { MessageFromBackend } from "./wxBackend.js";
 
 export interface ExtendedPermissionsResponse {
   newValue: boolean;
@@ -83,7 +65,9 @@ export interface UpgradeResponse {
 
 async function callBackend(operation: string, payload: any): Promise<any> {
   return new Promise<any>((resolve, reject) => {
+    // eslint-disable-next-line no-undef
     chrome.runtime.sendMessage({ operation, payload, id: "(none)" }, (resp) => 
{
+      // eslint-disable-next-line no-undef
       if (chrome.runtime.lastError) {
         console.log("Error calling backend");
         reject(
@@ -366,10 +350,13 @@ export function acceptTip(req: AcceptTipRequest): 
Promise<void> {
   return callBackend("acceptTip", req);
 }
 
-export function onUpdateNotification(f: () => void): () => void {
+export function onUpdateNotification(messageType: Array<NotificationType>, 
doCallback: () => void): () => void {
+  // eslint-disable-next-line no-undef
   const port = chrome.runtime.connect({ name: "notifications" });
-  const listener = (): void => {
-    f();
+  const listener = (message: MessageFromBackend): void => {
+    if (messageType.includes(message.type)) {
+      doCallback();
+    }
   };
   port.onMessage.addListener(listener);
   return () => {
diff --git a/packages/taler-wallet-webextension/src/wxBackend.ts 
b/packages/taler-wallet-webextension/src/wxBackend.ts
index 4004f04f..df311524 100644
--- a/packages/taler-wallet-webextension/src/wxBackend.ts
+++ b/packages/taler-wallet-webextension/src/wxBackend.ts
@@ -39,6 +39,7 @@ import {
   classifyTalerUri,
   CoreApiResponse,
   CoreApiResponseSuccess,
+  NotificationType,
   TalerErrorCode,
   TalerUriType,
   WalletDiagnostics,
@@ -237,6 +238,10 @@ function makeSyncWalletRedirect(
   return { redirectUrl: innerUrl.href };
 }
 
+export type MessageFromBackend = {
+  type: NotificationType
+}
+
 async function reinitWallet(): Promise<void> {
   if (currentWallet) {
     currentWallet.stop();
@@ -266,9 +271,10 @@ async function reinitWallet(): Promise<void> {
     return;
   }
   wallet.addNotificationListener((x) => {
-    for (const x of notificationPorts) {
+    for (const notif of notificationPorts) {
+      const message: MessageFromBackend = { type: x.type };
       try {
-        x.postMessage({ type: "notification" });
+        notif.postMessage(message);
       } catch (e) {
         console.error(e);
       }

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]