aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/checkable.ts5
-rw-r--r--src/crypto/cryptoApi.ts6
-rw-r--r--src/crypto/cryptoWorker.ts22
-rw-r--r--src/i18n/de.po97
-rw-r--r--src/i18n/en-US.po97
-rw-r--r--src/i18n/fr.po97
-rw-r--r--src/i18n/it.po97
-rw-r--r--src/i18n/strings.ts36
-rw-r--r--src/i18n/taler-wallet-webex.pot97
-rw-r--r--src/query.ts40
-rw-r--r--src/types.ts192
-rw-r--r--src/wallet.ts369
-rw-r--r--src/webex/messages.ts8
-rw-r--r--src/webex/pages/confirm-create-reserve.html30
-rw-r--r--src/webex/pages/popup.tsx44
-rw-r--r--src/webex/pages/return-coins.html19
-rw-r--r--src/webex/pages/return-coins.tsx271
-rw-r--r--src/webex/style/wallet.css16
-rw-r--r--src/webex/wxApi.ts27
-rw-r--r--src/webex/wxBackend.ts17
-rw-r--r--src/wire.ts53
21 files changed, 1268 insertions, 372 deletions
diff --git a/src/checkable.ts b/src/checkable.ts
index 802d8f32d..e6ef99336 100644
--- a/src/checkable.ts
+++ b/src/checkable.ts
@@ -217,12 +217,11 @@ export namespace Checkable {
type: target,
}, ["(root)"]);
if (opts.validate) {
- const instance = new target();
- if (typeof instance.validate !== "function") {
+ if (target.validate !== "function") {
throw Error("invalid Checkable annotion: validate method required");
}
// May throw exception
- instance.validate.call(cv);
+ target.validate(cv);
}
return cv;
};
diff --git a/src/crypto/cryptoApi.ts b/src/crypto/cryptoApi.ts
index 139f8ae88..227c3d346 100644
--- a/src/crypto/cryptoApi.ts
+++ b/src/crypto/cryptoApi.ts
@@ -26,8 +26,8 @@
import {
AmountJson,
CoinRecord,
+ ContractTerms,
DenominationRecord,
- ProposalRecord,
PayCoinInfo,
PaybackRequest,
PreCoinRecord,
@@ -277,9 +277,9 @@ export class CryptoApi {
return this.doRpc<PayCoinInfo>("isValidPaymentSignature", 1, sig, contractHash, merchantPub);
}
- signDeposit(proposal: ProposalRecord,
+ signDeposit(contractTerms: ContractTerms,
cds: CoinWithDenom[]): Promise<PayCoinInfo> {
- return this.doRpc<PayCoinInfo>("signDeposit", 3, proposal, cds);
+ return this.doRpc<PayCoinInfo>("signDeposit", 3, contractTerms, cds);
}
createEddsaKeypair(): Promise<{priv: string, pub: string}> {
diff --git a/src/crypto/cryptoWorker.ts b/src/crypto/cryptoWorker.ts
index 507a080ac..b05d7d184 100644
--- a/src/crypto/cryptoWorker.ts
+++ b/src/crypto/cryptoWorker.ts
@@ -28,8 +28,8 @@ import {
CoinPaySig,
CoinRecord,
CoinStatus,
+ ContractTerms,
DenominationRecord,
- ProposalRecord,
PayCoinInfo,
PaybackRequest,
PreCoinRecord,
@@ -39,6 +39,9 @@ import {
WireFee,
} from "../types";
import {
+ canonicalJson,
+} from "../helpers";
+import {
CoinWithDenom,
} from "../wallet";
@@ -227,16 +230,17 @@ namespace RpcFunctions {
* Generate updated coins (to store in the database)
* and deposit permissions for each given coin.
*/
- export function signDeposit(proposal: ProposalRecord,
+ export function signDeposit(contractTerms: ContractTerms,
cds: CoinWithDenom[]): PayCoinInfo {
const ret: PayCoinInfo = [];
+ const contractTermsHash = hashString(canonicalJson(contractTerms));
const feeList: AmountJson[] = cds.map((x) => x.denom.feeDeposit);
let fees = Amounts.add(Amounts.getZero(feeList[0].currency), ...feeList).amount;
// okay if saturates
- fees = Amounts.sub(fees, proposal.contractTerms.max_fee).amount;
- const total = Amounts.add(fees, proposal.contractTerms.amount).amount;
+ fees = Amounts.sub(fees, contractTerms.max_fee).amount;
+ const total = Amounts.add(fees, contractTerms.amount).amount;
const amountSpent = native.Amount.getZero(cds[0].coin.currentAmount.currency);
const amountRemaining = new native.Amount(total);
@@ -273,11 +277,11 @@ namespace RpcFunctions {
amount_with_fee: coinSpend.toNbo(),
coin_pub: native.EddsaPublicKey.fromCrock(cd.coin.coinPub),
deposit_fee: new native.Amount(cd.denom.feeDeposit).toNbo(),
- h_contract: native.HashCode.fromCrock(proposal.contractTermsHash),
- h_wire: native.HashCode.fromCrock(proposal.contractTerms.H_wire),
- merchant: native.EddsaPublicKey.fromCrock(proposal.contractTerms.merchant_pub),
- refund_deadline: native.AbsoluteTimeNbo.fromTalerString(proposal.contractTerms.refund_deadline),
- timestamp: native.AbsoluteTimeNbo.fromTalerString(proposal.contractTerms.timestamp),
+ h_contract: native.HashCode.fromCrock(contractTermsHash),
+ h_wire: native.HashCode.fromCrock(contractTerms.H_wire),
+ merchant: native.EddsaPublicKey.fromCrock(contractTerms.merchant_pub),
+ refund_deadline: native.AbsoluteTimeNbo.fromTalerString(contractTerms.refund_deadline),
+ timestamp: native.AbsoluteTimeNbo.fromTalerString(contractTerms.timestamp),
});
const coinSig = native.eddsaSign(d.toPurpose(),
diff --git a/src/i18n/de.po b/src/i18n/de.po
index 5bf73c8ab..887dc2c47 100644
--- a/src/i18n/de.po
+++ b/src/i18n/de.po
@@ -56,67 +56,67 @@ msgid ""
"wallet."
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:212
+#: src/webex/pages/confirm-create-reserve.tsx:213
#, fuzzy, c-format
msgid "Withdrawal fees:"
msgstr "Abheben bei %1$s"
-#: src/webex/pages/confirm-create-reserve.tsx:213
+#: src/webex/pages/confirm-create-reserve.tsx:214
#, c-format
msgid "Rounding loss:"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:214
+#: src/webex/pages/confirm-create-reserve.tsx:215
#, c-format
msgid "Earliest expiration (for deposit): %1$s"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:219
+#: src/webex/pages/confirm-create-reserve.tsx:220
#, c-format
msgid "# Coins"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:220
+#: src/webex/pages/confirm-create-reserve.tsx:221
#, c-format
msgid "Value"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:221
+#: src/webex/pages/confirm-create-reserve.tsx:222
#, fuzzy, c-format
msgid "Withdraw Fee"
msgstr "Abheben bei %1$s"
-#: src/webex/pages/confirm-create-reserve.tsx:222
+#: src/webex/pages/confirm-create-reserve.tsx:223
#, c-format
msgid "Refresh Fee"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:223
+#: src/webex/pages/confirm-create-reserve.tsx:224
#, c-format
msgid "Deposit Fee"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:276
+#: src/webex/pages/confirm-create-reserve.tsx:278
#, c-format
msgid "Select"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:292
+#: src/webex/pages/confirm-create-reserve.tsx:294
#, c-format
msgid "Error: URL may not be relative"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:360
+#: src/webex/pages/confirm-create-reserve.tsx:362
#, c-format
msgid "The exchange is trusted by the wallet.\n"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:366
+#: src/webex/pages/confirm-create-reserve.tsx:368
#, c-format
msgid "The exchange is audited by a trusted auditor.\n"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:372
+#: src/webex/pages/confirm-create-reserve.tsx:374
#, 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:381
+#: src/webex/pages/confirm-create-reserve.tsx:383
#, c-format
msgid ""
"Using exchange provider%1$s.\n"
@@ -132,151 +132,166 @@ msgid ""
" %2$s in fees.\n"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:395
+#: src/webex/pages/confirm-create-reserve.tsx:397
#, c-format
msgid ""
"Waiting for a response from\n"
" %1$s"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:406
+#: src/webex/pages/confirm-create-reserve.tsx:408
#, c-format
msgid "A problem occured, see below. %1$s"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:412
+#: src/webex/pages/confirm-create-reserve.tsx:414
#, c-format
msgid ""
"Information about fees will be available when an exchange provider is "
"selected."
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:455
+#: src/webex/pages/confirm-create-reserve.tsx:457
#, c-format
msgid "Accept fees and withdraw"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:460
+#: src/webex/pages/confirm-create-reserve.tsx:462
#, c-format
msgid "Change Exchange Provider"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:517
+#: src/webex/pages/confirm-create-reserve.tsx:519
#, 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:600
+#: src/webex/pages/confirm-create-reserve.tsx:607
#, c-format
msgid ""
"Oops, something went wrong. The wallet responded with error status (%1$s)."
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:611
+#: src/webex/pages/confirm-create-reserve.tsx:616
#, c-format
msgid "Checking URL, please wait ..."
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:625
+#: src/webex/pages/confirm-create-reserve.tsx:630
#, c-format
msgid "Can't parse amount: %1$s"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:632
+#: src/webex/pages/confirm-create-reserve.tsx:637
#, 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:652
+#: src/webex/pages/confirm-create-reserve.tsx:663
#, c-format
msgid "Fatal error: \"%1$s\"."
msgstr ""
-#: src/webex/pages/popup.tsx:160
+#: src/webex/pages/popup.tsx:161
#, c-format
msgid "Balance"
msgstr "Saldo"
-#: src/webex/pages/popup.tsx:163
+#: src/webex/pages/popup.tsx:164
#, c-format
msgid "History"
msgstr "Verlauf"
-#: src/webex/pages/popup.tsx:166
+#: src/webex/pages/popup.tsx:167
#, c-format
msgid "Debug"
msgstr "Debug"
-#: src/webex/pages/popup.tsx:242
+#: src/webex/pages/popup.tsx:247
#, c-format
msgid "help"
msgstr ""
-#: src/webex/pages/popup.tsx:247
+#: src/webex/pages/popup.tsx:252
#, 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:264
+#: src/webex/pages/popup.tsx:269
#, c-format
msgid "%1$s incoming\n"
msgstr ""
-#: src/webex/pages/popup.tsx:277
+#: src/webex/pages/popup.tsx:282
#, c-format
msgid "%1$s being spent\n"
msgstr ""
-#: src/webex/pages/popup.tsx:303
+#: src/webex/pages/popup.tsx:308
#, c-format
msgid "Error: could not retrieve balance information."
msgstr ""
-#: src/webex/pages/popup.tsx:342
+#: src/webex/pages/popup.tsx:335
+#, c-format
+msgid "Payback"
+msgstr ""
+
+#: src/webex/pages/popup.tsx:336
+#, c-format
+msgid "Return Electronic Cash to Bank Account"
+msgstr ""
+
+#: src/webex/pages/popup.tsx:337
+#, c-format
+msgid "Manage Trusted Auditors and Exchanges"
+msgstr ""
+
+#: src/webex/pages/popup.tsx:349
#, 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:353
+#: src/webex/pages/popup.tsx:360
#, 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:363
+#: src/webex/pages/popup.tsx:370
#, c-format
msgid "Merchant%1$soffered contract%2$s;\n"
msgstr ""
-#: src/webex/pages/popup.tsx:373
+#: src/webex/pages/popup.tsx:380
#, 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:383
+#: src/webex/pages/popup.tsx:390
#, 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:392
+#: src/webex/pages/popup.tsx:399
#, c-format
msgid "Unknown event (%1$s)"
msgstr ""
-#: src/webex/pages/popup.tsx:435
+#: src/webex/pages/popup.tsx:442
#, c-format
msgid "Error: could not retrieve event history"
msgstr ""
-#: src/webex/pages/popup.tsx:469
+#: src/webex/pages/popup.tsx:476
#, 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 e548014e6..ec879f580 100644
--- a/src/i18n/en-US.po
+++ b/src/i18n/en-US.po
@@ -56,67 +56,67 @@ msgid ""
"wallet."
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:212
+#: src/webex/pages/confirm-create-reserve.tsx:213
#, c-format
msgid "Withdrawal fees:"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:213
+#: src/webex/pages/confirm-create-reserve.tsx:214
#, c-format
msgid "Rounding loss:"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:214
+#: src/webex/pages/confirm-create-reserve.tsx:215
#, c-format
msgid "Earliest expiration (for deposit): %1$s"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:219
+#: src/webex/pages/confirm-create-reserve.tsx:220
#, c-format
msgid "# Coins"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:220
+#: src/webex/pages/confirm-create-reserve.tsx:221
#, c-format
msgid "Value"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:221
+#: src/webex/pages/confirm-create-reserve.tsx:222
#, c-format
msgid "Withdraw Fee"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:222
+#: src/webex/pages/confirm-create-reserve.tsx:223
#, c-format
msgid "Refresh Fee"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:223
+#: src/webex/pages/confirm-create-reserve.tsx:224
#, c-format
msgid "Deposit Fee"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:276
+#: src/webex/pages/confirm-create-reserve.tsx:278
#, c-format
msgid "Select"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:292
+#: src/webex/pages/confirm-create-reserve.tsx:294
#, c-format
msgid "Error: URL may not be relative"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:360
+#: src/webex/pages/confirm-create-reserve.tsx:362
#, c-format
msgid "The exchange is trusted by the wallet.\n"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:366
+#: src/webex/pages/confirm-create-reserve.tsx:368
#, c-format
msgid "The exchange is audited by a trusted auditor.\n"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:372
+#: src/webex/pages/confirm-create-reserve.tsx:374
#, 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:381
+#: src/webex/pages/confirm-create-reserve.tsx:383
#, c-format
msgid ""
"Using exchange provider%1$s.\n"
@@ -132,151 +132,166 @@ msgid ""
" %2$s in fees.\n"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:395
+#: src/webex/pages/confirm-create-reserve.tsx:397
#, c-format
msgid ""
"Waiting for a response from\n"
" %1$s"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:406
+#: src/webex/pages/confirm-create-reserve.tsx:408
#, c-format
msgid "A problem occured, see below. %1$s"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:412
+#: src/webex/pages/confirm-create-reserve.tsx:414
#, c-format
msgid ""
"Information about fees will be available when an exchange provider is "
"selected."
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:455
+#: src/webex/pages/confirm-create-reserve.tsx:457
#, c-format
msgid "Accept fees and withdraw"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:460
+#: src/webex/pages/confirm-create-reserve.tsx:462
#, c-format
msgid "Change Exchange Provider"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:517
+#: src/webex/pages/confirm-create-reserve.tsx:519
#, 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:600
+#: src/webex/pages/confirm-create-reserve.tsx:607
#, c-format
msgid ""
"Oops, something went wrong. The wallet responded with error status (%1$s)."
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:611
+#: src/webex/pages/confirm-create-reserve.tsx:616
#, c-format
msgid "Checking URL, please wait ..."
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:625
+#: src/webex/pages/confirm-create-reserve.tsx:630
#, c-format
msgid "Can't parse amount: %1$s"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:632
+#: src/webex/pages/confirm-create-reserve.tsx:637
#, 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:652
+#: src/webex/pages/confirm-create-reserve.tsx:663
#, c-format
msgid "Fatal error: \"%1$s\"."
msgstr ""
-#: src/webex/pages/popup.tsx:160
+#: src/webex/pages/popup.tsx:161
#, c-format
msgid "Balance"
msgstr ""
-#: src/webex/pages/popup.tsx:163
+#: src/webex/pages/popup.tsx:164
#, c-format
msgid "History"
msgstr ""
-#: src/webex/pages/popup.tsx:166
+#: src/webex/pages/popup.tsx:167
#, c-format
msgid "Debug"
msgstr ""
-#: src/webex/pages/popup.tsx:242
+#: src/webex/pages/popup.tsx:247
#, c-format
msgid "help"
msgstr ""
-#: src/webex/pages/popup.tsx:247
+#: src/webex/pages/popup.tsx:252
#, c-format
msgid ""
"You have no balance to show. Need some\n"
" %1$s getting started?\n"
msgstr ""
-#: src/webex/pages/popup.tsx:264
+#: src/webex/pages/popup.tsx:269
#, c-format
msgid "%1$s incoming\n"
msgstr ""
-#: src/webex/pages/popup.tsx:277
+#: src/webex/pages/popup.tsx:282
#, c-format
msgid "%1$s being spent\n"
msgstr ""
-#: src/webex/pages/popup.tsx:303
+#: src/webex/pages/popup.tsx:308
#, c-format
msgid "Error: could not retrieve balance information."
msgstr ""
-#: src/webex/pages/popup.tsx:342
+#: src/webex/pages/popup.tsx:335
+#, c-format
+msgid "Payback"
+msgstr ""
+
+#: src/webex/pages/popup.tsx:336
+#, c-format
+msgid "Return Electronic Cash to Bank Account"
+msgstr ""
+
+#: src/webex/pages/popup.tsx:337
+#, c-format
+msgid "Manage Trusted Auditors and Exchanges"
+msgstr ""
+
+#: src/webex/pages/popup.tsx:349
#, c-format
msgid ""
"Bank requested reserve (%1$s) for\n"
" %2$s.\n"
msgstr ""
-#: src/webex/pages/popup.tsx:353
+#: src/webex/pages/popup.tsx:360
#, c-format
msgid ""
"Started to withdraw\n"
" %1$s from%2$s(%3$s).\n"
msgstr ""
-#: src/webex/pages/popup.tsx:363
+#: src/webex/pages/popup.tsx:370
#, c-format
msgid "Merchant%1$soffered contract%2$s;\n"
msgstr ""
-#: src/webex/pages/popup.tsx:373
+#: src/webex/pages/popup.tsx:380
#, c-format
msgid "Withdrew%1$sfrom%2$s(%3$s).\n"
msgstr ""
-#: src/webex/pages/popup.tsx:383
+#: src/webex/pages/popup.tsx:390
#, c-format
msgid ""
"Paid%1$sto merchant%2$s.\n"
" (%3$s)\n"
msgstr ""
-#: src/webex/pages/popup.tsx:392
+#: src/webex/pages/popup.tsx:399
#, c-format
msgid "Unknown event (%1$s)"
msgstr ""
-#: src/webex/pages/popup.tsx:435
+#: src/webex/pages/popup.tsx:442
#, c-format
msgid "Error: could not retrieve event history"
msgstr ""
-#: src/webex/pages/popup.tsx:469
+#: src/webex/pages/popup.tsx:476
#, c-format
msgid "Your wallet has no events recorded."
msgstr ""
diff --git a/src/i18n/fr.po b/src/i18n/fr.po
index da860ad8f..705b1cba6 100644
--- a/src/i18n/fr.po
+++ b/src/i18n/fr.po
@@ -56,67 +56,67 @@ msgid ""
"wallet."
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:212
+#: src/webex/pages/confirm-create-reserve.tsx:213
#, c-format
msgid "Withdrawal fees:"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:213
+#: src/webex/pages/confirm-create-reserve.tsx:214
#, c-format
msgid "Rounding loss:"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:214
+#: src/webex/pages/confirm-create-reserve.tsx:215
#, c-format
msgid "Earliest expiration (for deposit): %1$s"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:219
+#: src/webex/pages/confirm-create-reserve.tsx:220
#, c-format
msgid "# Coins"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:220
+#: src/webex/pages/confirm-create-reserve.tsx:221
#, c-format
msgid "Value"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:221
+#: src/webex/pages/confirm-create-reserve.tsx:222
#, c-format
msgid "Withdraw Fee"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:222
+#: src/webex/pages/confirm-create-reserve.tsx:223
#, c-format
msgid "Refresh Fee"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:223
+#: src/webex/pages/confirm-create-reserve.tsx:224
#, c-format
msgid "Deposit Fee"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:276
+#: src/webex/pages/confirm-create-reserve.tsx:278
#, c-format
msgid "Select"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:292
+#: src/webex/pages/confirm-create-reserve.tsx:294
#, c-format
msgid "Error: URL may not be relative"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:360
+#: src/webex/pages/confirm-create-reserve.tsx:362
#, c-format
msgid "The exchange is trusted by the wallet.\n"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:366
+#: src/webex/pages/confirm-create-reserve.tsx:368
#, c-format
msgid "The exchange is audited by a trusted auditor.\n"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:372
+#: src/webex/pages/confirm-create-reserve.tsx:374
#, 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:381
+#: src/webex/pages/confirm-create-reserve.tsx:383
#, c-format
msgid ""
"Using exchange provider%1$s.\n"
@@ -132,151 +132,166 @@ msgid ""
" %2$s in fees.\n"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:395
+#: src/webex/pages/confirm-create-reserve.tsx:397
#, c-format
msgid ""
"Waiting for a response from\n"
" %1$s"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:406
+#: src/webex/pages/confirm-create-reserve.tsx:408
#, c-format
msgid "A problem occured, see below. %1$s"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:412
+#: src/webex/pages/confirm-create-reserve.tsx:414
#, c-format
msgid ""
"Information about fees will be available when an exchange provider is "
"selected."
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:455
+#: src/webex/pages/confirm-create-reserve.tsx:457
#, c-format
msgid "Accept fees and withdraw"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:460
+#: src/webex/pages/confirm-create-reserve.tsx:462
#, c-format
msgid "Change Exchange Provider"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:517
+#: src/webex/pages/confirm-create-reserve.tsx:519
#, 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:600
+#: src/webex/pages/confirm-create-reserve.tsx:607
#, c-format
msgid ""
"Oops, something went wrong. The wallet responded with error status (%1$s)."
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:611
+#: src/webex/pages/confirm-create-reserve.tsx:616
#, c-format
msgid "Checking URL, please wait ..."
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:625
+#: src/webex/pages/confirm-create-reserve.tsx:630
#, c-format
msgid "Can't parse amount: %1$s"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:632
+#: src/webex/pages/confirm-create-reserve.tsx:637
#, 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:652
+#: src/webex/pages/confirm-create-reserve.tsx:663
#, c-format
msgid "Fatal error: \"%1$s\"."
msgstr ""
-#: src/webex/pages/popup.tsx:160
+#: src/webex/pages/popup.tsx:161
#, c-format
msgid "Balance"
msgstr ""
-#: src/webex/pages/popup.tsx:163
+#: src/webex/pages/popup.tsx:164
#, c-format
msgid "History"
msgstr ""
-#: src/webex/pages/popup.tsx:166
+#: src/webex/pages/popup.tsx:167
#, c-format
msgid "Debug"
msgstr ""
-#: src/webex/pages/popup.tsx:242
+#: src/webex/pages/popup.tsx:247
#, c-format
msgid "help"
msgstr ""
-#: src/webex/pages/popup.tsx:247
+#: src/webex/pages/popup.tsx:252
#, c-format
msgid ""
"You have no balance to show. Need some\n"
" %1$s getting started?\n"
msgstr ""
-#: src/webex/pages/popup.tsx:264
+#: src/webex/pages/popup.tsx:269
#, c-format
msgid "%1$s incoming\n"
msgstr ""
-#: src/webex/pages/popup.tsx:277
+#: src/webex/pages/popup.tsx:282
#, c-format
msgid "%1$s being spent\n"
msgstr ""
-#: src/webex/pages/popup.tsx:303
+#: src/webex/pages/popup.tsx:308
#, c-format
msgid "Error: could not retrieve balance information."
msgstr ""
-#: src/webex/pages/popup.tsx:342
+#: src/webex/pages/popup.tsx:335
+#, c-format
+msgid "Payback"
+msgstr ""
+
+#: src/webex/pages/popup.tsx:336
+#, c-format
+msgid "Return Electronic Cash to Bank Account"
+msgstr ""
+
+#: src/webex/pages/popup.tsx:337
+#, c-format
+msgid "Manage Trusted Auditors and Exchanges"
+msgstr ""
+
+#: src/webex/pages/popup.tsx:349
#, c-format
msgid ""
"Bank requested reserve (%1$s) for\n"
" %2$s.\n"
msgstr ""
-#: src/webex/pages/popup.tsx:353
+#: src/webex/pages/popup.tsx:360
#, c-format
msgid ""
"Started to withdraw\n"
" %1$s from%2$s(%3$s).\n"
msgstr ""
-#: src/webex/pages/popup.tsx:363
+#: src/webex/pages/popup.tsx:370
#, c-format
msgid "Merchant%1$soffered contract%2$s;\n"
msgstr ""
-#: src/webex/pages/popup.tsx:373
+#: src/webex/pages/popup.tsx:380
#, c-format
msgid "Withdrew%1$sfrom%2$s(%3$s).\n"
msgstr ""
-#: src/webex/pages/popup.tsx:383
+#: src/webex/pages/popup.tsx:390
#, c-format
msgid ""
"Paid%1$sto merchant%2$s.\n"
" (%3$s)\n"
msgstr ""
-#: src/webex/pages/popup.tsx:392
+#: src/webex/pages/popup.tsx:399
#, c-format
msgid "Unknown event (%1$s)"
msgstr ""
-#: src/webex/pages/popup.tsx:435
+#: src/webex/pages/popup.tsx:442
#, c-format
msgid "Error: could not retrieve event history"
msgstr ""
-#: src/webex/pages/popup.tsx:469
+#: src/webex/pages/popup.tsx:476
#, c-format
msgid "Your wallet has no events recorded."
msgstr ""
diff --git a/src/i18n/it.po b/src/i18n/it.po
index da860ad8f..705b1cba6 100644
--- a/src/i18n/it.po
+++ b/src/i18n/it.po
@@ -56,67 +56,67 @@ msgid ""
"wallet."
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:212
+#: src/webex/pages/confirm-create-reserve.tsx:213
#, c-format
msgid "Withdrawal fees:"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:213
+#: src/webex/pages/confirm-create-reserve.tsx:214
#, c-format
msgid "Rounding loss:"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:214
+#: src/webex/pages/confirm-create-reserve.tsx:215
#, c-format
msgid "Earliest expiration (for deposit): %1$s"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:219
+#: src/webex/pages/confirm-create-reserve.tsx:220
#, c-format
msgid "# Coins"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:220
+#: src/webex/pages/confirm-create-reserve.tsx:221
#, c-format
msgid "Value"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:221
+#: src/webex/pages/confirm-create-reserve.tsx:222
#, c-format
msgid "Withdraw Fee"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:222
+#: src/webex/pages/confirm-create-reserve.tsx:223
#, c-format
msgid "Refresh Fee"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:223
+#: src/webex/pages/confirm-create-reserve.tsx:224
#, c-format
msgid "Deposit Fee"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:276
+#: src/webex/pages/confirm-create-reserve.tsx:278
#, c-format
msgid "Select"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:292
+#: src/webex/pages/confirm-create-reserve.tsx:294
#, c-format
msgid "Error: URL may not be relative"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:360
+#: src/webex/pages/confirm-create-reserve.tsx:362
#, c-format
msgid "The exchange is trusted by the wallet.\n"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:366
+#: src/webex/pages/confirm-create-reserve.tsx:368
#, c-format
msgid "The exchange is audited by a trusted auditor.\n"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:372
+#: src/webex/pages/confirm-create-reserve.tsx:374
#, 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:381
+#: src/webex/pages/confirm-create-reserve.tsx:383
#, c-format
msgid ""
"Using exchange provider%1$s.\n"
@@ -132,151 +132,166 @@ msgid ""
" %2$s in fees.\n"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:395
+#: src/webex/pages/confirm-create-reserve.tsx:397
#, c-format
msgid ""
"Waiting for a response from\n"
" %1$s"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:406
+#: src/webex/pages/confirm-create-reserve.tsx:408
#, c-format
msgid "A problem occured, see below. %1$s"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:412
+#: src/webex/pages/confirm-create-reserve.tsx:414
#, c-format
msgid ""
"Information about fees will be available when an exchange provider is "
"selected."
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:455
+#: src/webex/pages/confirm-create-reserve.tsx:457
#, c-format
msgid "Accept fees and withdraw"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:460
+#: src/webex/pages/confirm-create-reserve.tsx:462
#, c-format
msgid "Change Exchange Provider"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:517
+#: src/webex/pages/confirm-create-reserve.tsx:519
#, 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:600
+#: src/webex/pages/confirm-create-reserve.tsx:607
#, c-format
msgid ""
"Oops, something went wrong. The wallet responded with error status (%1$s)."
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:611
+#: src/webex/pages/confirm-create-reserve.tsx:616
#, c-format
msgid "Checking URL, please wait ..."
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:625
+#: src/webex/pages/confirm-create-reserve.tsx:630
#, c-format
msgid "Can't parse amount: %1$s"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:632
+#: src/webex/pages/confirm-create-reserve.tsx:637
#, 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:652
+#: src/webex/pages/confirm-create-reserve.tsx:663
#, c-format
msgid "Fatal error: \"%1$s\"."
msgstr ""
-#: src/webex/pages/popup.tsx:160
+#: src/webex/pages/popup.tsx:161
#, c-format
msgid "Balance"
msgstr ""
-#: src/webex/pages/popup.tsx:163
+#: src/webex/pages/popup.tsx:164
#, c-format
msgid "History"
msgstr ""
-#: src/webex/pages/popup.tsx:166
+#: src/webex/pages/popup.tsx:167
#, c-format
msgid "Debug"
msgstr ""
-#: src/webex/pages/popup.tsx:242
+#: src/webex/pages/popup.tsx:247
#, c-format
msgid "help"
msgstr ""
-#: src/webex/pages/popup.tsx:247
+#: src/webex/pages/popup.tsx:252
#, c-format
msgid ""
"You have no balance to show. Need some\n"
" %1$s getting started?\n"
msgstr ""
-#: src/webex/pages/popup.tsx:264
+#: src/webex/pages/popup.tsx:269
#, c-format
msgid "%1$s incoming\n"
msgstr ""
-#: src/webex/pages/popup.tsx:277
+#: src/webex/pages/popup.tsx:282
#, c-format
msgid "%1$s being spent\n"
msgstr ""
-#: src/webex/pages/popup.tsx:303
+#: src/webex/pages/popup.tsx:308
#, c-format
msgid "Error: could not retrieve balance information."
msgstr ""
-#: src/webex/pages/popup.tsx:342
+#: src/webex/pages/popup.tsx:335
+#, c-format
+msgid "Payback"
+msgstr ""
+
+#: src/webex/pages/popup.tsx:336
+#, c-format
+msgid "Return Electronic Cash to Bank Account"
+msgstr ""
+
+#: src/webex/pages/popup.tsx:337
+#, c-format
+msgid "Manage Trusted Auditors and Exchanges"
+msgstr ""
+
+#: src/webex/pages/popup.tsx:349
#, c-format
msgid ""
"Bank requested reserve (%1$s) for\n"
" %2$s.\n"
msgstr ""
-#: src/webex/pages/popup.tsx:353
+#: src/webex/pages/popup.tsx:360
#, c-format
msgid ""
"Started to withdraw\n"
" %1$s from%2$s(%3$s).\n"
msgstr ""
-#: src/webex/pages/popup.tsx:363
+#: src/webex/pages/popup.tsx:370
#, c-format
msgid "Merchant%1$soffered contract%2$s;\n"
msgstr ""
-#: src/webex/pages/popup.tsx:373
+#: src/webex/pages/popup.tsx:380
#, c-format
msgid "Withdrew%1$sfrom%2$s(%3$s).\n"
msgstr ""
-#: src/webex/pages/popup.tsx:383
+#: src/webex/pages/popup.tsx:390
#, c-format
msgid ""
"Paid%1$sto merchant%2$s.\n"
" (%3$s)\n"
msgstr ""
-#: src/webex/pages/popup.tsx:392
+#: src/webex/pages/popup.tsx:399
#, c-format
msgid "Unknown event (%1$s)"
msgstr ""
-#: src/webex/pages/popup.tsx:435
+#: src/webex/pages/popup.tsx:442
#, c-format
msgid "Error: could not retrieve event history"
msgstr ""
-#: src/webex/pages/popup.tsx:469
+#: src/webex/pages/popup.tsx:476
#, c-format
msgid "Your wallet has no events recorded."
msgstr ""
diff --git a/src/i18n/strings.ts b/src/i18n/strings.ts
index 242fecf5f..aa883403e 100644
--- a/src/i18n/strings.ts
+++ b/src/i18n/strings.ts
@@ -138,6 +138,15 @@ strings['de'] = {
"Error: could not retrieve balance information.": [
""
],
+ "Payback": [
+ ""
+ ],
+ "Return Electronic Cash to Bank Account": [
+ ""
+ ],
+ "Manage Trusted Auditors and Exchanges": [
+ ""
+ ],
"Bank requested reserve (%1$s) for\n %2$s.\n": [
"Bank bestätig anlegen der Reserve (%1$s) bei %2$s"
],
@@ -294,6 +303,15 @@ strings['en-US'] = {
"Error: could not retrieve balance information.": [
""
],
+ "Payback": [
+ ""
+ ],
+ "Return Electronic Cash to Bank Account": [
+ ""
+ ],
+ "Manage Trusted Auditors and Exchanges": [
+ ""
+ ],
"Bank requested reserve (%1$s) for\n %2$s.\n": [
""
],
@@ -450,6 +468,15 @@ strings['fr'] = {
"Error: could not retrieve balance information.": [
""
],
+ "Payback": [
+ ""
+ ],
+ "Return Electronic Cash to Bank Account": [
+ ""
+ ],
+ "Manage Trusted Auditors and Exchanges": [
+ ""
+ ],
"Bank requested reserve (%1$s) for\n %2$s.\n": [
""
],
@@ -606,6 +633,15 @@ strings['it'] = {
"Error: could not retrieve balance information.": [
""
],
+ "Payback": [
+ ""
+ ],
+ "Return Electronic Cash to Bank Account": [
+ ""
+ ],
+ "Manage Trusted Auditors and Exchanges": [
+ ""
+ ],
"Bank requested reserve (%1$s) for\n %2$s.\n": [
""
],
diff --git a/src/i18n/taler-wallet-webex.pot b/src/i18n/taler-wallet-webex.pot
index da860ad8f..705b1cba6 100644
--- a/src/i18n/taler-wallet-webex.pot
+++ b/src/i18n/taler-wallet-webex.pot
@@ -56,67 +56,67 @@ msgid ""
"wallet."
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:212
+#: src/webex/pages/confirm-create-reserve.tsx:213
#, c-format
msgid "Withdrawal fees:"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:213
+#: src/webex/pages/confirm-create-reserve.tsx:214
#, c-format
msgid "Rounding loss:"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:214
+#: src/webex/pages/confirm-create-reserve.tsx:215
#, c-format
msgid "Earliest expiration (for deposit): %1$s"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:219
+#: src/webex/pages/confirm-create-reserve.tsx:220
#, c-format
msgid "# Coins"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:220
+#: src/webex/pages/confirm-create-reserve.tsx:221
#, c-format
msgid "Value"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:221
+#: src/webex/pages/confirm-create-reserve.tsx:222
#, c-format
msgid "Withdraw Fee"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:222
+#: src/webex/pages/confirm-create-reserve.tsx:223
#, c-format
msgid "Refresh Fee"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:223
+#: src/webex/pages/confirm-create-reserve.tsx:224
#, c-format
msgid "Deposit Fee"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:276
+#: src/webex/pages/confirm-create-reserve.tsx:278
#, c-format
msgid "Select"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:292
+#: src/webex/pages/confirm-create-reserve.tsx:294
#, c-format
msgid "Error: URL may not be relative"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:360
+#: src/webex/pages/confirm-create-reserve.tsx:362
#, c-format
msgid "The exchange is trusted by the wallet.\n"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:366
+#: src/webex/pages/confirm-create-reserve.tsx:368
#, c-format
msgid "The exchange is audited by a trusted auditor.\n"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:372
+#: src/webex/pages/confirm-create-reserve.tsx:374
#, 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:381
+#: src/webex/pages/confirm-create-reserve.tsx:383
#, c-format
msgid ""
"Using exchange provider%1$s.\n"
@@ -132,151 +132,166 @@ msgid ""
" %2$s in fees.\n"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:395
+#: src/webex/pages/confirm-create-reserve.tsx:397
#, c-format
msgid ""
"Waiting for a response from\n"
" %1$s"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:406
+#: src/webex/pages/confirm-create-reserve.tsx:408
#, c-format
msgid "A problem occured, see below. %1$s"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:412
+#: src/webex/pages/confirm-create-reserve.tsx:414
#, c-format
msgid ""
"Information about fees will be available when an exchange provider is "
"selected."
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:455
+#: src/webex/pages/confirm-create-reserve.tsx:457
#, c-format
msgid "Accept fees and withdraw"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:460
+#: src/webex/pages/confirm-create-reserve.tsx:462
#, c-format
msgid "Change Exchange Provider"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:517
+#: src/webex/pages/confirm-create-reserve.tsx:519
#, 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:600
+#: src/webex/pages/confirm-create-reserve.tsx:607
#, c-format
msgid ""
"Oops, something went wrong. The wallet responded with error status (%1$s)."
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:611
+#: src/webex/pages/confirm-create-reserve.tsx:616
#, c-format
msgid "Checking URL, please wait ..."
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:625
+#: src/webex/pages/confirm-create-reserve.tsx:630
#, c-format
msgid "Can't parse amount: %1$s"
msgstr ""
-#: src/webex/pages/confirm-create-reserve.tsx:632
+#: src/webex/pages/confirm-create-reserve.tsx:637
#, 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:652
+#: src/webex/pages/confirm-create-reserve.tsx:663
#, c-format
msgid "Fatal error: \"%1$s\"."
msgstr ""
-#: src/webex/pages/popup.tsx:160
+#: src/webex/pages/popup.tsx:161
#, c-format
msgid "Balance"
msgstr ""
-#: src/webex/pages/popup.tsx:163
+#: src/webex/pages/popup.tsx:164
#, c-format
msgid "History"
msgstr ""
-#: src/webex/pages/popup.tsx:166
+#: src/webex/pages/popup.tsx:167
#, c-format
msgid "Debug"
msgstr ""
-#: src/webex/pages/popup.tsx:242
+#: src/webex/pages/popup.tsx:247
#, c-format
msgid "help"
msgstr ""
-#: src/webex/pages/popup.tsx:247
+#: src/webex/pages/popup.tsx:252
#, c-format
msgid ""
"You have no balance to show. Need some\n"
" %1$s getting started?\n"
msgstr ""
-#: src/webex/pages/popup.tsx:264
+#: src/webex/pages/popup.tsx:269
#, c-format
msgid "%1$s incoming\n"
msgstr ""
-#: src/webex/pages/popup.tsx:277
+#: src/webex/pages/popup.tsx:282
#, c-format
msgid "%1$s being spent\n"
msgstr ""
-#: src/webex/pages/popup.tsx:303
+#: src/webex/pages/popup.tsx:308
#, c-format
msgid "Error: could not retrieve balance information."
msgstr ""
-#: src/webex/pages/popup.tsx:342
+#: src/webex/pages/popup.tsx:335
+#, c-format
+msgid "Payback"
+msgstr ""
+
+#: src/webex/pages/popup.tsx:336
+#, c-format
+msgid "Return Electronic Cash to Bank Account"
+msgstr ""
+
+#: src/webex/pages/popup.tsx:337
+#, c-format
+msgid "Manage Trusted Auditors and Exchanges"
+msgstr ""
+
+#: src/webex/pages/popup.tsx:349
#, c-format
msgid ""
"Bank requested reserve (%1$s) for\n"
" %2$s.\n"
msgstr ""
-#: src/webex/pages/popup.tsx:353
+#: src/webex/pages/popup.tsx:360
#, c-format
msgid ""
"Started to withdraw\n"
" %1$s from%2$s(%3$s).\n"
msgstr ""
-#: src/webex/pages/popup.tsx:363
+#: src/webex/pages/popup.tsx:370
#, c-format
msgid "Merchant%1$soffered contract%2$s;\n"
msgstr ""
-#: src/webex/pages/popup.tsx:373
+#: src/webex/pages/popup.tsx:380
#, c-format
msgid "Withdrew%1$sfrom%2$s(%3$s).\n"
msgstr ""
-#: src/webex/pages/popup.tsx:383
+#: src/webex/pages/popup.tsx:390
#, c-format
msgid ""
"Paid%1$sto merchant%2$s.\n"
" (%3$s)\n"
msgstr ""
-#: src/webex/pages/popup.tsx:392
+#: src/webex/pages/popup.tsx:399
#, c-format
msgid "Unknown event (%1$s)"
msgstr ""
-#: src/webex/pages/popup.tsx:435
+#: src/webex/pages/popup.tsx:442
#, c-format
msgid "Error: could not retrieve event history"
msgstr ""
-#: src/webex/pages/popup.tsx:469
+#: src/webex/pages/popup.tsx:476
#, c-format
msgid "Your wallet has no events recorded."
msgstr ""
diff --git a/src/query.ts b/src/query.ts
index 24db4de56..9a6162807 100644
--- a/src/query.ts
+++ b/src/query.ts
@@ -130,7 +130,11 @@ export interface QueryStream<T> {
*/
first(): QueryValue<T>;
- then(onfulfill: any, onreject: any): any;
+ /**
+ * Run the query without returning a result.
+ * Useful for queries with side effects.
+ */
+ run(): Promise<void>;
}
@@ -225,7 +229,7 @@ export const AbortTransaction = Symbol("abort_transaction");
* function.
*/
export function openPromise<T>(): any {
- let resolve: ((value?: T | PromiseLike<T>) => void) | null = null;
+ let resolve: ((x?: any) => void) | null = null;
let reject: ((reason?: any) => void) | null = null;
const promise = new Promise<T>((res, rej) => {
resolve = res;
@@ -239,7 +243,7 @@ export function openPromise<T>(): any {
}
-abstract class QueryStreamBase<T> implements QueryStream<T>, PromiseLike<void> {
+abstract class QueryStreamBase<T> implements QueryStream<T> {
abstract subscribe(f: (isDone: boolean,
value: any,
tx: IDBTransaction) => void): void;
@@ -250,11 +254,6 @@ abstract class QueryStreamBase<T> implements QueryStream<T>, PromiseLike<void> {
return new FirstQueryValue(this);
}
- then<R>(onfulfilled: (value: void) => R | PromiseLike<R>,
- onrejected: (reason: any) => R | PromiseLike<R>): PromiseLike<R> {
- return this.root.then(onfulfilled, onrejected);
- }
-
flatMap<S>(f: (x: T) => S[]): QueryStream<S> {
return new QueryStreamFlatMap<T, S>(this, f);
}
@@ -279,8 +278,7 @@ abstract class QueryStreamBase<T> implements QueryStream<T>, PromiseLike<void> {
keyFn: (obj: T) => I): QueryStream<JoinResult<T, S>> {
this.root.addStoreAccess(store.name, false);
return new QueryStreamKeyJoin<T, S>(this, store.name, keyFn);
- }
-
+ }
filter(f: (x: any) => boolean): QueryStream<T> {
return new QueryStreamFilter(this, f);
}
@@ -318,6 +316,21 @@ abstract class QueryStreamBase<T> implements QueryStream<T>, PromiseLike<void> {
.then(() => this.root.finish())
.then(() => promise);
}
+
+ run(): Promise<void> {
+ const {resolve, promise} = openPromise();
+
+ this.subscribe((isDone, value) => {
+ if (isDone) {
+ resolve();
+ return;
+ }
+ });
+
+ return Promise.resolve()
+ .then(() => this.root.finish())
+ .then(() => promise);
+ }
}
type FilterFn = (e: any) => boolean;
@@ -519,7 +532,7 @@ class IterQueryStream<T> extends QueryStreamBase<T> {
/**
* Root wrapper around an IndexedDB for queries with a fluent interface.
*/
-export class QueryRoot implements PromiseLike<void> {
+export class QueryRoot {
private work: Array<((t: IDBTransaction) => void)> = [];
private stores = new Set();
private kickoffPromise: Promise<void>;
@@ -537,11 +550,6 @@ export class QueryRoot implements PromiseLike<void> {
constructor(public db: IDBDatabase) {
}
- then<R>(onfulfilled: (value: void) => R | PromiseLike<R>,
- onrejected: (reason: any) => R | PromiseLike<R>): PromiseLike<R> {
- return this.finish().then(onfulfilled, onrejected);
- }
-
private checkFinished() {
if (this.finished) {
throw Error("Can't add work to query after it was started");
diff --git a/src/types.ts b/src/types.ts
index 68fde2690..9031b19b7 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -126,6 +126,12 @@ export interface ReserveRecord {
* withdraw money from it.
*/
hasPayback: boolean;
+
+ /**
+ * Wire information for the bank account that
+ * transfered funds for this reserve.
+ */
+ senderWire?: object;
}
@@ -814,6 +820,61 @@ export enum CoinStatus {
}
+
+/**
+ * State of returning a list of coins
+ * to the customer's bank account.
+ */
+export interface CoinsReturnRecord {
+ /**
+ * Coins that we're returning.
+ */
+ coins: CoinPaySig[];
+
+ /**
+ * Responses to the deposit requests.
+ */
+ responses: any;
+
+ /**
+ * Ephemeral dummy merchant key for
+ * the coins returns operation.
+ */
+ dummyMerchantPub: string;
+
+ /**
+ * Ephemeral dummy merchant key for
+ * the coins returns operation.
+ */
+ dummyMerchantPriv: string;
+
+ /**
+ * Contract terms.
+ */
+ contractTerms: string;
+
+ /**
+ * Hash of contract terms.
+ */
+ contractTermsHash: string;
+
+ /**
+ * Wire info to send the money for the coins to.
+ */
+ wire: object;
+
+ /**
+ * Hash of the wire object.
+ */
+ wireHash: string;
+
+ /**
+ * All coins were deposited.
+ */
+ finished: boolean;
+}
+
+
/**
* CoinRecord as stored in the "coins" data store
* of the wallet database.
@@ -903,14 +964,19 @@ export class ExchangeHandle {
/**
- * Mapping from currency names to detailed balance
- * information for that particular currency.
+ * Mapping from currency/exchange to detailed balance
+ * information.
*/
export interface WalletBalance {
/**
- * Mapping from currency name to defailed balance info.
+ * Mapping from currency name to detailed balance info.
*/
- [currency: string]: WalletBalanceEntry;
+ byExchange: { [exchangeBaseUrl: string]: WalletBalanceEntry };
+
+ /**
+ * Mapping from currency name to detailed balance info.
+ */
+ byCurrency: { [currency: string]: WalletBalanceEntry };
}
@@ -942,8 +1008,8 @@ export interface WalletBalanceEntry {
*/
@Checkable.Class({validate: true})
export class ContractTerms {
- validate() {
- if (this.exchanges.length === 0) {
+ static validate(x: ContractTerms) {
+ if (x.exchanges.length === 0) {
throw Error("no exchanges in contract terms");
}
}
@@ -1361,6 +1427,18 @@ export namespace Amounts {
value: Number.parseInt(res[2]),
};
}
+
+ export function toFloat(a: AmountJson): number {
+ return a.value + (a.fraction / fractionalBase);
+ }
+
+ export function fromFloat(floatVal: number, currency: string) {
+ return {
+ currency,
+ fraction: (floatVal - Math.floor(floatVal)) * fractionalBase,
+ value: Math.floor(floatVal),
+ };
+ }
}
@@ -1457,7 +1535,7 @@ export interface PayReq {
order_id: string;
/**
- * Exchange that the coins are from.
+ * Exchange that the coins are from (base URL).
*/
exchange: string;
}
@@ -1484,3 +1562,103 @@ export interface QueryPaymentFound {
contractTerms: ContractTerms;
payReq: PayReq;
}
+
+/**
+ * Information about all sender wire details known to the wallet,
+ * as well as exchanges that accept these wire types.
+ */
+export interface SenderWireInfos {
+ /**
+ * Mapping from exchange base url to list of accepted
+ * wire types.
+ */
+ exchangeWireTypes: { [exchangeBaseUrl: string]: string[] };
+
+ /**
+ * Sender wire types stored in the wallet.
+ */
+ senderWires: object[];
+}
+
+
+/**
+ * Request to mark a reserve as confirmed.
+ */
+@Checkable.Class()
+export class CreateReserveRequest {
+ /**
+ * The initial amount for the reserve.
+ */
+ @Checkable.Value(AmountJson)
+ amount: AmountJson;
+
+ /**
+ * Exchange URL where the bank should create the reserve.
+ */
+ @Checkable.String
+ exchange: string;
+
+ /**
+ * Wire details for the bank account that sent the funds to the exchange.
+ */
+ @Checkable.Optional(Checkable.Any)
+ senderWire?: object;
+
+ /**
+ * Verify that a value matches the schema of this class and convert it into a
+ * member.
+ */
+ static checked: (obj: any) => CreateReserveRequest;
+}
+
+
+/**
+ * Request to mark a reserve as confirmed.
+ */
+@Checkable.Class()
+export class ConfirmReserveRequest {
+ /**
+ * Public key of then reserve that should be marked
+ * as confirmed.
+ */
+ @Checkable.String
+ reservePub: string;
+
+ /**
+ * Verify that a value matches the schema of this class and convert it into a
+ * member.
+ */
+ static checked: (obj: any) => ConfirmReserveRequest;
+}
+
+
+/**
+ * Wire coins to the user's own bank account.
+ */
+@Checkable.Class()
+export class ReturnCoinsRequest {
+ /**
+ * The amount to wire.
+ */
+ @Checkable.Value(AmountJson)
+ amount: AmountJson;
+
+ /**
+ * The exchange to take the coins from.
+ */
+ @Checkable.String
+ exchange: string;
+
+ /**
+ * Wire details for the bank account of the customer that will
+ * receive the funds.
+ */
+ @Checkable.Any
+ senderWire?: object;
+
+ /**
+ * Verify that a value matches the schema of this class and convert it into a
+ * member.
+ */
+ static checked: (obj: any) => ReturnCoinsRequest;
+}
diff --git a/src/wallet.ts b/src/wallet.ts
index 5de3906dc..68d70b0bb 100644
--- a/src/wallet.ts
+++ b/src/wallet.ts
@@ -49,10 +49,13 @@ import {
Amounts,
Auditor,
CheckPayResult,
+ CoinPaySig,
CoinRecord,
CoinStatus,
ConfirmPayResult,
+ ConfirmReserveRequest,
ContractTerms,
+ CreateReserveRequest,
CreateReserveResponse,
CurrencyRecord,
Denomination,
@@ -73,6 +76,8 @@ import {
RefreshSessionRecord,
ReserveCreationInfo,
ReserveRecord,
+ ReturnCoinsRequest,
+ SenderWireInfos,
WalletBalance,
WalletBalanceEntry,
WireFee,
@@ -236,51 +241,6 @@ class WireDetailJson {
}
-/**
- * Request to mark a reserve as confirmed.
- */
-@Checkable.Class()
-export class CreateReserveRequest {
- /**
- * The initial amount for the reserve.
- */
- @Checkable.Value(AmountJson)
- amount: AmountJson;
-
- /**
- * Exchange URL where the bank should create the reserve.
- */
- @Checkable.String
- exchange: string;
-
- /**
- * Verify that a value matches the schema of this class and convert it into a
- * member.
- */
- static checked: (obj: any) => CreateReserveRequest;
-}
-
-
-/**
- * Request to mark a reserve as confirmed.
- */
-@Checkable.Class()
-export class ConfirmReserveRequest {
- /**
- * Public key of then reserve that should be marked
- * as confirmed.
- */
- @Checkable.String
- reservePub: string;
-
- /**
- * Verify that a value matches the schema of this class and convert it into a
- * member.
- */
- static checked: (obj: any) => ConfirmReserveRequest;
-}
-
-
interface TransactionRecord {
contractTermsHash: string;
contractTerms: ContractTerms;
@@ -330,6 +290,48 @@ export interface ConfigRecord {
/**
+ * Coin that we're depositing ourselves.
+ */
+export interface DepositCoin {
+ coinPaySig: CoinPaySig;
+
+ /**
+ * Undefined if coin not deposited, otherwise signature
+ * from the exchange confirming the deposit.
+ */
+ depositedSig?: string;
+}
+
+export interface CoinsReturnRecord {
+ /**
+ * Hash of the contract for sending coins to our own bank account.
+ */
+ contractTermsHash: string;
+
+ contractTerms: ContractTerms;
+
+ /**
+ * Private key where corresponding
+ * public key is used in the contract terms
+ * as merchant pub.
+ */
+ merchantPriv: string;
+
+ coins: DepositCoin[];
+
+ /**
+ * Exchange base URL to deposit coins at.
+ */
+ exchange: string;
+
+ /**
+ * Our own wire information for the deposit.
+ */
+ wire: any;
+}
+
+
+/**
* Wallet protocol version spoken with the exchange
* and merchant.
*
@@ -343,7 +345,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 = 18;
+export const WALLET_DB_VERSION = 19;
const builtinCurrencies: CurrencyRecord[] = [
{
@@ -421,6 +423,7 @@ export function selectPayCoins(cds: CoinWithDenom[], paymentAmount: AmountJson,
Amounts.add(paymentAmount,
denom.feeDeposit).amount) >= 0;
isBelowFee = Amounts.cmp(accFee, depositFeeLimit) <= 0;
+
if ((coversAmount && isBelowFee) || coversAmountWithFee) {
return cdsResult;
}
@@ -553,6 +556,7 @@ export namespace Stores {
}
export const coins = new CoinsStore();
+ export const coinsReturns = new Store<CoinsReturnRecord>("coinsReturns", {keyPath: "contractTermsHash"});
export const config = new ConfigStore();
export const currencies = new CurrenciesStore();
export const denominations = new DenominationsStore();
@@ -700,6 +704,12 @@ export class Wallet {
this.continueRefreshSession(r);
});
+ this.q()
+ .iter(Stores.coinsReturns)
+ .reduce((r: CoinsReturnRecord) => {
+ this.depositReturnedCoins(r);
+ });
+
// FIXME: optimize via index
this.q()
.iter(Stores.coins)
@@ -712,6 +722,58 @@ export class Wallet {
}
+ private async getCoinsForReturn(exchangeBaseUrl: string, amount: AmountJson): Promise<CoinWithDenom[] | undefined> {
+ const exchange = await this.q().get(Stores.exchanges, exchangeBaseUrl);
+ if (!exchange) {
+ throw Error(`Exchange ${exchangeBaseUrl} not known to the wallet`);
+ }
+
+ const coins: CoinRecord[] = await (
+ this.q()
+ .iterIndex(Stores.coins.exchangeBaseUrlIndex, exchange.baseUrl)
+ .toArray()
+ );
+
+ if (!coins || !coins.length) {
+ return [];
+ }
+
+ // Denomination of the first coin, we assume that all other
+ // coins have the same currency
+ const firstDenom = await this.q().get(Stores.denominations,
+ [
+ exchange.baseUrl,
+ coins[0].denomPub,
+ ]);
+ if (!firstDenom) {
+ throw Error("db inconsistent");
+ }
+ const currency = firstDenom.value.currency;
+
+ const cds: CoinWithDenom[] = [];
+ for (const coin of coins) {
+ const denom = await this.q().get(Stores.denominations,
+ [exchange.baseUrl, coin.denomPub]);
+ if (!denom) {
+ throw Error("db inconsistent");
+ }
+ if (denom.value.currency !== currency) {
+ console.warn(`same pubkey for different currencies at exchange ${exchange.baseUrl}`);
+ continue;
+ }
+ if (coin.suspended) {
+ continue;
+ }
+ if (coin.status !== CoinStatus.Fresh) {
+ continue;
+ }
+ cds.push({coin, denom});
+ }
+
+ return selectPayCoins(cds, amount, amount);
+ }
+
+
/**
* Get exchanges and associated coins that are still spendable,
* but only if the sum the coins' remaining value exceeds the payment amount.
@@ -769,6 +831,7 @@ export class Wallet {
if (!coins || coins.length === 0) {
continue;
}
+
// Denomination of the first coin, we assume that all other
// coins have the same currency
const firstDenom = await this.q().get(Stores.denominations,
@@ -903,7 +966,7 @@ export class Wallet {
*/
async confirmPay(proposalId: number): Promise<ConfirmPayResult> {
console.log("executing confirmPay");
- const proposal = await this.q().get(Stores.proposals, proposalId);
+ const proposal: ProposalRecord|undefined = await this.q().get(Stores.proposals, proposalId);
if (!proposal) {
throw Error(`proposal with id ${proposalId} not found`);
@@ -936,7 +999,7 @@ export class Wallet {
}
const {exchangeUrl, cds} = res;
- const ds = await this.cryptoApi.signDeposit(proposal, cds);
+ const ds = await this.cryptoApi.signDeposit(proposal.contractTerms, cds);
await this.recordConfirmPay(proposal, ds, exchangeUrl);
return "paid";
}
@@ -1138,6 +1201,7 @@ export class Wallet {
requested_amount: req.amount,
reserve_priv: keypair.priv,
reserve_pub: keypair.pub,
+ senderWire: req.senderWire,
};
const historyEntry = {
@@ -1755,22 +1819,26 @@ export class Wallet {
/**
- * Retrieve a mapping from currency name to the amount
- * that is currenctly available for spending in the wallet.
+ * Get detailed balance information, sliced by exchange and by currency.
*/
async getBalances(): Promise<WalletBalance> {
- function ensureEntry(balance: WalletBalance, currency: string) {
- let entry: WalletBalanceEntry|undefined = balance[currency];
- const z = Amounts.getZero(currency);
- if (!entry) {
- balance[currency] = entry = {
- available: z,
- paybackAmount: z,
- pendingIncoming: z,
- pendingPayment: z,
- };
+ /**
+ * Add amount to a balance field, both for
+ * the slicing by exchange and currency.
+ */
+ function addTo(balance: WalletBalance, field: keyof WalletBalanceEntry, amount: AmountJson, exchange: string): void {
+ const z = Amounts.getZero(amount.currency);
+ const balanceIdentity = {available: z, paybackAmount: z, pendingIncoming: z, pendingPayment: z};
+ let entryCurr = balance.byCurrency[amount.currency];
+ if (!entryCurr) {
+ balance.byCurrency[amount.currency] = entryCurr = { ...balanceIdentity };
+ }
+ let entryEx = balance.byExchange[exchange];
+ if (!entryEx) {
+ balance.byExchange[exchange] = entryEx = { ...balanceIdentity };
}
- return entry;
+ entryCurr[field] = Amounts.add(entryCurr[field], amount).amount;
+ entryEx[field] = Amounts.add(entryEx[field], amount).amount;
}
function collectBalances(c: CoinRecord, balance: WalletBalance) {
@@ -1780,9 +1848,8 @@ export class Wallet {
if (!(c.status === CoinStatus.Dirty || c.status === CoinStatus.Fresh)) {
return balance;
}
- const currency = c.currentAmount.currency;
- const entry = ensureEntry(balance, currency);
- entry.available = Amounts.add(entry.available, c.currentAmount).amount;
+ console.log("collecting balance");
+ addTo(balance, "available", c.currentAmount, c.exchangeBaseUrl);
return balance;
}
@@ -1790,15 +1857,13 @@ export class Wallet {
if (!r.confirmed) {
return balance;
}
- const entry = ensureEntry(balance, r.requested_amount.currency);
let amount = r.current_amount;
if (!amount) {
amount = r.requested_amount;
}
amount = Amounts.add(amount, r.precoin_amount).amount;
if (Amounts.cmp(smallestWithdraw[r.exchange_base_url], amount) < 0) {
- entry.pendingIncoming = Amounts.add(entry.pendingIncoming,
- amount).amount;
+ addTo(balance, "pendingIncoming", amount, r.exchange_base_url);
}
return balance;
}
@@ -1807,9 +1872,8 @@ export class Wallet {
if (!r.hasPayback) {
return balance;
}
- const entry = ensureEntry(balance, r.requested_amount.currency);
if (Amounts.cmp(smallestWithdraw[r.exchange_base_url], r.current_amount!) < 0) {
- entry.paybackAmount = Amounts.add(entry.paybackAmount, r.current_amount!).amount;
+ addTo(balance, "paybackAmount", r.current_amount!, r.exchange_base_url);
}
return balance;
}
@@ -1821,9 +1885,7 @@ export class Wallet {
if (r.finished) {
return balance;
}
- const entry = ensureEntry(balance, r.valueWithFee.currency);
- entry.pendingIncoming = Amounts.add(entry.pendingIncoming,
- r.valueOutput).amount;
+ addTo(balance, "pendingIncoming", r.valueOutput, r.exchangeBaseUrl);
return balance;
}
@@ -1832,10 +1894,7 @@ export class Wallet {
if (t.finished) {
return balance;
}
- const entry = ensureEntry(balance, t.contractTerms.amount.currency);
- entry.pendingPayment = Amounts.add(entry.pendingPayment,
- t.contractTerms.amount).amount;
-
+ addTo(balance, "pendingIncoming", t.contractTerms.amount, t.payReq.exchange);
return balance;
}
@@ -1852,7 +1911,10 @@ export class Wallet {
return sw;
}
- const balance = {};
+ const balance = {
+ byExchange: {},
+ byCurrency: {},
+ };
// Mapping from exchange pub to smallest
// possible amount we can withdraw
let smallestWithdraw: {[baseUrl: string]: AmountJson} = {};
@@ -1876,7 +1938,6 @@ export class Wallet {
.reduce(collectPayments, balance);
await tx.finish();
return balance;
-
}
@@ -2347,4 +2408,156 @@ export class Wallet {
stop() {
this.timerGroup.stopCurrentAndFutureTimers();
}
+
+ async getSenderWireInfos(): Promise<SenderWireInfos> {
+ const m: { [url: string]: Set<string> } = {};
+ await this.q().iter(Stores.exchangeWireFees).map((x) => {
+ const s = m[x.exchangeBaseUrl] = m[x.exchangeBaseUrl] || new Set();
+ Object.keys(x.feesForType).map((k) => s.add(k));
+ }).run();
+ console.log(m);
+ const exchangeWireTypes: { [url: string]: string[] } = {};
+ Object.keys(m).map((e) => { exchangeWireTypes[e] = Array.from(m[e]); });
+
+ const senderWiresSet = new Set();
+ await this.q().iter(Stores.reserves).map((x) => {
+ if (x.senderWire) {
+ senderWiresSet.add(JSON.stringify(x.senderWire));
+ }
+ }).run();
+ const senderWires = Array.from(senderWiresSet).map((x) => JSON.parse(x));
+
+ return {
+ exchangeWireTypes,
+ senderWires,
+ };
+ }
+
+ /**
+ * Trigger paying coins back into the user's account.
+ */
+ async returnCoins(req: ReturnCoinsRequest): Promise<void> {
+ console.log("got returnCoins request", req);
+ const wireType = (req.senderWire as any).type;
+ console.log("wireType", wireType);
+ if (!wireType || typeof wireType !== "string") {
+ console.error(`wire type must be a non-empty string, not ${wireType}`);
+ return;
+ }
+ const stampSecNow = Math.floor((new Date()).getTime() / 1000);
+ const exchange = await this.q().get(Stores.exchanges, req.exchange);
+ if (!exchange) {
+ console.error(`Exchange ${req.exchange} not known to the wallet`);
+ return;
+ }
+ const cds = await this.getCoinsForReturn(req.exchange, req.amount);
+ console.log(cds);
+
+ if (!cds) {
+ throw Error("coin return impossible, can't select coins");
+ }
+
+ const { priv, pub } = await this.cryptoApi.createEddsaKeypair();
+
+ const wireHash = await this.cryptoApi.hashString(canonicalJson(req.senderWire));
+
+ const contractTerms: ContractTerms = {
+ H_wire: wireHash,
+ amount: req.amount,
+ auditors: [],
+ wire_method: wireType,
+ pay_deadline: `/Date(${stampSecNow + 60 * 5})/`,
+ locations: [],
+ max_fee: req.amount,
+ merchant: {},
+ merchant_pub: pub,
+ exchanges: [ { master_pub: exchange.masterPublicKey, url: exchange.baseUrl } ],
+ products: [],
+ refund_deadline: `/Date(${stampSecNow + 60 * 5})/`,
+ timestamp: `/Date(${stampSecNow})/`,
+ order_id: "none",
+ pay_url: "",
+ fulfillment_url: "",
+ extra: {},
+ };
+
+ const contractTermsHash = await this.cryptoApi.hashString(canonicalJson(contractTerms));
+
+ const payCoinInfo = await this.cryptoApi.signDeposit(contractTerms, cds);
+
+ console.log("pci", payCoinInfo);
+
+ const coins = payCoinInfo.map((pci) => ({ coinPaySig: pci.sig }));
+
+ const coinsReturnRecord: CoinsReturnRecord = {
+ coins,
+ exchange: exchange.baseUrl,
+ contractTerms,
+ contractTermsHash,
+ merchantPriv: priv,
+ wire: req.senderWire,
+ }
+
+ await this.q()
+ .put(Stores.coinsReturns, coinsReturnRecord)
+ .putAll(Stores.coins, payCoinInfo.map((pci) => pci.updatedCoin))
+ .finish();
+
+ this.depositReturnedCoins(coinsReturnRecord);
+ }
+
+ async depositReturnedCoins(coinsReturnRecord: CoinsReturnRecord): Promise<void> {
+ for (const c of coinsReturnRecord.coins) {
+ if (c.depositedSig) {
+ continue;
+ }
+ const req = {
+ f: c.coinPaySig.f,
+ wire: coinsReturnRecord.wire,
+ H_wire: coinsReturnRecord.contractTerms.H_wire,
+ h_contract_terms: coinsReturnRecord.contractTermsHash,
+ coin_pub: c.coinPaySig.coin_pub,
+ denom_pub: c.coinPaySig.denom_pub,
+ ub_sig: c.coinPaySig.ub_sig,
+ timestamp: coinsReturnRecord.contractTerms.timestamp,
+ wire_transfer_deadline: coinsReturnRecord.contractTerms.pay_deadline,
+ pay_deadline: coinsReturnRecord.contractTerms.pay_deadline,
+ refund_deadline: coinsReturnRecord.contractTerms.refund_deadline,
+ merchant_pub: coinsReturnRecord.contractTerms.merchant_pub,
+ coin_sig: c.coinPaySig.coin_sig,
+ };
+ console.log("req", req);
+ const reqUrl = (new URI("deposit")).absoluteTo(coinsReturnRecord.exchange);
+ const resp = await this.http.postJson(reqUrl.href(), req);
+ if (resp.status !== 200) {
+ console.error("deposit failed due to status code", resp);
+ continue;
+ }
+ const respJson = JSON.parse(resp.responseText);
+ if (respJson.status !== "DEPOSIT_OK") {
+ console.error("deposit failed", resp);
+ continue;
+ }
+
+ if (!respJson.sig) {
+ console.error("invalid 'sig' field", resp);
+ continue;
+ }
+
+ // FIXME: verify signature
+
+ // For every successful deposit, we replace the old record with an updated one
+ const currentCrr = await this.q().get(Stores.coinsReturns, coinsReturnRecord.contractTermsHash);
+ if (!currentCrr) {
+ console.error("database inconsistent");
+ continue;
+ }
+ for (const nc of currentCrr.coins) {
+ if (nc.coinPaySig.coin_pub === c.coinPaySig.coin_pub) {
+ nc.depositedSig = respJson.sig;
+ }
+ }
+ await this.q().put(Stores.coinsReturns, currentCrr);
+ }
+ }
}
diff --git a/src/webex/messages.ts b/src/webex/messages.ts
index bf9ca00b0..d7ecd06a1 100644
--- a/src/webex/messages.ts
+++ b/src/webex/messages.ts
@@ -168,6 +168,14 @@ export interface MessageMap {
request: { };
response: void;
};
+ "get-sender-wire-infos": {
+ request: { };
+ response: void;
+ };
+ "return-coins": {
+ request: { };
+ response: void;
+ };
}
/**
diff --git a/src/webex/pages/confirm-create-reserve.html b/src/webex/pages/confirm-create-reserve.html
index 493a6fb5f..17daf4dde 100644
--- a/src/webex/pages/confirm-create-reserve.html
+++ b/src/webex/pages/confirm-create-reserve.html
@@ -6,40 +6,12 @@
<title>Taler Wallet: Select Taler Provider</title>
<link rel="icon" href="/img/icon.png">
- <link rel="stylesheet" type="text/css" href="../style/wallet.css">
<link rel="stylesheet" type="text/css" href="../style/pure.css">
+ <link rel="stylesheet" type="text/css" href="../style/wallet.css">
<script src="/dist/page-common-bundle.js"></script>
<script src="/dist/confirm-create-reserve-bundle.js"></script>
- <style>
- body {
- font-size: 100%;
- overflow-y: scroll;
- }
- .button-success {
- background: rgb(28, 184, 65); /* this is a green */
- color: white;
- border-radius: 4px;
- text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
- }
- .button-secondary {
- background: rgb(66, 184, 221); /* this is a light blue */
- color: white;
- border-radius: 4px;
- text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
- }
- a.opener {
- color: black;
- }
- .opener-open::before {
- content: "\25bc"
- }
- .opener-collapsed::before {
- content: "\25b6 "
- }
- </style>
-
</head>
<body>
diff --git a/src/webex/pages/popup.tsx b/src/webex/pages/popup.tsx
index f1f0353ad..7d12d365e 100644
--- a/src/webex/pages/popup.tsx
+++ b/src/webex/pages/popup.tsx
@@ -219,22 +219,26 @@ class WalletBalanceView extends React.Component<any, any> {
this.unmount = true;
}
- updateBalance() {
- chrome.runtime.sendMessage({type: "balances"}, (resp) => {
+ async updateBalance() {
+ let balance: WalletBalance;
+ try {
+ balance = await wxApi.getBalance();
+ } catch (e) {
if (this.unmount) {
return;
}
- if (resp.error) {
- this.gotError = true;
- console.error("could not retrieve balances", resp);
- this.setState({});
- return;
- }
- this.gotError = false;
- console.log("got wallet", resp);
- this.balance = resp;
+ this.gotError = true;
+ console.error("could not retrieve balances", e);
this.setState({});
- });
+ return;
+ }
+ if (this.unmount) {
+ return;
+ }
+ this.gotError = false;
+ console.log("got balance", balance);
+ this.balance = balance;
+ this.setState({});
}
renderEmpty(): JSX.Element {
@@ -308,8 +312,8 @@ class WalletBalanceView extends React.Component<any, any> {
}
console.log(wallet);
let paybackAvailable = false;
- const listing = Object.keys(wallet).map((key) => {
- const entry: WalletBalanceEntry = wallet[key];
+ const listing = Object.keys(wallet.byCurrency).map((key) => {
+ const entry: WalletBalanceEntry = wallet.byCurrency[key];
if (entry.paybackAmount.value !== 0 || entry.paybackAmount.fraction !== 0) {
paybackAvailable = true;
}
@@ -321,14 +325,16 @@ class WalletBalanceView extends React.Component<any, any> {
</p>
);
});
- const link = chrome.extension.getURL("/src/webex/pages/auditors.html");
- const linkElem = <a className="actionLink" href={link} target="_blank">Trusted Auditors and Exchanges</a>;
- const paybackLinkElem = <a className="actionLink" href={link} target="_blank">Trusted Auditors and Exchanges</a>;
+ const makeLink = (page: string, name: string) => {
+ const url = chrome.extension.getURL(`/src/webex/pages/${page}`);
+ return <div><a className="actionLink" href={url} target="_blank">{name}</a></div>;
+ };
return (
<div>
{listing.length > 0 ? listing : this.renderEmpty()}
- {paybackAvailable && paybackLinkElem}
- {linkElem}
+ {paybackAvailable && makeLink("payback", i18n.str`Payback`)}
+ {makeLink("return-coins.html#dissolve", i18n.str`Return Electronic Cash to Bank Account`)}
+ {makeLink("auditors.html", i18n.str`Manage Trusted Auditors and Exchanges`)}
</div>
);
}
diff --git a/src/webex/pages/return-coins.html b/src/webex/pages/return-coins.html
new file mode 100644
index 000000000..c0ab218d2
--- /dev/null
+++ b/src/webex/pages/return-coins.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <meta charset="UTF-8">
+ <title>Taler Wallet: Return Coins to Bank Account</title>
+
+ <link rel="stylesheet" type="text/css" href="../style/pure.css">
+ <link rel="stylesheet" type="text/css" href="../style/wallet.css">
+
+ <link rel="icon" href="/img/icon.png">
+
+ <script src="/dist/page-common-bundle.js"></script>
+ <script src="/dist/return-coins-bundle.js"></script>
+
+ <body>
+ <div id="container"></div>
+ </body>
+</html>
diff --git a/src/webex/pages/return-coins.tsx b/src/webex/pages/return-coins.tsx
new file mode 100644
index 000000000..1fdadd2e9
--- /dev/null
+++ b/src/webex/pages/return-coins.tsx
@@ -0,0 +1,271 @@
+/*
+ This file is part of TALER
+ (C) 2017 Inria
+
+ 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/>
+ */
+
+/**
+ * View and edit auditors.
+ *
+ * @author Florian Dold
+ */
+
+
+/**
+ * Imports.
+ */
+
+import {
+ AmountJson,
+ Amounts,
+ SenderWireInfos,
+ WalletBalance,
+} from "../../types";
+
+import * as i18n from "../../i18n";
+
+import * as wire from "../../wire";
+
+import {
+ getBalance,
+ getSenderWireInfos,
+ returnCoins,
+} from "../wxApi";
+
+import { renderAmount } from "../renderHtml";
+
+import * as React from "react";
+import * as ReactDOM from "react-dom";
+
+interface ReturnSelectionItemProps extends ReturnSelectionListProps {
+ exchangeUrl: string;
+ senderWireInfos: SenderWireInfos;
+}
+
+interface ReturnSelectionItemState {
+ selectedValue: string;
+ supportedWires: object[];
+ selectedWire: string;
+ currency: string;
+}
+
+class ReturnSelectionItem extends React.Component<ReturnSelectionItemProps, ReturnSelectionItemState> {
+ constructor(props: ReturnSelectionItemProps) {
+ super(props);
+ const exchange = this.props.exchangeUrl;
+ const wireTypes = this.props.senderWireInfos.exchangeWireTypes;
+ const supportedWires = this.props.senderWireInfos.senderWires.filter((x) => {
+ return wireTypes[exchange] && wireTypes[exchange].indexOf((x as any).type) >= 0;
+ });
+ this.state = {
+ currency: props.balance.byExchange[props.exchangeUrl].available.currency,
+ selectedValue: Amounts.toFloat(props.balance.byExchange[props.exchangeUrl].available).toString(),
+ selectedWire: "",
+ supportedWires,
+ };
+ }
+ render(): JSX.Element {
+ const exchange = this.props.exchangeUrl;
+ const byExchange = this.props.balance.byExchange;
+ const wireTypes = this.props.senderWireInfos.exchangeWireTypes;
+ return (
+ <div key={exchange}>
+ <h2>Exchange {exchange}</h2>
+ <p>Available amount: {renderAmount(byExchange[exchange].available)}</p>
+ <p>Supported wire methods: {wireTypes[exchange].length ? wireTypes[exchange].join(", ") : "none"}</p>
+ <p>Wire {""}
+ <input
+ type="text"
+ size={this.state.selectedValue.length || 1}
+ value={this.state.selectedValue}
+ onChange={(evt) => this.setState({selectedValue: evt.target.value})}
+ style={{textAlign: "center"}}
+ /> {this.props.balance.byExchange[exchange].available.currency} {""}
+ to account {""}
+ <select value={this.state.selectedWire} onChange={(evt) => this.setState({selectedWire: evt.target.value})}>
+ <option style={{display: "none"}}>Select account</option>
+ {this.state.supportedWires.map((w, n) =>
+ <option value={n.toString()} key={JSON.stringify(w)}>{n+1}: {wire.summarizeWire(w)}</option>
+ )}
+ </select>.
+ </p>
+ {this.state.selectedWire
+ ? <button className="pure-button button-success" onClick={() => this.select()}>
+ {i18n.str`Wire to bank account`}
+ </button>
+ : null}
+ </div>
+ );
+ }
+
+ select() {
+ let val: number;
+ let selectedWire: number;
+ try {
+ val = Number.parseFloat(this.state.selectedValue);
+ selectedWire = Number.parseInt(this.state.selectedWire);
+ } catch (e) {
+ console.error(e);
+ return;
+ }
+ this.props.selectDetail({
+ amount: Amounts.fromFloat(val, this.state.currency),
+ exchange: this.props.exchangeUrl,
+ senderWire: this.state.supportedWires[selectedWire],
+ });
+ }
+}
+
+interface ReturnSelectionListProps {
+ balance: WalletBalance;
+ senderWireInfos: SenderWireInfos;
+ selectDetail(d: SelectedDetail): void;
+}
+
+class ReturnSelectionList extends React.Component<ReturnSelectionListProps, {}> {
+ render(): JSX.Element {
+ const byExchange = this.props.balance.byExchange;
+ const exchanges = Object.keys(byExchange);
+ if (!exchanges.length) {
+ return <p className="errorbox">Currently no funds available to transfer.</p>;
+ }
+ return (
+ <div>
+ {exchanges.map((e) => <ReturnSelectionItem key={e} exchangeUrl={e} {...this.props} />)}
+ </div>
+ );
+ }
+}
+
+interface SelectedDetail {
+ amount: AmountJson;
+ senderWire: any;
+ exchange: string;
+}
+
+
+interface ReturnConfirmationProps {
+ detail: SelectedDetail;
+ cancel(): void;
+ confirm(): void;
+}
+
+class ReturnConfirmation extends React.Component<ReturnConfirmationProps, {}> {
+ render() {
+ return (
+ <div>
+ <p>Please confirm if you want to transmit <strong>{renderAmount(this.props.detail.amount)}</strong> at {""}
+ {this.props.detail.exchange} to account {""}
+ <strong style={{whiteSpace: "nowrap"}}>{wire.summarizeWire(this.props.detail.senderWire)}</strong>.
+ </p>
+ <button className="pure-button button-success" onClick={() => this.props.confirm()}>
+ {i18n.str`Confirm`}
+ </button>
+ <button className="pure-button" onClick={() => this.props.cancel()}>
+ {i18n.str`Cancel`}
+ </button>
+ </div>
+ );
+ }
+}
+
+interface ReturnCoinsState {
+ balance: WalletBalance | undefined;
+ senderWireInfos: SenderWireInfos | undefined;
+ selectedReturn: SelectedDetail | undefined;
+ /**
+ * Last confirmed detail, so we can show a nice box.
+ */
+ lastConfirmedDetail: SelectedDetail | undefined;
+}
+
+class ReturnCoins extends React.Component<any, ReturnCoinsState> {
+ constructor() {
+ super();
+ const port = chrome.runtime.connect();
+ port.onMessage.addListener((msg: any) => {
+ if (msg.notify) {
+ console.log("got notified");
+ this.update();
+ }
+ });
+ this.update();
+ this.state = {} as any;
+ }
+
+ async update() {
+ const balance = await getBalance();
+ const senderWireInfos = await getSenderWireInfos();
+ console.log("got swi", senderWireInfos);
+ console.log("got bal", balance);
+ this.setState({ balance, senderWireInfos });
+ }
+
+ selectDetail(d: SelectedDetail) {
+ this.setState({selectedReturn: d});
+ }
+
+ async confirm() {
+ const selectedReturn = this.state.selectedReturn;
+ if (!selectedReturn) {
+ return;
+ }
+ await returnCoins(selectedReturn);
+ await this.update();
+ this.setState({selectedReturn: undefined, lastConfirmedDetail: selectedReturn});
+ }
+
+ async cancel() {
+ this.setState({selectedReturn: undefined, lastConfirmedDetail: undefined});
+ }
+
+ render() {
+ const balance = this.state.balance;
+ const senderWireInfos = this.state.senderWireInfos;
+ if (!balance || !senderWireInfos) {
+ return <span>...</span>;
+ }
+ if (this.state.selectedReturn) {
+ return (
+ <div id="main">
+ <ReturnConfirmation
+ detail={this.state.selectedReturn}
+ cancel={() => this.cancel()}
+ confirm={() => this.confirm()}
+ />
+ </div>
+ );
+ }
+ return (
+ <div id="main">
+ <h1>Wire electronic cash back to own bank account</h1>
+ <p>You can send coins back into your own bank account. Note that
+ you're acting as a merchant when doing this, and thus the same fees apply.</p>
+ {this.state.lastConfirmedDetail
+ ? <p className="okaybox">Transfer of {renderAmount(this.state.lastConfirmedDetail.amount)} successfully initiated.</p>
+ : null}
+ <ReturnSelectionList
+ selectDetail={(d) => this.selectDetail(d)}
+ balance={balance}
+ senderWireInfos={senderWireInfos} />
+ </div>
+ );
+ }
+}
+
+
+function main() {
+ ReactDOM.render(<ReturnCoins />, document.getElementById("container")!);
+}
+
+document.addEventListener("DOMContentLoaded", main);
diff --git a/src/webex/style/wallet.css b/src/webex/style/wallet.css
index 5773eb396..61dd611e9 100644
--- a/src/webex/style/wallet.css
+++ b/src/webex/style/wallet.css
@@ -1,3 +1,8 @@
+body {
+ font-size: 100%;
+ overflow-y: scroll;
+}
+
#main {
border: solid 1px black;
border-radius: 10px;
@@ -235,3 +240,14 @@ a.actionLink {
font-weight: bold;
background: #00FA9A;
}
+
+
+a.opener {
+ color: black;
+}
+.opener-open::before {
+ content: "\25bc"
+}
+.opener-collapsed::before {
+ content: "\25b6 "
+}
diff --git a/src/webex/wxApi.ts b/src/webex/wxApi.ts
index 9d8ba4d1d..1371e27e4 100644
--- a/src/webex/wxApi.ts
+++ b/src/webex/wxApi.ts
@@ -31,9 +31,11 @@ import {
DenominationRecord,
ExchangeRecord,
PreCoinRecord,
+ QueryPaymentResult,
ReserveCreationInfo,
ReserveRecord,
- QueryPaymentResult,
+ SenderWireInfos,
+ WalletBalance,
} from "../types";
import { MessageType, MessageMap } from "./messages";
@@ -296,3 +298,26 @@ export function createReserve(args: { amount: AmountJson, exchange: string, send
export function resetDb(): Promise<void> {
return callBackend("reset-db", { });
}
+
+/**
+ * Get balances for all currencies/exchanges.
+ */
+export function getBalance(): Promise<WalletBalance> {
+ return callBackend("balances", { });
+}
+
+
+/**
+ * Get possible sender wire infos for getting money
+ * wired from an exchange.
+ */
+export function getSenderWireInfos(): Promise<SenderWireInfos> {
+ return callBackend("get-sender-wire-infos", { });
+}
+
+/**
+ * Return coins to a bank account.
+ */
+export function returnCoins(args: { amount: AmountJson, exchange: string, senderWire: object }): Promise<void> {
+ return callBackend("return-coins", args);
+}
diff --git a/src/webex/wxBackend.ts b/src/webex/wxBackend.ts
index 261477386..974bcb3c2 100644
--- a/src/webex/wxBackend.ts
+++ b/src/webex/wxBackend.ts
@@ -32,12 +32,13 @@ import {
} from "../query";
import {
AmountJson,
+ ConfirmReserveRequest,
+ CreateReserveRequest,
Notifier,
ProposalRecord,
+ ReturnCoinsRequest,
} from "../types";
import {
- ConfirmReserveRequest,
- CreateReserveRequest,
Stores,
WALLET_DB_VERSION,
Wallet,
@@ -278,6 +279,18 @@ function handleMessage(sender: MessageSender,
}
return needsWallet().paymentSucceeded(contractTermsHash, merchantSig);
}
+ case "get-sender-wire-infos": {
+ return needsWallet().getSenderWireInfos();
+ }
+ case "return-coins": {
+ const d = {
+ amount: detail.amount,
+ exchange: detail.exchange,
+ senderWire: detail.senderWire,
+ };
+ const req = ReturnCoinsRequest.checked(d);
+ return needsWallet().returnCoins(req);
+ }
case "check-upgrade": {
let dbResetRequired = false;
if (!currentWallet) {
diff --git a/src/wire.ts b/src/wire.ts
new file mode 100644
index 000000000..d61e8eab2
--- /dev/null
+++ b/src/wire.ts
@@ -0,0 +1,53 @@
+/*
+ This file is part of TALER
+ (C) 2017 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/>
+ */
+
+
+/**
+ * Display and manipulate wire information.
+ *
+ * Right now, all types are hard-coded. In the future, there might be plugins / configurable
+ * methods or support for the "payto://" URI scheme.
+ */
+
+/**
+ * Imports.
+ */
+import * as i18n from "./i18n";
+
+/**
+ * Short summary of the wire information.
+ *
+ * Might abbreviate and return the same summary for different
+ * wire details.
+ */
+export function summarizeWire(w: any): string {
+ if (!w.type) {
+ return i18n.str`Invalid Wire`;
+ }
+ switch (w.type.toLowerCase()) {
+ case "test":
+ if (!w.account_number && w.account_number !== 0) {
+ return i18n.str`Invalid Test Wire Detail`;
+ }
+ if (!w.bank_uri) {
+ return i18n.str`Invalid Test Wire Detail`;
+ }
+ return i18n.str`Test Wire Acct #${w.account_number} on ${w.bank_uri}`;
+ default:
+ return i18n.str`Unknown Wire Detail`;
+ }
+}
+