aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2017-10-15 18:30:02 +0200
committerFlorian Dold <florian.dold@gmail.com>2017-10-15 18:30:02 +0200
commit03782f8aea043042aaa069de0b91cdb80fbb4679 (patch)
treecc8890eb060fe15cefa107c9261fc7e558d564b1 /src
parent23633cf1be5ce8f1bc4d7823d1d561149cb6c8a8 (diff)
derive history from db instead of storing it
Diffstat (limited to 'src')
-rw-r--r--src/checkable.ts5
-rw-r--r--src/crypto/cryptoApi-test.ts4
-rw-r--r--src/i18n/de.po38
-rw-r--r--src/i18n/en-US.po38
-rw-r--r--src/i18n/fr.po38
-rw-r--r--src/i18n/it.po38
-rw-r--r--src/i18n/taler-wallet-webex.pot38
-rw-r--r--src/types.ts58
-rw-r--r--src/wallet.ts266
-rw-r--r--src/webex/messages.ts4
-rw-r--r--src/webex/notify.ts16
-rw-r--r--src/webex/pages/popup.tsx23
-rw-r--r--src/webex/pages/refund.tsx6
-rw-r--r--src/webex/pages/tree.tsx2
-rw-r--r--src/webex/wxApi.ts9
-rw-r--r--src/webex/wxBackend.ts12
16 files changed, 307 insertions, 288 deletions
diff --git a/src/checkable.ts b/src/checkable.ts
index e6ef99336..0e437839e 100644
--- a/src/checkable.ts
+++ b/src/checkable.ts
@@ -183,7 +183,7 @@ export namespace Checkable {
if (innerProp.optional) {
continue;
}
- throw new SchemaError(`Property ${innerProp.propertyKey} missing on ${path}`);
+ throw new SchemaError(`Property ${innerProp.propertyKey} missing on ${path} of ${type.name||"??"}`);
}
if (!remainingPropNames.delete(innerProp.propertyKey)) {
throw new SchemaError("assertion failed");
@@ -195,8 +195,7 @@ export namespace Checkable {
}
if (!prop.extraAllowed && remainingPropNames.size !== 0) {
- throw new SchemaError("superfluous properties " + JSON.stringify(Array.from(
- remainingPropNames.values())));
+ throw new SchemaError(`superfluous properties ${JSON.stringify(Array.from(remainingPropNames.values()))} of ${type.name||"??"}`);
}
return obj;
}
diff --git a/src/crypto/cryptoApi-test.ts b/src/crypto/cryptoApi-test.ts
index 7e391cd91..d96d69e40 100644
--- a/src/crypto/cryptoApi-test.ts
+++ b/src/crypto/cryptoApi-test.ts
@@ -81,16 +81,16 @@ test("precoin creation", async (t) => {
const crypto = new CryptoApi();
const {priv, pub} = await crypto.createEddsaKeypair();
const r: ReserveRecord = {
- confirmed: false,
created: 0,
current_amount: null,
exchange_base_url: "https://example.com/exchange",
hasPayback: false,
- last_query: null,
precoin_amount: {currency: "PUDOS", value: 0, fraction: 0},
requested_amount: {currency: "PUDOS", value: 0, fraction: 0},
reserve_priv: priv,
reserve_pub: pub,
+ timestamp_confirmed: 0,
+ timestamp_depleted: 0,
};
const precoin = await crypto.createPreCoin(denomValid1, r);
diff --git a/src/i18n/de.po b/src/i18n/de.po
index 38ce00c45..1318654af 100644
--- a/src/i18n/de.po
+++ b/src/i18n/de.po
@@ -198,105 +198,105 @@ msgstr ""
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:247
+#: src/webex/pages/popup.tsx:246
#, c-format
msgid "help"
msgstr ""
-#: src/webex/pages/popup.tsx:252
+#: src/webex/pages/popup.tsx:251
#, 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:269
+#: src/webex/pages/popup.tsx:268
#, c-format
msgid "%1$s incoming\n"
msgstr ""
-#: src/webex/pages/popup.tsx:282
+#: src/webex/pages/popup.tsx:281
#, c-format
msgid "%1$s being spent\n"
msgstr ""
-#: src/webex/pages/popup.tsx:308
+#: src/webex/pages/popup.tsx:307
#, c-format
msgid "Error: could not retrieve balance information."
msgstr ""
-#: src/webex/pages/popup.tsx:335
+#: src/webex/pages/popup.tsx:334
#, c-format
msgid "Payback"
msgstr ""
-#: src/webex/pages/popup.tsx:336
+#: src/webex/pages/popup.tsx:335
#, c-format
msgid "Return Electronic Cash to Bank Account"
msgstr ""
-#: src/webex/pages/popup.tsx:337
+#: src/webex/pages/popup.tsx:336
#, c-format
msgid "Manage Trusted Auditors and Exchanges"
msgstr ""
-#: src/webex/pages/popup.tsx:349
+#: src/webex/pages/popup.tsx:348
#, 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:360
+#: src/webex/pages/popup.tsx:359
#, 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:370
+#: src/webex/pages/popup.tsx:369
#, c-format
msgid "Merchant%1$soffered contract%2$s;\n"
msgstr ""
-#: src/webex/pages/popup.tsx:380
+#: src/webex/pages/popup.tsx:379
#, 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:390
+#: src/webex/pages/popup.tsx:389
#, 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:399
+#: src/webex/pages/popup.tsx:398
#, c-format
msgid "Unknown event (%1$s)"
msgstr ""
-#: src/webex/pages/popup.tsx:442
+#: src/webex/pages/popup.tsx:441
#, c-format
msgid "Error: could not retrieve event history"
msgstr ""
-#: src/webex/pages/popup.tsx:476
+#: src/webex/pages/popup.tsx:466
#, c-format
msgid "Your wallet has no events recorded."
msgstr "Ihre Geldbörse verzeichnet keine Vorkommnisse."
diff --git a/src/i18n/en-US.po b/src/i18n/en-US.po
index 66d4bd118..1418b0f58 100644
--- a/src/i18n/en-US.po
+++ b/src/i18n/en-US.po
@@ -198,105 +198,105 @@ msgstr ""
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:247
+#: src/webex/pages/popup.tsx:246
#, c-format
msgid "help"
msgstr ""
-#: src/webex/pages/popup.tsx:252
+#: src/webex/pages/popup.tsx:251
#, c-format
msgid ""
"You have no balance to show. Need some\n"
" %1$s getting started?\n"
msgstr ""
-#: src/webex/pages/popup.tsx:269
+#: src/webex/pages/popup.tsx:268
#, c-format
msgid "%1$s incoming\n"
msgstr ""
-#: src/webex/pages/popup.tsx:282
+#: src/webex/pages/popup.tsx:281
#, c-format
msgid "%1$s being spent\n"
msgstr ""
-#: src/webex/pages/popup.tsx:308
+#: src/webex/pages/popup.tsx:307
#, c-format
msgid "Error: could not retrieve balance information."
msgstr ""
-#: src/webex/pages/popup.tsx:335
+#: src/webex/pages/popup.tsx:334
#, c-format
msgid "Payback"
msgstr ""
-#: src/webex/pages/popup.tsx:336
+#: src/webex/pages/popup.tsx:335
#, c-format
msgid "Return Electronic Cash to Bank Account"
msgstr ""
-#: src/webex/pages/popup.tsx:337
+#: src/webex/pages/popup.tsx:336
#, c-format
msgid "Manage Trusted Auditors and Exchanges"
msgstr ""
-#: src/webex/pages/popup.tsx:349
+#: src/webex/pages/popup.tsx:348
#, c-format
msgid ""
"Bank requested reserve (%1$s) for\n"
" %2$s.\n"
msgstr ""
-#: src/webex/pages/popup.tsx:360
+#: src/webex/pages/popup.tsx:359
#, c-format
msgid ""
"Started to withdraw\n"
" %1$s from%2$s(%3$s).\n"
msgstr ""
-#: src/webex/pages/popup.tsx:370
+#: src/webex/pages/popup.tsx:369
#, c-format
msgid "Merchant%1$soffered contract%2$s;\n"
msgstr ""
-#: src/webex/pages/popup.tsx:380
+#: src/webex/pages/popup.tsx:379
#, c-format
msgid "Withdrew%1$sfrom%2$s(%3$s).\n"
msgstr ""
-#: src/webex/pages/popup.tsx:390
+#: src/webex/pages/popup.tsx:389
#, c-format
msgid ""
"Paid%1$sto merchant%2$s.\n"
" (%3$s)\n"
msgstr ""
-#: src/webex/pages/popup.tsx:399
+#: src/webex/pages/popup.tsx:398
#, c-format
msgid "Unknown event (%1$s)"
msgstr ""
-#: src/webex/pages/popup.tsx:442
+#: src/webex/pages/popup.tsx:441
#, c-format
msgid "Error: could not retrieve event history"
msgstr ""
-#: src/webex/pages/popup.tsx:476
+#: src/webex/pages/popup.tsx:466
#, c-format
msgid "Your wallet has no events recorded."
msgstr ""
diff --git a/src/i18n/fr.po b/src/i18n/fr.po
index d804bd204..33e2a1755 100644
--- a/src/i18n/fr.po
+++ b/src/i18n/fr.po
@@ -198,105 +198,105 @@ msgstr ""
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:247
+#: src/webex/pages/popup.tsx:246
#, c-format
msgid "help"
msgstr ""
-#: src/webex/pages/popup.tsx:252
+#: src/webex/pages/popup.tsx:251
#, c-format
msgid ""
"You have no balance to show. Need some\n"
" %1$s getting started?\n"
msgstr ""
-#: src/webex/pages/popup.tsx:269
+#: src/webex/pages/popup.tsx:268
#, c-format
msgid "%1$s incoming\n"
msgstr ""
-#: src/webex/pages/popup.tsx:282
+#: src/webex/pages/popup.tsx:281
#, c-format
msgid "%1$s being spent\n"
msgstr ""
-#: src/webex/pages/popup.tsx:308
+#: src/webex/pages/popup.tsx:307
#, c-format
msgid "Error: could not retrieve balance information."
msgstr ""
-#: src/webex/pages/popup.tsx:335
+#: src/webex/pages/popup.tsx:334
#, c-format
msgid "Payback"
msgstr ""
-#: src/webex/pages/popup.tsx:336
+#: src/webex/pages/popup.tsx:335
#, c-format
msgid "Return Electronic Cash to Bank Account"
msgstr ""
-#: src/webex/pages/popup.tsx:337
+#: src/webex/pages/popup.tsx:336
#, c-format
msgid "Manage Trusted Auditors and Exchanges"
msgstr ""
-#: src/webex/pages/popup.tsx:349
+#: src/webex/pages/popup.tsx:348
#, c-format
msgid ""
"Bank requested reserve (%1$s) for\n"
" %2$s.\n"
msgstr ""
-#: src/webex/pages/popup.tsx:360
+#: src/webex/pages/popup.tsx:359
#, c-format
msgid ""
"Started to withdraw\n"
" %1$s from%2$s(%3$s).\n"
msgstr ""
-#: src/webex/pages/popup.tsx:370
+#: src/webex/pages/popup.tsx:369
#, c-format
msgid "Merchant%1$soffered contract%2$s;\n"
msgstr ""
-#: src/webex/pages/popup.tsx:380
+#: src/webex/pages/popup.tsx:379
#, c-format
msgid "Withdrew%1$sfrom%2$s(%3$s).\n"
msgstr ""
-#: src/webex/pages/popup.tsx:390
+#: src/webex/pages/popup.tsx:389
#, c-format
msgid ""
"Paid%1$sto merchant%2$s.\n"
" (%3$s)\n"
msgstr ""
-#: src/webex/pages/popup.tsx:399
+#: src/webex/pages/popup.tsx:398
#, c-format
msgid "Unknown event (%1$s)"
msgstr ""
-#: src/webex/pages/popup.tsx:442
+#: src/webex/pages/popup.tsx:441
#, c-format
msgid "Error: could not retrieve event history"
msgstr ""
-#: src/webex/pages/popup.tsx:476
+#: src/webex/pages/popup.tsx:466
#, c-format
msgid "Your wallet has no events recorded."
msgstr ""
diff --git a/src/i18n/it.po b/src/i18n/it.po
index d804bd204..33e2a1755 100644
--- a/src/i18n/it.po
+++ b/src/i18n/it.po
@@ -198,105 +198,105 @@ msgstr ""
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:247
+#: src/webex/pages/popup.tsx:246
#, c-format
msgid "help"
msgstr ""
-#: src/webex/pages/popup.tsx:252
+#: src/webex/pages/popup.tsx:251
#, c-format
msgid ""
"You have no balance to show. Need some\n"
" %1$s getting started?\n"
msgstr ""
-#: src/webex/pages/popup.tsx:269
+#: src/webex/pages/popup.tsx:268
#, c-format
msgid "%1$s incoming\n"
msgstr ""
-#: src/webex/pages/popup.tsx:282
+#: src/webex/pages/popup.tsx:281
#, c-format
msgid "%1$s being spent\n"
msgstr ""
-#: src/webex/pages/popup.tsx:308
+#: src/webex/pages/popup.tsx:307
#, c-format
msgid "Error: could not retrieve balance information."
msgstr ""
-#: src/webex/pages/popup.tsx:335
+#: src/webex/pages/popup.tsx:334
#, c-format
msgid "Payback"
msgstr ""
-#: src/webex/pages/popup.tsx:336
+#: src/webex/pages/popup.tsx:335
#, c-format
msgid "Return Electronic Cash to Bank Account"
msgstr ""
-#: src/webex/pages/popup.tsx:337
+#: src/webex/pages/popup.tsx:336
#, c-format
msgid "Manage Trusted Auditors and Exchanges"
msgstr ""
-#: src/webex/pages/popup.tsx:349
+#: src/webex/pages/popup.tsx:348
#, c-format
msgid ""
"Bank requested reserve (%1$s) for\n"
" %2$s.\n"
msgstr ""
-#: src/webex/pages/popup.tsx:360
+#: src/webex/pages/popup.tsx:359
#, c-format
msgid ""
"Started to withdraw\n"
" %1$s from%2$s(%3$s).\n"
msgstr ""
-#: src/webex/pages/popup.tsx:370
+#: src/webex/pages/popup.tsx:369
#, c-format
msgid "Merchant%1$soffered contract%2$s;\n"
msgstr ""
-#: src/webex/pages/popup.tsx:380
+#: src/webex/pages/popup.tsx:379
#, c-format
msgid "Withdrew%1$sfrom%2$s(%3$s).\n"
msgstr ""
-#: src/webex/pages/popup.tsx:390
+#: src/webex/pages/popup.tsx:389
#, c-format
msgid ""
"Paid%1$sto merchant%2$s.\n"
" (%3$s)\n"
msgstr ""
-#: src/webex/pages/popup.tsx:399
+#: src/webex/pages/popup.tsx:398
#, c-format
msgid "Unknown event (%1$s)"
msgstr ""
-#: src/webex/pages/popup.tsx:442
+#: src/webex/pages/popup.tsx:441
#, c-format
msgid "Error: could not retrieve event history"
msgstr ""
-#: src/webex/pages/popup.tsx:476
+#: src/webex/pages/popup.tsx:466
#, c-format
msgid "Your wallet has no events recorded."
msgstr ""
diff --git a/src/i18n/taler-wallet-webex.pot b/src/i18n/taler-wallet-webex.pot
index d804bd204..33e2a1755 100644
--- a/src/i18n/taler-wallet-webex.pot
+++ b/src/i18n/taler-wallet-webex.pot
@@ -198,105 +198,105 @@ msgstr ""
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:247
+#: src/webex/pages/popup.tsx:246
#, c-format
msgid "help"
msgstr ""
-#: src/webex/pages/popup.tsx:252
+#: src/webex/pages/popup.tsx:251
#, c-format
msgid ""
"You have no balance to show. Need some\n"
" %1$s getting started?\n"
msgstr ""
-#: src/webex/pages/popup.tsx:269
+#: src/webex/pages/popup.tsx:268
#, c-format
msgid "%1$s incoming\n"
msgstr ""
-#: src/webex/pages/popup.tsx:282
+#: src/webex/pages/popup.tsx:281
#, c-format
msgid "%1$s being spent\n"
msgstr ""
-#: src/webex/pages/popup.tsx:308
+#: src/webex/pages/popup.tsx:307
#, c-format
msgid "Error: could not retrieve balance information."
msgstr ""
-#: src/webex/pages/popup.tsx:335
+#: src/webex/pages/popup.tsx:334
#, c-format
msgid "Payback"
msgstr ""
-#: src/webex/pages/popup.tsx:336
+#: src/webex/pages/popup.tsx:335
#, c-format
msgid "Return Electronic Cash to Bank Account"
msgstr ""
-#: src/webex/pages/popup.tsx:337
+#: src/webex/pages/popup.tsx:336
#, c-format
msgid "Manage Trusted Auditors and Exchanges"
msgstr ""
-#: src/webex/pages/popup.tsx:349
+#: src/webex/pages/popup.tsx:348
#, c-format
msgid ""
"Bank requested reserve (%1$s) for\n"
" %2$s.\n"
msgstr ""
-#: src/webex/pages/popup.tsx:360
+#: src/webex/pages/popup.tsx:359
#, c-format
msgid ""
"Started to withdraw\n"
" %1$s from%2$s(%3$s).\n"
msgstr ""
-#: src/webex/pages/popup.tsx:370
+#: src/webex/pages/popup.tsx:369
#, c-format
msgid "Merchant%1$soffered contract%2$s;\n"
msgstr ""
-#: src/webex/pages/popup.tsx:380
+#: src/webex/pages/popup.tsx:379
#, c-format
msgid "Withdrew%1$sfrom%2$s(%3$s).\n"
msgstr ""
-#: src/webex/pages/popup.tsx:390
+#: src/webex/pages/popup.tsx:389
#, c-format
msgid ""
"Paid%1$sto merchant%2$s.\n"
" (%3$s)\n"
msgstr ""
-#: src/webex/pages/popup.tsx:399
+#: src/webex/pages/popup.tsx:398
#, c-format
msgid "Unknown event (%1$s)"
msgstr ""
-#: src/webex/pages/popup.tsx:442
+#: src/webex/pages/popup.tsx:441
#, c-format
msgid "Error: could not retrieve event history"
msgstr ""
-#: src/webex/pages/popup.tsx:476
+#: src/webex/pages/popup.tsx:466
#, c-format
msgid "Your wallet has no events recorded."
msgstr ""
diff --git a/src/types.ts b/src/types.ts
index 90fe7cf9c..4dea8f3b4 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -84,43 +84,53 @@ export interface ReserveRecord {
* The reserve public key.
*/
reserve_pub: string;
+
/**
* The reserve private key.
*/
reserve_priv: string;
+
/**
* The exchange base URL.
*/
exchange_base_url: string;
+
/**
* Time when the reserve was created.
*/
created: number;
+
/**
- * Time when the reserve was last queried,
- * or 'null' if it was never queried.
+ * Time when the reserve was depleted.
+ * Set to 0 if not depleted yet.
+ */
+ timestamp_depleted: number;
+
+ /**
+ * Time when the reserve was confirmed.
+ *
+ * Set to 0 if not confirmed yet.
*/
- last_query: number | null;
+ timestamp_confirmed: number;
+
/**
* Current amount left in the reserve
*/
current_amount: AmountJson | null;
+
/**
* Amount requested when the reserve was created.
* When a reserve is re-used (rare!) the current_amount can
* be higher than the requested_amount
*/
requested_amount: AmountJson;
+
/**
* What's the current amount that sits
* in precoins?
*/
precoin_amount: AmountJson;
- /**
- * The bank conformed that the reserve will eventually
- * be filled with money.
- */
- confirmed: boolean;
+
/**
* We got some payback to this reserve. We'll cease to automatically
* withdraw money from it.
@@ -1188,6 +1198,9 @@ export class ProposalRecord {
@Checkable.Optional(Checkable.Number)
id?: number;
+ @Checkable.Number
+ timestamp: number;
+
/**
* Verify that a value matches the schema of this class and convert it into a
* member.
@@ -1482,18 +1495,6 @@ export interface CheckPayResult {
export type ConfirmPayResult = "paid" | "insufficient-balance";
-/**
- * Level of detail at which a history
- * entry should be shown.
- */
-export enum HistoryLevel {
- Trace = 1,
- Developer = 2,
- Expert = 3,
- User = 4,
-}
-
-
/*
* Activity history record.
*/
@@ -1518,11 +1519,6 @@ export interface HistoryRecord {
* Details used when rendering the history record.
*/
detail: any;
-
- /**
- * Level of detail of the history entry.
- */
- level: HistoryLevel;
}
@@ -1701,6 +1697,18 @@ export interface PurchaseRecord {
refundsPending: { [refundSig: string]: RefundPermission };
refundsDone: { [refundSig: string]: RefundPermission };
+
+ /**
+ * When was the purchase made?
+ * Refers to the time that the user accepted.
+ */
+ timestamp: number;
+
+ /**
+ * When was the last refund made?
+ * Set to 0 if no refund was made on the purchase.
+ */
+ timestamp_refund: number;
}
diff --git a/src/wallet.ts b/src/wallet.ts
index f194755e8..2f848c870 100644
--- a/src/wallet.ts
+++ b/src/wallet.ts
@@ -66,7 +66,6 @@ import {
ExchangeHandle,
ExchangeRecord,
ExchangeWireFeesRecord,
- HistoryLevel,
HistoryRecord,
Notifier,
PayCoinInfo,
@@ -321,7 +320,7 @@ export const WALLET_PROTOCOL_VERSION = "0:0:0";
* In the future we might consider adding migration functions for
* each version increment.
*/
-export const WALLET_DB_VERSION = 19;
+export const WALLET_DB_VERSION = 20;
const builtinCurrencies: CurrencyRecord[] = [
{
@@ -518,17 +517,6 @@ export namespace Stores {
denomPubIndex = new Index<string, CoinRecord>(this, "denomPub", "denomPub");
}
- class HistoryStore extends Store<HistoryRecord> {
- constructor() {
- super("history", {
- autoIncrement: true,
- keyPath: "id",
- });
- }
-
- timestampIndex = new Index<number, HistoryRecord>(this, "timestamp", "timestamp");
- }
-
class ProposalsStore extends Store<ProposalRecord> {
constructor() {
super("proposals", {
@@ -536,6 +524,7 @@ export namespace Stores {
keyPath: "id",
});
}
+ timestampIndex = new Index<string, ProposalRecord>(this, "timestamp", "timestamp");
}
class PurchasesStore extends Store<PurchaseRecord> {
@@ -545,6 +534,7 @@ export namespace Stores {
fulfillmentUrlIndex = new Index<string, PurchaseRecord>(this, "fulfillment_url", "contractTerms.fulfillment_url");
orderIdIndex = new Index<string, PurchaseRecord>(this, "order_id", "contractTerms.order_id");
+ timestampIndex = new Index<string, PurchaseRecord>(this, "timestamp", "timestamp");
}
class DenominationsStore extends Store<DenominationRecord> {
@@ -577,6 +567,15 @@ export namespace Stores {
}
}
+ class ReservesStore extends Store<ReserveRecord> {
+ constructor() {
+ super("reserves", {keyPath: "reserve_pub"});
+ }
+ timestampCreatedIndex = new Index<string, ReserveRecord>(this, "timestampCreated", "created");
+ timestampConfirmedIndex = new Index<string, ReserveRecord>(this, "timestampConfirmed", "timestamp_confirmed");
+ timestampDepletedIndex = new Index<string, ReserveRecord>(this, "timestampDepleted", "timestamp_depleted");
+ }
+
export const coins = new CoinsStore();
export const coinsReturns = new Store<CoinsReturnRecord>("coinsReturns", {keyPath: "contractTermsHash"});
export const config = new ConfigStore();
@@ -584,12 +583,11 @@ export namespace Stores {
export const denominations = new DenominationsStore();
export const exchangeWireFees = new ExchangeWireFeesStore();
export const exchanges = new ExchangeStore();
- export const history = new HistoryStore();
export const nonces = new NonceStore();
export const precoins = new Store<PreCoinRecord>("precoins", {keyPath: "coinPub"});
export const proposals = new ProposalsStore();
export const refresh = new Store<RefreshSessionRecord>("refresh", {keyPath: "id", autoIncrement: true});
- export const reserves = new Store<ReserveRecord>("reserves", {keyPath: "reserve_pub"});
+ export const reserves = new ReservesStore();
export const purchases = new PurchasesStore();
}
@@ -952,24 +950,12 @@ export class Wallet {
payReq,
refundsDone: {},
refundsPending: {},
- };
-
- const historyEntry: HistoryRecord = {
- detail: {
- amount: proposal.contractTerms.amount,
- contractTermsHash: proposal.contractTermsHash,
- fulfillmentUrl: proposal.contractTerms.fulfillment_url,
- merchantName: proposal.contractTerms.merchant.name,
- },
- level: HistoryLevel.User,
- subjectId: `contract-${proposal.contractTermsHash}`,
timestamp: (new Date()).getTime(),
- type: "pay",
+ timestamp_refund: 0,
};
await this.q()
.put(Stores.purchases, t)
- .put(Stores.history, historyEntry)
.putAll(Stores.coins, payCoinInfo.map((pci) => pci.updatedCoin))
.finish();
@@ -977,12 +963,6 @@ export class Wallet {
}
- async putHistory(historyEntry: HistoryRecord): Promise<void> {
- await this.q().put(Stores.history, historyEntry).finish();
- this.notifier.notify();
- }
-
-
/**
* Save a proposal in the database and return an id for it to
* retrieve it later.
@@ -1115,23 +1095,7 @@ export class Wallet {
try {
const reserve = await this.updateReserve(reserveRecord.reserve_pub);
- const n = await this.depleteReserve(reserve);
-
- if (n !== 0) {
- const depleted: HistoryRecord = {
- detail: {
- currentAmount: reserveRecord.current_amount,
- exchangeBaseUrl: reserveRecord.exchange_base_url,
- requestedAmount: reserveRecord.requested_amount,
- reservePub: reserveRecord.reserve_pub,
- },
- level: HistoryLevel.User,
- subjectId: `reserve-progress-${reserveRecord.reserve_pub}`,
- timestamp: (new Date()).getTime(),
- type: "depleted-reserve",
- };
- await this.q().put(Stores.history, depleted).finish();
- }
+ await this.depleteReserve(reserve);
} catch (e) {
// random, exponential backoff truncated at 3 minutes
const nextDelay = Math.min(2 * retryDelayMs + retryDelayMs * Math.random(), 3000 * 60);
@@ -1143,8 +1107,12 @@ export class Wallet {
}
+ /**
+ * Given a planchet, withdraw a coin from the exchange.
+ */
private async processPreCoin(preCoin: PreCoinRecord,
retryDelayMs = 200): Promise<void> {
+ // Throttle concurrent executions of this function, so we don't withdraw too many coins at once.
if (this.processPreCoinConcurrent >= 4 || this.processPreCoinThrottle[preCoin.exchangeBaseUrl]) {
console.log("delaying processPreCoin");
this.timerGroup.after(retryDelayMs, () => this.processPreCoin(preCoin, Math.min(retryDelayMs * 2, 5 * 60 * 1000)));
@@ -1156,7 +1124,7 @@ export class Wallet {
const exchange = await this.q().get(Stores.exchanges,
preCoin.exchangeBaseUrl);
if (!exchange) {
- console.error("db inconsistend: exchange for precoin not found");
+ console.error("db inconsistent: exchange for precoin not found");
return;
}
const denom = await this.q().get(Stores.denominations,
@@ -1182,20 +1150,10 @@ export class Wallet {
return r;
};
- const historyEntry: HistoryRecord = {
- detail: {
- coinPub: coin.coinPub,
- },
- level: HistoryLevel.Expert,
- timestamp: (new Date()).getTime(),
- type: "withdraw",
- };
-
await this.q()
.mutate(Stores.reserves, preCoin.reservePub, mutateReserve)
.delete("precoins", coin.coinPub)
.add(Stores.coins, coin)
- .add(Stores.history, historyEntry)
.finish();
this.notifier.notify();
@@ -1228,28 +1186,17 @@ export class Wallet {
const canonExchange = canonicalizeBaseUrl(req.exchange);
const reserveRecord: ReserveRecord = {
- confirmed: false,
created: now,
current_amount: null,
exchange_base_url: canonExchange,
hasPayback: false,
- last_query: null,
precoin_amount: Amounts.getZero(req.amount.currency),
requested_amount: req.amount,
reserve_priv: keypair.priv,
reserve_pub: keypair.pub,
senderWire: req.senderWire,
- };
-
- const historyEntry = {
- detail: {
- requestedAmount: req.amount,
- reservePub: reserveRecord.reserve_pub,
- },
- level: HistoryLevel.Expert,
- subjectId: `reserve-progress-${reserveRecord.reserve_pub}`,
- timestamp: now,
- type: "create-reserve",
+ timestamp_confirmed: 0,
+ timestamp_depleted: 0,
};
const exchangeInfo = await this.updateExchangeFromUrl(req.exchange);
@@ -1271,7 +1218,6 @@ export class Wallet {
await this.q()
.put(Stores.currencies, currencyRecord)
.put(Stores.reserves, reserveRecord)
- .put(Stores.history, historyEntry)
.finish();
const r: CreateReserveResponse = {
@@ -1301,21 +1247,9 @@ export class Wallet {
return;
}
console.log("reserve confirmed");
- const historyEntry: HistoryRecord = {
- detail: {
- exchangeBaseUrl: reserve.exchange_base_url,
- requestedAmount: reserve.requested_amount,
- reservePub: req.reservePub,
- },
- level: HistoryLevel.User,
- subjectId: `reserve-progress-${reserve.reserve_pub}`,
- timestamp: now,
- type: "confirm-reserve",
- };
- reserve.confirmed = true;
+ reserve.timestamp_confirmed = now;
await this.q()
.put(Stores.reserves, reserve)
- .put(Stores.history, historyEntry)
.finish();
this.notifier.notify();
@@ -1366,8 +1300,11 @@ export class Wallet {
/**
* Withdraw coins from a reserve until it is empty.
+ *
+ * When finished, marks the reserve as depleted by setting
+ * the depleted timestamp.
*/
- private async depleteReserve(reserve: ReserveRecord): Promise<number> {
+ private async depleteReserve(reserve: ReserveRecord): Promise<void> {
console.log("depleting reserve");
if (!reserve.current_amount) {
throw Error("can't withdraw when amount is unknown");
@@ -1377,6 +1314,7 @@ export class Wallet {
throw Error("can't withdraw when amount is unknown");
}
const denomsForWithdraw = await this.getVerifiedWithdrawDenomList(reserve.exchange_base_url, withdrawAmount);
+ const smallestAmount = await this.getVerifiedSmallestWithdrawAmount(reserve.exchange_base_url);
console.log(`withdrawing ${denomsForWithdraw.length} coins`);
@@ -1398,6 +1336,11 @@ export class Wallet {
}
r.current_amount = result.amount;
+ // Reserve is depleted if the amount left is too small to withdraw
+ if (Amounts.cmp(r.current_amount, smallestAmount) < 0) {
+ r.timestamp_depleted = (new Date()).getTime();
+ }
+
console.log(`after creating precoin: current ${amountToPretty(r.current_amount)}, precoin: ${amountToPretty(
r.precoin_amount)})}`);
@@ -1413,7 +1356,6 @@ export class Wallet {
});
await Promise.all(ps);
- return ps.length;
}
@@ -1437,24 +1379,9 @@ export class Wallet {
if (!reserveInfo) {
throw Error();
}
- const oldAmount = reserve.current_amount;
- const newAmount = reserveInfo.balance;
reserve.current_amount = reserveInfo.balance;
- const historyEntry = {
- detail: {
- newAmount,
- oldAmount,
- requestedAmount: reserve.requested_amount,
- reservePub,
- },
- level: HistoryLevel.Developer,
- subjectId: `reserve-progress-${reserve.reserve_pub}`,
- timestamp: (new Date()).getTime(),
- type: "reserve-update",
- };
await this.q()
.put(Stores.reserves, reserve)
- .put(Stores.history, historyEntry)
.finish();
this.notifier.notify();
return reserve;
@@ -1489,6 +1416,52 @@ export class Wallet {
);
}
+
+ /**
+ * Compute the smallest withdrawable amount possible, based on verified denominations.
+ *
+ * Writes to the DB in order to record the result from verifying
+ * denominations.
+ */
+ async getVerifiedSmallestWithdrawAmount(exchangeBaseUrl: string): Promise<AmountJson> {
+ const exchange = await this.q().get(Stores.exchanges, exchangeBaseUrl);
+ if (!exchange) {
+ throw Error(`exchange ${exchangeBaseUrl} not found`);
+ }
+
+ const possibleDenoms = await (
+ this.q().iterIndex(Stores.denominations.exchangeBaseUrlIndex,
+ exchange.baseUrl)
+ .filter((d) => d.status === DenominationStatus.Unverified || d.status === DenominationStatus.VerifiedGood)
+ .toArray()
+ );
+ possibleDenoms.sort((d1, d2) => {
+ let a1 = Amounts.add(d1.feeWithdraw, d1.value).amount;
+ let a2 = Amounts.add(d2.feeWithdraw, d2.value).amount;
+ return Amounts.cmp(a1, a2);
+ });
+
+ for (let denom of possibleDenoms) {
+ if (denom.status === DenominationStatus.VerifiedGood) {
+ return Amounts.add(denom.feeWithdraw, denom.value).amount;
+ }
+ console.log(`verifying denom ${denom.denomPub.substr(0, 15)}`);
+ const valid = await this.cryptoApi.isValidDenom(denom,
+ exchange.masterPublicKey);
+ if (!valid) {
+ denom.status = DenominationStatus.VerifiedBad;
+ } else {
+ denom.status = DenominationStatus.VerifiedGood;
+ }
+ await this.q().put(Stores.denominations, denom).finish();
+ if (valid) {
+ return Amounts.add(denom.feeWithdraw, denom.value).amount;
+ }
+ }
+ return Amounts.getZero(exchange.currency);
+ }
+
+
/**
* Get a list of denominations to withdraw from the given exchange for the
* given amount, making sure that all denominations' signatures are verified.
@@ -1895,7 +1868,7 @@ export class Wallet {
}
function collectPendingWithdraw(r: ReserveRecord, balance: WalletBalance) {
- if (!r.confirmed) {
+ if (!r.timestamp_confirmed) {
return balance;
}
let amount = r.current_amount;
@@ -2253,16 +2226,79 @@ export class Wallet {
* Retrive the full event history for this wallet.
*/
async getHistory(): Promise<{history: HistoryRecord[]}> {
- function collect(x: any, acc: any) {
- acc.push(x);
- return acc;
+ const history: HistoryRecord[] = [];
+
+ // FIXME: do pagination instead of generating the full history
+
+ const proposals = await this.q().iter<ProposalRecord>(Stores.proposals).toArray();
+ for (let p of proposals) {
+ history.push({
+ type: "offer-contract",
+ timestamp: p.timestamp,
+ detail: {
+ contractTermsHash: p.contractTermsHash,
+ merchantName: p.contractTerms.merchant.name,
+ },
+ });
}
- const history = await (
- this.q()
- .iterIndex(Stores.history.timestampIndex)
- .reduce(collect, []));
+ const purchases = await this.q().iter<PurchaseRecord>(Stores.purchases).toArray();
+ for (let p of purchases) {
+ history.push({
+ type: "pay",
+ timestamp: p.timestamp,
+ detail: {
+ amount: p.contractTerms.amount,
+ contractTermsHash: p.contractTermsHash,
+ fulfillmentUrl: p.contractTerms.fulfillment_url,
+ merchantName: p.contractTerms.merchant.name,
+ },
+ });
+ if (p.timestamp_refund) {
+ const amountsPending = Object.keys(p.refundsPending).map((x) => p.refundsPending[x].refund_amount);
+ const amountsDone = Object.keys(p.refundsDone).map((x) => p.refundsDone[x].refund_amount);
+ const amounts: AmountJson[] = amountsPending.concat(amountsDone);
+ const amount = Amounts.add(Amounts.getZero(p.contractTerms.amount.currency), ...amounts).amount;
+
+ history.push({
+ type: "refund",
+ timestamp: p.timestamp_refund,
+ detail: {
+ refundAmount: amount,
+ contractTermsHash: p.contractTermsHash,
+ fulfillmentUrl: p.contractTerms.fulfillment_url,
+ merchantName: p.contractTerms.merchant.name,
+ },
+ });
+ }
+ }
+ const reserves: ReserveRecord[] = await this.q().iter<ReserveRecord>(Stores.reserves).toArray();
+ for (let r of reserves) {
+ history.push({
+ type: "create-reserve",
+ timestamp: r.created,
+ detail: {
+ exchangeBaseUrl: r.exchange_base_url,
+ requestedAmount: r.requested_amount,
+ reservePub: r.reserve_pub,
+ },
+ });
+ if (r.timestamp_depleted) {
+ history.push({
+ type: "depleted-reserve",
+ timestamp: r.timestamp_depleted,
+ detail: {
+ exchangeBaseUrl: r.exchange_base_url,
+ requestedAmount: r.requested_amount,
+ reservePub: r.reserve_pub,
+ },
+ });
+ }
+ }
+
+ history.sort((h1, h2) => Math.sign(h1.timestamp - h2.timestamp));
+
return {history};
}
@@ -2656,6 +2692,8 @@ export class Wallet {
return;
}
+ t.timestamp_refund = (new Date()).getTime();
+
for (const perm of refundPermissions) {
if (!t.refundsPending[perm.merchant_sig] && !t.refundsDone[perm.merchant_sig]) {
t.refundsPending[perm.merchant_sig] = perm;
diff --git a/src/webex/messages.ts b/src/webex/messages.ts
index 122bd8fe2..ca51abf1d 100644
--- a/src/webex/messages.ts
+++ b/src/webex/messages.ts
@@ -92,10 +92,6 @@ export interface MessageMap {
request: { contract: object };
response: string;
};
- "put-history-entry": {
- request: { historyEntry: types.HistoryRecord };
- response: void;
- };
"save-proposal": {
request: { proposal: types.ProposalRecord };
response: void;
diff --git a/src/webex/notify.ts b/src/webex/notify.ts
index 5e024d619..7086ca95d 100644
--- a/src/webex/notify.ts
+++ b/src/webex/notify.ts
@@ -210,6 +210,7 @@ async function downloadContract(url: string, nonce: string): Promise<any> {
}
async function processProposal(proposal: any) {
+
if (!proposal.data) {
console.error("field proposal.data field missing");
return;
@@ -235,17 +236,12 @@ async function processProposal(proposal: any) {
// bad contract / name not included
}
- const historyEntry = {
- detail: {
- contractHash,
- merchantName,
- },
- subjectId: `contract-${contractHash}`,
+ let proposalId = await wxApi.saveProposal({
timestamp: (new Date()).getTime(),
- type: "offer-contract",
- };
- await wxApi.putHistory(historyEntry);
- let proposalId = await wxApi.saveProposal(proposal);
+ contractTerms: proposal.data,
+ contractTermsHash: proposal.hash,
+ merchantSig: proposal.sig,
+ });
const uri = new URI(chrome.extension.getURL("/src/webex/pages/confirm-contract.html"));
const params = {
diff --git a/src/webex/pages/popup.tsx b/src/webex/pages/popup.tsx
index 7d12d365e..4e4e9687c 100644
--- a/src/webex/pages/popup.tsx
+++ b/src/webex/pages/popup.tsx
@@ -29,7 +29,6 @@ import * as i18n from "../../i18n";
import {
AmountJson,
Amounts,
- HistoryLevel,
HistoryRecord,
WalletBalance,
WalletBalanceEntry,
@@ -354,8 +353,7 @@ function formatHistoryItem(historyItem: HistoryRecord) {
</i18n.Translate>
);
case "confirm-reserve": {
- // FIXME: eventually remove compat fix
- const exchange = d.exchangeBaseUrl ? (new URI(d.exchangeBaseUrl)).host() : "??";
+ const exchange = (new URI(d.exchangeBaseUrl)).host();
const pub = abbrev(d.reservePub);
return (
<i18n.Translate wrap="p">
@@ -369,7 +367,7 @@ function formatHistoryItem(historyItem: HistoryRecord) {
const link = chrome.extension.getURL("view-contract.html");
return (
<i18n.Translate wrap="p">
- Merchant <em>{abbrev(d.merchantName, 15)}</em> offered contract <a href={link}>{abbrev(d.contractHash)}</a>;
+ Merchant <em>{abbrev(d.merchantName, 15)}</em> offered contract <a href={link}>{abbrev(d.contractTermsHash)}</a>;
</i18n.Translate>
);
}
@@ -395,6 +393,14 @@ function formatHistoryItem(historyItem: HistoryRecord) {
</i18n.Translate>
);
}
+ case "refund": {
+ const merchantElem = <em>{abbrev(d.merchantName, 15)}</em>;
+ return (
+ <i18n.Translate wrap="p">
+ Merchant <span>{merchantElem}</span> gave a refund over <span>{renderAmount(d.refundAmount)}</span>.
+ </i18n.Translate>
+ );
+ }
default:
return (<p>{i18n.str`Unknown event (${historyItem.type})`}</p>);
}
@@ -447,17 +453,8 @@ class WalletHistory extends React.Component<any, any> {
return <span />;
}
- const subjectMemo: {[s: string]: boolean} = {};
const listing: any[] = [];
for (const record of history.reverse()) {
- if (record.subjectId && subjectMemo[record.subjectId]) {
- continue;
- }
- if (record.level !== undefined && record.level < HistoryLevel.User) {
- continue;
- }
- subjectMemo[record.subjectId as string] = true;
-
const item = (
<div className="historyItem">
<div className="historyDate">
diff --git a/src/webex/pages/refund.tsx b/src/webex/pages/refund.tsx
index d2c21c2f4..73bed30ee 100644
--- a/src/webex/pages/refund.tsx
+++ b/src/webex/pages/refund.tsx
@@ -63,10 +63,12 @@ const RefundDetail = ({purchase, fullRefundFees}: {purchase: types.PurchaseRecor
amountDone = types.Amounts.add(amountDone, purchase.refundsDone[k].refund_amount).amount;
}
+ const hasPending = amountPending.fraction !== 0 || amountPending.value !== 0;
+
return (
<div>
- <p>Refund fully received: <AmountDisplay amount={amountDone} /> (refund fees: <AmountDisplay amount={fullRefundFees} />)</p>
- <p>Refund incoming: <AmountDisplay amount={amountPending} /></p>
+ {hasPending ? <p>Refund pending: <AmountDisplay amount={amountPending} /></p> : null}
+ <p>Refund received: <AmountDisplay amount={amountDone} /> (refund fees: <AmountDisplay amount={fullRefundFees} />)</p>
</div>
);
};
diff --git a/src/webex/pages/tree.tsx b/src/webex/pages/tree.tsx
index 2d542f01d..072150312 100644
--- a/src/webex/pages/tree.tsx
+++ b/src/webex/pages/tree.tsx
@@ -61,7 +61,7 @@ class ReserveView extends React.Component<ReserveViewProps, {}> {
<li>Created: {(new Date(r.created * 1000).toString())}</li>
<li>Current: {r.current_amount ? renderAmount(r.current_amount!) : "null"}</li>
<li>Requested: {renderAmount(r.requested_amount)}</li>
- <li>Confirmed: {r.confirmed}</li>
+ <li>Confirmed: {r.timestamp_confirmed}</li>
</ul>
</div>
);
diff --git a/src/webex/wxApi.ts b/src/webex/wxApi.ts
index 096d855e0..c1c6eb2d3 100644
--- a/src/webex/wxApi.ts
+++ b/src/webex/wxApi.ts
@@ -226,7 +226,7 @@ export function hashContract(contract: object): Promise<string> {
* the proposal is stored under.
*/
export function saveProposal(proposal: any): Promise<number> {
- return callBackend("save-proposal", proposal);
+ return callBackend("save-proposal", { proposal });
}
/**
@@ -244,13 +244,6 @@ export function queryPayment(url: string): Promise<QueryPaymentResult> {
}
/**
- * Add a new history item.
- */
-export function putHistory(historyEntry: any): Promise<void> {
- return callBackend("put-history-entry", { historyEntry });
-}
-
-/**
* Mark a payment as succeeded.
*/
export function paymentSucceeded(contractTermsHash: string, merchantSig: string): Promise<void> {
diff --git a/src/webex/wxBackend.ts b/src/webex/wxBackend.ts
index 16da3d97d..e7f571b92 100644
--- a/src/webex/wxBackend.ts
+++ b/src/webex/wxBackend.ts
@@ -177,19 +177,9 @@ function handleMessage(sender: MessageSender,
return hash;
});
}
- case "put-history-entry": {
- if (!detail.historyEntry) {
- return Promise.resolve({ error: "historyEntry missing" });
- }
- return needsWallet().putHistory(detail.historyEntry);
- }
case "save-proposal": {
console.log("handling save-proposal", detail);
- const checkedRecord = ProposalRecord.checked({
- contractTerms: detail.data,
- contractTermsHash: detail.hash,
- merchantSig: detail.sig,
- });
+ const checkedRecord = ProposalRecord.checked(detail.proposal);
return needsWallet().saveProposal(checkedRecord);
}
case "reserve-creation-info": {