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,
}, ["(root)"]);
if (opts.validate) {
const instance = new target();
if (typeof instance.validate !== "function") {
if (target.validate !== "function") {
throw Error("invalid Checkable annotion: validate method required");
}
// May throw exception
instance.validate.call(cv);
target.validate(cv);
}
return cv;
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -138,6 +138,15 @@ strings['de'] = {
"Error: could not retrieve balance information.": [
""
],
"Payback": [
""
],
"Return Electronic Cash to Bank Account": [
""
],
"Manage Trusted Auditors and Exchanges": [
""
],
"Bank requested reserve (%1$s) for\n %2$s.\n": [
"Bank bestätig anlegen der Reserve (%1$s) bei %2$s"
],
@ -294,6 +303,15 @@ strings['en-US'] = {
"Error: could not retrieve balance information.": [
""
],
"Payback": [
""
],
"Return Electronic Cash to Bank Account": [
""
],
"Manage Trusted Auditors and Exchanges": [
""
],
"Bank requested reserve (%1$s) for\n %2$s.\n": [
""
],
@ -450,6 +468,15 @@ strings['fr'] = {
"Error: could not retrieve balance information.": [
""
],
"Payback": [
""
],
"Return Electronic Cash to Bank Account": [
""
],
"Manage Trusted Auditors and Exchanges": [
""
],
"Bank requested reserve (%1$s) for\n %2$s.\n": [
""
],
@ -606,6 +633,15 @@ strings['it'] = {
"Error: could not retrieve balance information.": [
""
],
"Payback": [
""
],
"Return Electronic Cash to Bank Account": [
""
],
"Manage Trusted Auditors and Exchanges": [
""
],
"Bank requested reserve (%1$s) for\n %2$s.\n": [
""
],

View File

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

View File

@ -130,7 +130,11 @@ export interface QueryStream<T> {
*/
first(): QueryValue<T>;
then(onfulfill: any, onreject: any): any;
/**
* Run the query without returning a result.
* Useful for queries with side effects.
*/
run(): Promise<void>;
}
@ -225,7 +229,7 @@ export const AbortTransaction = Symbol("abort_transaction");
* function.
*/
export function openPromise<T>(): any {
let resolve: ((value?: T | PromiseLike<T>) => void) | null = null;
let resolve: ((x?: any) => void) | null = null;
let reject: ((reason?: any) => void) | null = null;
const promise = new Promise<T>((res, rej) => {
resolve = res;
@ -239,7 +243,7 @@ export function openPromise<T>(): any {
}
abstract class QueryStreamBase<T> implements QueryStream<T>, PromiseLike<void> {
abstract class QueryStreamBase<T> implements QueryStream<T> {
abstract subscribe(f: (isDone: boolean,
value: any,
tx: IDBTransaction) => void): void;
@ -250,11 +254,6 @@ abstract class QueryStreamBase<T> implements QueryStream<T>, PromiseLike<void> {
return new FirstQueryValue(this);
}
then<R>(onfulfilled: (value: void) => R | PromiseLike<R>,
onrejected: (reason: any) => R | PromiseLike<R>): PromiseLike<R> {
return this.root.then(onfulfilled, onrejected);
}
flatMap<S>(f: (x: T) => S[]): QueryStream<S> {
return new QueryStreamFlatMap<T, S>(this, f);
}
@ -279,8 +278,7 @@ abstract class QueryStreamBase<T> implements QueryStream<T>, PromiseLike<void> {
keyFn: (obj: T) => I): QueryStream<JoinResult<T, S>> {
this.root.addStoreAccess(store.name, false);
return new QueryStreamKeyJoin<T, S>(this, store.name, keyFn);
}
}
filter(f: (x: any) => boolean): QueryStream<T> {
return new QueryStreamFilter(this, f);
}
@ -318,6 +316,21 @@ abstract class QueryStreamBase<T> implements QueryStream<T>, PromiseLike<void> {
.then(() => this.root.finish())
.then(() => promise);
}
run(): Promise<void> {
const {resolve, promise} = openPromise();
this.subscribe((isDone, value) => {
if (isDone) {
resolve();
return;
}
});
return Promise.resolve()
.then(() => this.root.finish())
.then(() => promise);
}
}
type FilterFn = (e: any) => boolean;
@ -519,7 +532,7 @@ class IterQueryStream<T> extends QueryStreamBase<T> {
/**
* Root wrapper around an IndexedDB for queries with a fluent interface.
*/
export class QueryRoot implements PromiseLike<void> {
export class QueryRoot {
private work: Array<((t: IDBTransaction) => void)> = [];
private stores = new Set();
private kickoffPromise: Promise<void>;
@ -537,11 +550,6 @@ export class QueryRoot implements PromiseLike<void> {
constructor(public db: IDBDatabase) {
}
then<R>(onfulfilled: (value: void) => R | PromiseLike<R>,
onrejected: (reason: any) => R | PromiseLike<R>): PromiseLike<R> {
return this.finish().then(onfulfilled, onrejected);
}
private checkFinished() {
if (this.finished) {
throw Error("Can't add work to query after it was started");

View File

@ -126,6 +126,12 @@ export interface ReserveRecord {
* withdraw money from it.
*/
hasPayback: boolean;
/**
* Wire information for the bank account that
* transfered funds for this reserve.
*/
senderWire?: object;
}
@ -814,6 +820,61 @@ export enum CoinStatus {
}
/**
* State of returning a list of coins
* to the customer's bank account.
*/
export interface CoinsReturnRecord {
/**
* Coins that we're returning.
*/
coins: CoinPaySig[];
/**
* Responses to the deposit requests.
*/
responses: any;
/**
* Ephemeral dummy merchant key for
* the coins returns operation.
*/
dummyMerchantPub: string;
/**
* Ephemeral dummy merchant key for
* the coins returns operation.
*/
dummyMerchantPriv: string;
/**
* Contract terms.
*/
contractTerms: string;
/**
* Hash of contract terms.
*/
contractTermsHash: string;
/**
* Wire info to send the money for the coins to.
*/
wire: object;
/**
* Hash of the wire object.
*/
wireHash: string;
/**
* All coins were deposited.
*/
finished: boolean;
}
/**
* CoinRecord as stored in the "coins" data store
* of the wallet database.
@ -903,14 +964,19 @@ export class ExchangeHandle {
/**
* Mapping from currency names to detailed balance
* information for that particular currency.
* Mapping from currency/exchange to detailed balance
* information.
*/
export interface WalletBalance {
/**
* Mapping from currency name to defailed balance info.
* Mapping from currency name to detailed balance info.
*/
[currency: string]: WalletBalanceEntry;
byExchange: { [exchangeBaseUrl: string]: WalletBalanceEntry };
/**
* Mapping from currency name to detailed balance info.
*/
byCurrency: { [currency: string]: WalletBalanceEntry };
}
@ -942,8 +1008,8 @@ export interface WalletBalanceEntry {
*/
@Checkable.Class({validate: true})
export class ContractTerms {
validate() {
if (this.exchanges.length === 0) {
static validate(x: ContractTerms) {
if (x.exchanges.length === 0) {
throw Error("no exchanges in contract terms");
}
}
@ -1361,6 +1427,18 @@ export namespace Amounts {
value: Number.parseInt(res[2]),
};
}
export function toFloat(a: AmountJson): number {
return a.value + (a.fraction / fractionalBase);
}
export function fromFloat(floatVal: number, currency: string) {
return {
currency,
fraction: (floatVal - Math.floor(floatVal)) * fractionalBase,
value: Math.floor(floatVal),
};
}
}
@ -1457,7 +1535,7 @@ export interface PayReq {
order_id: string;
/**
* Exchange that the coins are from.
* Exchange that the coins are from (base URL).
*/
exchange: string;
}
@ -1484,3 +1562,103 @@ export interface QueryPaymentFound {
contractTerms: ContractTerms;
payReq: PayReq;
}
/**
* Information about all sender wire details known to the wallet,
* as well as exchanges that accept these wire types.
*/
export interface SenderWireInfos {
/**
* Mapping from exchange base url to list of accepted
* wire types.
*/
exchangeWireTypes: { [exchangeBaseUrl: string]: string[] };
/**
* Sender wire types stored in the wallet.
*/
senderWires: object[];
}
/**
* Request to mark a reserve as confirmed.
*/
@Checkable.Class()
export class CreateReserveRequest {
/**
* The initial amount for the reserve.
*/
@Checkable.Value(AmountJson)
amount: AmountJson;
/**
* Exchange URL where the bank should create the reserve.
*/
@Checkable.String
exchange: string;
/**
* Wire details for the bank account that sent the funds to the exchange.
*/
@Checkable.Optional(Checkable.Any)
senderWire?: object;
/**
* Verify that a value matches the schema of this class and convert it into a
* member.
*/
static checked: (obj: any) => CreateReserveRequest;
}
/**
* Request to mark a reserve as confirmed.
*/
@Checkable.Class()
export class ConfirmReserveRequest {
/**
* Public key of then reserve that should be marked
* as confirmed.
*/
@Checkable.String
reservePub: string;
/**
* Verify that a value matches the schema of this class and convert it into a
* member.
*/
static checked: (obj: any) => ConfirmReserveRequest;
}
/**
* Wire coins to the user's own bank account.
*/
@Checkable.Class()
export class ReturnCoinsRequest {
/**
* The amount to wire.
*/
@Checkable.Value(AmountJson)
amount: AmountJson;
/**
* The exchange to take the coins from.
*/
@Checkable.String
exchange: string;
/**
* Wire details for the bank account of the customer that will
* receive the funds.
*/
@Checkable.Any
senderWire?: object;
/**
* Verify that a value matches the schema of this class and convert it into a
* member.
*/
static checked: (obj: any) => ReturnCoinsRequest;
}

View File

@ -49,10 +49,13 @@ import {
Amounts,
Auditor,
CheckPayResult,
CoinPaySig,
CoinRecord,
CoinStatus,
ConfirmPayResult,
ConfirmReserveRequest,
ContractTerms,
CreateReserveRequest,
CreateReserveResponse,
CurrencyRecord,
Denomination,
@ -73,6 +76,8 @@ import {
RefreshSessionRecord,
ReserveCreationInfo,
ReserveRecord,
ReturnCoinsRequest,
SenderWireInfos,
WalletBalance,
WalletBalanceEntry,
WireFee,
@ -236,51 +241,6 @@ class WireDetailJson {
}
/**
* Request to mark a reserve as confirmed.
*/
@Checkable.Class()
export class CreateReserveRequest {
/**
* The initial amount for the reserve.
*/
@Checkable.Value(AmountJson)
amount: AmountJson;
/**
* Exchange URL where the bank should create the reserve.
*/
@Checkable.String
exchange: string;
/**
* Verify that a value matches the schema of this class and convert it into a
* member.
*/
static checked: (obj: any) => CreateReserveRequest;
}
/**
* Request to mark a reserve as confirmed.
*/
@Checkable.Class()
export class ConfirmReserveRequest {
/**
* Public key of then reserve that should be marked
* as confirmed.
*/
@Checkable.String
reservePub: string;
/**
* Verify that a value matches the schema of this class and convert it into a
* member.
*/
static checked: (obj: any) => ConfirmReserveRequest;
}
interface TransactionRecord {
contractTermsHash: string;
contractTerms: ContractTerms;
@ -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
* and merchant.
@ -343,7 +345,7 @@ export const WALLET_PROTOCOL_VERSION = "0:0:0";
* In the future we might consider adding migration functions for
* each version increment.
*/
export const WALLET_DB_VERSION = 18;
export const WALLET_DB_VERSION = 19;
const builtinCurrencies: CurrencyRecord[] = [
{
@ -421,6 +423,7 @@ export function selectPayCoins(cds: CoinWithDenom[], paymentAmount: AmountJson,
Amounts.add(paymentAmount,
denom.feeDeposit).amount) >= 0;
isBelowFee = Amounts.cmp(accFee, depositFeeLimit) <= 0;
if ((coversAmount && isBelowFee) || coversAmountWithFee) {
return cdsResult;
}
@ -553,6 +556,7 @@ export namespace Stores {
}
export const coins = new CoinsStore();
export const coinsReturns = new Store<CoinsReturnRecord>("coinsReturns", {keyPath: "contractTermsHash"});
export const config = new ConfigStore();
export const currencies = new CurrenciesStore();
export const denominations = new DenominationsStore();
@ -700,6 +704,12 @@ export class Wallet {
this.continueRefreshSession(r);
});
this.q()
.iter(Stores.coinsReturns)
.reduce((r: CoinsReturnRecord) => {
this.depositReturnedCoins(r);
});
// FIXME: optimize via index
this.q()
.iter(Stores.coins)
@ -712,6 +722,58 @@ export class Wallet {
}
private async getCoinsForReturn(exchangeBaseUrl: string, amount: AmountJson): Promise<CoinWithDenom[] | undefined> {
const exchange = await this.q().get(Stores.exchanges, exchangeBaseUrl);
if (!exchange) {
throw Error(`Exchange ${exchangeBaseUrl} not known to the wallet`);
}
const coins: CoinRecord[] = await (
this.q()
.iterIndex(Stores.coins.exchangeBaseUrlIndex, exchange.baseUrl)
.toArray()
);
if (!coins || !coins.length) {
return [];
}
// Denomination of the first coin, we assume that all other
// coins have the same currency
const firstDenom = await this.q().get(Stores.denominations,
[
exchange.baseUrl,
coins[0].denomPub,
]);
if (!firstDenom) {
throw Error("db inconsistent");
}
const currency = firstDenom.value.currency;
const cds: CoinWithDenom[] = [];
for (const coin of coins) {
const denom = await this.q().get(Stores.denominations,
[exchange.baseUrl, coin.denomPub]);
if (!denom) {
throw Error("db inconsistent");
}
if (denom.value.currency !== currency) {
console.warn(`same pubkey for different currencies at exchange ${exchange.baseUrl}`);
continue;
}
if (coin.suspended) {
continue;
}
if (coin.status !== CoinStatus.Fresh) {
continue;
}
cds.push({coin, denom});
}
return selectPayCoins(cds, amount, amount);
}
/**
* Get exchanges and associated coins that are still spendable,
* but only if the sum the coins' remaining value exceeds the payment amount.
@ -769,6 +831,7 @@ export class Wallet {
if (!coins || coins.length === 0) {
continue;
}
// Denomination of the first coin, we assume that all other
// coins have the same currency
const firstDenom = await this.q().get(Stores.denominations,
@ -903,7 +966,7 @@ export class Wallet {
*/
async confirmPay(proposalId: number): Promise<ConfirmPayResult> {
console.log("executing confirmPay");
const proposal = await this.q().get(Stores.proposals, proposalId);
const proposal: ProposalRecord|undefined = await this.q().get(Stores.proposals, proposalId);
if (!proposal) {
throw Error(`proposal with id ${proposalId} not found`);
@ -936,7 +999,7 @@ export class Wallet {
}
const {exchangeUrl, cds} = res;
const ds = await this.cryptoApi.signDeposit(proposal, cds);
const ds = await this.cryptoApi.signDeposit(proposal.contractTerms, cds);
await this.recordConfirmPay(proposal, ds, exchangeUrl);
return "paid";
}
@ -1138,6 +1201,7 @@ export class Wallet {
requested_amount: req.amount,
reserve_priv: keypair.priv,
reserve_pub: keypair.pub,
senderWire: req.senderWire,
};
const historyEntry = {
@ -1755,22 +1819,26 @@ export class Wallet {
/**
* Retrieve a mapping from currency name to the amount
* that is currenctly available for spending in the wallet.
* Get detailed balance information, sliced by exchange and by currency.
*/
async getBalances(): Promise<WalletBalance> {
function ensureEntry(balance: WalletBalance, currency: string) {
let entry: WalletBalanceEntry|undefined = balance[currency];
const z = Amounts.getZero(currency);
if (!entry) {
balance[currency] = entry = {
available: z,
paybackAmount: z,
pendingIncoming: z,
pendingPayment: z,
};
/**
* Add amount to a balance field, both for
* the slicing by exchange and currency.
*/
function addTo(balance: WalletBalance, field: keyof WalletBalanceEntry, amount: AmountJson, exchange: string): void {
const z = Amounts.getZero(amount.currency);
const balanceIdentity = {available: z, paybackAmount: z, pendingIncoming: z, pendingPayment: z};
let entryCurr = balance.byCurrency[amount.currency];
if (!entryCurr) {
balance.byCurrency[amount.currency] = entryCurr = { ...balanceIdentity };
}
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) {
@ -1780,9 +1848,8 @@ export class Wallet {
if (!(c.status === CoinStatus.Dirty || c.status === CoinStatus.Fresh)) {
return balance;
}
const currency = c.currentAmount.currency;
const entry = ensureEntry(balance, currency);
entry.available = Amounts.add(entry.available, c.currentAmount).amount;
console.log("collecting balance");
addTo(balance, "available", c.currentAmount, c.exchangeBaseUrl);
return balance;
}
@ -1790,15 +1857,13 @@ export class Wallet {
if (!r.confirmed) {
return balance;
}
const entry = ensureEntry(balance, r.requested_amount.currency);
let amount = r.current_amount;
if (!amount) {
amount = r.requested_amount;
}
amount = Amounts.add(amount, r.precoin_amount).amount;
if (Amounts.cmp(smallestWithdraw[r.exchange_base_url], amount) < 0) {
entry.pendingIncoming = Amounts.add(entry.pendingIncoming,
amount).amount;
addTo(balance, "pendingIncoming", amount, r.exchange_base_url);
}
return balance;
}
@ -1807,9 +1872,8 @@ export class Wallet {
if (!r.hasPayback) {
return balance;
}
const entry = ensureEntry(balance, r.requested_amount.currency);
if (Amounts.cmp(smallestWithdraw[r.exchange_base_url], r.current_amount!) < 0) {
entry.paybackAmount = Amounts.add(entry.paybackAmount, r.current_amount!).amount;
addTo(balance, "paybackAmount", r.current_amount!, r.exchange_base_url);
}
return balance;
}
@ -1821,9 +1885,7 @@ export class Wallet {
if (r.finished) {
return balance;
}
const entry = ensureEntry(balance, r.valueWithFee.currency);
entry.pendingIncoming = Amounts.add(entry.pendingIncoming,
r.valueOutput).amount;
addTo(balance, "pendingIncoming", r.valueOutput, r.exchangeBaseUrl);
return balance;
}
@ -1832,10 +1894,7 @@ export class Wallet {
if (t.finished) {
return balance;
}
const entry = ensureEntry(balance, t.contractTerms.amount.currency);
entry.pendingPayment = Amounts.add(entry.pendingPayment,
t.contractTerms.amount).amount;
addTo(balance, "pendingIncoming", t.contractTerms.amount, t.payReq.exchange);
return balance;
}
@ -1852,7 +1911,10 @@ export class Wallet {
return sw;
}
const balance = {};
const balance = {
byExchange: {},
byCurrency: {},
};
// Mapping from exchange pub to smallest
// possible amount we can withdraw
let smallestWithdraw: {[baseUrl: string]: AmountJson} = {};
@ -1876,7 +1938,6 @@ export class Wallet {
.reduce(collectPayments, balance);
await tx.finish();
return balance;
}
@ -2347,4 +2408,156 @@ export class Wallet {
stop() {
this.timerGroup.stopCurrentAndFutureTimers();
}
async getSenderWireInfos(): Promise<SenderWireInfos> {
const m: { [url: string]: Set<string> } = {};
await this.q().iter(Stores.exchangeWireFees).map((x) => {
const s = m[x.exchangeBaseUrl] = m[x.exchangeBaseUrl] || new Set();
Object.keys(x.feesForType).map((k) => s.add(k));
}).run();
console.log(m);
const exchangeWireTypes: { [url: string]: string[] } = {};
Object.keys(m).map((e) => { exchangeWireTypes[e] = Array.from(m[e]); });
const senderWiresSet = new Set();
await this.q().iter(Stores.reserves).map((x) => {
if (x.senderWire) {
senderWiresSet.add(JSON.stringify(x.senderWire));
}
}).run();
const senderWires = Array.from(senderWiresSet).map((x) => JSON.parse(x));
return {
exchangeWireTypes,
senderWires,
};
}
/**
* Trigger paying coins back into the user's account.
*/
async returnCoins(req: ReturnCoinsRequest): Promise<void> {
console.log("got returnCoins request", req);
const wireType = (req.senderWire as any).type;
console.log("wireType", wireType);
if (!wireType || typeof wireType !== "string") {
console.error(`wire type must be a non-empty string, not ${wireType}`);
return;
}
const stampSecNow = Math.floor((new Date()).getTime() / 1000);
const exchange = await this.q().get(Stores.exchanges, req.exchange);
if (!exchange) {
console.error(`Exchange ${req.exchange} not known to the wallet`);
return;
}
const cds = await this.getCoinsForReturn(req.exchange, req.amount);
console.log(cds);
if (!cds) {
throw Error("coin return impossible, can't select coins");
}
const { priv, pub } = await this.cryptoApi.createEddsaKeypair();
const wireHash = await this.cryptoApi.hashString(canonicalJson(req.senderWire));
const contractTerms: ContractTerms = {
H_wire: wireHash,
amount: req.amount,
auditors: [],
wire_method: wireType,
pay_deadline: `/Date(${stampSecNow + 60 * 5})/`,
locations: [],
max_fee: req.amount,
merchant: {},
merchant_pub: pub,
exchanges: [ { master_pub: exchange.masterPublicKey, url: exchange.baseUrl } ],
products: [],
refund_deadline: `/Date(${stampSecNow + 60 * 5})/`,
timestamp: `/Date(${stampSecNow})/`,
order_id: "none",
pay_url: "",
fulfillment_url: "",
extra: {},
};
const contractTermsHash = await this.cryptoApi.hashString(canonicalJson(contractTerms));
const payCoinInfo = await this.cryptoApi.signDeposit(contractTerms, cds);
console.log("pci", payCoinInfo);
const coins = payCoinInfo.map((pci) => ({ coinPaySig: pci.sig }));
const coinsReturnRecord: CoinsReturnRecord = {
coins,
exchange: exchange.baseUrl,
contractTerms,
contractTermsHash,
merchantPriv: priv,
wire: req.senderWire,
}
await this.q()
.put(Stores.coinsReturns, coinsReturnRecord)
.putAll(Stores.coins, payCoinInfo.map((pci) => pci.updatedCoin))
.finish();
this.depositReturnedCoins(coinsReturnRecord);
}
async depositReturnedCoins(coinsReturnRecord: CoinsReturnRecord): Promise<void> {
for (const c of coinsReturnRecord.coins) {
if (c.depositedSig) {
continue;
}
const req = {
f: c.coinPaySig.f,
wire: coinsReturnRecord.wire,
H_wire: coinsReturnRecord.contractTerms.H_wire,
h_contract_terms: coinsReturnRecord.contractTermsHash,
coin_pub: c.coinPaySig.coin_pub,
denom_pub: c.coinPaySig.denom_pub,
ub_sig: c.coinPaySig.ub_sig,
timestamp: coinsReturnRecord.contractTerms.timestamp,
wire_transfer_deadline: coinsReturnRecord.contractTerms.pay_deadline,
pay_deadline: coinsReturnRecord.contractTerms.pay_deadline,
refund_deadline: coinsReturnRecord.contractTerms.refund_deadline,
merchant_pub: coinsReturnRecord.contractTerms.merchant_pub,
coin_sig: c.coinPaySig.coin_sig,
};
console.log("req", req);
const reqUrl = (new URI("deposit")).absoluteTo(coinsReturnRecord.exchange);
const resp = await this.http.postJson(reqUrl.href(), req);
if (resp.status !== 200) {
console.error("deposit failed due to status code", resp);
continue;
}
const respJson = JSON.parse(resp.responseText);
if (respJson.status !== "DEPOSIT_OK") {
console.error("deposit failed", resp);
continue;
}
if (!respJson.sig) {
console.error("invalid 'sig' field", resp);
continue;
}
// FIXME: verify signature
// For every successful deposit, we replace the old record with an updated one
const currentCrr = await this.q().get(Stores.coinsReturns, coinsReturnRecord.contractTermsHash);
if (!currentCrr) {
console.error("database inconsistent");
continue;
}
for (const nc of currentCrr.coins) {
if (nc.coinPaySig.coin_pub === c.coinPaySig.coin_pub) {
nc.depositedSig = respJson.sig;
}
}
await this.q().put(Stores.coinsReturns, currentCrr);
}
}
}

View File

@ -168,6 +168,14 @@ export interface MessageMap {
request: { };
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>
<link rel="icon" href="/img/icon.png">
<link rel="stylesheet" type="text/css" href="../style/wallet.css">
<link rel="stylesheet" type="text/css" href="../style/pure.css">
<link rel="stylesheet" type="text/css" href="../style/wallet.css">
<script src="/dist/page-common-bundle.js"></script>
<script src="/dist/confirm-create-reserve-bundle.js"></script>
<style>
body {
font-size: 100%;
overflow-y: scroll;
}
.button-success {
background: rgb(28, 184, 65); /* this is a green */
color: white;
border-radius: 4px;
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
}
.button-secondary {
background: rgb(66, 184, 221); /* this is a light blue */
color: white;
border-radius: 4px;
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
}
a.opener {
color: black;
}
.opener-open::before {
content: "\25bc"
}
.opener-collapsed::before {
content: "\25b6 "
}
</style>
</head>
<body>

View File

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

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 {
border: solid 1px black;
border-radius: 10px;
@ -235,3 +240,14 @@ a.actionLink {
font-weight: bold;
background: #00FA9A;
}
a.opener {
color: black;
}
.opener-open::before {
content: "\25bc"
}
.opener-collapsed::before {
content: "\25b6 "
}

View File

@ -31,9 +31,11 @@ import {
DenominationRecord,
ExchangeRecord,
PreCoinRecord,
QueryPaymentResult,
ReserveCreationInfo,
ReserveRecord,
QueryPaymentResult,
SenderWireInfos,
WalletBalance,
} from "../types";
import { MessageType, MessageMap } from "./messages";
@ -296,3 +298,26 @@ export function createReserve(args: { amount: AmountJson, exchange: string, send
export function resetDb(): Promise<void> {
return callBackend("reset-db", { });
}
/**
* Get balances for all currencies/exchanges.
*/
export function getBalance(): Promise<WalletBalance> {
return callBackend("balances", { });
}
/**
* Get possible sender wire infos for getting money
* wired from an exchange.
*/
export function getSenderWireInfos(): Promise<SenderWireInfos> {
return callBackend("get-sender-wire-infos", { });
}
/**
* Return coins to a bank account.
*/
export function returnCoins(args: { amount: AmountJson, exchange: string, senderWire: object }): Promise<void> {
return callBackend("return-coins", args);
}

View File

@ -32,12 +32,13 @@ import {
} from "../query";
import {
AmountJson,
Notifier,
ProposalRecord,
} from "../types";
import {
ConfirmReserveRequest,
CreateReserveRequest,
Notifier,
ProposalRecord,
ReturnCoinsRequest,
} from "../types";
import {
Stores,
WALLET_DB_VERSION,
Wallet,
@ -278,6 +279,18 @@ function handleMessage(sender: MessageSender,
}
return needsWallet().paymentSucceeded(contractTermsHash, merchantSig);
}
case "get-sender-wire-infos": {
return needsWallet().getSenderWireInfos();
}
case "return-coins": {
const d = {
amount: detail.amount,
exchange: detail.exchange,
senderWire: detail.senderWire,
};
const req = ReturnCoinsRequest.checked(d);
return needsWallet().returnCoins(req);
}
case "check-upgrade": {
let dbResetRequired = false;
if (!currentWallet) {

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/popup.tsx",
"src/webex/pages/reset-required.tsx",
"src/webex/pages/return-coins.tsx",
"src/webex/pages/show-db.ts",
"src/webex/pages/tree.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",
"error": "./src/webex/pages/error.tsx",
"logs": "./src/webex/pages/logs.tsx",
"payback": "./src/webex/pages/payback.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",
"tree": "./src/webex/pages/tree.tsx",
"payback": "./src/webex/pages/payback.tsx",
"reset-required": "./src/webex/pages/reset-required.tsx",
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({

1012
yarn.lock

File diff suppressed because it is too large Load Diff