implement returning coins to user's account

This commit is contained in:
Florian Dold 2017-08-14 04:16:12 +02:00
parent 419a05e801
commit d5bba630a3
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
24 changed files with 1834 additions and 828 deletions

View File

@ -217,12 +217,11 @@ export namespace Checkable {
type: target, type: target,
}, ["(root)"]); }, ["(root)"]);
if (opts.validate) { if (opts.validate) {
const instance = new target(); if (target.validate !== "function") {
if (typeof instance.validate !== "function") {
throw Error("invalid Checkable annotion: validate method required"); throw Error("invalid Checkable annotion: validate method required");
} }
// May throw exception // May throw exception
instance.validate.call(cv); target.validate(cv);
} }
return cv; return cv;
}; };

View File

@ -26,8 +26,8 @@
import { import {
AmountJson, AmountJson,
CoinRecord, CoinRecord,
ContractTerms,
DenominationRecord, DenominationRecord,
ProposalRecord,
PayCoinInfo, PayCoinInfo,
PaybackRequest, PaybackRequest,
PreCoinRecord, PreCoinRecord,
@ -277,9 +277,9 @@ export class CryptoApi {
return this.doRpc<PayCoinInfo>("isValidPaymentSignature", 1, sig, contractHash, merchantPub); return this.doRpc<PayCoinInfo>("isValidPaymentSignature", 1, sig, contractHash, merchantPub);
} }
signDeposit(proposal: ProposalRecord, signDeposit(contractTerms: ContractTerms,
cds: CoinWithDenom[]): Promise<PayCoinInfo> { 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}> { createEddsaKeypair(): Promise<{priv: string, pub: string}> {

View File

@ -28,8 +28,8 @@ import {
CoinPaySig, CoinPaySig,
CoinRecord, CoinRecord,
CoinStatus, CoinStatus,
ContractTerms,
DenominationRecord, DenominationRecord,
ProposalRecord,
PayCoinInfo, PayCoinInfo,
PaybackRequest, PaybackRequest,
PreCoinRecord, PreCoinRecord,
@ -38,6 +38,9 @@ import {
ReserveRecord, ReserveRecord,
WireFee, WireFee,
} from "../types"; } from "../types";
import {
canonicalJson,
} from "../helpers";
import { import {
CoinWithDenom, CoinWithDenom,
} from "../wallet"; } from "../wallet";
@ -227,16 +230,17 @@ namespace RpcFunctions {
* Generate updated coins (to store in the database) * Generate updated coins (to store in the database)
* and deposit permissions for each given coin. * and deposit permissions for each given coin.
*/ */
export function signDeposit(proposal: ProposalRecord, export function signDeposit(contractTerms: ContractTerms,
cds: CoinWithDenom[]): PayCoinInfo { cds: CoinWithDenom[]): PayCoinInfo {
const ret: PayCoinInfo = []; const ret: PayCoinInfo = [];
const contractTermsHash = hashString(canonicalJson(contractTerms));
const feeList: AmountJson[] = cds.map((x) => x.denom.feeDeposit); const feeList: AmountJson[] = cds.map((x) => x.denom.feeDeposit);
let fees = Amounts.add(Amounts.getZero(feeList[0].currency), ...feeList).amount; let fees = Amounts.add(Amounts.getZero(feeList[0].currency), ...feeList).amount;
// okay if saturates // okay if saturates
fees = Amounts.sub(fees, proposal.contractTerms.max_fee).amount; fees = Amounts.sub(fees, contractTerms.max_fee).amount;
const total = Amounts.add(fees, proposal.contractTerms.amount).amount; const total = Amounts.add(fees, contractTerms.amount).amount;
const amountSpent = native.Amount.getZero(cds[0].coin.currentAmount.currency); const amountSpent = native.Amount.getZero(cds[0].coin.currentAmount.currency);
const amountRemaining = new native.Amount(total); const amountRemaining = new native.Amount(total);
@ -273,11 +277,11 @@ namespace RpcFunctions {
amount_with_fee: coinSpend.toNbo(), amount_with_fee: coinSpend.toNbo(),
coin_pub: native.EddsaPublicKey.fromCrock(cd.coin.coinPub), coin_pub: native.EddsaPublicKey.fromCrock(cd.coin.coinPub),
deposit_fee: new native.Amount(cd.denom.feeDeposit).toNbo(), deposit_fee: new native.Amount(cd.denom.feeDeposit).toNbo(),
h_contract: native.HashCode.fromCrock(proposal.contractTermsHash), h_contract: native.HashCode.fromCrock(contractTermsHash),
h_wire: native.HashCode.fromCrock(proposal.contractTerms.H_wire), h_wire: native.HashCode.fromCrock(contractTerms.H_wire),
merchant: native.EddsaPublicKey.fromCrock(proposal.contractTerms.merchant_pub), merchant: native.EddsaPublicKey.fromCrock(contractTerms.merchant_pub),
refund_deadline: native.AbsoluteTimeNbo.fromTalerString(proposal.contractTerms.refund_deadline), refund_deadline: native.AbsoluteTimeNbo.fromTalerString(contractTerms.refund_deadline),
timestamp: native.AbsoluteTimeNbo.fromTalerString(proposal.contractTerms.timestamp), timestamp: native.AbsoluteTimeNbo.fromTalerString(contractTerms.timestamp),
}); });
const coinSig = native.eddsaSign(d.toPurpose(), const coinSig = native.eddsaSign(d.toPurpose(),

View File

@ -56,67 +56,67 @@ msgid ""
"wallet." "wallet."
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:212 #: src/webex/pages/confirm-create-reserve.tsx:213
#, fuzzy, c-format #, fuzzy, c-format
msgid "Withdrawal fees:" msgid "Withdrawal fees:"
msgstr "Abheben bei %1$s" msgstr "Abheben bei %1$s"
#: src/webex/pages/confirm-create-reserve.tsx:213 #: src/webex/pages/confirm-create-reserve.tsx:214
#, c-format #, c-format
msgid "Rounding loss:" msgid "Rounding loss:"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:214 #: src/webex/pages/confirm-create-reserve.tsx:215
#, c-format #, c-format
msgid "Earliest expiration (for deposit): %1$s" msgid "Earliest expiration (for deposit): %1$s"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:219 #: src/webex/pages/confirm-create-reserve.tsx:220
#, c-format #, c-format
msgid "# Coins" msgid "# Coins"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:220 #: src/webex/pages/confirm-create-reserve.tsx:221
#, c-format #, c-format
msgid "Value" msgid "Value"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:221 #: src/webex/pages/confirm-create-reserve.tsx:222
#, fuzzy, c-format #, fuzzy, c-format
msgid "Withdraw Fee" msgid "Withdraw Fee"
msgstr "Abheben bei %1$s" msgstr "Abheben bei %1$s"
#: src/webex/pages/confirm-create-reserve.tsx:222 #: src/webex/pages/confirm-create-reserve.tsx:223
#, c-format #, c-format
msgid "Refresh Fee" msgid "Refresh Fee"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:223 #: src/webex/pages/confirm-create-reserve.tsx:224
#, c-format #, c-format
msgid "Deposit Fee" msgid "Deposit Fee"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:276 #: src/webex/pages/confirm-create-reserve.tsx:278
#, c-format #, c-format
msgid "Select" msgid "Select"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:292 #: src/webex/pages/confirm-create-reserve.tsx:294
#, c-format #, c-format
msgid "Error: URL may not be relative" msgid "Error: URL may not be relative"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:360 #: src/webex/pages/confirm-create-reserve.tsx:362
#, c-format #, c-format
msgid "The exchange is trusted by the wallet.\n" msgid "The exchange is trusted by the wallet.\n"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:366 #: src/webex/pages/confirm-create-reserve.tsx:368
#, c-format #, c-format
msgid "The exchange is audited by a trusted auditor.\n" msgid "The exchange is audited by a trusted auditor.\n"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:372 #: src/webex/pages/confirm-create-reserve.tsx:374
#, c-format #, c-format
msgid "" msgid ""
"Warning: The exchange is neither directly trusted nor audited by a trusted " "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" "If you withdraw from this exchange, it will be trusted in the future.\n"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:381 #: src/webex/pages/confirm-create-reserve.tsx:383
#, c-format #, c-format
msgid "" msgid ""
"Using exchange provider%1$s.\n" "Using exchange provider%1$s.\n"
@ -132,151 +132,166 @@ msgid ""
" %2$s in fees.\n" " %2$s in fees.\n"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:395 #: src/webex/pages/confirm-create-reserve.tsx:397
#, c-format #, c-format
msgid "" msgid ""
"Waiting for a response from\n" "Waiting for a response from\n"
" %1$s" " %1$s"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:406 #: src/webex/pages/confirm-create-reserve.tsx:408
#, c-format #, c-format
msgid "A problem occured, see below. %1$s" msgid "A problem occured, see below. %1$s"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:412 #: src/webex/pages/confirm-create-reserve.tsx:414
#, c-format #, c-format
msgid "" msgid ""
"Information about fees will be available when an exchange provider is " "Information about fees will be available when an exchange provider is "
"selected." "selected."
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:455 #: src/webex/pages/confirm-create-reserve.tsx:457
#, c-format #, c-format
msgid "Accept fees and withdraw" msgid "Accept fees and withdraw"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:460 #: src/webex/pages/confirm-create-reserve.tsx:462
#, c-format #, c-format
msgid "Change Exchange Provider" msgid "Change Exchange Provider"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:517 #: src/webex/pages/confirm-create-reserve.tsx:519
#, c-format #, c-format
msgid "You are about to withdraw %1$s from your bank account into your wallet." msgid "You are about to withdraw %1$s from your bank account into your wallet."
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:600 #: src/webex/pages/confirm-create-reserve.tsx:607
#, c-format #, c-format
msgid "" msgid ""
"Oops, something went wrong. The wallet responded with error status (%1$s)." "Oops, something went wrong. The wallet responded with error status (%1$s)."
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:611 #: src/webex/pages/confirm-create-reserve.tsx:616
#, c-format #, c-format
msgid "Checking URL, please wait ..." msgid "Checking URL, please wait ..."
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:625 #: src/webex/pages/confirm-create-reserve.tsx:630
#, c-format #, c-format
msgid "Can't parse amount: %1$s" msgid "Can't parse amount: %1$s"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:632 #: src/webex/pages/confirm-create-reserve.tsx:637
#, c-format #, c-format
msgid "Can't parse wire_types: %1$s" msgid "Can't parse wire_types: %1$s"
msgstr "" msgstr ""
#. TODO:generic error reporting function or component. #. 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 #, c-format
msgid "Fatal error: \"%1$s\"." msgid "Fatal error: \"%1$s\"."
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:160 #: src/webex/pages/popup.tsx:161
#, c-format #, c-format
msgid "Balance" msgid "Balance"
msgstr "Saldo" msgstr "Saldo"
#: src/webex/pages/popup.tsx:163 #: src/webex/pages/popup.tsx:164
#, c-format #, c-format
msgid "History" msgid "History"
msgstr "Verlauf" msgstr "Verlauf"
#: src/webex/pages/popup.tsx:166 #: src/webex/pages/popup.tsx:167
#, c-format #, c-format
msgid "Debug" msgid "Debug"
msgstr "Debug" msgstr "Debug"
#: src/webex/pages/popup.tsx:242 #: src/webex/pages/popup.tsx:247
#, c-format #, c-format
msgid "help" msgid "help"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:247 #: src/webex/pages/popup.tsx:252
#, fuzzy, c-format #, fuzzy, c-format
msgid "" msgid ""
"You have no balance to show. Need some\n" "You have no balance to show. Need some\n"
" %1$s getting started?\n" " %1$s getting started?\n"
msgstr "Sie haben kein Digitalgeld. Wollen Sie %1$s? abheben?" msgstr "Sie haben kein Digitalgeld. Wollen Sie %1$s? abheben?"
#: src/webex/pages/popup.tsx:264 #: src/webex/pages/popup.tsx:269
#, c-format #, c-format
msgid "%1$s incoming\n" msgid "%1$s incoming\n"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:277 #: src/webex/pages/popup.tsx:282
#, c-format #, c-format
msgid "%1$s being spent\n" msgid "%1$s being spent\n"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:303 #: src/webex/pages/popup.tsx:308
#, c-format #, c-format
msgid "Error: could not retrieve balance information." msgid "Error: could not retrieve balance information."
msgstr "" 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 #, fuzzy, c-format
msgid "" msgid ""
"Bank requested reserve (%1$s) for\n" "Bank requested reserve (%1$s) for\n"
" %2$s.\n" " %2$s.\n"
msgstr "Bank bestätig anlegen der Reserve (%1$s) bei %2$s" 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 #, fuzzy, c-format
msgid "" msgid ""
"Started to withdraw\n" "Started to withdraw\n"
" %1$s from%2$s(%3$s).\n" " %1$s from%2$s(%3$s).\n"
msgstr "Reserve (%1$s) mit %2$s bei %3$s erzeugt" 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 #, c-format
msgid "Merchant%1$soffered contract%2$s;\n" msgid "Merchant%1$soffered contract%2$s;\n"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:373 #: src/webex/pages/popup.tsx:380
#, fuzzy, c-format #, fuzzy, c-format
msgid "Withdrew%1$sfrom%2$s(%3$s).\n" msgid "Withdrew%1$sfrom%2$s(%3$s).\n"
msgstr "Reserve (%1$s) mit %2$s bei %3$s erzeugt" 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 #, fuzzy, c-format
msgid "" msgid ""
"Paid%1$sto merchant%2$s.\n" "Paid%1$sto merchant%2$s.\n"
" (%3$s)\n" " (%3$s)\n"
msgstr "Reserve (%1$s) mit %2$s bei %3$s erzeugt" 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 #, c-format
msgid "Unknown event (%1$s)" msgid "Unknown event (%1$s)"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:435 #: src/webex/pages/popup.tsx:442
#, c-format #, c-format
msgid "Error: could not retrieve event history" msgid "Error: could not retrieve event history"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:469 #: src/webex/pages/popup.tsx:476
#, c-format #, c-format
msgid "Your wallet has no events recorded." msgid "Your wallet has no events recorded."
msgstr "Ihre Geldbörse verzeichnet keine Vorkommnisse." msgstr "Ihre Geldbörse verzeichnet keine Vorkommnisse."

View File

@ -56,67 +56,67 @@ msgid ""
"wallet." "wallet."
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:212 #: src/webex/pages/confirm-create-reserve.tsx:213
#, c-format #, c-format
msgid "Withdrawal fees:" msgid "Withdrawal fees:"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:213 #: src/webex/pages/confirm-create-reserve.tsx:214
#, c-format #, c-format
msgid "Rounding loss:" msgid "Rounding loss:"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:214 #: src/webex/pages/confirm-create-reserve.tsx:215
#, c-format #, c-format
msgid "Earliest expiration (for deposit): %1$s" msgid "Earliest expiration (for deposit): %1$s"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:219 #: src/webex/pages/confirm-create-reserve.tsx:220
#, c-format #, c-format
msgid "# Coins" msgid "# Coins"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:220 #: src/webex/pages/confirm-create-reserve.tsx:221
#, c-format #, c-format
msgid "Value" msgid "Value"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:221 #: src/webex/pages/confirm-create-reserve.tsx:222
#, c-format #, c-format
msgid "Withdraw Fee" msgid "Withdraw Fee"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:222 #: src/webex/pages/confirm-create-reserve.tsx:223
#, c-format #, c-format
msgid "Refresh Fee" msgid "Refresh Fee"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:223 #: src/webex/pages/confirm-create-reserve.tsx:224
#, c-format #, c-format
msgid "Deposit Fee" msgid "Deposit Fee"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:276 #: src/webex/pages/confirm-create-reserve.tsx:278
#, c-format #, c-format
msgid "Select" msgid "Select"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:292 #: src/webex/pages/confirm-create-reserve.tsx:294
#, c-format #, c-format
msgid "Error: URL may not be relative" msgid "Error: URL may not be relative"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:360 #: src/webex/pages/confirm-create-reserve.tsx:362
#, c-format #, c-format
msgid "The exchange is trusted by the wallet.\n" msgid "The exchange is trusted by the wallet.\n"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:366 #: src/webex/pages/confirm-create-reserve.tsx:368
#, c-format #, c-format
msgid "The exchange is audited by a trusted auditor.\n" msgid "The exchange is audited by a trusted auditor.\n"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:372 #: src/webex/pages/confirm-create-reserve.tsx:374
#, c-format #, c-format
msgid "" msgid ""
"Warning: The exchange is neither directly trusted nor audited by a trusted " "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" "If you withdraw from this exchange, it will be trusted in the future.\n"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:381 #: src/webex/pages/confirm-create-reserve.tsx:383
#, c-format #, c-format
msgid "" msgid ""
"Using exchange provider%1$s.\n" "Using exchange provider%1$s.\n"
@ -132,151 +132,166 @@ msgid ""
" %2$s in fees.\n" " %2$s in fees.\n"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:395 #: src/webex/pages/confirm-create-reserve.tsx:397
#, c-format #, c-format
msgid "" msgid ""
"Waiting for a response from\n" "Waiting for a response from\n"
" %1$s" " %1$s"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:406 #: src/webex/pages/confirm-create-reserve.tsx:408
#, c-format #, c-format
msgid "A problem occured, see below. %1$s" msgid "A problem occured, see below. %1$s"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:412 #: src/webex/pages/confirm-create-reserve.tsx:414
#, c-format #, c-format
msgid "" msgid ""
"Information about fees will be available when an exchange provider is " "Information about fees will be available when an exchange provider is "
"selected." "selected."
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:455 #: src/webex/pages/confirm-create-reserve.tsx:457
#, c-format #, c-format
msgid "Accept fees and withdraw" msgid "Accept fees and withdraw"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:460 #: src/webex/pages/confirm-create-reserve.tsx:462
#, c-format #, c-format
msgid "Change Exchange Provider" msgid "Change Exchange Provider"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:517 #: src/webex/pages/confirm-create-reserve.tsx:519
#, c-format #, c-format
msgid "You are about to withdraw %1$s from your bank account into your wallet." msgid "You are about to withdraw %1$s from your bank account into your wallet."
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:600 #: src/webex/pages/confirm-create-reserve.tsx:607
#, c-format #, c-format
msgid "" msgid ""
"Oops, something went wrong. The wallet responded with error status (%1$s)." "Oops, something went wrong. The wallet responded with error status (%1$s)."
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:611 #: src/webex/pages/confirm-create-reserve.tsx:616
#, c-format #, c-format
msgid "Checking URL, please wait ..." msgid "Checking URL, please wait ..."
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:625 #: src/webex/pages/confirm-create-reserve.tsx:630
#, c-format #, c-format
msgid "Can't parse amount: %1$s" msgid "Can't parse amount: %1$s"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:632 #: src/webex/pages/confirm-create-reserve.tsx:637
#, c-format #, c-format
msgid "Can't parse wire_types: %1$s" msgid "Can't parse wire_types: %1$s"
msgstr "" msgstr ""
#. TODO:generic error reporting function or component. #. 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 #, c-format
msgid "Fatal error: \"%1$s\"." msgid "Fatal error: \"%1$s\"."
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:160 #: src/webex/pages/popup.tsx:161
#, c-format #, c-format
msgid "Balance" msgid "Balance"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:163 #: src/webex/pages/popup.tsx:164
#, c-format #, c-format
msgid "History" msgid "History"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:166 #: src/webex/pages/popup.tsx:167
#, c-format #, c-format
msgid "Debug" msgid "Debug"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:242 #: src/webex/pages/popup.tsx:247
#, c-format #, c-format
msgid "help" msgid "help"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:247 #: src/webex/pages/popup.tsx:252
#, c-format #, c-format
msgid "" msgid ""
"You have no balance to show. Need some\n" "You have no balance to show. Need some\n"
" %1$s getting started?\n" " %1$s getting started?\n"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:264 #: src/webex/pages/popup.tsx:269
#, c-format #, c-format
msgid "%1$s incoming\n" msgid "%1$s incoming\n"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:277 #: src/webex/pages/popup.tsx:282
#, c-format #, c-format
msgid "%1$s being spent\n" msgid "%1$s being spent\n"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:303 #: src/webex/pages/popup.tsx:308
#, c-format #, c-format
msgid "Error: could not retrieve balance information." msgid "Error: could not retrieve balance information."
msgstr "" 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 #, c-format
msgid "" msgid ""
"Bank requested reserve (%1$s) for\n" "Bank requested reserve (%1$s) for\n"
" %2$s.\n" " %2$s.\n"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:353 #: src/webex/pages/popup.tsx:360
#, c-format #, c-format
msgid "" msgid ""
"Started to withdraw\n" "Started to withdraw\n"
" %1$s from%2$s(%3$s).\n" " %1$s from%2$s(%3$s).\n"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:363 #: src/webex/pages/popup.tsx:370
#, c-format #, c-format
msgid "Merchant%1$soffered contract%2$s;\n" msgid "Merchant%1$soffered contract%2$s;\n"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:373 #: src/webex/pages/popup.tsx:380
#, c-format #, c-format
msgid "Withdrew%1$sfrom%2$s(%3$s).\n" msgid "Withdrew%1$sfrom%2$s(%3$s).\n"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:383 #: src/webex/pages/popup.tsx:390
#, c-format #, c-format
msgid "" msgid ""
"Paid%1$sto merchant%2$s.\n" "Paid%1$sto merchant%2$s.\n"
" (%3$s)\n" " (%3$s)\n"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:392 #: src/webex/pages/popup.tsx:399
#, c-format #, c-format
msgid "Unknown event (%1$s)" msgid "Unknown event (%1$s)"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:435 #: src/webex/pages/popup.tsx:442
#, c-format #, c-format
msgid "Error: could not retrieve event history" msgid "Error: could not retrieve event history"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:469 #: src/webex/pages/popup.tsx:476
#, c-format #, c-format
msgid "Your wallet has no events recorded." msgid "Your wallet has no events recorded."
msgstr "" msgstr ""

View File

@ -56,67 +56,67 @@ msgid ""
"wallet." "wallet."
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:212 #: src/webex/pages/confirm-create-reserve.tsx:213
#, c-format #, c-format
msgid "Withdrawal fees:" msgid "Withdrawal fees:"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:213 #: src/webex/pages/confirm-create-reserve.tsx:214
#, c-format #, c-format
msgid "Rounding loss:" msgid "Rounding loss:"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:214 #: src/webex/pages/confirm-create-reserve.tsx:215
#, c-format #, c-format
msgid "Earliest expiration (for deposit): %1$s" msgid "Earliest expiration (for deposit): %1$s"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:219 #: src/webex/pages/confirm-create-reserve.tsx:220
#, c-format #, c-format
msgid "# Coins" msgid "# Coins"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:220 #: src/webex/pages/confirm-create-reserve.tsx:221
#, c-format #, c-format
msgid "Value" msgid "Value"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:221 #: src/webex/pages/confirm-create-reserve.tsx:222
#, c-format #, c-format
msgid "Withdraw Fee" msgid "Withdraw Fee"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:222 #: src/webex/pages/confirm-create-reserve.tsx:223
#, c-format #, c-format
msgid "Refresh Fee" msgid "Refresh Fee"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:223 #: src/webex/pages/confirm-create-reserve.tsx:224
#, c-format #, c-format
msgid "Deposit Fee" msgid "Deposit Fee"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:276 #: src/webex/pages/confirm-create-reserve.tsx:278
#, c-format #, c-format
msgid "Select" msgid "Select"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:292 #: src/webex/pages/confirm-create-reserve.tsx:294
#, c-format #, c-format
msgid "Error: URL may not be relative" msgid "Error: URL may not be relative"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:360 #: src/webex/pages/confirm-create-reserve.tsx:362
#, c-format #, c-format
msgid "The exchange is trusted by the wallet.\n" msgid "The exchange is trusted by the wallet.\n"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:366 #: src/webex/pages/confirm-create-reserve.tsx:368
#, c-format #, c-format
msgid "The exchange is audited by a trusted auditor.\n" msgid "The exchange is audited by a trusted auditor.\n"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:372 #: src/webex/pages/confirm-create-reserve.tsx:374
#, c-format #, c-format
msgid "" msgid ""
"Warning: The exchange is neither directly trusted nor audited by a trusted " "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" "If you withdraw from this exchange, it will be trusted in the future.\n"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:381 #: src/webex/pages/confirm-create-reserve.tsx:383
#, c-format #, c-format
msgid "" msgid ""
"Using exchange provider%1$s.\n" "Using exchange provider%1$s.\n"
@ -132,151 +132,166 @@ msgid ""
" %2$s in fees.\n" " %2$s in fees.\n"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:395 #: src/webex/pages/confirm-create-reserve.tsx:397
#, c-format #, c-format
msgid "" msgid ""
"Waiting for a response from\n" "Waiting for a response from\n"
" %1$s" " %1$s"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:406 #: src/webex/pages/confirm-create-reserve.tsx:408
#, c-format #, c-format
msgid "A problem occured, see below. %1$s" msgid "A problem occured, see below. %1$s"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:412 #: src/webex/pages/confirm-create-reserve.tsx:414
#, c-format #, c-format
msgid "" msgid ""
"Information about fees will be available when an exchange provider is " "Information about fees will be available when an exchange provider is "
"selected." "selected."
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:455 #: src/webex/pages/confirm-create-reserve.tsx:457
#, c-format #, c-format
msgid "Accept fees and withdraw" msgid "Accept fees and withdraw"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:460 #: src/webex/pages/confirm-create-reserve.tsx:462
#, c-format #, c-format
msgid "Change Exchange Provider" msgid "Change Exchange Provider"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:517 #: src/webex/pages/confirm-create-reserve.tsx:519
#, c-format #, c-format
msgid "You are about to withdraw %1$s from your bank account into your wallet." msgid "You are about to withdraw %1$s from your bank account into your wallet."
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:600 #: src/webex/pages/confirm-create-reserve.tsx:607
#, c-format #, c-format
msgid "" msgid ""
"Oops, something went wrong. The wallet responded with error status (%1$s)." "Oops, something went wrong. The wallet responded with error status (%1$s)."
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:611 #: src/webex/pages/confirm-create-reserve.tsx:616
#, c-format #, c-format
msgid "Checking URL, please wait ..." msgid "Checking URL, please wait ..."
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:625 #: src/webex/pages/confirm-create-reserve.tsx:630
#, c-format #, c-format
msgid "Can't parse amount: %1$s" msgid "Can't parse amount: %1$s"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:632 #: src/webex/pages/confirm-create-reserve.tsx:637
#, c-format #, c-format
msgid "Can't parse wire_types: %1$s" msgid "Can't parse wire_types: %1$s"
msgstr "" msgstr ""
#. TODO:generic error reporting function or component. #. 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 #, c-format
msgid "Fatal error: \"%1$s\"." msgid "Fatal error: \"%1$s\"."
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:160 #: src/webex/pages/popup.tsx:161
#, c-format #, c-format
msgid "Balance" msgid "Balance"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:163 #: src/webex/pages/popup.tsx:164
#, c-format #, c-format
msgid "History" msgid "History"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:166 #: src/webex/pages/popup.tsx:167
#, c-format #, c-format
msgid "Debug" msgid "Debug"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:242 #: src/webex/pages/popup.tsx:247
#, c-format #, c-format
msgid "help" msgid "help"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:247 #: src/webex/pages/popup.tsx:252
#, c-format #, c-format
msgid "" msgid ""
"You have no balance to show. Need some\n" "You have no balance to show. Need some\n"
" %1$s getting started?\n" " %1$s getting started?\n"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:264 #: src/webex/pages/popup.tsx:269
#, c-format #, c-format
msgid "%1$s incoming\n" msgid "%1$s incoming\n"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:277 #: src/webex/pages/popup.tsx:282
#, c-format #, c-format
msgid "%1$s being spent\n" msgid "%1$s being spent\n"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:303 #: src/webex/pages/popup.tsx:308
#, c-format #, c-format
msgid "Error: could not retrieve balance information." msgid "Error: could not retrieve balance information."
msgstr "" 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 #, c-format
msgid "" msgid ""
"Bank requested reserve (%1$s) for\n" "Bank requested reserve (%1$s) for\n"
" %2$s.\n" " %2$s.\n"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:353 #: src/webex/pages/popup.tsx:360
#, c-format #, c-format
msgid "" msgid ""
"Started to withdraw\n" "Started to withdraw\n"
" %1$s from%2$s(%3$s).\n" " %1$s from%2$s(%3$s).\n"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:363 #: src/webex/pages/popup.tsx:370
#, c-format #, c-format
msgid "Merchant%1$soffered contract%2$s;\n" msgid "Merchant%1$soffered contract%2$s;\n"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:373 #: src/webex/pages/popup.tsx:380
#, c-format #, c-format
msgid "Withdrew%1$sfrom%2$s(%3$s).\n" msgid "Withdrew%1$sfrom%2$s(%3$s).\n"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:383 #: src/webex/pages/popup.tsx:390
#, c-format #, c-format
msgid "" msgid ""
"Paid%1$sto merchant%2$s.\n" "Paid%1$sto merchant%2$s.\n"
" (%3$s)\n" " (%3$s)\n"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:392 #: src/webex/pages/popup.tsx:399
#, c-format #, c-format
msgid "Unknown event (%1$s)" msgid "Unknown event (%1$s)"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:435 #: src/webex/pages/popup.tsx:442
#, c-format #, c-format
msgid "Error: could not retrieve event history" msgid "Error: could not retrieve event history"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:469 #: src/webex/pages/popup.tsx:476
#, c-format #, c-format
msgid "Your wallet has no events recorded." msgid "Your wallet has no events recorded."
msgstr "" msgstr ""

View File

@ -56,67 +56,67 @@ msgid ""
"wallet." "wallet."
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:212 #: src/webex/pages/confirm-create-reserve.tsx:213
#, c-format #, c-format
msgid "Withdrawal fees:" msgid "Withdrawal fees:"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:213 #: src/webex/pages/confirm-create-reserve.tsx:214
#, c-format #, c-format
msgid "Rounding loss:" msgid "Rounding loss:"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:214 #: src/webex/pages/confirm-create-reserve.tsx:215
#, c-format #, c-format
msgid "Earliest expiration (for deposit): %1$s" msgid "Earliest expiration (for deposit): %1$s"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:219 #: src/webex/pages/confirm-create-reserve.tsx:220
#, c-format #, c-format
msgid "# Coins" msgid "# Coins"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:220 #: src/webex/pages/confirm-create-reserve.tsx:221
#, c-format #, c-format
msgid "Value" msgid "Value"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:221 #: src/webex/pages/confirm-create-reserve.tsx:222
#, c-format #, c-format
msgid "Withdraw Fee" msgid "Withdraw Fee"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:222 #: src/webex/pages/confirm-create-reserve.tsx:223
#, c-format #, c-format
msgid "Refresh Fee" msgid "Refresh Fee"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:223 #: src/webex/pages/confirm-create-reserve.tsx:224
#, c-format #, c-format
msgid "Deposit Fee" msgid "Deposit Fee"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:276 #: src/webex/pages/confirm-create-reserve.tsx:278
#, c-format #, c-format
msgid "Select" msgid "Select"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:292 #: src/webex/pages/confirm-create-reserve.tsx:294
#, c-format #, c-format
msgid "Error: URL may not be relative" msgid "Error: URL may not be relative"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:360 #: src/webex/pages/confirm-create-reserve.tsx:362
#, c-format #, c-format
msgid "The exchange is trusted by the wallet.\n" msgid "The exchange is trusted by the wallet.\n"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:366 #: src/webex/pages/confirm-create-reserve.tsx:368
#, c-format #, c-format
msgid "The exchange is audited by a trusted auditor.\n" msgid "The exchange is audited by a trusted auditor.\n"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:372 #: src/webex/pages/confirm-create-reserve.tsx:374
#, c-format #, c-format
msgid "" msgid ""
"Warning: The exchange is neither directly trusted nor audited by a trusted " "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" "If you withdraw from this exchange, it will be trusted in the future.\n"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:381 #: src/webex/pages/confirm-create-reserve.tsx:383
#, c-format #, c-format
msgid "" msgid ""
"Using exchange provider%1$s.\n" "Using exchange provider%1$s.\n"
@ -132,151 +132,166 @@ msgid ""
" %2$s in fees.\n" " %2$s in fees.\n"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:395 #: src/webex/pages/confirm-create-reserve.tsx:397
#, c-format #, c-format
msgid "" msgid ""
"Waiting for a response from\n" "Waiting for a response from\n"
" %1$s" " %1$s"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:406 #: src/webex/pages/confirm-create-reserve.tsx:408
#, c-format #, c-format
msgid "A problem occured, see below. %1$s" msgid "A problem occured, see below. %1$s"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:412 #: src/webex/pages/confirm-create-reserve.tsx:414
#, c-format #, c-format
msgid "" msgid ""
"Information about fees will be available when an exchange provider is " "Information about fees will be available when an exchange provider is "
"selected." "selected."
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:455 #: src/webex/pages/confirm-create-reserve.tsx:457
#, c-format #, c-format
msgid "Accept fees and withdraw" msgid "Accept fees and withdraw"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:460 #: src/webex/pages/confirm-create-reserve.tsx:462
#, c-format #, c-format
msgid "Change Exchange Provider" msgid "Change Exchange Provider"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:517 #: src/webex/pages/confirm-create-reserve.tsx:519
#, c-format #, c-format
msgid "You are about to withdraw %1$s from your bank account into your wallet." msgid "You are about to withdraw %1$s from your bank account into your wallet."
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:600 #: src/webex/pages/confirm-create-reserve.tsx:607
#, c-format #, c-format
msgid "" msgid ""
"Oops, something went wrong. The wallet responded with error status (%1$s)." "Oops, something went wrong. The wallet responded with error status (%1$s)."
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:611 #: src/webex/pages/confirm-create-reserve.tsx:616
#, c-format #, c-format
msgid "Checking URL, please wait ..." msgid "Checking URL, please wait ..."
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:625 #: src/webex/pages/confirm-create-reserve.tsx:630
#, c-format #, c-format
msgid "Can't parse amount: %1$s" msgid "Can't parse amount: %1$s"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:632 #: src/webex/pages/confirm-create-reserve.tsx:637
#, c-format #, c-format
msgid "Can't parse wire_types: %1$s" msgid "Can't parse wire_types: %1$s"
msgstr "" msgstr ""
#. TODO:generic error reporting function or component. #. 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 #, c-format
msgid "Fatal error: \"%1$s\"." msgid "Fatal error: \"%1$s\"."
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:160 #: src/webex/pages/popup.tsx:161
#, c-format #, c-format
msgid "Balance" msgid "Balance"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:163 #: src/webex/pages/popup.tsx:164
#, c-format #, c-format
msgid "History" msgid "History"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:166 #: src/webex/pages/popup.tsx:167
#, c-format #, c-format
msgid "Debug" msgid "Debug"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:242 #: src/webex/pages/popup.tsx:247
#, c-format #, c-format
msgid "help" msgid "help"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:247 #: src/webex/pages/popup.tsx:252
#, c-format #, c-format
msgid "" msgid ""
"You have no balance to show. Need some\n" "You have no balance to show. Need some\n"
" %1$s getting started?\n" " %1$s getting started?\n"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:264 #: src/webex/pages/popup.tsx:269
#, c-format #, c-format
msgid "%1$s incoming\n" msgid "%1$s incoming\n"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:277 #: src/webex/pages/popup.tsx:282
#, c-format #, c-format
msgid "%1$s being spent\n" msgid "%1$s being spent\n"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:303 #: src/webex/pages/popup.tsx:308
#, c-format #, c-format
msgid "Error: could not retrieve balance information." msgid "Error: could not retrieve balance information."
msgstr "" 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 #, c-format
msgid "" msgid ""
"Bank requested reserve (%1$s) for\n" "Bank requested reserve (%1$s) for\n"
" %2$s.\n" " %2$s.\n"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:353 #: src/webex/pages/popup.tsx:360
#, c-format #, c-format
msgid "" msgid ""
"Started to withdraw\n" "Started to withdraw\n"
" %1$s from%2$s(%3$s).\n" " %1$s from%2$s(%3$s).\n"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:363 #: src/webex/pages/popup.tsx:370
#, c-format #, c-format
msgid "Merchant%1$soffered contract%2$s;\n" msgid "Merchant%1$soffered contract%2$s;\n"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:373 #: src/webex/pages/popup.tsx:380
#, c-format #, c-format
msgid "Withdrew%1$sfrom%2$s(%3$s).\n" msgid "Withdrew%1$sfrom%2$s(%3$s).\n"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:383 #: src/webex/pages/popup.tsx:390
#, c-format #, c-format
msgid "" msgid ""
"Paid%1$sto merchant%2$s.\n" "Paid%1$sto merchant%2$s.\n"
" (%3$s)\n" " (%3$s)\n"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:392 #: src/webex/pages/popup.tsx:399
#, c-format #, c-format
msgid "Unknown event (%1$s)" msgid "Unknown event (%1$s)"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:435 #: src/webex/pages/popup.tsx:442
#, c-format #, c-format
msgid "Error: could not retrieve event history" msgid "Error: could not retrieve event history"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:469 #: src/webex/pages/popup.tsx:476
#, c-format #, c-format
msgid "Your wallet has no events recorded." msgid "Your wallet has no events recorded."
msgstr "" msgstr ""

View File

@ -138,6 +138,15 @@ strings['de'] = {
"Error: could not retrieve balance information.": [ "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 requested reserve (%1$s) for\n %2$s.\n": [
"Bank bestätig anlegen der Reserve (%1$s) bei %2$s" "Bank bestätig anlegen der Reserve (%1$s) bei %2$s"
], ],
@ -294,6 +303,15 @@ strings['en-US'] = {
"Error: could not retrieve balance information.": [ "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 requested reserve (%1$s) for\n %2$s.\n": [
"" ""
], ],
@ -450,6 +468,15 @@ strings['fr'] = {
"Error: could not retrieve balance information.": [ "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 requested reserve (%1$s) for\n %2$s.\n": [
"" ""
], ],
@ -606,6 +633,15 @@ strings['it'] = {
"Error: could not retrieve balance information.": [ "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 requested reserve (%1$s) for\n %2$s.\n": [
"" ""
], ],

View File

@ -56,67 +56,67 @@ msgid ""
"wallet." "wallet."
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:212 #: src/webex/pages/confirm-create-reserve.tsx:213
#, c-format #, c-format
msgid "Withdrawal fees:" msgid "Withdrawal fees:"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:213 #: src/webex/pages/confirm-create-reserve.tsx:214
#, c-format #, c-format
msgid "Rounding loss:" msgid "Rounding loss:"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:214 #: src/webex/pages/confirm-create-reserve.tsx:215
#, c-format #, c-format
msgid "Earliest expiration (for deposit): %1$s" msgid "Earliest expiration (for deposit): %1$s"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:219 #: src/webex/pages/confirm-create-reserve.tsx:220
#, c-format #, c-format
msgid "# Coins" msgid "# Coins"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:220 #: src/webex/pages/confirm-create-reserve.tsx:221
#, c-format #, c-format
msgid "Value" msgid "Value"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:221 #: src/webex/pages/confirm-create-reserve.tsx:222
#, c-format #, c-format
msgid "Withdraw Fee" msgid "Withdraw Fee"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:222 #: src/webex/pages/confirm-create-reserve.tsx:223
#, c-format #, c-format
msgid "Refresh Fee" msgid "Refresh Fee"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:223 #: src/webex/pages/confirm-create-reserve.tsx:224
#, c-format #, c-format
msgid "Deposit Fee" msgid "Deposit Fee"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:276 #: src/webex/pages/confirm-create-reserve.tsx:278
#, c-format #, c-format
msgid "Select" msgid "Select"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:292 #: src/webex/pages/confirm-create-reserve.tsx:294
#, c-format #, c-format
msgid "Error: URL may not be relative" msgid "Error: URL may not be relative"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:360 #: src/webex/pages/confirm-create-reserve.tsx:362
#, c-format #, c-format
msgid "The exchange is trusted by the wallet.\n" msgid "The exchange is trusted by the wallet.\n"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:366 #: src/webex/pages/confirm-create-reserve.tsx:368
#, c-format #, c-format
msgid "The exchange is audited by a trusted auditor.\n" msgid "The exchange is audited by a trusted auditor.\n"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:372 #: src/webex/pages/confirm-create-reserve.tsx:374
#, c-format #, c-format
msgid "" msgid ""
"Warning: The exchange is neither directly trusted nor audited by a trusted " "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" "If you withdraw from this exchange, it will be trusted in the future.\n"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:381 #: src/webex/pages/confirm-create-reserve.tsx:383
#, c-format #, c-format
msgid "" msgid ""
"Using exchange provider%1$s.\n" "Using exchange provider%1$s.\n"
@ -132,151 +132,166 @@ msgid ""
" %2$s in fees.\n" " %2$s in fees.\n"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:395 #: src/webex/pages/confirm-create-reserve.tsx:397
#, c-format #, c-format
msgid "" msgid ""
"Waiting for a response from\n" "Waiting for a response from\n"
" %1$s" " %1$s"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:406 #: src/webex/pages/confirm-create-reserve.tsx:408
#, c-format #, c-format
msgid "A problem occured, see below. %1$s" msgid "A problem occured, see below. %1$s"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:412 #: src/webex/pages/confirm-create-reserve.tsx:414
#, c-format #, c-format
msgid "" msgid ""
"Information about fees will be available when an exchange provider is " "Information about fees will be available when an exchange provider is "
"selected." "selected."
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:455 #: src/webex/pages/confirm-create-reserve.tsx:457
#, c-format #, c-format
msgid "Accept fees and withdraw" msgid "Accept fees and withdraw"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:460 #: src/webex/pages/confirm-create-reserve.tsx:462
#, c-format #, c-format
msgid "Change Exchange Provider" msgid "Change Exchange Provider"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:517 #: src/webex/pages/confirm-create-reserve.tsx:519
#, c-format #, c-format
msgid "You are about to withdraw %1$s from your bank account into your wallet." msgid "You are about to withdraw %1$s from your bank account into your wallet."
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:600 #: src/webex/pages/confirm-create-reserve.tsx:607
#, c-format #, c-format
msgid "" msgid ""
"Oops, something went wrong. The wallet responded with error status (%1$s)." "Oops, something went wrong. The wallet responded with error status (%1$s)."
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:611 #: src/webex/pages/confirm-create-reserve.tsx:616
#, c-format #, c-format
msgid "Checking URL, please wait ..." msgid "Checking URL, please wait ..."
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:625 #: src/webex/pages/confirm-create-reserve.tsx:630
#, c-format #, c-format
msgid "Can't parse amount: %1$s" msgid "Can't parse amount: %1$s"
msgstr "" msgstr ""
#: src/webex/pages/confirm-create-reserve.tsx:632 #: src/webex/pages/confirm-create-reserve.tsx:637
#, c-format #, c-format
msgid "Can't parse wire_types: %1$s" msgid "Can't parse wire_types: %1$s"
msgstr "" msgstr ""
#. TODO:generic error reporting function or component. #. 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 #, c-format
msgid "Fatal error: \"%1$s\"." msgid "Fatal error: \"%1$s\"."
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:160 #: src/webex/pages/popup.tsx:161
#, c-format #, c-format
msgid "Balance" msgid "Balance"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:163 #: src/webex/pages/popup.tsx:164
#, c-format #, c-format
msgid "History" msgid "History"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:166 #: src/webex/pages/popup.tsx:167
#, c-format #, c-format
msgid "Debug" msgid "Debug"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:242 #: src/webex/pages/popup.tsx:247
#, c-format #, c-format
msgid "help" msgid "help"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:247 #: src/webex/pages/popup.tsx:252
#, c-format #, c-format
msgid "" msgid ""
"You have no balance to show. Need some\n" "You have no balance to show. Need some\n"
" %1$s getting started?\n" " %1$s getting started?\n"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:264 #: src/webex/pages/popup.tsx:269
#, c-format #, c-format
msgid "%1$s incoming\n" msgid "%1$s incoming\n"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:277 #: src/webex/pages/popup.tsx:282
#, c-format #, c-format
msgid "%1$s being spent\n" msgid "%1$s being spent\n"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:303 #: src/webex/pages/popup.tsx:308
#, c-format #, c-format
msgid "Error: could not retrieve balance information." msgid "Error: could not retrieve balance information."
msgstr "" 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 #, c-format
msgid "" msgid ""
"Bank requested reserve (%1$s) for\n" "Bank requested reserve (%1$s) for\n"
" %2$s.\n" " %2$s.\n"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:353 #: src/webex/pages/popup.tsx:360
#, c-format #, c-format
msgid "" msgid ""
"Started to withdraw\n" "Started to withdraw\n"
" %1$s from%2$s(%3$s).\n" " %1$s from%2$s(%3$s).\n"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:363 #: src/webex/pages/popup.tsx:370
#, c-format #, c-format
msgid "Merchant%1$soffered contract%2$s;\n" msgid "Merchant%1$soffered contract%2$s;\n"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:373 #: src/webex/pages/popup.tsx:380
#, c-format #, c-format
msgid "Withdrew%1$sfrom%2$s(%3$s).\n" msgid "Withdrew%1$sfrom%2$s(%3$s).\n"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:383 #: src/webex/pages/popup.tsx:390
#, c-format #, c-format
msgid "" msgid ""
"Paid%1$sto merchant%2$s.\n" "Paid%1$sto merchant%2$s.\n"
" (%3$s)\n" " (%3$s)\n"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:392 #: src/webex/pages/popup.tsx:399
#, c-format #, c-format
msgid "Unknown event (%1$s)" msgid "Unknown event (%1$s)"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:435 #: src/webex/pages/popup.tsx:442
#, c-format #, c-format
msgid "Error: could not retrieve event history" msgid "Error: could not retrieve event history"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:469 #: src/webex/pages/popup.tsx:476
#, c-format #, c-format
msgid "Your wallet has no events recorded." msgid "Your wallet has no events recorded."
msgstr "" msgstr ""

View File

@ -130,7 +130,11 @@ export interface QueryStream<T> {
*/ */
first(): QueryValue<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. * function.
*/ */
export function openPromise<T>(): any { 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; let reject: ((reason?: any) => void) | null = null;
const promise = new Promise<T>((res, rej) => { const promise = new Promise<T>((res, rej) => {
resolve = res; 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, abstract subscribe(f: (isDone: boolean,
value: any, value: any,
tx: IDBTransaction) => void): void; tx: IDBTransaction) => void): void;
@ -250,11 +254,6 @@ abstract class QueryStreamBase<T> implements QueryStream<T>, PromiseLike<void> {
return new FirstQueryValue(this); 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> { flatMap<S>(f: (x: T) => S[]): QueryStream<S> {
return new QueryStreamFlatMap<T, S>(this, f); 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>> { keyFn: (obj: T) => I): QueryStream<JoinResult<T, S>> {
this.root.addStoreAccess(store.name, false); this.root.addStoreAccess(store.name, false);
return new QueryStreamKeyJoin<T, S>(this, store.name, keyFn); return new QueryStreamKeyJoin<T, S>(this, store.name, keyFn);
} }
filter(f: (x: any) => boolean): QueryStream<T> { filter(f: (x: any) => boolean): QueryStream<T> {
return new QueryStreamFilter(this, f); return new QueryStreamFilter(this, f);
} }
@ -318,6 +316,21 @@ abstract class QueryStreamBase<T> implements QueryStream<T>, PromiseLike<void> {
.then(() => this.root.finish()) .then(() => this.root.finish())
.then(() => promise); .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; 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. * 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 work: Array<((t: IDBTransaction) => void)> = [];
private stores = new Set(); private stores = new Set();
private kickoffPromise: Promise<void>; private kickoffPromise: Promise<void>;
@ -537,11 +550,6 @@ export class QueryRoot implements PromiseLike<void> {
constructor(public db: IDBDatabase) { 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() { private checkFinished() {
if (this.finished) { if (this.finished) {
throw Error("Can't add work to query after it was started"); throw Error("Can't add work to query after it was started");

View File

@ -126,6 +126,12 @@ export interface ReserveRecord {
* withdraw money from it. * withdraw money from it.
*/ */
hasPayback: boolean; 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 * CoinRecord as stored in the "coins" data store
* of the wallet database. * of the wallet database.
@ -903,14 +964,19 @@ export class ExchangeHandle {
/** /**
* Mapping from currency names to detailed balance * Mapping from currency/exchange to detailed balance
* information for that particular currency. * information.
*/ */
export interface WalletBalance { 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}) @Checkable.Class({validate: true})
export class ContractTerms { export class ContractTerms {
validate() { static validate(x: ContractTerms) {
if (this.exchanges.length === 0) { if (x.exchanges.length === 0) {
throw Error("no exchanges in contract terms"); throw Error("no exchanges in contract terms");
} }
} }
@ -1361,6 +1427,18 @@ export namespace Amounts {
value: Number.parseInt(res[2]), 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; order_id: string;
/** /**
* Exchange that the coins are from. * Exchange that the coins are from (base URL).
*/ */
exchange: string; exchange: string;
} }
@ -1484,3 +1562,103 @@ export interface QueryPaymentFound {
contractTerms: ContractTerms; contractTerms: ContractTerms;
payReq: PayReq; 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;
}

View File

@ -49,10 +49,13 @@ import {
Amounts, Amounts,
Auditor, Auditor,
CheckPayResult, CheckPayResult,
CoinPaySig,
CoinRecord, CoinRecord,
CoinStatus, CoinStatus,
ConfirmPayResult, ConfirmPayResult,
ConfirmReserveRequest,
ContractTerms, ContractTerms,
CreateReserveRequest,
CreateReserveResponse, CreateReserveResponse,
CurrencyRecord, CurrencyRecord,
Denomination, Denomination,
@ -73,6 +76,8 @@ import {
RefreshSessionRecord, RefreshSessionRecord,
ReserveCreationInfo, ReserveCreationInfo,
ReserveRecord, ReserveRecord,
ReturnCoinsRequest,
SenderWireInfos,
WalletBalance, WalletBalance,
WalletBalanceEntry, WalletBalanceEntry,
WireFee, 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 { interface TransactionRecord {
contractTermsHash: string; contractTermsHash: string;
contractTerms: ContractTerms; contractTerms: ContractTerms;
@ -329,6 +289,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 * Wallet protocol version spoken with the exchange
* and merchant. * and merchant.
@ -343,7 +345,7 @@ export const WALLET_PROTOCOL_VERSION = "0:0:0";
* In the future we might consider adding migration functions for * In the future we might consider adding migration functions for
* each version increment. * each version increment.
*/ */
export const WALLET_DB_VERSION = 18; export const WALLET_DB_VERSION = 19;
const builtinCurrencies: CurrencyRecord[] = [ const builtinCurrencies: CurrencyRecord[] = [
{ {
@ -421,6 +423,7 @@ export function selectPayCoins(cds: CoinWithDenom[], paymentAmount: AmountJson,
Amounts.add(paymentAmount, Amounts.add(paymentAmount,
denom.feeDeposit).amount) >= 0; denom.feeDeposit).amount) >= 0;
isBelowFee = Amounts.cmp(accFee, depositFeeLimit) <= 0; isBelowFee = Amounts.cmp(accFee, depositFeeLimit) <= 0;
if ((coversAmount && isBelowFee) || coversAmountWithFee) { if ((coversAmount && isBelowFee) || coversAmountWithFee) {
return cdsResult; return cdsResult;
} }
@ -553,6 +556,7 @@ export namespace Stores {
} }
export const coins = new CoinsStore(); export const coins = new CoinsStore();
export const coinsReturns = new Store<CoinsReturnRecord>("coinsReturns", {keyPath: "contractTermsHash"});
export const config = new ConfigStore(); export const config = new ConfigStore();
export const currencies = new CurrenciesStore(); export const currencies = new CurrenciesStore();
export const denominations = new DenominationsStore(); export const denominations = new DenominationsStore();
@ -700,6 +704,12 @@ export class Wallet {
this.continueRefreshSession(r); this.continueRefreshSession(r);
}); });
this.q()
.iter(Stores.coinsReturns)
.reduce((r: CoinsReturnRecord) => {
this.depositReturnedCoins(r);
});
// FIXME: optimize via index // FIXME: optimize via index
this.q() this.q()
.iter(Stores.coins) .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, * Get exchanges and associated coins that are still spendable,
* but only if the sum the coins' remaining value exceeds the payment amount. * 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) { if (!coins || coins.length === 0) {
continue; continue;
} }
// Denomination of the first coin, we assume that all other // Denomination of the first coin, we assume that all other
// coins have the same currency // coins have the same currency
const firstDenom = await this.q().get(Stores.denominations, const firstDenom = await this.q().get(Stores.denominations,
@ -903,7 +966,7 @@ export class Wallet {
*/ */
async confirmPay(proposalId: number): Promise<ConfirmPayResult> { async confirmPay(proposalId: number): Promise<ConfirmPayResult> {
console.log("executing confirmPay"); 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) { if (!proposal) {
throw Error(`proposal with id ${proposalId} not found`); throw Error(`proposal with id ${proposalId} not found`);
@ -936,7 +999,7 @@ export class Wallet {
} }
const {exchangeUrl, cds} = res; 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); await this.recordConfirmPay(proposal, ds, exchangeUrl);
return "paid"; return "paid";
} }
@ -1138,6 +1201,7 @@ export class Wallet {
requested_amount: req.amount, requested_amount: req.amount,
reserve_priv: keypair.priv, reserve_priv: keypair.priv,
reserve_pub: keypair.pub, reserve_pub: keypair.pub,
senderWire: req.senderWire,
}; };
const historyEntry = { const historyEntry = {
@ -1755,22 +1819,26 @@ export class Wallet {
/** /**
* Retrieve a mapping from currency name to the amount * Get detailed balance information, sliced by exchange and by currency.
* that is currenctly available for spending in the wallet.
*/ */
async getBalances(): Promise<WalletBalance> { async getBalances(): Promise<WalletBalance> {
function ensureEntry(balance: WalletBalance, currency: string) { /**
let entry: WalletBalanceEntry|undefined = balance[currency]; * Add amount to a balance field, both for
const z = Amounts.getZero(currency); * the slicing by exchange and currency.
if (!entry) { */
balance[currency] = entry = { function addTo(balance: WalletBalance, field: keyof WalletBalanceEntry, amount: AmountJson, exchange: string): void {
available: z, const z = Amounts.getZero(amount.currency);
paybackAmount: z, const balanceIdentity = {available: z, paybackAmount: z, pendingIncoming: z, pendingPayment: z};
pendingIncoming: z, let entryCurr = balance.byCurrency[amount.currency];
pendingPayment: z, if (!entryCurr) {
}; balance.byCurrency[amount.currency] = entryCurr = { ...balanceIdentity };
} }
return entry; let entryEx = balance.byExchange[exchange];
if (!entryEx) {
balance.byExchange[exchange] = entryEx = { ...balanceIdentity };
}
entryCurr[field] = Amounts.add(entryCurr[field], amount).amount;
entryEx[field] = Amounts.add(entryEx[field], amount).amount;
} }
function collectBalances(c: CoinRecord, balance: WalletBalance) { function collectBalances(c: CoinRecord, balance: WalletBalance) {
@ -1780,9 +1848,8 @@ export class Wallet {
if (!(c.status === CoinStatus.Dirty || c.status === CoinStatus.Fresh)) { if (!(c.status === CoinStatus.Dirty || c.status === CoinStatus.Fresh)) {
return balance; return balance;
} }
const currency = c.currentAmount.currency; console.log("collecting balance");
const entry = ensureEntry(balance, currency); addTo(balance, "available", c.currentAmount, c.exchangeBaseUrl);
entry.available = Amounts.add(entry.available, c.currentAmount).amount;
return balance; return balance;
} }
@ -1790,15 +1857,13 @@ export class Wallet {
if (!r.confirmed) { if (!r.confirmed) {
return balance; return balance;
} }
const entry = ensureEntry(balance, r.requested_amount.currency);
let amount = r.current_amount; let amount = r.current_amount;
if (!amount) { if (!amount) {
amount = r.requested_amount; amount = r.requested_amount;
} }
amount = Amounts.add(amount, r.precoin_amount).amount; amount = Amounts.add(amount, r.precoin_amount).amount;
if (Amounts.cmp(smallestWithdraw[r.exchange_base_url], amount) < 0) { if (Amounts.cmp(smallestWithdraw[r.exchange_base_url], amount) < 0) {
entry.pendingIncoming = Amounts.add(entry.pendingIncoming, addTo(balance, "pendingIncoming", amount, r.exchange_base_url);
amount).amount;
} }
return balance; return balance;
} }
@ -1807,9 +1872,8 @@ export class Wallet {
if (!r.hasPayback) { if (!r.hasPayback) {
return balance; return balance;
} }
const entry = ensureEntry(balance, r.requested_amount.currency);
if (Amounts.cmp(smallestWithdraw[r.exchange_base_url], r.current_amount!) < 0) { 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; return balance;
} }
@ -1821,9 +1885,7 @@ export class Wallet {
if (r.finished) { if (r.finished) {
return balance; return balance;
} }
const entry = ensureEntry(balance, r.valueWithFee.currency); addTo(balance, "pendingIncoming", r.valueOutput, r.exchangeBaseUrl);
entry.pendingIncoming = Amounts.add(entry.pendingIncoming,
r.valueOutput).amount;
return balance; return balance;
} }
@ -1832,10 +1894,7 @@ export class Wallet {
if (t.finished) { if (t.finished) {
return balance; return balance;
} }
const entry = ensureEntry(balance, t.contractTerms.amount.currency); addTo(balance, "pendingIncoming", t.contractTerms.amount, t.payReq.exchange);
entry.pendingPayment = Amounts.add(entry.pendingPayment,
t.contractTerms.amount).amount;
return balance; return balance;
} }
@ -1852,7 +1911,10 @@ export class Wallet {
return sw; return sw;
} }
const balance = {}; const balance = {
byExchange: {},
byCurrency: {},
};
// Mapping from exchange pub to smallest // Mapping from exchange pub to smallest
// possible amount we can withdraw // possible amount we can withdraw
let smallestWithdraw: {[baseUrl: string]: AmountJson} = {}; let smallestWithdraw: {[baseUrl: string]: AmountJson} = {};
@ -1876,7 +1938,6 @@ export class Wallet {
.reduce(collectPayments, balance); .reduce(collectPayments, balance);
await tx.finish(); await tx.finish();
return balance; return balance;
} }
@ -2347,4 +2408,156 @@ export class Wallet {
stop() { stop() {
this.timerGroup.stopCurrentAndFutureTimers(); 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);
}
}
} }

View File

@ -168,6 +168,14 @@ export interface MessageMap {
request: { }; request: { };
response: void; response: void;
}; };
"get-sender-wire-infos": {
request: { };
response: void;
};
"return-coins": {
request: { };
response: void;
};
} }
/** /**

View File

@ -6,40 +6,12 @@
<title>Taler Wallet: Select Taler Provider</title> <title>Taler Wallet: Select Taler Provider</title>
<link rel="icon" href="/img/icon.png"> <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/pure.css">
<link rel="stylesheet" type="text/css" href="../style/wallet.css">
<script src="/dist/page-common-bundle.js"></script> <script src="/dist/page-common-bundle.js"></script>
<script src="/dist/confirm-create-reserve-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> </head>
<body> <body>

View File

@ -219,22 +219,26 @@ class WalletBalanceView extends React.Component<any, any> {
this.unmount = true; this.unmount = true;
} }
updateBalance() { async updateBalance() {
chrome.runtime.sendMessage({type: "balances"}, (resp) => { let balance: WalletBalance;
try {
balance = await wxApi.getBalance();
} catch (e) {
if (this.unmount) { if (this.unmount) {
return; return;
} }
if (resp.error) { this.gotError = true;
this.gotError = true; console.error("could not retrieve balances", e);
console.error("could not retrieve balances", resp);
this.setState({});
return;
}
this.gotError = false;
console.log("got wallet", resp);
this.balance = resp;
this.setState({}); this.setState({});
}); return;
}
if (this.unmount) {
return;
}
this.gotError = false;
console.log("got balance", balance);
this.balance = balance;
this.setState({});
} }
renderEmpty(): JSX.Element { renderEmpty(): JSX.Element {
@ -308,8 +312,8 @@ class WalletBalanceView extends React.Component<any, any> {
} }
console.log(wallet); console.log(wallet);
let paybackAvailable = false; let paybackAvailable = false;
const listing = Object.keys(wallet).map((key) => { const listing = Object.keys(wallet.byCurrency).map((key) => {
const entry: WalletBalanceEntry = wallet[key]; const entry: WalletBalanceEntry = wallet.byCurrency[key];
if (entry.paybackAmount.value !== 0 || entry.paybackAmount.fraction !== 0) { if (entry.paybackAmount.value !== 0 || entry.paybackAmount.fraction !== 0) {
paybackAvailable = true; paybackAvailable = true;
} }
@ -321,14 +325,16 @@ class WalletBalanceView extends React.Component<any, any> {
</p> </p>
); );
}); });
const link = chrome.extension.getURL("/src/webex/pages/auditors.html"); const makeLink = (page: string, name: string) => {
const linkElem = <a className="actionLink" href={link} target="_blank">Trusted Auditors and Exchanges</a>; const url = chrome.extension.getURL(`/src/webex/pages/${page}`);
const paybackLinkElem = <a className="actionLink" href={link} target="_blank">Trusted Auditors and Exchanges</a>; return <div><a className="actionLink" href={url} target="_blank">{name}</a></div>;
};
return ( return (
<div> <div>
{listing.length > 0 ? listing : this.renderEmpty()} {listing.length > 0 ? listing : this.renderEmpty()}
{paybackAvailable && paybackLinkElem} {paybackAvailable && makeLink("payback", i18n.str`Payback`)}
{linkElem} {makeLink("return-coins.html#dissolve", i18n.str`Return Electronic Cash to Bank Account`)}
{makeLink("auditors.html", i18n.str`Manage Trusted Auditors and Exchanges`)}
</div> </div>
); );
} }

View File

@ -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>

View File

@ -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);

View File

@ -1,3 +1,8 @@
body {
font-size: 100%;
overflow-y: scroll;
}
#main { #main {
border: solid 1px black; border: solid 1px black;
border-radius: 10px; border-radius: 10px;
@ -235,3 +240,14 @@ a.actionLink {
font-weight: bold; font-weight: bold;
background: #00FA9A; background: #00FA9A;
} }
a.opener {
color: black;
}
.opener-open::before {
content: "\25bc"
}
.opener-collapsed::before {
content: "\25b6 "
}

View File

@ -31,9 +31,11 @@ import {
DenominationRecord, DenominationRecord,
ExchangeRecord, ExchangeRecord,
PreCoinRecord, PreCoinRecord,
QueryPaymentResult,
ReserveCreationInfo, ReserveCreationInfo,
ReserveRecord, ReserveRecord,
QueryPaymentResult, SenderWireInfos,
WalletBalance,
} from "../types"; } from "../types";
import { MessageType, MessageMap } from "./messages"; import { MessageType, MessageMap } from "./messages";
@ -296,3 +298,26 @@ export function createReserve(args: { amount: AmountJson, exchange: string, send
export function resetDb(): Promise<void> { export function resetDb(): Promise<void> {
return callBackend("reset-db", { }); 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);
}

View File

@ -32,12 +32,13 @@ import {
} from "../query"; } from "../query";
import { import {
AmountJson, AmountJson,
Notifier,
ProposalRecord,
} from "../types";
import {
ConfirmReserveRequest, ConfirmReserveRequest,
CreateReserveRequest, CreateReserveRequest,
Notifier,
ProposalRecord,
ReturnCoinsRequest,
} from "../types";
import {
Stores, Stores,
WALLET_DB_VERSION, WALLET_DB_VERSION,
Wallet, Wallet,
@ -278,6 +279,18 @@ function handleMessage(sender: MessageSender,
} }
return needsWallet().paymentSucceeded(contractTermsHash, merchantSig); 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": { case "check-upgrade": {
let dbResetRequired = false; let dbResetRequired = false;
if (!currentWallet) { if (!currentWallet) {

53
src/wire.ts Normal file
View File

@ -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`;
}
}

View File

@ -64,6 +64,7 @@
"src/webex/pages/payback.tsx", "src/webex/pages/payback.tsx",
"src/webex/pages/popup.tsx", "src/webex/pages/popup.tsx",
"src/webex/pages/reset-required.tsx", "src/webex/pages/reset-required.tsx",
"src/webex/pages/return-coins.tsx",
"src/webex/pages/show-db.ts", "src/webex/pages/show-db.ts",
"src/webex/pages/tree.tsx", "src/webex/pages/tree.tsx",
"src/webex/renderHtml.tsx", "src/webex/renderHtml.tsx",

View File

@ -69,11 +69,12 @@ module.exports = function (env) {
"confirm-create-reserve": "./src/webex/pages/confirm-create-reserve.tsx", "confirm-create-reserve": "./src/webex/pages/confirm-create-reserve.tsx",
"error": "./src/webex/pages/error.tsx", "error": "./src/webex/pages/error.tsx",
"logs": "./src/webex/pages/logs.tsx", "logs": "./src/webex/pages/logs.tsx",
"payback": "./src/webex/pages/payback.tsx",
"popup": "./src/webex/pages/popup.tsx", "popup": "./src/webex/pages/popup.tsx",
"reset-required": "./src/webex/pages/reset-required.tsx",
"return-coins": "./src/webex/pages/return-coins.tsx",
"show-db": "./src/webex/pages/show-db.ts", "show-db": "./src/webex/pages/show-db.ts",
"tree": "./src/webex/pages/tree.tsx", "tree": "./src/webex/pages/tree.tsx",
"payback": "./src/webex/pages/payback.tsx",
"reset-required": "./src/webex/pages/reset-required.tsx",
}, },
plugins: [ plugins: [
new webpack.optimize.CommonsChunkPlugin({ new webpack.optimize.CommonsChunkPlugin({

1012
yarn.lock

File diff suppressed because it is too large Load Diff