gnunet-svn
[Top][All Lists]
Advanced

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

[GNUnet-SVN] [taler-wallet-webex] branch master updated (8c215a19 -> c88


From: gnunet
Subject: [GNUnet-SVN] [taler-wallet-webex] branch master updated (8c215a19 -> c88bcce9)
Date: Sun, 04 Jun 2017 17:56:57 +0200

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

dold pushed a change to branch master
in repository wallet-webex.

    from 8c215a19 remove unnecessary/broken flatmap
     new 84c2a0ee missing property
     new e0e496b8 memidb work in progress
     new c88bcce9 some UI fixes

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:
 node_modules/nyc/node_modules/yargs/package.json | 138 +++++++--
 package.json                                     |   1 +
 src/i18n/de.po                                   |  96 +++----
 src/i18n/en-US.po                                |  94 +++---
 src/i18n/fr.po                                   |  94 +++---
 src/i18n/it.po                                   |  94 +++---
 src/i18n/strings.ts                              |  16 +-
 src/i18n/taler-wallet-webex.pot                  |  94 +++---
 src/memidb-test.ts                               | 114 ++++++++
 src/memidb.ts                                    | 352 ++++++++++++++++++++---
 src/webex/pages/confirm-contract.tsx             |  15 +-
 src/webex/pages/confirm-create-reserve.tsx       |  28 +-
 src/webex/pages/payback.tsx                      |   4 +-
 src/webex/pages/popup.tsx                        |  19 +-
 src/webex/pages/tree.tsx                         |  20 +-
 src/webex/renderHtml.tsx                         |  12 +
 tsconfig.json                                    |   1 +
 yarn.lock                                        |   4 +
 18 files changed, 852 insertions(+), 344 deletions(-)
 create mode 100644 src/memidb-test.ts

diff --git a/node_modules/nyc/node_modules/yargs/package.json 
b/node_modules/nyc/node_modules/yargs/package.json
index a396ea7b..42fa3bd3 100644
--- a/node_modules/nyc/node_modules/yargs/package.json
+++ b/node_modules/nyc/node_modules/yargs/package.json
@@ -1,16 +1,57 @@
 {
-  "name": "yargs",
-  "version": "7.1.0",
-  "description": "yargs the modern, pirate-themed, successor to optimist.",
-  "main": "./index.js",
-  "files": [
-    "index.js",
-    "yargs.js",
-    "lib",
-    "locales",
-    "completion.sh.hbs",
-    "LICENSE"
+  "_args": [
+    [
+      {
+        "raw": "address@hidden",
+        "scope": null,
+        "escapedName": "yargs",
+        "name": "yargs",
+        "rawSpec": "^7.1.0",
+        "spec": ">=7.1.0 <8.0.0",
+        "type": "range"
+      },
+      "/Users/benjamincoe/oss/nyc"
+    ]
+  ],
+  "_from": "yargs@>=7.1.0 <8.0.0",
+  "_id": "address@hidden",
+  "_inCache": true,
+  "_location": "/yargs",
+  "_nodeVersion": "6.9.5",
+  "_npmOperationalInternal": {
+    "host": "packages-12-west.internal.npmjs.com",
+    "tmp": "tmp/yargs-7.1.0.tgz_1492119927787_0.18849953636527061"
+  },
+  "_npmUser": {
+    "name": "bcoe",
+    "email": "address@hidden"
+  },
+  "_npmVersion": "4.5.0",
+  "_phantomChildren": {
+    "string-width": "1.0.2",
+    "strip-ansi": "3.0.1",
+    "wrap-ansi": "2.1.0"
+  },
+  "_requested": {
+    "raw": "address@hidden",
+    "scope": null,
+    "escapedName": "yargs",
+    "name": "yargs",
+    "rawSpec": "^7.1.0",
+    "spec": ">=7.1.0 <8.0.0",
+    "type": "range"
+  },
+  "_requiredBy": [
+    "/"
   ],
+  "_resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz";,
+  "_shasum": "6ba318eb16961727f5d284f8ea003e8d6154d0c8",
+  "_shrinkwrap": null,
+  "_spec": "address@hidden",
+  "_where": "/Users/benjamincoe/oss/nyc",
+  "bugs": {
+    "url": "https://github.com/yargs/yargs/issues";
+  },
   "dependencies": {
     "camelcase": "^3.0.0",
     "cliui": "^3.2.0",
@@ -26,6 +67,7 @@
     "y18n": "^3.2.1",
     "yargs-parser": "^5.0.0"
   },
+  "description": "yargs the modern, pirate-themed, successor to optimist.",
   "devDependencies": {
     "chai": "^3.4.1",
     "chalk": "^1.1.3",
@@ -41,22 +83,31 @@
     "standard-version": "^3.0.0",
     "which": "^1.2.9"
   },
-  "scripts": {
-    "pretest": "standard",
-    "test": "nyc --cache mocha --require ./test/before.js --timeout=8000 
--check-leaks",
-    "coverage": "nyc report --reporter=text-lcov | coveralls",
-    "release": "standard-version"
+  "directories": {},
+  "dist": {
+    "shasum": "6ba318eb16961727f5d284f8ea003e8d6154d0c8",
+    "tarball": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz";
   },
-  "repository": {
-    "type": "git",
-    "url": "http://github.com/yargs/yargs.git";
+  "engine": {
+    "node": ">=0.10"
   },
-  "homepage": "http://yargs.js.org/";,
-  "standard": {
+  "files": [
+    "index.js",
+    "yargs.js",
+    "lib",
+    "locales",
+    "completion.sh.hbs",
+    "LICENSE"
+  ],
+  "gitHead": "e7359d632595c3a5fcfd691994859b66e8943c85",
+  "greenkeeper": {
     "ignore": [
-      "**/example/**"
+      "string-width",
+      "read-pkg-up",
+      "camelcase"
     ]
   },
+  "homepage": "http://yargs.js.org/";,
   "keywords": [
     "argument",
     "args",
@@ -67,14 +118,43 @@
     "command"
   ],
   "license": "MIT",
-  "engine": {
-    "node": ">=0.10"
+  "main": "./index.js",
+  "maintainers": [
+    {
+      "name": "bcoe",
+      "email": "address@hidden"
+    },
+    {
+      "name": "chevex",
+      "email": "address@hidden"
+    },
+    {
+      "name": "nexdrew",
+      "email": "address@hidden"
+    },
+    {
+      "name": "nylen",
+      "email": "address@hidden"
+    }
+  ],
+  "name": "yargs",
+  "optionalDependencies": {},
+  "readme": "  yargs\n========\n\nYargs be a node.js library fer hearties 
tryin' ter parse optstrings.\n\nWith yargs, ye be havin' a map that leads 
straight to yer treasure! Treasure of course, being a simple option 
hash.\n\n[![Build Status][travis-image]][travis-url]\n[![Coverage 
Status][coveralls-image]][coveralls-url]\n[![NPM 
version][npm-image]][npm-url]\n[![Windows 
Tests][windows-image]][windows-url]\n[![js-standard-style][standard-image]][standard-url]\n[![Conventional
 Commits][con [...]
+  "readmeFilename": "README.md",
+  "repository": {
+    "type": "git",
+    "url": "git+ssh://address@hidden/yargs/yargs.git"
   },
-  "greenkeeper": {
+  "scripts": {
+    "coverage": "nyc report --reporter=text-lcov | coveralls",
+    "pretest": "standard",
+    "release": "standard-version",
+    "test": "nyc --cache mocha --require ./test/before.js --timeout=8000 
--check-leaks"
+  },
+  "standard": {
     "ignore": [
-      "string-width",
-      "read-pkg-up",
-      "camelcase"
+      "**/example/**"
     ]
-  }
+  },
+  "version": "7.1.0"
 }
diff --git a/package.json b/package.json
index 0bf0ae3c..b4b79b6a 100644
--- a/package.json
+++ b/package.json
@@ -47,6 +47,7 @@
     "pogen": "file:tooling/pogen/",
     "react": "^15.5.4",
     "react-dom": "^15.5.4",
+    "structured-clone": "^0.2.2",
     "through2": "^2.0.1",
     "ts-loader": "^2.0.3",
     "tslint": "^5.3.2",
diff --git a/src/i18n/de.po b/src/i18n/de.po
index 79f6a6e1..6fa2e657 100644
--- a/src/i18n/de.po
+++ b/src/i18n/de.po
@@ -42,13 +42,13 @@ msgstr ""
 msgid "Exchanges in the wallet:"
 msgstr ""
 
-#: src/webex/pages/confirm-contract.tsx:142
+#: src/webex/pages/confirm-contract.tsx:154
 #, c-format
 msgid "You have insufficient funds of the requested currency in your wallet."
 msgstr ""
 
 #. tslint:disable-next-line:max-line-length
-#: src/webex/pages/confirm-contract.tsx:144
+#: src/webex/pages/confirm-contract.tsx:156
 #, c-format
 msgid ""
 "You do not have any funds from an exchange that is accepted by this "
@@ -56,67 +56,67 @@ msgid ""
 "wallet."
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:204
+#: src/webex/pages/confirm-create-reserve.tsx:206
 #, fuzzy, c-format
-msgid "Withdrawal fees: %1$s"
+msgid "Withdrawal fees:"
 msgstr "Abheben bei %1$s"
 
-#: src/webex/pages/confirm-create-reserve.tsx:205
+#: src/webex/pages/confirm-create-reserve.tsx:207
 #, c-format
-msgid "Rounding loss: %1$s"
+msgid "Rounding loss:"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:206
+#: src/webex/pages/confirm-create-reserve.tsx:208
 #, c-format
 msgid "Earliest expiration (for deposit): %1$s"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:211
+#: src/webex/pages/confirm-create-reserve.tsx:213
 #, c-format
 msgid "# Coins"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:212
+#: src/webex/pages/confirm-create-reserve.tsx:214
 #, c-format
 msgid "Value"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:213
+#: src/webex/pages/confirm-create-reserve.tsx:215
 #, fuzzy, c-format
 msgid "Withdraw Fee"
 msgstr "Abheben bei %1$s"
 
-#: src/webex/pages/confirm-create-reserve.tsx:214
+#: src/webex/pages/confirm-create-reserve.tsx:216
 #, c-format
 msgid "Refresh Fee"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:215
+#: src/webex/pages/confirm-create-reserve.tsx:217
 #, c-format
 msgid "Deposit Fee"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:268
+#: src/webex/pages/confirm-create-reserve.tsx:270
 #, c-format
 msgid "Select"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:284
+#: src/webex/pages/confirm-create-reserve.tsx:286
 #, c-format
 msgid "Error: URL may not be relative"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:352
+#: src/webex/pages/confirm-create-reserve.tsx:354
 #, c-format
 msgid "The exchange is trusted by the wallet.\n"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:358
+#: src/webex/pages/confirm-create-reserve.tsx:360
 #, c-format
 msgid "The exchange is audited by a trusted auditor.\n"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:364
+#: src/webex/pages/confirm-create-reserve.tsx:366
 #, c-format
 msgid ""
 "Warning:  The exchange is neither directly trusted nor audited by a trusted "
@@ -124,7 +124,7 @@ msgid ""
 "If you withdraw from this exchange, it will be trusted in the future.\n"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:373
+#: src/webex/pages/confirm-create-reserve.tsx:375
 #, c-format
 msgid ""
 "Using exchange provider%1$s.\n"
@@ -132,156 +132,156 @@ msgid ""
 " %2$s in fees.\n"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:387
+#: src/webex/pages/confirm-create-reserve.tsx:389
 #, c-format
 msgid ""
 "Waiting for a response from\n"
 " %1$s"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:398
+#: src/webex/pages/confirm-create-reserve.tsx:400
 #, c-format
 msgid "A problem occured, see below. %1$s"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:404
+#: src/webex/pages/confirm-create-reserve.tsx:406
 #, c-format
 msgid ""
 "Information about fees will be available when an exchange provider is "
 "selected."
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:416
+#: src/webex/pages/confirm-create-reserve.tsx:418
 #, c-format
 msgid "Accept fees and withdraw"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:421
+#: src/webex/pages/confirm-create-reserve.tsx:423
 #, c-format
 msgid "Change Exchange Provider"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:477
+#: src/webex/pages/confirm-create-reserve.tsx:479
 #, c-format
 msgid "You are about to withdraw %1$s from your bank account into your wallet."
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:560
+#: src/webex/pages/confirm-create-reserve.tsx:562
 #, c-format
 msgid ""
 "Oops, something went wrong. The wallet responded with error status (%1$s)."
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:571
+#: src/webex/pages/confirm-create-reserve.tsx:573
 #, c-format
 msgid "Checking URL, please wait ..."
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:585
+#: src/webex/pages/confirm-create-reserve.tsx:587
 #, c-format
 msgid "Can't parse amount: %1$s"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:592
+#: src/webex/pages/confirm-create-reserve.tsx:594
 #, c-format
 msgid "Can't parse wire_types: %1$s"
 msgstr ""
 
 #. TODO:generic error reporting function or component.
-#: src/webex/pages/confirm-create-reserve.tsx:612
+#: src/webex/pages/confirm-create-reserve.tsx:614
 #, c-format
 msgid "Fatal error: \"%1$s\"."
 msgstr ""
 
-#: src/webex/pages/popup.tsx:161
+#: src/webex/pages/popup.tsx:160
 #, c-format
 msgid "Balance"
 msgstr "Saldo"
 
-#: src/webex/pages/popup.tsx:164
+#: src/webex/pages/popup.tsx:163
 #, c-format
 msgid "History"
 msgstr "Verlauf"
 
-#: src/webex/pages/popup.tsx:167
+#: src/webex/pages/popup.tsx:166
 #, c-format
 msgid "Debug"
 msgstr "Debug"
 
-#: src/webex/pages/popup.tsx:243
+#: src/webex/pages/popup.tsx:242
 #, c-format
 msgid "help"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:248
+#: src/webex/pages/popup.tsx:247
 #, fuzzy, c-format
 msgid ""
 "You have no balance to show. Need some\n"
 " %1$s getting started?\n"
 msgstr "Sie haben kein Digitalgeld. Wollen Sie %1$s? abheben?"
 
-#: src/webex/pages/popup.tsx:265
+#: src/webex/pages/popup.tsx:264
 #, c-format
 msgid "%1$s incoming\n"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:278
+#: src/webex/pages/popup.tsx:277
 #, c-format
 msgid "%1$s being spent\n"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:304
+#: src/webex/pages/popup.tsx:303
 #, c-format
 msgid "Error: could not retrieve balance information."
 msgstr ""
 
-#: src/webex/pages/popup.tsx:343
+#: src/webex/pages/popup.tsx:342
 #, fuzzy, c-format
 msgid ""
 "Bank requested reserve (%1$s) for\n"
 " %2$s.\n"
 msgstr "Bank bestätig anlegen der Reserve (%1$s) bei %2$s"
 
-#: src/webex/pages/popup.tsx:354
+#: src/webex/pages/popup.tsx:353
 #, fuzzy, c-format
 msgid ""
 "Started to withdraw\n"
 " %1$s from%2$s(%3$s).\n"
 msgstr "Reserve (%1$s) mit %2$s bei %3$s erzeugt"
 
-#: src/webex/pages/popup.tsx:364
+#: src/webex/pages/popup.tsx:363
 #, c-format
 msgid "Merchant%1$soffered contract%2$s;\n"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:374
+#: src/webex/pages/popup.tsx:373
 #, fuzzy, c-format
 msgid "Withdrew%1$sfrom%2$s(%3$s).\n"
 msgstr "Reserve (%1$s) mit %2$s bei %3$s erzeugt"
 
-#: src/webex/pages/popup.tsx:384
+#: src/webex/pages/popup.tsx:383
 #, fuzzy, c-format
 msgid ""
 "Paid%1$sto merchant%2$s.\n"
 " (%3$s)\n"
 msgstr "Reserve (%1$s) mit %2$s bei %3$s erzeugt"
 
-#: src/webex/pages/popup.tsx:393
+#: src/webex/pages/popup.tsx:392
 #, c-format
 msgid "Unknown event (%1$s)"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:436
+#: src/webex/pages/popup.tsx:435
 #, c-format
 msgid "Error: could not retrieve event history"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:470
+#: src/webex/pages/popup.tsx:469
 #, c-format
 msgid "Your wallet has no events recorded."
 msgstr "Ihre Geldbörse verzeichnet keine Vorkommnisse."
 
-#: src/webex/renderHtml.tsx:49
+#: src/webex/renderHtml.tsx:51
 #, fuzzy, c-format
 msgid "The merchant%1$swants to enter a contract over%2$s with you.\n"
 msgstr ""
@@ -289,13 +289,13 @@ msgstr ""
 "               möchte einen Vertrag über %2$s\n"
 "               mit Ihnen abschließen."
 
-#: src/webex/renderHtml.tsx:54
+#: src/webex/renderHtml.tsx:56
 #, fuzzy, c-format
 msgid "You are about to purchase:"
 msgstr "Sie sind dabei, Folgendes zu kaufen:"
 
 #, fuzzy
-#~ msgid "Withdraw fees:"
+#~ msgid "Withdrawal fees: %1$s"
 #~ msgstr "Abheben bei %1$s"
 
 #~ msgid "Confirm Payment"
diff --git a/src/i18n/en-US.po b/src/i18n/en-US.po
index 2e97531f..ab6e9d36 100644
--- a/src/i18n/en-US.po
+++ b/src/i18n/en-US.po
@@ -42,13 +42,13 @@ msgstr ""
 msgid "Exchanges in the wallet:"
 msgstr ""
 
-#: src/webex/pages/confirm-contract.tsx:142
+#: src/webex/pages/confirm-contract.tsx:154
 #, c-format
 msgid "You have insufficient funds of the requested currency in your wallet."
 msgstr ""
 
 #. tslint:disable-next-line:max-line-length
-#: src/webex/pages/confirm-contract.tsx:144
+#: src/webex/pages/confirm-contract.tsx:156
 #, c-format
 msgid ""
 "You do not have any funds from an exchange that is accepted by this "
@@ -56,67 +56,67 @@ msgid ""
 "wallet."
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:204
+#: src/webex/pages/confirm-create-reserve.tsx:206
 #, c-format
-msgid "Withdrawal fees: %1$s"
+msgid "Withdrawal fees:"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:205
+#: src/webex/pages/confirm-create-reserve.tsx:207
 #, c-format
-msgid "Rounding loss: %1$s"
+msgid "Rounding loss:"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:206
+#: src/webex/pages/confirm-create-reserve.tsx:208
 #, c-format
 msgid "Earliest expiration (for deposit): %1$s"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:211
+#: src/webex/pages/confirm-create-reserve.tsx:213
 #, c-format
 msgid "# Coins"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:212
+#: src/webex/pages/confirm-create-reserve.tsx:214
 #, c-format
 msgid "Value"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:213
+#: src/webex/pages/confirm-create-reserve.tsx:215
 #, c-format
 msgid "Withdraw Fee"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:214
+#: src/webex/pages/confirm-create-reserve.tsx:216
 #, c-format
 msgid "Refresh Fee"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:215
+#: src/webex/pages/confirm-create-reserve.tsx:217
 #, c-format
 msgid "Deposit Fee"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:268
+#: src/webex/pages/confirm-create-reserve.tsx:270
 #, c-format
 msgid "Select"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:284
+#: src/webex/pages/confirm-create-reserve.tsx:286
 #, c-format
 msgid "Error: URL may not be relative"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:352
+#: src/webex/pages/confirm-create-reserve.tsx:354
 #, c-format
 msgid "The exchange is trusted by the wallet.\n"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:358
+#: src/webex/pages/confirm-create-reserve.tsx:360
 #, c-format
 msgid "The exchange is audited by a trusted auditor.\n"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:364
+#: src/webex/pages/confirm-create-reserve.tsx:366
 #, c-format
 msgid ""
 "Warning:  The exchange is neither directly trusted nor audited by a trusted "
@@ -124,7 +124,7 @@ msgid ""
 "If you withdraw from this exchange, it will be trusted in the future.\n"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:373
+#: src/webex/pages/confirm-create-reserve.tsx:375
 #, c-format
 msgid ""
 "Using exchange provider%1$s.\n"
@@ -132,161 +132,161 @@ msgid ""
 " %2$s in fees.\n"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:387
+#: src/webex/pages/confirm-create-reserve.tsx:389
 #, c-format
 msgid ""
 "Waiting for a response from\n"
 " %1$s"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:398
+#: src/webex/pages/confirm-create-reserve.tsx:400
 #, c-format
 msgid "A problem occured, see below. %1$s"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:404
+#: src/webex/pages/confirm-create-reserve.tsx:406
 #, c-format
 msgid ""
 "Information about fees will be available when an exchange provider is "
 "selected."
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:416
+#: src/webex/pages/confirm-create-reserve.tsx:418
 #, c-format
 msgid "Accept fees and withdraw"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:421
+#: src/webex/pages/confirm-create-reserve.tsx:423
 #, c-format
 msgid "Change Exchange Provider"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:477
+#: src/webex/pages/confirm-create-reserve.tsx:479
 #, c-format
 msgid "You are about to withdraw %1$s from your bank account into your wallet."
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:560
+#: src/webex/pages/confirm-create-reserve.tsx:562
 #, c-format
 msgid ""
 "Oops, something went wrong. The wallet responded with error status (%1$s)."
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:571
+#: src/webex/pages/confirm-create-reserve.tsx:573
 #, c-format
 msgid "Checking URL, please wait ..."
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:585
+#: src/webex/pages/confirm-create-reserve.tsx:587
 #, c-format
 msgid "Can't parse amount: %1$s"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:592
+#: src/webex/pages/confirm-create-reserve.tsx:594
 #, c-format
 msgid "Can't parse wire_types: %1$s"
 msgstr ""
 
 #. TODO:generic error reporting function or component.
-#: src/webex/pages/confirm-create-reserve.tsx:612
+#: src/webex/pages/confirm-create-reserve.tsx:614
 #, c-format
 msgid "Fatal error: \"%1$s\"."
 msgstr ""
 
-#: src/webex/pages/popup.tsx:161
+#: src/webex/pages/popup.tsx:160
 #, c-format
 msgid "Balance"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:164
+#: src/webex/pages/popup.tsx:163
 #, c-format
 msgid "History"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:167
+#: src/webex/pages/popup.tsx:166
 #, c-format
 msgid "Debug"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:243
+#: src/webex/pages/popup.tsx:242
 #, c-format
 msgid "help"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:248
+#: src/webex/pages/popup.tsx:247
 #, c-format
 msgid ""
 "You have no balance to show. Need some\n"
 " %1$s getting started?\n"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:265
+#: src/webex/pages/popup.tsx:264
 #, c-format
 msgid "%1$s incoming\n"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:278
+#: src/webex/pages/popup.tsx:277
 #, c-format
 msgid "%1$s being spent\n"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:304
+#: src/webex/pages/popup.tsx:303
 #, c-format
 msgid "Error: could not retrieve balance information."
 msgstr ""
 
-#: src/webex/pages/popup.tsx:343
+#: src/webex/pages/popup.tsx:342
 #, c-format
 msgid ""
 "Bank requested reserve (%1$s) for\n"
 " %2$s.\n"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:354
+#: src/webex/pages/popup.tsx:353
 #, c-format
 msgid ""
 "Started to withdraw\n"
 " %1$s from%2$s(%3$s).\n"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:364
+#: src/webex/pages/popup.tsx:363
 #, c-format
 msgid "Merchant%1$soffered contract%2$s;\n"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:374
+#: src/webex/pages/popup.tsx:373
 #, c-format
 msgid "Withdrew%1$sfrom%2$s(%3$s).\n"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:384
+#: src/webex/pages/popup.tsx:383
 #, c-format
 msgid ""
 "Paid%1$sto merchant%2$s.\n"
 " (%3$s)\n"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:393
+#: src/webex/pages/popup.tsx:392
 #, c-format
 msgid "Unknown event (%1$s)"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:436
+#: src/webex/pages/popup.tsx:435
 #, c-format
 msgid "Error: could not retrieve event history"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:470
+#: src/webex/pages/popup.tsx:469
 #, c-format
 msgid "Your wallet has no events recorded."
 msgstr ""
 
-#: src/webex/renderHtml.tsx:49
+#: src/webex/renderHtml.tsx:51
 #, c-format
 msgid "The merchant%1$swants to enter a contract over%2$s with you.\n"
 msgstr ""
 
-#: src/webex/renderHtml.tsx:54
+#: src/webex/renderHtml.tsx:56
 #, c-format
 msgid "You are about to purchase:"
 msgstr ""
diff --git a/src/i18n/fr.po b/src/i18n/fr.po
index c6fddd9c..b08b98b8 100644
--- a/src/i18n/fr.po
+++ b/src/i18n/fr.po
@@ -42,13 +42,13 @@ msgstr ""
 msgid "Exchanges in the wallet:"
 msgstr ""
 
-#: src/webex/pages/confirm-contract.tsx:142
+#: src/webex/pages/confirm-contract.tsx:154
 #, c-format
 msgid "You have insufficient funds of the requested currency in your wallet."
 msgstr ""
 
 #. tslint:disable-next-line:max-line-length
-#: src/webex/pages/confirm-contract.tsx:144
+#: src/webex/pages/confirm-contract.tsx:156
 #, c-format
 msgid ""
 "You do not have any funds from an exchange that is accepted by this "
@@ -56,67 +56,67 @@ msgid ""
 "wallet."
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:204
+#: src/webex/pages/confirm-create-reserve.tsx:206
 #, c-format
-msgid "Withdrawal fees: %1$s"
+msgid "Withdrawal fees:"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:205
+#: src/webex/pages/confirm-create-reserve.tsx:207
 #, c-format
-msgid "Rounding loss: %1$s"
+msgid "Rounding loss:"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:206
+#: src/webex/pages/confirm-create-reserve.tsx:208
 #, c-format
 msgid "Earliest expiration (for deposit): %1$s"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:211
+#: src/webex/pages/confirm-create-reserve.tsx:213
 #, c-format
 msgid "# Coins"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:212
+#: src/webex/pages/confirm-create-reserve.tsx:214
 #, c-format
 msgid "Value"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:213
+#: src/webex/pages/confirm-create-reserve.tsx:215
 #, c-format
 msgid "Withdraw Fee"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:214
+#: src/webex/pages/confirm-create-reserve.tsx:216
 #, c-format
 msgid "Refresh Fee"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:215
+#: src/webex/pages/confirm-create-reserve.tsx:217
 #, c-format
 msgid "Deposit Fee"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:268
+#: src/webex/pages/confirm-create-reserve.tsx:270
 #, c-format
 msgid "Select"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:284
+#: src/webex/pages/confirm-create-reserve.tsx:286
 #, c-format
 msgid "Error: URL may not be relative"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:352
+#: src/webex/pages/confirm-create-reserve.tsx:354
 #, c-format
 msgid "The exchange is trusted by the wallet.\n"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:358
+#: src/webex/pages/confirm-create-reserve.tsx:360
 #, c-format
 msgid "The exchange is audited by a trusted auditor.\n"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:364
+#: src/webex/pages/confirm-create-reserve.tsx:366
 #, c-format
 msgid ""
 "Warning:  The exchange is neither directly trusted nor audited by a trusted "
@@ -124,7 +124,7 @@ msgid ""
 "If you withdraw from this exchange, it will be trusted in the future.\n"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:373
+#: src/webex/pages/confirm-create-reserve.tsx:375
 #, c-format
 msgid ""
 "Using exchange provider%1$s.\n"
@@ -132,161 +132,161 @@ msgid ""
 " %2$s in fees.\n"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:387
+#: src/webex/pages/confirm-create-reserve.tsx:389
 #, c-format
 msgid ""
 "Waiting for a response from\n"
 " %1$s"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:398
+#: src/webex/pages/confirm-create-reserve.tsx:400
 #, c-format
 msgid "A problem occured, see below. %1$s"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:404
+#: src/webex/pages/confirm-create-reserve.tsx:406
 #, c-format
 msgid ""
 "Information about fees will be available when an exchange provider is "
 "selected."
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:416
+#: src/webex/pages/confirm-create-reserve.tsx:418
 #, c-format
 msgid "Accept fees and withdraw"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:421
+#: src/webex/pages/confirm-create-reserve.tsx:423
 #, c-format
 msgid "Change Exchange Provider"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:477
+#: src/webex/pages/confirm-create-reserve.tsx:479
 #, c-format
 msgid "You are about to withdraw %1$s from your bank account into your wallet."
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:560
+#: src/webex/pages/confirm-create-reserve.tsx:562
 #, c-format
 msgid ""
 "Oops, something went wrong. The wallet responded with error status (%1$s)."
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:571
+#: src/webex/pages/confirm-create-reserve.tsx:573
 #, c-format
 msgid "Checking URL, please wait ..."
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:585
+#: src/webex/pages/confirm-create-reserve.tsx:587
 #, c-format
 msgid "Can't parse amount: %1$s"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:592
+#: src/webex/pages/confirm-create-reserve.tsx:594
 #, c-format
 msgid "Can't parse wire_types: %1$s"
 msgstr ""
 
 #. TODO:generic error reporting function or component.
-#: src/webex/pages/confirm-create-reserve.tsx:612
+#: src/webex/pages/confirm-create-reserve.tsx:614
 #, c-format
 msgid "Fatal error: \"%1$s\"."
 msgstr ""
 
-#: src/webex/pages/popup.tsx:161
+#: src/webex/pages/popup.tsx:160
 #, c-format
 msgid "Balance"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:164
+#: src/webex/pages/popup.tsx:163
 #, c-format
 msgid "History"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:167
+#: src/webex/pages/popup.tsx:166
 #, c-format
 msgid "Debug"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:243
+#: src/webex/pages/popup.tsx:242
 #, c-format
 msgid "help"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:248
+#: src/webex/pages/popup.tsx:247
 #, c-format
 msgid ""
 "You have no balance to show. Need some\n"
 " %1$s getting started?\n"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:265
+#: src/webex/pages/popup.tsx:264
 #, c-format
 msgid "%1$s incoming\n"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:278
+#: src/webex/pages/popup.tsx:277
 #, c-format
 msgid "%1$s being spent\n"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:304
+#: src/webex/pages/popup.tsx:303
 #, c-format
 msgid "Error: could not retrieve balance information."
 msgstr ""
 
-#: src/webex/pages/popup.tsx:343
+#: src/webex/pages/popup.tsx:342
 #, c-format
 msgid ""
 "Bank requested reserve (%1$s) for\n"
 " %2$s.\n"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:354
+#: src/webex/pages/popup.tsx:353
 #, c-format
 msgid ""
 "Started to withdraw\n"
 " %1$s from%2$s(%3$s).\n"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:364
+#: src/webex/pages/popup.tsx:363
 #, c-format
 msgid "Merchant%1$soffered contract%2$s;\n"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:374
+#: src/webex/pages/popup.tsx:373
 #, c-format
 msgid "Withdrew%1$sfrom%2$s(%3$s).\n"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:384
+#: src/webex/pages/popup.tsx:383
 #, c-format
 msgid ""
 "Paid%1$sto merchant%2$s.\n"
 " (%3$s)\n"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:393
+#: src/webex/pages/popup.tsx:392
 #, c-format
 msgid "Unknown event (%1$s)"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:436
+#: src/webex/pages/popup.tsx:435
 #, c-format
 msgid "Error: could not retrieve event history"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:470
+#: src/webex/pages/popup.tsx:469
 #, c-format
 msgid "Your wallet has no events recorded."
 msgstr ""
 
-#: src/webex/renderHtml.tsx:49
+#: src/webex/renderHtml.tsx:51
 #, c-format
 msgid "The merchant%1$swants to enter a contract over%2$s with you.\n"
 msgstr ""
 
-#: src/webex/renderHtml.tsx:54
+#: src/webex/renderHtml.tsx:56
 #, c-format
 msgid "You are about to purchase:"
 msgstr ""
diff --git a/src/i18n/it.po b/src/i18n/it.po
index c6fddd9c..b08b98b8 100644
--- a/src/i18n/it.po
+++ b/src/i18n/it.po
@@ -42,13 +42,13 @@ msgstr ""
 msgid "Exchanges in the wallet:"
 msgstr ""
 
-#: src/webex/pages/confirm-contract.tsx:142
+#: src/webex/pages/confirm-contract.tsx:154
 #, c-format
 msgid "You have insufficient funds of the requested currency in your wallet."
 msgstr ""
 
 #. tslint:disable-next-line:max-line-length
-#: src/webex/pages/confirm-contract.tsx:144
+#: src/webex/pages/confirm-contract.tsx:156
 #, c-format
 msgid ""
 "You do not have any funds from an exchange that is accepted by this "
@@ -56,67 +56,67 @@ msgid ""
 "wallet."
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:204
+#: src/webex/pages/confirm-create-reserve.tsx:206
 #, c-format
-msgid "Withdrawal fees: %1$s"
+msgid "Withdrawal fees:"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:205
+#: src/webex/pages/confirm-create-reserve.tsx:207
 #, c-format
-msgid "Rounding loss: %1$s"
+msgid "Rounding loss:"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:206
+#: src/webex/pages/confirm-create-reserve.tsx:208
 #, c-format
 msgid "Earliest expiration (for deposit): %1$s"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:211
+#: src/webex/pages/confirm-create-reserve.tsx:213
 #, c-format
 msgid "# Coins"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:212
+#: src/webex/pages/confirm-create-reserve.tsx:214
 #, c-format
 msgid "Value"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:213
+#: src/webex/pages/confirm-create-reserve.tsx:215
 #, c-format
 msgid "Withdraw Fee"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:214
+#: src/webex/pages/confirm-create-reserve.tsx:216
 #, c-format
 msgid "Refresh Fee"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:215
+#: src/webex/pages/confirm-create-reserve.tsx:217
 #, c-format
 msgid "Deposit Fee"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:268
+#: src/webex/pages/confirm-create-reserve.tsx:270
 #, c-format
 msgid "Select"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:284
+#: src/webex/pages/confirm-create-reserve.tsx:286
 #, c-format
 msgid "Error: URL may not be relative"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:352
+#: src/webex/pages/confirm-create-reserve.tsx:354
 #, c-format
 msgid "The exchange is trusted by the wallet.\n"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:358
+#: src/webex/pages/confirm-create-reserve.tsx:360
 #, c-format
 msgid "The exchange is audited by a trusted auditor.\n"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:364
+#: src/webex/pages/confirm-create-reserve.tsx:366
 #, c-format
 msgid ""
 "Warning:  The exchange is neither directly trusted nor audited by a trusted "
@@ -124,7 +124,7 @@ msgid ""
 "If you withdraw from this exchange, it will be trusted in the future.\n"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:373
+#: src/webex/pages/confirm-create-reserve.tsx:375
 #, c-format
 msgid ""
 "Using exchange provider%1$s.\n"
@@ -132,161 +132,161 @@ msgid ""
 " %2$s in fees.\n"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:387
+#: src/webex/pages/confirm-create-reserve.tsx:389
 #, c-format
 msgid ""
 "Waiting for a response from\n"
 " %1$s"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:398
+#: src/webex/pages/confirm-create-reserve.tsx:400
 #, c-format
 msgid "A problem occured, see below. %1$s"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:404
+#: src/webex/pages/confirm-create-reserve.tsx:406
 #, c-format
 msgid ""
 "Information about fees will be available when an exchange provider is "
 "selected."
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:416
+#: src/webex/pages/confirm-create-reserve.tsx:418
 #, c-format
 msgid "Accept fees and withdraw"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:421
+#: src/webex/pages/confirm-create-reserve.tsx:423
 #, c-format
 msgid "Change Exchange Provider"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:477
+#: src/webex/pages/confirm-create-reserve.tsx:479
 #, c-format
 msgid "You are about to withdraw %1$s from your bank account into your wallet."
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:560
+#: src/webex/pages/confirm-create-reserve.tsx:562
 #, c-format
 msgid ""
 "Oops, something went wrong. The wallet responded with error status (%1$s)."
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:571
+#: src/webex/pages/confirm-create-reserve.tsx:573
 #, c-format
 msgid "Checking URL, please wait ..."
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:585
+#: src/webex/pages/confirm-create-reserve.tsx:587
 #, c-format
 msgid "Can't parse amount: %1$s"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:592
+#: src/webex/pages/confirm-create-reserve.tsx:594
 #, c-format
 msgid "Can't parse wire_types: %1$s"
 msgstr ""
 
 #. TODO:generic error reporting function or component.
-#: src/webex/pages/confirm-create-reserve.tsx:612
+#: src/webex/pages/confirm-create-reserve.tsx:614
 #, c-format
 msgid "Fatal error: \"%1$s\"."
 msgstr ""
 
-#: src/webex/pages/popup.tsx:161
+#: src/webex/pages/popup.tsx:160
 #, c-format
 msgid "Balance"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:164
+#: src/webex/pages/popup.tsx:163
 #, c-format
 msgid "History"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:167
+#: src/webex/pages/popup.tsx:166
 #, c-format
 msgid "Debug"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:243
+#: src/webex/pages/popup.tsx:242
 #, c-format
 msgid "help"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:248
+#: src/webex/pages/popup.tsx:247
 #, c-format
 msgid ""
 "You have no balance to show. Need some\n"
 " %1$s getting started?\n"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:265
+#: src/webex/pages/popup.tsx:264
 #, c-format
 msgid "%1$s incoming\n"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:278
+#: src/webex/pages/popup.tsx:277
 #, c-format
 msgid "%1$s being spent\n"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:304
+#: src/webex/pages/popup.tsx:303
 #, c-format
 msgid "Error: could not retrieve balance information."
 msgstr ""
 
-#: src/webex/pages/popup.tsx:343
+#: src/webex/pages/popup.tsx:342
 #, c-format
 msgid ""
 "Bank requested reserve (%1$s) for\n"
 " %2$s.\n"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:354
+#: src/webex/pages/popup.tsx:353
 #, c-format
 msgid ""
 "Started to withdraw\n"
 " %1$s from%2$s(%3$s).\n"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:364
+#: src/webex/pages/popup.tsx:363
 #, c-format
 msgid "Merchant%1$soffered contract%2$s;\n"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:374
+#: src/webex/pages/popup.tsx:373
 #, c-format
 msgid "Withdrew%1$sfrom%2$s(%3$s).\n"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:384
+#: src/webex/pages/popup.tsx:383
 #, c-format
 msgid ""
 "Paid%1$sto merchant%2$s.\n"
 " (%3$s)\n"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:393
+#: src/webex/pages/popup.tsx:392
 #, c-format
 msgid "Unknown event (%1$s)"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:436
+#: src/webex/pages/popup.tsx:435
 #, c-format
 msgid "Error: could not retrieve event history"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:470
+#: src/webex/pages/popup.tsx:469
 #, c-format
 msgid "Your wallet has no events recorded."
 msgstr ""
 
-#: src/webex/renderHtml.tsx:49
+#: src/webex/renderHtml.tsx:51
 #, c-format
 msgid "The merchant%1$swants to enter a contract over%2$s with you.\n"
 msgstr ""
 
-#: src/webex/renderHtml.tsx:54
+#: src/webex/renderHtml.tsx:56
 #, c-format
 msgid "You are about to purchase:"
 msgstr ""
diff --git a/src/i18n/strings.ts b/src/i18n/strings.ts
index f2112254..242fecf5 100644
--- a/src/i18n/strings.ts
+++ b/src/i18n/strings.ts
@@ -39,10 +39,10 @@ strings['de'] = {
       "You do not have any funds from an exchange that is accepted by this 
merchant. None of the exchanges accepted by the merchant is known to your 
wallet.": [
         ""
       ],
-      "Withdrawal fees: %1$s": [
+      "Withdrawal fees:": [
         "Abheben bei %1$s"
       ],
-      "Rounding loss: %1$s": [
+      "Rounding loss:": [
         ""
       ],
       "Earliest expiration (for deposit): %1$s": [
@@ -195,10 +195,10 @@ strings['en-US'] = {
       "You do not have any funds from an exchange that is accepted by this 
merchant. None of the exchanges accepted by the merchant is known to your 
wallet.": [
         ""
       ],
-      "Withdrawal fees: %1$s": [
+      "Withdrawal fees:": [
         ""
       ],
-      "Rounding loss: %1$s": [
+      "Rounding loss:": [
         ""
       ],
       "Earliest expiration (for deposit): %1$s": [
@@ -351,10 +351,10 @@ strings['fr'] = {
       "You do not have any funds from an exchange that is accepted by this 
merchant. None of the exchanges accepted by the merchant is known to your 
wallet.": [
         ""
       ],
-      "Withdrawal fees: %1$s": [
+      "Withdrawal fees:": [
         ""
       ],
-      "Rounding loss: %1$s": [
+      "Rounding loss:": [
         ""
       ],
       "Earliest expiration (for deposit): %1$s": [
@@ -507,10 +507,10 @@ strings['it'] = {
       "You do not have any funds from an exchange that is accepted by this 
merchant. None of the exchanges accepted by the merchant is known to your 
wallet.": [
         ""
       ],
-      "Withdrawal fees: %1$s": [
+      "Withdrawal fees:": [
         ""
       ],
-      "Rounding loss: %1$s": [
+      "Rounding loss:": [
         ""
       ],
       "Earliest expiration (for deposit): %1$s": [
diff --git a/src/i18n/taler-wallet-webex.pot b/src/i18n/taler-wallet-webex.pot
index c6fddd9c..b08b98b8 100644
--- a/src/i18n/taler-wallet-webex.pot
+++ b/src/i18n/taler-wallet-webex.pot
@@ -42,13 +42,13 @@ msgstr ""
 msgid "Exchanges in the wallet:"
 msgstr ""
 
-#: src/webex/pages/confirm-contract.tsx:142
+#: src/webex/pages/confirm-contract.tsx:154
 #, c-format
 msgid "You have insufficient funds of the requested currency in your wallet."
 msgstr ""
 
 #. tslint:disable-next-line:max-line-length
-#: src/webex/pages/confirm-contract.tsx:144
+#: src/webex/pages/confirm-contract.tsx:156
 #, c-format
 msgid ""
 "You do not have any funds from an exchange that is accepted by this "
@@ -56,67 +56,67 @@ msgid ""
 "wallet."
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:204
+#: src/webex/pages/confirm-create-reserve.tsx:206
 #, c-format
-msgid "Withdrawal fees: %1$s"
+msgid "Withdrawal fees:"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:205
+#: src/webex/pages/confirm-create-reserve.tsx:207
 #, c-format
-msgid "Rounding loss: %1$s"
+msgid "Rounding loss:"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:206
+#: src/webex/pages/confirm-create-reserve.tsx:208
 #, c-format
 msgid "Earliest expiration (for deposit): %1$s"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:211
+#: src/webex/pages/confirm-create-reserve.tsx:213
 #, c-format
 msgid "# Coins"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:212
+#: src/webex/pages/confirm-create-reserve.tsx:214
 #, c-format
 msgid "Value"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:213
+#: src/webex/pages/confirm-create-reserve.tsx:215
 #, c-format
 msgid "Withdraw Fee"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:214
+#: src/webex/pages/confirm-create-reserve.tsx:216
 #, c-format
 msgid "Refresh Fee"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:215
+#: src/webex/pages/confirm-create-reserve.tsx:217
 #, c-format
 msgid "Deposit Fee"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:268
+#: src/webex/pages/confirm-create-reserve.tsx:270
 #, c-format
 msgid "Select"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:284
+#: src/webex/pages/confirm-create-reserve.tsx:286
 #, c-format
 msgid "Error: URL may not be relative"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:352
+#: src/webex/pages/confirm-create-reserve.tsx:354
 #, c-format
 msgid "The exchange is trusted by the wallet.\n"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:358
+#: src/webex/pages/confirm-create-reserve.tsx:360
 #, c-format
 msgid "The exchange is audited by a trusted auditor.\n"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:364
+#: src/webex/pages/confirm-create-reserve.tsx:366
 #, c-format
 msgid ""
 "Warning:  The exchange is neither directly trusted nor audited by a trusted "
@@ -124,7 +124,7 @@ msgid ""
 "If you withdraw from this exchange, it will be trusted in the future.\n"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:373
+#: src/webex/pages/confirm-create-reserve.tsx:375
 #, c-format
 msgid ""
 "Using exchange provider%1$s.\n"
@@ -132,161 +132,161 @@ msgid ""
 " %2$s in fees.\n"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:387
+#: src/webex/pages/confirm-create-reserve.tsx:389
 #, c-format
 msgid ""
 "Waiting for a response from\n"
 " %1$s"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:398
+#: src/webex/pages/confirm-create-reserve.tsx:400
 #, c-format
 msgid "A problem occured, see below. %1$s"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:404
+#: src/webex/pages/confirm-create-reserve.tsx:406
 #, c-format
 msgid ""
 "Information about fees will be available when an exchange provider is "
 "selected."
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:416
+#: src/webex/pages/confirm-create-reserve.tsx:418
 #, c-format
 msgid "Accept fees and withdraw"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:421
+#: src/webex/pages/confirm-create-reserve.tsx:423
 #, c-format
 msgid "Change Exchange Provider"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:477
+#: src/webex/pages/confirm-create-reserve.tsx:479
 #, c-format
 msgid "You are about to withdraw %1$s from your bank account into your wallet."
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:560
+#: src/webex/pages/confirm-create-reserve.tsx:562
 #, c-format
 msgid ""
 "Oops, something went wrong. The wallet responded with error status (%1$s)."
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:571
+#: src/webex/pages/confirm-create-reserve.tsx:573
 #, c-format
 msgid "Checking URL, please wait ..."
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:585
+#: src/webex/pages/confirm-create-reserve.tsx:587
 #, c-format
 msgid "Can't parse amount: %1$s"
 msgstr ""
 
-#: src/webex/pages/confirm-create-reserve.tsx:592
+#: src/webex/pages/confirm-create-reserve.tsx:594
 #, c-format
 msgid "Can't parse wire_types: %1$s"
 msgstr ""
 
 #. TODO:generic error reporting function or component.
-#: src/webex/pages/confirm-create-reserve.tsx:612
+#: src/webex/pages/confirm-create-reserve.tsx:614
 #, c-format
 msgid "Fatal error: \"%1$s\"."
 msgstr ""
 
-#: src/webex/pages/popup.tsx:161
+#: src/webex/pages/popup.tsx:160
 #, c-format
 msgid "Balance"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:164
+#: src/webex/pages/popup.tsx:163
 #, c-format
 msgid "History"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:167
+#: src/webex/pages/popup.tsx:166
 #, c-format
 msgid "Debug"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:243
+#: src/webex/pages/popup.tsx:242
 #, c-format
 msgid "help"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:248
+#: src/webex/pages/popup.tsx:247
 #, c-format
 msgid ""
 "You have no balance to show. Need some\n"
 " %1$s getting started?\n"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:265
+#: src/webex/pages/popup.tsx:264
 #, c-format
 msgid "%1$s incoming\n"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:278
+#: src/webex/pages/popup.tsx:277
 #, c-format
 msgid "%1$s being spent\n"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:304
+#: src/webex/pages/popup.tsx:303
 #, c-format
 msgid "Error: could not retrieve balance information."
 msgstr ""
 
-#: src/webex/pages/popup.tsx:343
+#: src/webex/pages/popup.tsx:342
 #, c-format
 msgid ""
 "Bank requested reserve (%1$s) for\n"
 " %2$s.\n"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:354
+#: src/webex/pages/popup.tsx:353
 #, c-format
 msgid ""
 "Started to withdraw\n"
 " %1$s from%2$s(%3$s).\n"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:364
+#: src/webex/pages/popup.tsx:363
 #, c-format
 msgid "Merchant%1$soffered contract%2$s;\n"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:374
+#: src/webex/pages/popup.tsx:373
 #, c-format
 msgid "Withdrew%1$sfrom%2$s(%3$s).\n"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:384
+#: src/webex/pages/popup.tsx:383
 #, c-format
 msgid ""
 "Paid%1$sto merchant%2$s.\n"
 " (%3$s)\n"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:393
+#: src/webex/pages/popup.tsx:392
 #, c-format
 msgid "Unknown event (%1$s)"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:436
+#: src/webex/pages/popup.tsx:435
 #, c-format
 msgid "Error: could not retrieve event history"
 msgstr ""
 
-#: src/webex/pages/popup.tsx:470
+#: src/webex/pages/popup.tsx:469
 #, c-format
 msgid "Your wallet has no events recorded."
 msgstr ""
 
-#: src/webex/renderHtml.tsx:49
+#: src/webex/renderHtml.tsx:51
 #, c-format
 msgid "The merchant%1$swants to enter a contract over%2$s with you.\n"
 msgstr ""
 
-#: src/webex/renderHtml.tsx:54
+#: src/webex/renderHtml.tsx:56
 #, c-format
 msgid "You are about to purchase:"
 msgstr ""
diff --git a/src/memidb-test.ts b/src/memidb-test.ts
new file mode 100644
index 00000000..8f8498a6
--- /dev/null
+++ b/src/memidb-test.ts
@@ -0,0 +1,114 @@
+/*
+ This file is part of TALER
+ (C) 2017 Inria and 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 {test} from "ava";
+import * as memidb from "./memidb";
+
+test.cb("db open", (t) => {
+  let ncb = 0;
+  const idb = new memidb.MemoryIDBFactory();
+  const req = idb.open("testdb");
+  let called = false;
+  req.onupgradeneeded = (evt) => {
+    ncb += 1;
+    called = true;
+    t.is(req.result, evt.target);
+    t.is(evt.oldVersion, 0);
+    t.is(evt.newVersion, 1);
+    t.truthy(req.result);
+    t.pass();
+  }
+  req.onsuccess = (evt) => {
+    t.is(ncb, 1);
+    t.is(req.result, evt.target);
+    t.truthy(req.result);
+    t.end();
+  }
+});
+
+test.cb("store creation", (t) => {
+  const idb = new memidb.MemoryIDBFactory();
+  const req = idb.open("testdb");
+  req.onupgradeneeded = (evt) => {
+    const db: IDBDatabase = req.result;
+
+    const store1 = db.createObjectStore("b-store");
+    t.is(store1.name, "b-store");
+    t.deepEqual(Array.from(db.objectStoreNames), ["b-store"]);
+
+    const store2 = db.createObjectStore("a-store");
+    t.is(store2.name, "a-store");
+    t.deepEqual(Array.from(db.objectStoreNames), ["a-store", "b-store"]);
+
+    const store3 = db.createObjectStore("c-store");
+    t.is(store3.name, "c-store");
+    t.deepEqual(Array.from(db.objectStoreNames), ["a-store", "b-store", 
"c-store"]);
+    t.pass();
+  }
+  req.onsuccess = (evt) => {
+    t.end();
+  }
+});
+
+
+test.cb("put and get", (t) => {
+  const idb = new memidb.MemoryIDBFactory();
+  const req = idb.open("testdb");
+  req.onupgradeneeded = (evt) => {
+    const db: IDBDatabase = req.result;
+    const store1 = db.createObjectStore("mystore");
+    store1.put({answer: 42}, "a");
+  }
+  req.onsuccess = (evt) => {
+    t.end()
+  }
+});
+
+
+test("key path evaluation", (t) => {
+  const obj = {
+    a: {
+      b: {
+        c: 42,
+      },
+    },
+    b: "hello",
+    "": "spam",
+    arr: ["foo", "bar"],
+  }
+  t.deepEqual(memidb.evaluateKeyPath(obj, ""), obj);
+  t.deepEqual(memidb.evaluateKeyPath(obj, "a.b.c"), 42);
+  t.deepEqual(memidb.evaluateKeyPath(obj, "a.b"), {c: 42});
+  t.deepEqual(memidb.evaluateKeyPath(obj, "foo"), undefined);
+  t.deepEqual(memidb.evaluateKeyPath(obj, ["a.b.c", "foo"]), undefined);
+  t.deepEqual(memidb.evaluateKeyPath(obj, ["a.b.c", "b"]), [42, "hello"]);
+  t.deepEqual(memidb.evaluateKeyPath(obj, "arr.0"), "foo");
+  t.deepEqual(memidb.evaluateKeyPath(obj, "."), "spam");
+});
+
+test("key path evaluation with replacement", (t) => {
+  const obj: any = {
+    a: {
+      b: {
+        c: 42,
+      },
+    },
+  }
+  memidb.evaluateKeyPath(obj, "a.b.c", 24);
+  t.is(obj.a.b.c, 24);
+  memidb.evaluateKeyPath(obj, "a.b", 24);
+  t.is(obj.a.b, 24);
+});
diff --git a/src/memidb.ts b/src/memidb.ts
index 36607d71..14a1efbe 100644
--- a/src/memidb.ts
+++ b/src/memidb.ts
@@ -29,20 +29,25 @@
 
 
 const structuredClone = require("structured-clone");
-const structuredSerialize = require("structured-clone").serialize;
 
 
-interface StoredObject {
-  key: any;
-  object: string;
-}
-
 interface Store {
   name: string;
-  keyPath: string | string[];
+  keyPath?: string | string[];
   keyGenerator: number;
   autoIncrement: boolean;
-  objects: { [strKey: string]: StoredObject };
+  objects: { [primaryKey: string]: any };
+  indices: { [indexName: string]: Index };
+}
+
+interface Index {
+  multiEntry: boolean;
+  unique: boolean;
+
+  /**
+   * Map the index's key to the primary key.
+   */
+  map: { [indexKey: string]: string[] };
 }
 
 
@@ -80,11 +85,80 @@ class MyDomStringList extends Array<string> implements 
DOMStringList {
 }
 
 
-function callEventHandler(h: EventListenerOrEventListenerObject, evt: Event, 
target: any) {
-  if ("handleEvent" in h) {
-    (h as EventListenerObject).handleEvent(evt);
-  } else {
-    (h as EventListener).call(target, evt);
+class MyKeyRange implements IDBKeyRange {
+  static only(value: any): IDBKeyRange {
+    return new MyKeyRange(value, value, false, false);
+  }
+
+  static bound(lower: any, upper: any, lowerOpen: boolean = false, upperOpen: 
boolean = false) {
+    return new MyKeyRange(lower, upper, lowerOpen, upperOpen);
+  }
+
+  static lowerBound(lower: any, lowerOpen: boolean = false) {
+    return new MyKeyRange(lower, undefined, lowerOpen, true);
+  }
+
+  static upperBound(upper: any, upperOpen: boolean = false) {
+    return new MyKeyRange(undefined, upper, true, upperOpen);
+  }
+
+  constructor(public lower: any, public upper: any, public lowerOpen: boolean, 
public upperOpen: boolean) {
+  }
+}
+
+
+/**
+ * Type guard for an IDBKeyRange.
+ */
+export function isKeyRange(obj: any): obj is IDBKeyRange {
+  return (typeof obj === "object" &&
+          "lower" in obj && "upper" in obj &&
+          "lowerOpen" in obj && "upperOpen" in obj);
+}
+
+
+class IndexHandle implements IDBIndex {
+
+  _unique: boolean;
+  _multiEntry: boolean;
+
+  get keyPath(): string | string[] {
+    throw Error("not implemented");
+  }
+
+  get name () {
+    return this.indexName;
+  }
+
+  get unique() {
+    return this._unique;
+  }
+
+  get multiEntry() {
+    return this._multiEntry;
+  }
+
+  constructor(public objectStore: MyObjectStore, public indexName: string) {
+  }
+
+  count(key?: IDBKeyRange | IDBValidKey): IDBRequest {
+    throw Error("not implemented");
+  }
+
+  get(key: IDBKeyRange | IDBValidKey): IDBRequest {
+    throw Error("not implemented");
+  }
+
+  getKey(key: IDBKeyRange | IDBValidKey): IDBRequest {
+    throw Error("not implemented");
+  }
+
+  openCursor(range?: IDBKeyRange | IDBValidKey, direction?: 
IDBCursorDirection): IDBRequest {
+    throw Error("not implemented");
+  }
+
+  openKeyCursor(range?: IDBKeyRange | IDBValidKey, direction?: 
IDBCursorDirection): IDBRequest {
+    throw Error("not implemented");
   }
 }
 
@@ -92,9 +166,10 @@ class MyRequest implements IDBRequest {
   onerror: (this: IDBRequest, ev: Event) => any;
 
   onsuccess: (this: IDBRequest, ev: Event) => any;
-  successHandlers: Array<(this: IDBRequest, ev: Event) => any>;
+  successHandlers: Array<(this: IDBRequest, ev: Event) => any> = [];
 
   done: boolean = false;
+  _result: any;
 
   constructor(public _transaction: Transaction, public runner: () => void) {
   }
@@ -113,7 +188,7 @@ class MyRequest implements IDBRequest {
   }
 
   get result(): any {
-    return null;
+    return this._result;
   }
 
   get source() {
@@ -188,17 +263,78 @@ class OpenDBRequest extends MyRequest implements 
IDBOpenDBRequest {
   }
 }
 
+function follow(x: any, s: string, replacement?: any): any {
+  if (s === "") {
+    return x;
+  }
+  const ptIdx = s.indexOf(".");
+  if (ptIdx < 0) {
+    const v = x[s];
+    if (replacement !== undefined) {
+      x[s] = replacement;
+    }
+    return v;
+  } else {
+    const identifier = s.substring(0, ptIdx);
+    const rest = s.substring(ptIdx + 1);
+    return follow(x[identifier], rest, replacement);
+  }
+}
+
+export function evaluateKeyPath(x: any, path: string | string[], replacement?: 
any): any {
+  if (typeof path === "string") {
+    return follow(x, path, replacement);
+  } else if (Array.isArray(path)) {
+    const res: any[] = [];
+    for (let s of path) {
+      let c = follow(x, s, replacement);
+      if (c === undefined) {
+        return undefined;
+      }
+      res.push(c);
+    }
+    return res;
+  } else {
+    throw Error("invalid key path, must be string or array of strings");
+  }
+}
+
+function stringifyKey(key: any) {
+  return JSON.stringify(key);
+}
+
+export function isValidKey(key: any, memo: any[] = []) {
+  if (typeof key === "string" || typeof key === "number" || key instanceof 
Date) {
+    return true;
+  }
+  if (Array.isArray(key)) {
+    for (const element of key) {
+      if (!isValidKey(element, memo.concat([key]))) {
+        return false;
+      }
+    }
+    return true;
+  }
+  return false;
+}
 
 class MyObjectStore implements IDBObjectStore  {
+
+  _keyPath: string | string[] | undefined;
+  _autoIncrement: boolean;
+
   get indexNames() {
     return new DOMStringList();
   }
 
-  constructor(public transaction: Transaction, public dbName: string, public 
storeName: string) {
+  constructor(public transaction: Transaction, public storeName: string) {
+    this._keyPath = 
this.transaction.transactionDbData.stores[this.storeName].keyPath as (string | 
string[]);
+    this._autoIncrement = 
this.transaction.transactionDbData.stores[this.storeName].autoIncrement;
   }
 
-  get keyPath() {
-    return this.transaction.db.dbData.stores[this.storeName].keyPath;
+  get keyPath(): string | string[] {
+    // TypeScript definitions are wrong here and don't permit a null keyPath
+    return this._keyPath as (string | string[]);
   }
 
   get name() {
@@ -206,15 +342,77 @@ class MyObjectStore implements IDBObjectStore  {
   }
 
   get autoIncrement() {
-    return this.transaction.db.dbData.stores[this.storeName].autoIncrement;
+    return this._autoIncrement;
   }
 
-  add(value: any, key?: any): IDBRequest {
-    throw Error("not implemented");
+  storeImpl(originalValue: any, key: any|undefined, allowExisting: boolean) {
+    if (this.transaction.mode === "readonly") {
+      throw Error();
+    }
+    if (!this.transaction.active) {
+      throw Error();
+    }
+    if 
(!this.transaction.transactionDbData.stores.hasOwnProperty(this.storeName)) {
+      throw Error("object store was deleted");
+    }
+
+    const store = this.transaction.transactionDbData.stores[this.storeName];
+
+    const value = structuredClone(originalValue);
+
+    if (this.keyPath) {
+      // we're dealine with in-line keys
+      if (key) {
+        throw Error("keys not allowed with in-line keys");
+      }
+      key = evaluateKeyPath(value, this.keyPath);
+      if (!key && !this.autoIncrement) {
+        throw Error("key path must evaluate to key for in-line stores without 
autoIncrement");
+      }
+      if (this.autoIncrement) {
+        if (key && typeof key === "number") {
+          store.keyGenerator = key + 1;
+        } else {
+          key = store.keyGenerator;
+          store.keyGenerator += 1;
+          evaluateKeyPath(value, this.keyPath, key);
+        }
+      }
+    } else {
+      // we're dealing with out-of-line keys
+      if (!key && !this.autoIncrement) {
+        throw Error("key must be provided for out-of-line stores without 
autoIncrement");
+      }
+      key = this.transaction.transactionDbData.stores
+      if (this.autoIncrement) {
+        if (key && typeof key === "number") {
+          store.keyGenerator = key + 1;
+        } else {
+          key = store.keyGenerator;
+          store.keyGenerator += 1;
+        }
+      }
+    }
+
+    const stringKey = stringifyKey(key);
+
+    if (store.objects.hasOwnProperty(stringKey) && !allowExisting) {
+      throw Error("key already exists");
+    }
+
+    store.objects[stringKey] = value;
+
+    const req = new MyRequest(this.transaction, () => {
+    });
+    return req;
   }
 
   put(value: any, key?: any): IDBRequest {
-    throw Error("not implemented");
+    return this.storeImpl(value, key, true);
+  }
+
+  add(value: any, key?: any): IDBRequest {
+    return this.storeImpl(value, key, false);
   }
 
   delete(key: any): IDBRequest {
@@ -242,7 +440,7 @@ class MyObjectStore implements IDBObjectStore  {
   }
 
   index(indexName: string): IDBIndex {
-    throw Error("not implemented");
+    return new IndexHandle(this, indexName);
   }
 
   openCursor(range?: IDBKeyRange | IDBValidKey, direction?: 
IDBCursorDirection): IDBRequest {
@@ -257,19 +455,31 @@ class Db implements IDBDatabase {
   onerror: (this: IDBDatabase, ev: Event) => any;
   onversionchange: (ev: IDBVersionChangeEvent) => any;
 
+  _storeNames: string[] = [];
+
   constructor(private _name: string, private _version: number, private 
factory: MemoryIDBFactory) {
+    for (let storeName in this.dbData.stores) {
+      if (this.dbData.stores.hasOwnProperty(storeName)) {
+        this._storeNames.push(storeName);
+      }
+    }
+    this._storeNames.sort();
   }
 
-  get dbData() {
+  get dbData(): Database {
     return this.factory.data[this._name];
   }
 
+  set dbData(data) {
+    this.factory.data[this._name] = data;
+  }
+
   get name() {
     return this._name;
   }
 
   get objectStoreNames() {
-    return new MyDomStringList();
+    return new MyDomStringList(...this._storeNames);
   }
 
   get version() {
@@ -284,11 +494,44 @@ class Db implements IDBDatabase {
     if (tx.mode !== "versionchange") {
       throw Error("invalid mode");
     }
-    throw Error("not implemented");
+
+    const td = tx.transactionDbData;
+    if (td.stores[name]) {
+      throw Error("object store already exists");
+    }
+
+    td.stores[name] = {
+      autoIncrement: !!(optionalParameters && 
optionalParameters.autoIncrement),
+      indices: {},
+      keyGenerator: 1,
+      name,
+      objects: [],
+    };
+
+    this._storeNames.push(name);
+    this._storeNames.sort();
+
+    return new MyObjectStore(tx, name);
   }
 
   deleteObjectStore(name: string): void {
-    throw Error("not implemented");
+    let tx = this.factory.getTransaction();
+    if (tx.mode !== "versionchange") {
+      throw Error("invalid mode");
+    }
+
+    const td = tx.transactionDbData;
+    if (td.stores[name]) {
+      throw Error("object store does not exists");
+    }
+
+    const idx = this._storeNames.indexOf(name);
+    if (idx < 0) {
+      throw Error();
+    }
+    this._storeNames.splice(idx, 1);
+    
+    delete td.stores[name];
   }
 
   transaction(storeNames: string | string[], mode: IDBTransactionMode = 
"readonly"): IDBTransaction {
@@ -342,11 +585,30 @@ class Transaction implements IDBTransaction {
     return this._mode;
   }
 
+  get active(): boolean {
+    return this.state === TransactionState.Running || this.state === 
TransactionState.Created;
+  }
+
   start() {
     if (this.state != TransactionState.Created) {
       throw Error();
     }
+    this.state = TransactionState.Running;
     this._transactionDbData = structuredClone(this.dbHandle.dbData);
+    if (!this._transactionDbData) {
+      throw Error();
+    }
+  }
+
+  commit() {
+    if (this.state != TransactionState.Running) {
+      throw Error();
+    }
+    if (!this._transactionDbData) {
+      throw Error();
+    }
+    this.state = TransactionState.Commited;
+    this.dbHandle.dbData = this._transactionDbData;
   }
 
   get error(): DOMException {
@@ -373,7 +635,7 @@ class Transaction implements IDBTransaction {
   }
 
   objectStore(storeName: string): IDBObjectStore {
-    return new MyObjectStore(this, this.dbName, storeName);
+    return new MyObjectStore(this, storeName);
   }
 
   dispatchEvent(evt: Event): boolean {
@@ -425,8 +687,9 @@ class MyEvent implements Event {
   _timeStamp: number = 0;
   _type: string;
 
-  constructor(typeArg: string) {
+  constructor(typeArg: string, target: any) {
     this._type = typeArg;
+    this._target = target;
   }
 
   get eventPhase() {
@@ -519,10 +782,10 @@ class MyEvent implements Event {
 class VersionChangeEvent extends MyEvent {
   _newVersion: number|null;
   _oldVersion: number;
-  constructor(oldVersion: number, newVersion?: number) {
-    super("VersionChange");
+  constructor(oldVersion: number, newVersion: number|null, target: any) {
+    super("VersionChange", target);
     this._oldVersion = oldVersion;
-    this._newVersion = newVersion || null;
+    this._newVersion = newVersion;
   }
 
   get newVersion() {
@@ -535,7 +798,7 @@ class VersionChangeEvent extends MyEvent {
 }
 
 
-class MemoryIDBFactory implements IDBFactory {
+export class MemoryIDBFactory implements IDBFactory {
   data: Databases = {};
 
   currentRequest: MyRequest|undefined;
@@ -570,7 +833,7 @@ class MemoryIDBFactory implements IDBFactory {
         // auto-commit the transaction that the
         // previous request worked on.
         let lastTx = prevRequest._transaction;
-        this.data[lastTx.dbName] = lastTx.transactionDbData;
+        lastTx.commit();
       }
     };
     alreadyResolved.then(() => {
@@ -604,13 +867,19 @@ class MemoryIDBFactory implements IDBFactory {
     }
 
     let upgradeNeeded = false;
+    let oldVersion: number;
     let mydb: Database;
     if (dbName in this.data) {
       mydb = this.data[dbName];
+      if (!mydb) {
+        throw Error();
+      }
+      oldVersion = mydb.version;
       if (version === undefined || version == mydb.version) {
         // we can open without upgrading
       } else if (version > mydb.version) {
         upgradeNeeded = true;
+        mydb.version = version;
       } else {
         throw Error("version error");
       }
@@ -621,17 +890,21 @@ class MemoryIDBFactory implements IDBFactory {
         version: (version || 1),
       };
       upgradeNeeded = true;
+      oldVersion = 0;
     }
 
+    this.data[dbName] = mydb;
+
     const db = new Db(dbName, mydb.version, this);
     const tx = new Transaction(dbName, db, "versionchange");
 
     const req = new OpenDBRequest(tx, () => {
+      req._result = db;
       if (upgradeNeeded) {
-        let versionChangeEvt = new VersionChangeEvent(mydb.version, version);
+        let versionChangeEvt = new VersionChangeEvent(oldVersion, 
mydb.version, db);
         req.callOnupgradeneeded(versionChangeEvt);
       }
-      let successEvent = new MyEvent("success");
+      let successEvent = new MyEvent("success", db);
       req.callSuccess(successEvent);
     });
 
@@ -640,3 +913,10 @@ class MemoryIDBFactory implements IDBFactory {
     return req;
   }
 }
+
+/**
+ * Inject our IndexedDb implementation in the global namespace,
+ * potentially replacing an existing implementation.
+ */
+export function injectGlobals() {
+}
diff --git a/src/webex/pages/confirm-contract.tsx 
b/src/webex/pages/confirm-contract.tsx
index 7d552341..fa71b102 100644
--- a/src/webex/pages/confirm-contract.tsx
+++ b/src/webex/pages/confirm-contract.tsx
@@ -107,16 +107,24 @@ interface ContractPromptState {
   payDisabled: boolean;
   alreadyPaid: boolean;
   exchanges: null|ExchangeRecord[];
+  /**
+   * Don't request updates to proposal state while
+   * this is set to true, to avoid UI flickering
+   * when pressing pay.
+   */
+  holdCheck: boolean;
 }
 
 class ContractPrompt extends React.Component<ContractPromptProps, 
ContractPromptState> {
   constructor() {
     super();
     this.state = {
+      alreadyPaid: false,
       error: null,
       exchanges: null,
       proposal: null,
       payDisabled: true,
+      holdCheck: false,
     };
   }
 
@@ -137,6 +145,10 @@ class ContractPrompt extends 
React.Component<ContractPromptProps, ContractPrompt
   }
 
   async checkPayment() {
+    window.setTimeout(() => this.checkPayment(), 500);
+    if (this.state.holdCheck) {
+      return;
+    }
     const payStatus = await wxApi.checkPay(this.props.proposalId);
     if (payStatus === "insufficient-balance") {
       const msgInsufficient = i18n.str`You have insufficient funds of the 
requested currency in your wallet.`;
@@ -159,11 +171,11 @@ class ContractPrompt extends 
React.Component<ContractPromptProps, ContractPrompt
     } else {
       this.setState({payDisabled: false, error: null});
     }
-    window.setTimeout(() => this.checkPayment(), 500);
   }
 
   async doPayment() {
     const proposal = this.state.proposal;
+    this.setState({holdCheck: true});
     if (!proposal) {
       return;
     }
@@ -177,6 +189,7 @@ class ContractPrompt extends 
React.Component<ContractPromptProps, ContractPrompt
         document.location.href = proposal.contractTerms.fulfillment_url;
         break;
     }
+    this.setState({holdCheck: false});
   }
 
 
diff --git a/src/webex/pages/confirm-create-reserve.tsx 
b/src/webex/pages/confirm-create-reserve.tsx
index 6e1cc4a8..a695d935 100644
--- a/src/webex/pages/confirm-create-reserve.tsx
+++ b/src/webex/pages/confirm-create-reserve.tsx
@@ -22,7 +22,7 @@
  * @author Florian Dold
  */
 
-import {amountToPretty, canonicalizeBaseUrl} from "../../helpers";
+import {canonicalizeBaseUrl} from "../../helpers";
 import * as i18n from "../../i18n";
 import {
   AmountJson,
@@ -40,6 +40,8 @@ import {
   getReserveCreationInfo,
 } from "../wxApi";
 
+import {renderAmount} from "../renderHtml";
+
 import * as React from "react";
 import * as ReactDOM from "react-dom";
 import URI = require("urijs");
@@ -163,10 +165,10 @@ function renderReserveCreationDetails(rci: 
ReserveCreationInfo|null) {
     return (
       <tr>
         <td>{countByPub[denom.denomPub] + "x"}</td>
-        <td>{amountToPretty(denom.value)}</td>
-        <td>{amountToPretty(denom.feeWithdraw)}</td>
-        <td>{amountToPretty(denom.feeRefresh)}</td>
-        <td>{amountToPretty(denom.feeDeposit)}</td>
+        <td>{renderAmount(denom.value)}</td>
+        <td>{renderAmount(denom.feeWithdraw)}</td>
+        <td>{renderAmount(denom.feeRefresh)}</td>
+        <td>{renderAmount(denom.feeDeposit)}</td>
       </tr>
     );
   }
@@ -187,22 +189,22 @@ function renderReserveCreationDetails(rci: 
ReserveCreationInfo|null) {
       {rci!.wireFees.feesForType[s].map((f) => (
         <tr>
           <td>{moment.unix(f.endStamp).format("llll")}</td>
-          <td>{amountToPretty(f.wireFee)}</td>
-          <td>{amountToPretty(f.closingFee)}</td>
+          <td>{renderAmount(f.wireFee)}</td>
+          <td>{renderAmount(f.closingFee)}</td>
         </tr>
       ))}
       </tbody>,
     ];
   }
 
-  const withdrawFeeStr = amountToPretty(rci.withdrawFee);
-  const overheadStr = amountToPretty(rci.overhead);
+  const withdrawFee = renderAmount(rci.withdrawFee);
+  const overhead = renderAmount(rci.overhead);
 
   return (
     <div>
       <h3>Overview</h3>
-      <p>{i18n.str`Withdrawal fees: ${withdrawFeeStr}`}</p>
-      <p>{i18n.str`Rounding loss: ${overheadStr}`}</p>
+      <p>{i18n.str`Withdrawal fees:`} {withdrawFee}</p>
+      <p>{i18n.str`Rounding loss:`} {overhead}</p>
       <p>{i18n.str`Earliest expiration (for deposit): 
${moment.unix(rci.earliestDepositExpiration).fromNow()}`}</p>
       <h3>Coin Fees</h3>
       <table className="pure-table">
@@ -374,7 +376,7 @@ class ExchangeSelection extends 
ImplicitStateComponent<ExchangeSelectionProps> {
           Using exchange provider <strong>{this.url()}</strong>.
           The exchange provider will charge
           {" "}
-          <span>{amountToPretty(totalCost)}</span>
+          <span>{renderAmount(totalCost)}</span>
           {" "}
           in fees.
         </i18n.Translate>
@@ -476,7 +478,7 @@ class ExchangeSelection extends 
ImplicitStateComponent<ExchangeSelectionProps> {
       <div>
         <i18n.Translate wrap="p">
           {"You are about to withdraw "}
-          <strong>{amountToPretty(this.props.amount)}</strong>
+          <strong>{renderAmount(this.props.amount)}</strong>
           {" from your bank account into your wallet."}
         </i18n.Translate>
         {this.selectingExchange() ? this.renderSelect() : this.renderConfirm()}
diff --git a/src/webex/pages/payback.tsx b/src/webex/pages/payback.tsx
index 4aadf5ad..51ad8612 100644
--- a/src/webex/pages/payback.tsx
+++ b/src/webex/pages/payback.tsx
@@ -24,7 +24,7 @@
 /**
  * Imports.
  */
-import { amountToPretty } from "../../helpers";
+import { renderAmount } from "../renderHtml";
 import {
   ReserveRecord,
 } from "../../types";
@@ -73,7 +73,7 @@ class Payback extends ImplicitStateComponent<any> {
       <div>
         {reserves.map((r) => (
           <div>
-            <h2>Reserve for ${amountToPretty(r.current_amount!)}</h2>
+            <h2>Reserve for ${renderAmount(r.current_amount!)}</h2>
             <ul>
               <li>Exchange: ${r.exchange_base_url}</li>
             </ul>
diff --git a/src/webex/pages/popup.tsx b/src/webex/pages/popup.tsx
index 54e4f3e2..ee9c8023 100644
--- a/src/webex/pages/popup.tsx
+++ b/src/webex/pages/popup.tsx
@@ -25,7 +25,6 @@
 /**
  * Imports.
  */
-import { amountToPretty } from "../../helpers";
 import * as i18n from "../../i18n";
 import {
   AmountJson,
@@ -36,7 +35,7 @@ import {
   WalletBalanceEntry,
 } from "../../types";
 
-import { abbrev } from "../renderHtml";
+import { abbrev, renderAmount } from "../renderHtml";
 
 import * as React from "react";
 import * as ReactDOM from "react-dom";
@@ -258,15 +257,15 @@ class WalletBalanceView extends React.Component<any, any> 
{
     let incoming: JSX.Element | undefined;
     let payment: JSX.Element | undefined;
 
-    console.log("available: ", entry.pendingIncoming ? 
amountToPretty(entry.available) : null);
-    console.log("incoming: ", entry.pendingIncoming ? 
amountToPretty(entry.pendingIncoming) : null);
+    console.log("available: ", entry.pendingIncoming ? 
renderAmount(entry.available) : null);
+    console.log("incoming: ", entry.pendingIncoming ? 
renderAmount(entry.pendingIncoming) : null);
 
     if (Amounts.isNonZero(entry.pendingIncoming)) {
       incoming = (
         <i18n.Translate wrap="span">
           <span style={{color: "darkgreen"}}>
             {"+"}
-            {amountToPretty(entry.pendingIncoming)}
+            {renderAmount(entry.pendingIncoming)}
           </span>
           {" "}
           incoming
@@ -278,7 +277,7 @@ class WalletBalanceView extends React.Component<any, any> {
       payment = (
         <i18n.Translate wrap="span">
           <span style={{color: "darkblue"}}>
-            {amountToPretty(entry.pendingPayment)}
+            {renderAmount(entry.pendingPayment)}
           </span>
           {" "}
           being spent
@@ -344,7 +343,7 @@ function formatHistoryItem(historyItem: HistoryRecord) {
         <i18n.Translate wrap="p">
           Bank requested reserve (<span>{abbrev(d.reservePub)}</span>) for
           {" "}
-          <span>{amountToPretty(d.requestedAmount)}</span>.
+          <span>{renderAmount(d.requestedAmount)}</span>.
         </i18n.Translate>
       );
     case "confirm-reserve": {
@@ -354,7 +353,7 @@ function formatHistoryItem(historyItem: HistoryRecord) {
       return (
         <i18n.Translate wrap="p">
           Started to withdraw
-          {" "}{amountToPretty(d.requestedAmount)}{" "}
+          {" "}{renderAmount(d.requestedAmount)}{" "}
           from <span>{exchange}</span> (<span>{pub}</span>).
         </i18n.Translate>
       );
@@ -369,7 +368,7 @@ function formatHistoryItem(historyItem: HistoryRecord) {
     }
     case "depleted-reserve": {
       const exchange = d.exchangeBaseUrl ? (new URI(d.exchangeBaseUrl)).host() 
: "??";
-      const amount = amountToPretty(d.requestedAmount);
+      const amount = renderAmount(d.requestedAmount);
       const pub = abbrev(d.reservePub);
       return (
         <i18n.Translate wrap="p">
@@ -383,7 +382,7 @@ function formatHistoryItem(historyItem: HistoryRecord) {
       const fulfillmentLinkElem = <a href={url} onClick={openTab(url)}>view 
product</a>;
       return (
         <i18n.Translate wrap="p">
-          Paid <span>{amountToPretty(d.amount)}</span> to merchant 
<span>{merchantElem}</span>.
+          Paid <span>{renderAmount(d.amount)}</span> to merchant 
<span>{merchantElem}</span>.
           {" "}
           (<span>{fulfillmentLinkElem}</span>)
         </i18n.Translate>
diff --git a/src/webex/pages/tree.tsx b/src/webex/pages/tree.tsx
index 3eafbbeb..ad1693fb 100644
--- a/src/webex/pages/tree.tsx
+++ b/src/webex/pages/tree.tsx
@@ -21,7 +21,7 @@
  */
 
 
-import { amountToPretty, getTalerStampDate } from "../../helpers";
+import { getTalerStampDate } from "../../helpers";
 import {
   CoinRecord,
   CoinStatus,
@@ -42,6 +42,8 @@ import {
   refresh,
 } from "../wxApi";
 
+import { renderAmount } from "../renderHtml";
+
 import * as React from "react";
 import * as ReactDOM from "react-dom";
 
@@ -57,8 +59,8 @@ class ReserveView extends React.Component<ReserveViewProps, 
void> {
         <ul>
           <li>Key: {r.reserve_pub}</li>
           <li>Created: {(new Date(r.created * 1000).toString())}</li>
-          <li>Current: {r.current_amount ? amountToPretty(r.current_amount!) : 
"null"}</li>
-          <li>Requested: {amountToPretty(r.requested_amount)}</li>
+          <li>Current: {r.current_amount ? renderAmount(r.current_amount!) : 
"null"}</li>
+          <li>Requested: {renderAmount(r.requested_amount)}</li>
           <li>Confirmed: {r.confirmed}</li>
         </ul>
       </div>
@@ -135,7 +137,7 @@ class CoinView extends React.Component<CoinViewProps, void> 
{
       <div className="tree-item">
         <ul>
           <li>Key: {c.coinPub}</li>
-          <li>Current amount: {amountToPretty(c.currentAmount)}</li>
+          <li>Current amount: {renderAmount(c.currentAmount)}</li>
           <li>Denomination: <ExpanderText text={c.denomPub} /></li>
           <li>Suspended: {(c.suspended || false).toString()}</li>
           <li>Status: {CoinStatus[c.status]}</li>
@@ -304,11 +306,11 @@ class DenominationList extends 
ImplicitStateComponent<DenominationListProps> {
       <div className="tree-item">
         <ul>
           <li>Offered: {d.isOffered ? "yes" : "no"}</li>
-          <li>Value: {amountToPretty(d.value)}</li>
-          <li>Withdraw fee: {amountToPretty(d.feeWithdraw)}</li>
-          <li>Refresh fee: {amountToPretty(d.feeRefresh)}</li>
-          <li>Deposit fee: {amountToPretty(d.feeDeposit)}</li>
-          <li>Refund fee: {amountToPretty(d.feeRefund)}</li>
+          <li>Value: {renderAmount(d.value)}</li>
+          <li>Withdraw fee: {renderAmount(d.feeWithdraw)}</li>
+          <li>Refresh fee: {renderAmount(d.feeRefresh)}</li>
+          <li>Deposit fee: {renderAmount(d.feeDeposit)}</li>
+          <li>Refund fee: {renderAmount(d.feeRefund)}</li>
           <li>Start: {getTalerStampDate(d.stampStart)!.toString()}</li>
           <li>Withdraw expiration: 
{getTalerStampDate(d.stampExpireWithdraw)!.toString()}</li>
           <li>Legal expiration: 
{getTalerStampDate(d.stampExpireLegal)!.toString()}</li>
diff --git a/src/webex/renderHtml.tsx b/src/webex/renderHtml.tsx
index 4dd7bade..51f9019e 100644
--- a/src/webex/renderHtml.tsx
+++ b/src/webex/renderHtml.tsx
@@ -27,6 +27,8 @@
 import { amountToPretty } from "../helpers";
 import * as i18n from "../i18n";
 import {
+  AmountJson,
+  Amounts,
   ContractTerms,
 } from "../types";
 
@@ -63,6 +65,16 @@ export function renderContractTerms(contractTerms: 
ContractTerms): JSX.Element {
 
 
 /**
+ * Render amount as HTML, which non-breaking space between
+ * decimal value and currency.
+ */
+export function renderAmount(amount: AmountJson) {
+  const x = amount.value + amount.fraction / Amounts.fractionalBase;
+  return <span>{x}&nbsp;{amount.currency}</span>;
+}
+
+
+/**
  * Abbreviate a string to a given length, and show the full
  * string on hover as a tooltip.
  */
diff --git a/tsconfig.json b/tsconfig.json
index 1f649ef1..dee558cf 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -40,6 +40,7 @@
     "src/i18n.tsx",
     "src/i18n/strings.ts",
     "src/logging.ts",
+    "src/memidb-test.ts",
     "src/memidb.ts",
     "src/query.ts",
     "src/timer.ts",
diff --git a/yarn.lock b/yarn.lock
index 5bd122cb..0769e283 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4610,6 +4610,10 @@ address@hidden:
   version "2.0.1"
   resolved 
"https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a";
 
address@hidden:
+  version "0.2.2"
+  resolved 
"https://registry.yarnpkg.com/structured-clone/-/structured-clone-0.2.2.tgz#ac92b6be31958a643db30f1335abc6a1b02dfdc2";
+
 address@hidden:
   version "0.2.0"
   resolved 
"https://registry.yarnpkg.com/supports-color/-/supports-color-0.2.0.tgz#d92de2694eb3f67323973d7ae3d8b55b4c22190a";

-- 
To stop receiving notification emails like this one, please contact
address@hidden



reply via email to

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