partial implementation of tipping
This commit is contained in:
parent
bc2c4aff8e
commit
b8ccc7c990
53
img/spinner-bars.svg
Normal file
53
img/spinner-bars.svg
Normal file
@ -0,0 +1,53 @@
|
||||
<!-- By Sam Herbert (@sherb), for everyone. More @ http://goo.gl/7AJzbL -->
|
||||
<svg width="135" height="140" viewBox="0 0 135 140" xmlns="http://www.w3.org/2000/svg" fill="#fff">
|
||||
<rect y="10" width="15" height="120" rx="6">
|
||||
<animate attributeName="height"
|
||||
begin="0.5s" dur="1s"
|
||||
values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
|
||||
repeatCount="indefinite" />
|
||||
<animate attributeName="y"
|
||||
begin="0.5s" dur="1s"
|
||||
values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
|
||||
repeatCount="indefinite" />
|
||||
</rect>
|
||||
<rect x="30" y="10" width="15" height="120" rx="6">
|
||||
<animate attributeName="height"
|
||||
begin="0.25s" dur="1s"
|
||||
values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
|
||||
repeatCount="indefinite" />
|
||||
<animate attributeName="y"
|
||||
begin="0.25s" dur="1s"
|
||||
values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
|
||||
repeatCount="indefinite" />
|
||||
</rect>
|
||||
<rect x="60" width="15" height="140" rx="6">
|
||||
<animate attributeName="height"
|
||||
begin="0s" dur="1s"
|
||||
values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
|
||||
repeatCount="indefinite" />
|
||||
<animate attributeName="y"
|
||||
begin="0s" dur="1s"
|
||||
values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
|
||||
repeatCount="indefinite" />
|
||||
</rect>
|
||||
<rect x="90" y="10" width="15" height="120" rx="6">
|
||||
<animate attributeName="height"
|
||||
begin="0.25s" dur="1s"
|
||||
values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
|
||||
repeatCount="indefinite" />
|
||||
<animate attributeName="y"
|
||||
begin="0.25s" dur="1s"
|
||||
values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
|
||||
repeatCount="indefinite" />
|
||||
</rect>
|
||||
<rect x="120" y="10" width="15" height="120" rx="6">
|
||||
<animate attributeName="height"
|
||||
begin="0.5s" dur="1s"
|
||||
values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
|
||||
repeatCount="indefinite" />
|
||||
<animate attributeName="y"
|
||||
begin="0.5s" dur="1s"
|
||||
values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
|
||||
repeatCount="indefinite" />
|
||||
</rect>
|
||||
</svg>
|
After Width: | Height: | Size: 2.3 KiB |
@ -34,6 +34,7 @@ import {
|
||||
PreCoinRecord,
|
||||
RefreshSessionRecord,
|
||||
ReserveRecord,
|
||||
TipPlanchet,
|
||||
WireFee,
|
||||
} from "../types";
|
||||
|
||||
@ -253,6 +254,10 @@ export class CryptoApi {
|
||||
return this.doRpc<PreCoinRecord>("createPreCoin", 1, denom, reserve);
|
||||
}
|
||||
|
||||
createTipPlanchet(denom: DenominationRecord): Promise<TipPlanchet> {
|
||||
return this.doRpc<TipPlanchet>("createTipPlanchet", 1, denom);
|
||||
}
|
||||
|
||||
hashString(str: string): Promise<string> {
|
||||
return this.doRpc<string>("hashString", 1, str);
|
||||
}
|
||||
|
@ -40,6 +40,7 @@ import {
|
||||
RefreshPreCoinRecord,
|
||||
RefreshSessionRecord,
|
||||
ReserveRecord,
|
||||
TipPlanchet,
|
||||
WireFee,
|
||||
} from "../types";
|
||||
|
||||
@ -103,6 +104,7 @@ namespace RpcFunctions {
|
||||
coinValue: denom.value,
|
||||
denomPub: denomPub.encode().toCrock(),
|
||||
exchangeBaseUrl: reserve.exchange_base_url,
|
||||
isFromTip: false,
|
||||
reservePub: reservePub.toCrock(),
|
||||
withdrawSig: sig.toCrock(),
|
||||
};
|
||||
@ -110,6 +112,35 @@ namespace RpcFunctions {
|
||||
}
|
||||
|
||||
|
||||
export function createTipPlanchet(denom: DenominationRecord): TipPlanchet {
|
||||
const denomPub = native.RsaPublicKey.fromCrock(denom.denomPub);
|
||||
const coinPriv = native.EddsaPrivateKey.create();
|
||||
const coinPub = coinPriv.getPublicKey();
|
||||
const blindingFactor = native.RsaBlindingKeySecret.create();
|
||||
const pubHash: native.HashCode = coinPub.hash();
|
||||
const ev = native.rsaBlind(pubHash, blindingFactor, denomPub);
|
||||
|
||||
if (!ev) {
|
||||
throw Error("couldn't blind (malicious exchange key?)");
|
||||
}
|
||||
|
||||
if (!denom.feeWithdraw) {
|
||||
throw Error("Field fee_withdraw missing");
|
||||
}
|
||||
|
||||
const tipPlanchet: TipPlanchet = {
|
||||
blindingKey: blindingFactor.toCrock(),
|
||||
coinEv: ev.toCrock(),
|
||||
coinPriv: coinPriv.toCrock(),
|
||||
coinPub: coinPub.toCrock(),
|
||||
coinValue: denom.value,
|
||||
denomPubHash: denomPub.encode().hash().toCrock(),
|
||||
denomPub: denomPub.encode().toCrock(),
|
||||
};
|
||||
return tipPlanchet;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create and sign a message to request payback for a coin.
|
||||
*/
|
||||
|
113
src/i18n/de.po
113
src/i18n/de.po
@ -66,67 +66,27 @@ msgstr ""
|
||||
msgid "Confirm payment"
|
||||
msgstr "Bezahlung bestätigen"
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:178
|
||||
#, fuzzy, c-format
|
||||
msgid "Withdrawal fees:"
|
||||
msgstr "Abheben bei %1$s"
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:179
|
||||
#, c-format
|
||||
msgid "Rounding loss:"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:180
|
||||
#, c-format
|
||||
msgid "Earliest expiration (for deposit): %1$s"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:185
|
||||
#, c-format
|
||||
msgid "# Coins"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:186
|
||||
#, c-format
|
||||
msgid "Value"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:187
|
||||
#, fuzzy, c-format
|
||||
msgid "Withdraw Fee"
|
||||
msgstr "Abheben bei %1$s"
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:188
|
||||
#, c-format
|
||||
msgid "Refresh Fee"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:189
|
||||
#, c-format
|
||||
msgid "Deposit Fee"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:243
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:121
|
||||
#, c-format
|
||||
msgid "Select"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:259
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:137
|
||||
#, c-format
|
||||
msgid "Error: URL may not be relative"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:327
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:205
|
||||
#, c-format
|
||||
msgid "The exchange is trusted by the wallet.\n"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:333
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:211
|
||||
#, c-format
|
||||
msgid "The exchange is audited by a trusted auditor.\n"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:339
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:217
|
||||
#, c-format
|
||||
msgid ""
|
||||
"Warning: The exchange is neither directly trusted nor audited by a trusted "
|
||||
@ -134,7 +94,7 @@ msgid ""
|
||||
"If you withdraw from this exchange, it will be trusted in the future.\n"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:348
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:226
|
||||
#, c-format
|
||||
msgid ""
|
||||
"Using exchange provider%1$s.\n"
|
||||
@ -142,58 +102,59 @@ msgid ""
|
||||
" %2$s in fees.\n"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:362
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:240
|
||||
#, c-format
|
||||
msgid ""
|
||||
"Waiting for a response from\n"
|
||||
" %1$s"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:379
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:257
|
||||
#, c-format
|
||||
msgid ""
|
||||
"Information about fees will be available when an exchange provider is "
|
||||
"selected."
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:422
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:300
|
||||
#, c-format
|
||||
msgid "Accept fees and withdraw"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:427
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:305
|
||||
#, c-format
|
||||
msgid "Change Exchange Provider"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:484
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:357
|
||||
#, 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:569
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:442
|
||||
#, c-format
|
||||
msgid ""
|
||||
"Oops, something went wrong. The wallet responded with error status (%1$s)."
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:578
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:451
|
||||
#, c-format
|
||||
msgid "Checking URL, please wait ..."
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:592
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:465
|
||||
#, c-format
|
||||
msgid "Can't parse amount: %1$s"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:599
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:472
|
||||
#, c-format
|
||||
msgid "Can't parse wire_types: %1$s"
|
||||
msgstr ""
|
||||
|
||||
#. #-#-#-#-# - (PACKAGE VERSION) #-#-#-#-#
|
||||
#. TODO:generic error reporting function or component.
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:625
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:498 src/webex/pages/tip.tsx:148
|
||||
#, c-format
|
||||
msgid "Fatal error: \"%1$s\"."
|
||||
msgstr ""
|
||||
@ -324,6 +285,46 @@ msgstr "Bezahlung bestätigen"
|
||||
msgid "Cancel"
|
||||
msgstr "Saldo"
|
||||
|
||||
#: src/webex/renderHtml.tsx:209
|
||||
#, fuzzy, c-format
|
||||
msgid "Withdrawal fees:"
|
||||
msgstr "Abheben bei %1$s"
|
||||
|
||||
#: src/webex/renderHtml.tsx:210
|
||||
#, c-format
|
||||
msgid "Rounding loss:"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/renderHtml.tsx:211
|
||||
#, c-format
|
||||
msgid "Earliest expiration (for deposit): %1$s"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/renderHtml.tsx:216
|
||||
#, c-format
|
||||
msgid "# Coins"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/renderHtml.tsx:217
|
||||
#, c-format
|
||||
msgid "Value"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/renderHtml.tsx:218
|
||||
#, fuzzy, c-format
|
||||
msgid "Withdraw Fee"
|
||||
msgstr "Abheben bei %1$s"
|
||||
|
||||
#: src/webex/renderHtml.tsx:219
|
||||
#, c-format
|
||||
msgid "Refresh Fee"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/renderHtml.tsx:220
|
||||
#, c-format
|
||||
msgid "Deposit Fee"
|
||||
msgstr ""
|
||||
|
||||
#: src/wire.ts:38
|
||||
#, c-format
|
||||
msgid "Invalid Wire"
|
||||
|
@ -66,67 +66,27 @@ msgstr ""
|
||||
msgid "Confirm payment"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:178
|
||||
#, c-format
|
||||
msgid "Withdrawal fees:"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:179
|
||||
#, c-format
|
||||
msgid "Rounding loss:"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:180
|
||||
#, c-format
|
||||
msgid "Earliest expiration (for deposit): %1$s"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:185
|
||||
#, c-format
|
||||
msgid "# Coins"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:186
|
||||
#, c-format
|
||||
msgid "Value"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:187
|
||||
#, c-format
|
||||
msgid "Withdraw Fee"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:188
|
||||
#, c-format
|
||||
msgid "Refresh Fee"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:189
|
||||
#, c-format
|
||||
msgid "Deposit Fee"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:243
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:121
|
||||
#, c-format
|
||||
msgid "Select"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:259
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:137
|
||||
#, c-format
|
||||
msgid "Error: URL may not be relative"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:327
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:205
|
||||
#, c-format
|
||||
msgid "The exchange is trusted by the wallet.\n"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:333
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:211
|
||||
#, c-format
|
||||
msgid "The exchange is audited by a trusted auditor.\n"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:339
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:217
|
||||
#, c-format
|
||||
msgid ""
|
||||
"Warning: The exchange is neither directly trusted nor audited by a trusted "
|
||||
@ -134,7 +94,7 @@ msgid ""
|
||||
"If you withdraw from this exchange, it will be trusted in the future.\n"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:348
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:226
|
||||
#, c-format
|
||||
msgid ""
|
||||
"Using exchange provider%1$s.\n"
|
||||
@ -142,58 +102,59 @@ msgid ""
|
||||
" %2$s in fees.\n"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:362
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:240
|
||||
#, c-format
|
||||
msgid ""
|
||||
"Waiting for a response from\n"
|
||||
" %1$s"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:379
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:257
|
||||
#, c-format
|
||||
msgid ""
|
||||
"Information about fees will be available when an exchange provider is "
|
||||
"selected."
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:422
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:300
|
||||
#, c-format
|
||||
msgid "Accept fees and withdraw"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:427
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:305
|
||||
#, c-format
|
||||
msgid "Change Exchange Provider"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:484
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:357
|
||||
#, 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:569
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:442
|
||||
#, c-format
|
||||
msgid ""
|
||||
"Oops, something went wrong. The wallet responded with error status (%1$s)."
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:578
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:451
|
||||
#, c-format
|
||||
msgid "Checking URL, please wait ..."
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:592
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:465
|
||||
#, c-format
|
||||
msgid "Can't parse amount: %1$s"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:599
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:472
|
||||
#, c-format
|
||||
msgid "Can't parse wire_types: %1$s"
|
||||
msgstr ""
|
||||
|
||||
#. #-#-#-#-# - (PACKAGE VERSION) #-#-#-#-#
|
||||
#. TODO:generic error reporting function or component.
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:625
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:498 src/webex/pages/tip.tsx:148
|
||||
#, c-format
|
||||
msgid "Fatal error: \"%1$s\"."
|
||||
msgstr ""
|
||||
@ -321,6 +282,46 @@ msgstr ""
|
||||
msgid "Cancel"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/renderHtml.tsx:209
|
||||
#, c-format
|
||||
msgid "Withdrawal fees:"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/renderHtml.tsx:210
|
||||
#, c-format
|
||||
msgid "Rounding loss:"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/renderHtml.tsx:211
|
||||
#, c-format
|
||||
msgid "Earliest expiration (for deposit): %1$s"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/renderHtml.tsx:216
|
||||
#, c-format
|
||||
msgid "# Coins"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/renderHtml.tsx:217
|
||||
#, c-format
|
||||
msgid "Value"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/renderHtml.tsx:218
|
||||
#, c-format
|
||||
msgid "Withdraw Fee"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/renderHtml.tsx:219
|
||||
#, c-format
|
||||
msgid "Refresh Fee"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/renderHtml.tsx:220
|
||||
#, c-format
|
||||
msgid "Deposit Fee"
|
||||
msgstr ""
|
||||
|
||||
#: src/wire.ts:38
|
||||
#, c-format
|
||||
msgid "Invalid Wire"
|
||||
|
113
src/i18n/fr.po
113
src/i18n/fr.po
@ -66,67 +66,27 @@ msgstr ""
|
||||
msgid "Confirm payment"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:178
|
||||
#, c-format
|
||||
msgid "Withdrawal fees:"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:179
|
||||
#, c-format
|
||||
msgid "Rounding loss:"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:180
|
||||
#, c-format
|
||||
msgid "Earliest expiration (for deposit): %1$s"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:185
|
||||
#, c-format
|
||||
msgid "# Coins"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:186
|
||||
#, c-format
|
||||
msgid "Value"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:187
|
||||
#, c-format
|
||||
msgid "Withdraw Fee"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:188
|
||||
#, c-format
|
||||
msgid "Refresh Fee"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:189
|
||||
#, c-format
|
||||
msgid "Deposit Fee"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:243
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:121
|
||||
#, c-format
|
||||
msgid "Select"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:259
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:137
|
||||
#, c-format
|
||||
msgid "Error: URL may not be relative"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:327
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:205
|
||||
#, c-format
|
||||
msgid "The exchange is trusted by the wallet.\n"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:333
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:211
|
||||
#, c-format
|
||||
msgid "The exchange is audited by a trusted auditor.\n"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:339
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:217
|
||||
#, c-format
|
||||
msgid ""
|
||||
"Warning: The exchange is neither directly trusted nor audited by a trusted "
|
||||
@ -134,7 +94,7 @@ msgid ""
|
||||
"If you withdraw from this exchange, it will be trusted in the future.\n"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:348
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:226
|
||||
#, c-format
|
||||
msgid ""
|
||||
"Using exchange provider%1$s.\n"
|
||||
@ -142,58 +102,59 @@ msgid ""
|
||||
" %2$s in fees.\n"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:362
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:240
|
||||
#, c-format
|
||||
msgid ""
|
||||
"Waiting for a response from\n"
|
||||
" %1$s"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:379
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:257
|
||||
#, c-format
|
||||
msgid ""
|
||||
"Information about fees will be available when an exchange provider is "
|
||||
"selected."
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:422
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:300
|
||||
#, c-format
|
||||
msgid "Accept fees and withdraw"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:427
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:305
|
||||
#, c-format
|
||||
msgid "Change Exchange Provider"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:484
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:357
|
||||
#, 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:569
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:442
|
||||
#, c-format
|
||||
msgid ""
|
||||
"Oops, something went wrong. The wallet responded with error status (%1$s)."
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:578
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:451
|
||||
#, c-format
|
||||
msgid "Checking URL, please wait ..."
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:592
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:465
|
||||
#, c-format
|
||||
msgid "Can't parse amount: %1$s"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:599
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:472
|
||||
#, c-format
|
||||
msgid "Can't parse wire_types: %1$s"
|
||||
msgstr ""
|
||||
|
||||
#. #-#-#-#-# - (PACKAGE VERSION) #-#-#-#-#
|
||||
#. TODO:generic error reporting function or component.
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:625
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:498 src/webex/pages/tip.tsx:148
|
||||
#, c-format
|
||||
msgid "Fatal error: \"%1$s\"."
|
||||
msgstr ""
|
||||
@ -321,6 +282,46 @@ msgstr ""
|
||||
msgid "Cancel"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/renderHtml.tsx:209
|
||||
#, c-format
|
||||
msgid "Withdrawal fees:"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/renderHtml.tsx:210
|
||||
#, c-format
|
||||
msgid "Rounding loss:"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/renderHtml.tsx:211
|
||||
#, c-format
|
||||
msgid "Earliest expiration (for deposit): %1$s"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/renderHtml.tsx:216
|
||||
#, c-format
|
||||
msgid "# Coins"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/renderHtml.tsx:217
|
||||
#, c-format
|
||||
msgid "Value"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/renderHtml.tsx:218
|
||||
#, c-format
|
||||
msgid "Withdraw Fee"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/renderHtml.tsx:219
|
||||
#, c-format
|
||||
msgid "Refresh Fee"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/renderHtml.tsx:220
|
||||
#, c-format
|
||||
msgid "Deposit Fee"
|
||||
msgstr ""
|
||||
|
||||
#: src/wire.ts:38
|
||||
#, c-format
|
||||
msgid "Invalid Wire"
|
||||
|
113
src/i18n/it.po
113
src/i18n/it.po
@ -66,67 +66,27 @@ msgstr ""
|
||||
msgid "Confirm payment"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:178
|
||||
#, c-format
|
||||
msgid "Withdrawal fees:"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:179
|
||||
#, c-format
|
||||
msgid "Rounding loss:"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:180
|
||||
#, c-format
|
||||
msgid "Earliest expiration (for deposit): %1$s"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:185
|
||||
#, c-format
|
||||
msgid "# Coins"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:186
|
||||
#, c-format
|
||||
msgid "Value"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:187
|
||||
#, c-format
|
||||
msgid "Withdraw Fee"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:188
|
||||
#, c-format
|
||||
msgid "Refresh Fee"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:189
|
||||
#, c-format
|
||||
msgid "Deposit Fee"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:243
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:121
|
||||
#, c-format
|
||||
msgid "Select"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:259
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:137
|
||||
#, c-format
|
||||
msgid "Error: URL may not be relative"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:327
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:205
|
||||
#, c-format
|
||||
msgid "The exchange is trusted by the wallet.\n"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:333
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:211
|
||||
#, c-format
|
||||
msgid "The exchange is audited by a trusted auditor.\n"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:339
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:217
|
||||
#, c-format
|
||||
msgid ""
|
||||
"Warning: The exchange is neither directly trusted nor audited by a trusted "
|
||||
@ -134,7 +94,7 @@ msgid ""
|
||||
"If you withdraw from this exchange, it will be trusted in the future.\n"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:348
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:226
|
||||
#, c-format
|
||||
msgid ""
|
||||
"Using exchange provider%1$s.\n"
|
||||
@ -142,58 +102,59 @@ msgid ""
|
||||
" %2$s in fees.\n"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:362
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:240
|
||||
#, c-format
|
||||
msgid ""
|
||||
"Waiting for a response from\n"
|
||||
" %1$s"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:379
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:257
|
||||
#, c-format
|
||||
msgid ""
|
||||
"Information about fees will be available when an exchange provider is "
|
||||
"selected."
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:422
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:300
|
||||
#, c-format
|
||||
msgid "Accept fees and withdraw"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:427
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:305
|
||||
#, c-format
|
||||
msgid "Change Exchange Provider"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:484
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:357
|
||||
#, 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:569
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:442
|
||||
#, c-format
|
||||
msgid ""
|
||||
"Oops, something went wrong. The wallet responded with error status (%1$s)."
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:578
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:451
|
||||
#, c-format
|
||||
msgid "Checking URL, please wait ..."
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:592
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:465
|
||||
#, c-format
|
||||
msgid "Can't parse amount: %1$s"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:599
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:472
|
||||
#, c-format
|
||||
msgid "Can't parse wire_types: %1$s"
|
||||
msgstr ""
|
||||
|
||||
#. #-#-#-#-# - (PACKAGE VERSION) #-#-#-#-#
|
||||
#. TODO:generic error reporting function or component.
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:625
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:498 src/webex/pages/tip.tsx:148
|
||||
#, c-format
|
||||
msgid "Fatal error: \"%1$s\"."
|
||||
msgstr ""
|
||||
@ -321,6 +282,46 @@ msgstr ""
|
||||
msgid "Cancel"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/renderHtml.tsx:209
|
||||
#, c-format
|
||||
msgid "Withdrawal fees:"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/renderHtml.tsx:210
|
||||
#, c-format
|
||||
msgid "Rounding loss:"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/renderHtml.tsx:211
|
||||
#, c-format
|
||||
msgid "Earliest expiration (for deposit): %1$s"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/renderHtml.tsx:216
|
||||
#, c-format
|
||||
msgid "# Coins"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/renderHtml.tsx:217
|
||||
#, c-format
|
||||
msgid "Value"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/renderHtml.tsx:218
|
||||
#, c-format
|
||||
msgid "Withdraw Fee"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/renderHtml.tsx:219
|
||||
#, c-format
|
||||
msgid "Refresh Fee"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/renderHtml.tsx:220
|
||||
#, c-format
|
||||
msgid "Deposit Fee"
|
||||
msgstr ""
|
||||
|
||||
#: src/wire.ts:38
|
||||
#, c-format
|
||||
msgid "Invalid Wire"
|
||||
|
@ -45,30 +45,6 @@ strings['de'] = {
|
||||
"Confirm payment": [
|
||||
"Bezahlung bestätigen"
|
||||
],
|
||||
"Withdrawal fees:": [
|
||||
"Abheben bei %1$s"
|
||||
],
|
||||
"Rounding loss:": [
|
||||
""
|
||||
],
|
||||
"Earliest expiration (for deposit): %1$s": [
|
||||
""
|
||||
],
|
||||
"# Coins": [
|
||||
""
|
||||
],
|
||||
"Value": [
|
||||
""
|
||||
],
|
||||
"Withdraw Fee": [
|
||||
"Abheben bei %1$s"
|
||||
],
|
||||
"Refresh Fee": [
|
||||
""
|
||||
],
|
||||
"Deposit Fee": [
|
||||
""
|
||||
],
|
||||
"Select": [
|
||||
""
|
||||
],
|
||||
@ -186,6 +162,30 @@ strings['de'] = {
|
||||
"Cancel": [
|
||||
"Saldo"
|
||||
],
|
||||
"Withdrawal fees:": [
|
||||
"Abheben bei %1$s"
|
||||
],
|
||||
"Rounding loss:": [
|
||||
""
|
||||
],
|
||||
"Earliest expiration (for deposit): %1$s": [
|
||||
""
|
||||
],
|
||||
"# Coins": [
|
||||
""
|
||||
],
|
||||
"Value": [
|
||||
""
|
||||
],
|
||||
"Withdraw Fee": [
|
||||
"Abheben bei %1$s"
|
||||
],
|
||||
"Refresh Fee": [
|
||||
""
|
||||
],
|
||||
"Deposit Fee": [
|
||||
""
|
||||
],
|
||||
"Invalid Wire": [
|
||||
""
|
||||
],
|
||||
@ -231,30 +231,6 @@ strings['en-US'] = {
|
||||
"Confirm payment": [
|
||||
""
|
||||
],
|
||||
"Withdrawal fees:": [
|
||||
""
|
||||
],
|
||||
"Rounding loss:": [
|
||||
""
|
||||
],
|
||||
"Earliest expiration (for deposit): %1$s": [
|
||||
""
|
||||
],
|
||||
"# Coins": [
|
||||
""
|
||||
],
|
||||
"Value": [
|
||||
""
|
||||
],
|
||||
"Withdraw Fee": [
|
||||
""
|
||||
],
|
||||
"Refresh Fee": [
|
||||
""
|
||||
],
|
||||
"Deposit Fee": [
|
||||
""
|
||||
],
|
||||
"Select": [
|
||||
""
|
||||
],
|
||||
@ -372,6 +348,30 @@ strings['en-US'] = {
|
||||
"Cancel": [
|
||||
""
|
||||
],
|
||||
"Withdrawal fees:": [
|
||||
""
|
||||
],
|
||||
"Rounding loss:": [
|
||||
""
|
||||
],
|
||||
"Earliest expiration (for deposit): %1$s": [
|
||||
""
|
||||
],
|
||||
"# Coins": [
|
||||
""
|
||||
],
|
||||
"Value": [
|
||||
""
|
||||
],
|
||||
"Withdraw Fee": [
|
||||
""
|
||||
],
|
||||
"Refresh Fee": [
|
||||
""
|
||||
],
|
||||
"Deposit Fee": [
|
||||
""
|
||||
],
|
||||
"Invalid Wire": [
|
||||
""
|
||||
],
|
||||
@ -417,30 +417,6 @@ strings['fr'] = {
|
||||
"Confirm payment": [
|
||||
""
|
||||
],
|
||||
"Withdrawal fees:": [
|
||||
""
|
||||
],
|
||||
"Rounding loss:": [
|
||||
""
|
||||
],
|
||||
"Earliest expiration (for deposit): %1$s": [
|
||||
""
|
||||
],
|
||||
"# Coins": [
|
||||
""
|
||||
],
|
||||
"Value": [
|
||||
""
|
||||
],
|
||||
"Withdraw Fee": [
|
||||
""
|
||||
],
|
||||
"Refresh Fee": [
|
||||
""
|
||||
],
|
||||
"Deposit Fee": [
|
||||
""
|
||||
],
|
||||
"Select": [
|
||||
""
|
||||
],
|
||||
@ -558,6 +534,30 @@ strings['fr'] = {
|
||||
"Cancel": [
|
||||
""
|
||||
],
|
||||
"Withdrawal fees:": [
|
||||
""
|
||||
],
|
||||
"Rounding loss:": [
|
||||
""
|
||||
],
|
||||
"Earliest expiration (for deposit): %1$s": [
|
||||
""
|
||||
],
|
||||
"# Coins": [
|
||||
""
|
||||
],
|
||||
"Value": [
|
||||
""
|
||||
],
|
||||
"Withdraw Fee": [
|
||||
""
|
||||
],
|
||||
"Refresh Fee": [
|
||||
""
|
||||
],
|
||||
"Deposit Fee": [
|
||||
""
|
||||
],
|
||||
"Invalid Wire": [
|
||||
""
|
||||
],
|
||||
@ -603,30 +603,6 @@ strings['it'] = {
|
||||
"Confirm payment": [
|
||||
""
|
||||
],
|
||||
"Withdrawal fees:": [
|
||||
""
|
||||
],
|
||||
"Rounding loss:": [
|
||||
""
|
||||
],
|
||||
"Earliest expiration (for deposit): %1$s": [
|
||||
""
|
||||
],
|
||||
"# Coins": [
|
||||
""
|
||||
],
|
||||
"Value": [
|
||||
""
|
||||
],
|
||||
"Withdraw Fee": [
|
||||
""
|
||||
],
|
||||
"Refresh Fee": [
|
||||
""
|
||||
],
|
||||
"Deposit Fee": [
|
||||
""
|
||||
],
|
||||
"Select": [
|
||||
""
|
||||
],
|
||||
@ -744,6 +720,30 @@ strings['it'] = {
|
||||
"Cancel": [
|
||||
""
|
||||
],
|
||||
"Withdrawal fees:": [
|
||||
""
|
||||
],
|
||||
"Rounding loss:": [
|
||||
""
|
||||
],
|
||||
"Earliest expiration (for deposit): %1$s": [
|
||||
""
|
||||
],
|
||||
"# Coins": [
|
||||
""
|
||||
],
|
||||
"Value": [
|
||||
""
|
||||
],
|
||||
"Withdraw Fee": [
|
||||
""
|
||||
],
|
||||
"Refresh Fee": [
|
||||
""
|
||||
],
|
||||
"Deposit Fee": [
|
||||
""
|
||||
],
|
||||
"Invalid Wire": [
|
||||
""
|
||||
],
|
||||
|
@ -66,67 +66,27 @@ msgstr ""
|
||||
msgid "Confirm payment"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:178
|
||||
#, c-format
|
||||
msgid "Withdrawal fees:"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:179
|
||||
#, c-format
|
||||
msgid "Rounding loss:"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:180
|
||||
#, c-format
|
||||
msgid "Earliest expiration (for deposit): %1$s"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:185
|
||||
#, c-format
|
||||
msgid "# Coins"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:186
|
||||
#, c-format
|
||||
msgid "Value"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:187
|
||||
#, c-format
|
||||
msgid "Withdraw Fee"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:188
|
||||
#, c-format
|
||||
msgid "Refresh Fee"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:189
|
||||
#, c-format
|
||||
msgid "Deposit Fee"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:243
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:121
|
||||
#, c-format
|
||||
msgid "Select"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:259
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:137
|
||||
#, c-format
|
||||
msgid "Error: URL may not be relative"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:327
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:205
|
||||
#, c-format
|
||||
msgid "The exchange is trusted by the wallet.\n"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:333
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:211
|
||||
#, c-format
|
||||
msgid "The exchange is audited by a trusted auditor.\n"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:339
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:217
|
||||
#, c-format
|
||||
msgid ""
|
||||
"Warning: The exchange is neither directly trusted nor audited by a trusted "
|
||||
@ -134,7 +94,7 @@ msgid ""
|
||||
"If you withdraw from this exchange, it will be trusted in the future.\n"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:348
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:226
|
||||
#, c-format
|
||||
msgid ""
|
||||
"Using exchange provider%1$s.\n"
|
||||
@ -142,58 +102,59 @@ msgid ""
|
||||
" %2$s in fees.\n"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:362
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:240
|
||||
#, c-format
|
||||
msgid ""
|
||||
"Waiting for a response from\n"
|
||||
" %1$s"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:379
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:257
|
||||
#, c-format
|
||||
msgid ""
|
||||
"Information about fees will be available when an exchange provider is "
|
||||
"selected."
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:422
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:300
|
||||
#, c-format
|
||||
msgid "Accept fees and withdraw"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:427
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:305
|
||||
#, c-format
|
||||
msgid "Change Exchange Provider"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:484
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:357
|
||||
#, 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:569
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:442
|
||||
#, c-format
|
||||
msgid ""
|
||||
"Oops, something went wrong. The wallet responded with error status (%1$s)."
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:578
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:451
|
||||
#, c-format
|
||||
msgid "Checking URL, please wait ..."
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:592
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:465
|
||||
#, c-format
|
||||
msgid "Can't parse amount: %1$s"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:599
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:472
|
||||
#, c-format
|
||||
msgid "Can't parse wire_types: %1$s"
|
||||
msgstr ""
|
||||
|
||||
#. #-#-#-#-# - (PACKAGE VERSION) #-#-#-#-#
|
||||
#. TODO:generic error reporting function or component.
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:625
|
||||
#: src/webex/pages/confirm-create-reserve.tsx:498 src/webex/pages/tip.tsx:148
|
||||
#, c-format
|
||||
msgid "Fatal error: \"%1$s\"."
|
||||
msgstr ""
|
||||
@ -321,6 +282,46 @@ msgstr ""
|
||||
msgid "Cancel"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/renderHtml.tsx:209
|
||||
#, c-format
|
||||
msgid "Withdrawal fees:"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/renderHtml.tsx:210
|
||||
#, c-format
|
||||
msgid "Rounding loss:"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/renderHtml.tsx:211
|
||||
#, c-format
|
||||
msgid "Earliest expiration (for deposit): %1$s"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/renderHtml.tsx:216
|
||||
#, c-format
|
||||
msgid "# Coins"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/renderHtml.tsx:217
|
||||
#, c-format
|
||||
msgid "Value"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/renderHtml.tsx:218
|
||||
#, c-format
|
||||
msgid "Withdraw Fee"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/renderHtml.tsx:219
|
||||
#, c-format
|
||||
msgid "Refresh Fee"
|
||||
msgstr ""
|
||||
|
||||
#: src/webex/renderHtml.tsx:220
|
||||
#, c-format
|
||||
msgid "Deposit Fee"
|
||||
msgstr ""
|
||||
|
||||
#: src/wire.ts:38
|
||||
#, c-format
|
||||
msgid "Invalid Wire"
|
||||
|
56
src/query.ts
56
src/query.ts
@ -50,6 +50,20 @@ export class Store<T> {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Options for an index.
|
||||
*/
|
||||
export interface IndexOptions {
|
||||
/**
|
||||
* If true and the path resolves to an array, create an index entry for
|
||||
* each member of the array (instead of one index entry containing the full array).
|
||||
*
|
||||
* Defaults to false.
|
||||
*/
|
||||
multiEntry?: boolean;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Definition of an index.
|
||||
*/
|
||||
@ -59,7 +73,16 @@ export class Index<S extends IDBValidKey, T> {
|
||||
*/
|
||||
storeName: string;
|
||||
|
||||
constructor(s: Store<T>, public indexName: string, public keyPath: string | string[]) {
|
||||
/**
|
||||
* Options to use for the index.
|
||||
*/
|
||||
options: IndexOptions;
|
||||
|
||||
constructor(s: Store<T>, public indexName: string, public keyPath: string | string[], options?: IndexOptions) {
|
||||
const defaultOptions = {
|
||||
multiEntry: false,
|
||||
};
|
||||
this.options = { ...defaultOptions, ...(options || {}) };
|
||||
this.storeName = s.name;
|
||||
}
|
||||
|
||||
@ -671,17 +694,22 @@ export class QueryRoot {
|
||||
|
||||
|
||||
/**
|
||||
* Get, modify and store an element inside a transaction.
|
||||
* Update objects inside a transaction.
|
||||
*
|
||||
* If the mutation function throws AbortTransaction, the whole transaction will be aborted.
|
||||
* If the mutation function returns undefined or null, no modification will be made.
|
||||
*/
|
||||
mutate<T>(store: Store<T>, key: any, f: (v: T|undefined) => T|undefined): QueryRoot {
|
||||
this.checkFinished();
|
||||
const doPut = (tx: IDBTransaction) => {
|
||||
const reqGet = tx.objectStore(store.name).get(key);
|
||||
reqGet.onsuccess = () => {
|
||||
const r = reqGet.result;
|
||||
let m: T|undefined;
|
||||
const req = tx.objectStore(store.name).openCursor(IDBKeyRange.only(key));
|
||||
req.onsuccess = () => {
|
||||
const cursor = req.result;
|
||||
if (cursor) {
|
||||
const value = cursor.value;
|
||||
let modifiedValue: T|undefined;
|
||||
try {
|
||||
m = f(r);
|
||||
modifiedValue = f(value);
|
||||
} catch (e) {
|
||||
if (e === AbortTransaction) {
|
||||
tx.abort();
|
||||
@ -689,8 +717,10 @@ export class QueryRoot {
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
if (m !== undefined && m !== null) {
|
||||
tx.objectStore(store.name).put(m);
|
||||
if (modifiedValue !== undefined && modifiedValue !== null) {
|
||||
cursor.update(modifiedValue);
|
||||
}
|
||||
cursor.continue();
|
||||
}
|
||||
};
|
||||
};
|
||||
@ -702,8 +732,6 @@ export class QueryRoot {
|
||||
|
||||
/**
|
||||
* Add all object from an iterable to the given object store.
|
||||
* Fails if the object's key is already present
|
||||
* in the object store.
|
||||
*/
|
||||
putAll<T>(store: Store<T>, iterable: T[]): QueryRoot {
|
||||
this.checkFinished();
|
||||
@ -822,13 +850,13 @@ export class QueryRoot {
|
||||
/**
|
||||
* Delete an object by from the given object store.
|
||||
*/
|
||||
delete(storeName: string, key: any): QueryRoot {
|
||||
delete<T>(store: Store<T>, key: any): QueryRoot {
|
||||
this.checkFinished();
|
||||
const doDelete = (tx: IDBTransaction) => {
|
||||
tx.objectStore(storeName).delete(key);
|
||||
tx.objectStore(store.name).delete(key);
|
||||
};
|
||||
this.scheduleFinish();
|
||||
this.addWork(doDelete, storeName, true);
|
||||
this.addWork(doDelete, store.name, true);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
224
src/types.ts
224
src/types.ts
@ -598,6 +598,12 @@ export interface PreCoinRecord {
|
||||
coinEv: string;
|
||||
exchangeBaseUrl: string;
|
||||
coinValue: AmountJson;
|
||||
/**
|
||||
* Set to true if this pre-coin came from a tip.
|
||||
* Until the tip is marked as "accepted", the resulting
|
||||
* coin will not be used for payments.
|
||||
*/
|
||||
isFromTip: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -836,6 +842,10 @@ export enum CoinStatus {
|
||||
* Coin was dirty but can't be refreshed.
|
||||
*/
|
||||
Useless,
|
||||
/**
|
||||
* The coin was withdrawn for a tip that the user hasn't accepted yet.
|
||||
*/
|
||||
TainedByTip,
|
||||
}
|
||||
|
||||
|
||||
@ -1782,3 +1792,217 @@ export interface CoinWithDenom {
|
||||
*/
|
||||
denom: DenominationRecord;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Planchet detail sent to the merchant.
|
||||
*/
|
||||
export interface TipPlanchetDetail {
|
||||
/**
|
||||
* Hashed denomination public key.
|
||||
*/
|
||||
denom_pub_hash: string;
|
||||
|
||||
/**
|
||||
* Coin's blinded public key.
|
||||
*/
|
||||
coin_ev: string;
|
||||
}
|
||||
|
||||
|
||||
export interface TipPickupRequest {
|
||||
/**
|
||||
* Identifier of the tip.
|
||||
*/
|
||||
tip_id: string;
|
||||
|
||||
/**
|
||||
* List of planchets the wallet wants to use for the tip.
|
||||
*/
|
||||
planchets: TipPlanchetDetail[];
|
||||
}
|
||||
|
||||
@Checkable.Class()
|
||||
export class ReserveSigSingleton {
|
||||
@Checkable.String
|
||||
reserve_sig: string;
|
||||
|
||||
static checked: (obj: any) => ReserveSigSingleton;
|
||||
}
|
||||
|
||||
/**
|
||||
* Response of the merchant
|
||||
* to the TipPickupRequest.
|
||||
*/
|
||||
@Checkable.Class()
|
||||
export class TipResponse {
|
||||
/**
|
||||
* Public key of the reserve
|
||||
*/
|
||||
@Checkable.String
|
||||
reserve_pub: string;
|
||||
|
||||
/**
|
||||
* The order of the signatures matches the planchets list.
|
||||
*/
|
||||
@Checkable.List(Checkable.Value(ReserveSigSingleton))
|
||||
reserve_sigs: ReserveSigSingleton[];
|
||||
|
||||
static checked: (obj: any) => TipResponse;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tipping planchet stored in the database.
|
||||
*/
|
||||
export interface TipPlanchet {
|
||||
blindingKey: string;
|
||||
coinEv: string;
|
||||
coinPriv: string;
|
||||
coinPub: string;
|
||||
coinValue: AmountJson;
|
||||
denomPubHash: string;
|
||||
denomPub: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Status of a tip we got from a merchant.
|
||||
*/
|
||||
export interface TipRecord {
|
||||
/**
|
||||
* Has the user accepted the tip? Only after the tip has been accepted coins
|
||||
* withdrawn from the tip may be used.
|
||||
*/
|
||||
accepted: boolean;
|
||||
|
||||
/**
|
||||
* The tipped amount.
|
||||
*/
|
||||
amount: AmountJson;
|
||||
|
||||
/**
|
||||
* Coin public keys from the planchets.
|
||||
* This field is redundant and used for indexing the record via
|
||||
* a multi-entry index to look up tip records by coin public key.
|
||||
*/
|
||||
coinPubs: string[];
|
||||
|
||||
/**
|
||||
* Timestamp, the tip can't be picked up anymore after this deadline.
|
||||
*/
|
||||
deadline: number;
|
||||
|
||||
/**
|
||||
* The exchange that will sign our coins, chosen by the merchant.
|
||||
*/
|
||||
exchangeUrl: string;
|
||||
|
||||
/**
|
||||
* Domain of the merchant, necessary to uniquely identify the tip since
|
||||
* merchants can freely choose the ID and a malicious merchant might cause a
|
||||
* collision.
|
||||
*/
|
||||
merchantDomain: string;
|
||||
|
||||
/**
|
||||
* Planchets, the members included in TipPlanchetDetail will be sent to the
|
||||
* merchant.
|
||||
*/
|
||||
planchets: TipPlanchet[];
|
||||
|
||||
/**
|
||||
* Response if the merchant responded,
|
||||
* undefined otherwise.
|
||||
*/
|
||||
response?: TipResponse[];
|
||||
|
||||
/**
|
||||
* Identifier for the tip, chosen by the merchant.
|
||||
*/
|
||||
tipId: string;
|
||||
}
|
||||
|
||||
|
||||
export interface TipStatus {
|
||||
tip: TipRecord;
|
||||
rci?: ReserveCreationInfo;
|
||||
}
|
||||
|
||||
|
||||
@Checkable.Class()
|
||||
export class TipStatusRequest {
|
||||
@Checkable.String
|
||||
tipId: string;
|
||||
|
||||
@Checkable.String
|
||||
merchantDomain: string;
|
||||
|
||||
static checked: (obj: any) => TipStatusRequest;
|
||||
}
|
||||
|
||||
|
||||
@Checkable.Class()
|
||||
export class AcceptTipRequest {
|
||||
@Checkable.String
|
||||
tipId: string;
|
||||
|
||||
@Checkable.String
|
||||
merchantDomain: string;
|
||||
|
||||
static checked: (obj: any) => AcceptTipRequest;
|
||||
}
|
||||
|
||||
|
||||
@Checkable.Class()
|
||||
export class ProcessTipResponseRequest {
|
||||
@Checkable.String
|
||||
tipId: string;
|
||||
|
||||
@Checkable.String
|
||||
merchantDomain: string;
|
||||
|
||||
@Checkable.Value(TipResponse)
|
||||
tipResponse: TipResponse;
|
||||
|
||||
static checked: (obj: any) => ProcessTipResponseRequest;
|
||||
}
|
||||
|
||||
@Checkable.Class()
|
||||
export class GetTipPlanchetsRequest {
|
||||
@Checkable.String
|
||||
tipId: string;
|
||||
|
||||
@Checkable.String
|
||||
merchantDomain: string;
|
||||
|
||||
@Checkable.Optional(Checkable.Value(AmountJson))
|
||||
amount: AmountJson;
|
||||
|
||||
@Checkable.Number
|
||||
deadline: number;
|
||||
|
||||
@Checkable.String
|
||||
exchangeUrl: string;
|
||||
|
||||
static checked: (obj: any) => GetTipPlanchetsRequest;
|
||||
}
|
||||
|
||||
@Checkable.Class()
|
||||
export class TipToken {
|
||||
@Checkable.String
|
||||
expiration: string;
|
||||
|
||||
@Checkable.String
|
||||
exchange_url: string;
|
||||
|
||||
@Checkable.String
|
||||
pickup_url: string;
|
||||
|
||||
@Checkable.String
|
||||
tip_id: string;
|
||||
|
||||
@Checkable.Value(AmountJson)
|
||||
amount: AmountJson;
|
||||
|
||||
static checked: (obj: any) => TipToken;
|
||||
}
|
||||
|
179
src/wallet.ts
179
src/wallet.ts
@ -81,6 +81,10 @@ import {
|
||||
ReserveRecord,
|
||||
ReturnCoinsRequest,
|
||||
SenderWireInfos,
|
||||
TipPlanchetDetail,
|
||||
TipRecord,
|
||||
TipResponse,
|
||||
TipStatus,
|
||||
WalletBalance,
|
||||
WalletBalanceEntry,
|
||||
WireFee,
|
||||
@ -324,7 +328,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 = 20;
|
||||
export const WALLET_DB_VERSION = 21;
|
||||
|
||||
const builtinCurrencies: CurrencyRecord[] = [
|
||||
{
|
||||
@ -506,7 +510,7 @@ export namespace Stores {
|
||||
super("exchanges", {keyPath: "baseUrl"});
|
||||
}
|
||||
|
||||
pubKeyIndex = new Index<string, ExchangeRecord>(this, "pubKey", "masterPublicKey");
|
||||
pubKeyIndex = new Index<string, ExchangeRecord>(this, "pubKeyIndex", "masterPublicKey");
|
||||
}
|
||||
|
||||
class NonceStore extends Store<NonceRecord> {
|
||||
@ -521,7 +525,7 @@ export namespace Stores {
|
||||
}
|
||||
|
||||
exchangeBaseUrlIndex = new Index<string, CoinRecord>(this, "exchangeBaseUrl", "exchangeBaseUrl");
|
||||
denomPubIndex = new Index<string, CoinRecord>(this, "denomPub", "denomPub");
|
||||
denomPubIndex = new Index<string, CoinRecord>(this, "denomPubIndex", "denomPub");
|
||||
}
|
||||
|
||||
class ProposalsStore extends Store<ProposalRecord> {
|
||||
@ -531,7 +535,7 @@ export namespace Stores {
|
||||
keyPath: "id",
|
||||
});
|
||||
}
|
||||
timestampIndex = new Index<string, ProposalRecord>(this, "timestamp", "timestamp");
|
||||
timestampIndex = new Index<string, ProposalRecord>(this, "timestampIndex", "timestamp");
|
||||
}
|
||||
|
||||
class PurchasesStore extends Store<PurchaseRecord> {
|
||||
@ -539,9 +543,9 @@ export namespace Stores {
|
||||
super("purchases", {keyPath: "contractTermsHash"});
|
||||
}
|
||||
|
||||
fulfillmentUrlIndex = new Index<string, PurchaseRecord>(this, "fulfillment_url", "contractTerms.fulfillment_url");
|
||||
orderIdIndex = new Index<string, PurchaseRecord>(this, "order_id", "contractTerms.order_id");
|
||||
timestampIndex = new Index<string, PurchaseRecord>(this, "timestamp", "timestamp");
|
||||
fulfillmentUrlIndex = new Index<string, PurchaseRecord>(this, "fulfillmentUrlIndex", "contractTerms.fulfillment_url");
|
||||
orderIdIndex = new Index<string, PurchaseRecord>(this, "orderIdIndex", "contractTerms.order_id");
|
||||
timestampIndex = new Index<string, PurchaseRecord>(this, "timestampIndex", "timestamp");
|
||||
}
|
||||
|
||||
class DenominationsStore extends Store<DenominationRecord> {
|
||||
@ -551,9 +555,9 @@ export namespace Stores {
|
||||
{keyPath: ["exchangeBaseUrl", "denomPub"] as any as IDBKeyPath});
|
||||
}
|
||||
|
||||
denomPubHashIndex = new Index<string, DenominationRecord>(this, "denomPubHash", "denomPubHash");
|
||||
exchangeBaseUrlIndex = new Index<string, DenominationRecord>(this, "exchangeBaseUrl", "exchangeBaseUrl");
|
||||
denomPubIndex = new Index<string, DenominationRecord>(this, "denomPub", "denomPub");
|
||||
denomPubHashIndex = new Index<string, DenominationRecord>(this, "denomPubHashIndex", "denomPubHash");
|
||||
exchangeBaseUrlIndex = new Index<string, DenominationRecord>(this, "exchangeBaseUrlIndex", "exchangeBaseUrl");
|
||||
denomPubIndex = new Index<string, DenominationRecord>(this, "denomPubIndex", "denomPub");
|
||||
}
|
||||
|
||||
class CurrenciesStore extends Store<CurrencyRecord> {
|
||||
@ -578,9 +582,16 @@ export namespace Stores {
|
||||
constructor() {
|
||||
super("reserves", {keyPath: "reserve_pub"});
|
||||
}
|
||||
timestampCreatedIndex = new Index<string, ReserveRecord>(this, "timestampCreated", "created");
|
||||
timestampConfirmedIndex = new Index<string, ReserveRecord>(this, "timestampConfirmed", "timestamp_confirmed");
|
||||
timestampDepletedIndex = new Index<string, ReserveRecord>(this, "timestampDepleted", "timestamp_depleted");
|
||||
timestampCreatedIndex = new Index<string, ReserveRecord>(this, "timestampCreatedIndex", "created");
|
||||
timestampConfirmedIndex = new Index<string, ReserveRecord>(this, "timestampConfirmedIndex", "timestamp_confirmed");
|
||||
timestampDepletedIndex = new Index<string, ReserveRecord>(this, "timestampDepletedIndex", "timestamp_depleted");
|
||||
}
|
||||
|
||||
class TipsStore extends Store<TipRecord> {
|
||||
constructor() {
|
||||
super("tips", {keyPath: ["tipId", "merchantDomain"] as any as IDBKeyPath});
|
||||
}
|
||||
coinPubIndex = new Index<string, TipRecord>(this, "coinPubIndex", "coinPubs", { multiEntry: true });
|
||||
}
|
||||
|
||||
export const coins = new CoinsStore();
|
||||
@ -596,6 +607,7 @@ export namespace Stores {
|
||||
export const refresh = new Store<RefreshSessionRecord>("refresh", {keyPath: "id", autoIncrement: true});
|
||||
export const reserves = new ReservesStore();
|
||||
export const purchases = new PurchasesStore();
|
||||
export const tips = new TipsStore();
|
||||
}
|
||||
|
||||
/* tslint:enable:completed-docs */
|
||||
@ -1126,7 +1138,7 @@ export class Wallet {
|
||||
() => this.processPreCoin(preCoin, Math.min(retryDelayMs * 2, 5 * 60 * 1000)));
|
||||
return;
|
||||
}
|
||||
console.log("executing processPreCoin");
|
||||
console.log("executing processPreCoin", preCoin);
|
||||
this.processPreCoinConcurrent++;
|
||||
try {
|
||||
const exchange = await this.q().get(Stores.exchanges,
|
||||
@ -1143,6 +1155,7 @@ export class Wallet {
|
||||
}
|
||||
|
||||
const coin = await this.withdrawExecute(preCoin);
|
||||
console.log("processPreCoin: got coin", coin);
|
||||
|
||||
const mutateReserve = (r: ReserveRecord) => {
|
||||
|
||||
@ -1160,10 +1173,28 @@ export class Wallet {
|
||||
|
||||
await this.q()
|
||||
.mutate(Stores.reserves, preCoin.reservePub, mutateReserve)
|
||||
.delete("precoins", coin.coinPub)
|
||||
.delete(Stores.precoins, coin.coinPub)
|
||||
.add(Stores.coins, coin)
|
||||
.finish();
|
||||
|
||||
if (coin.status === CoinStatus.TainedByTip) {
|
||||
let tip = await this.q().getIndexed(Stores.tips.coinPubIndex, coin.coinPub);
|
||||
if (!tip) {
|
||||
throw Error(`inconsistent DB: tip for coin pub ${coin.coinPub} not found.`);
|
||||
}
|
||||
|
||||
if (tip.accepted) {
|
||||
// Transactionall set coin to fresh.
|
||||
const mutateCoin = (c: CoinRecord) => {
|
||||
if (c.status === CoinStatus.TainedByTip) {
|
||||
c.status = CoinStatus.Fresh;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
await this.q().mutate(Stores.coins, coin.coinPub, mutateCoin)
|
||||
}
|
||||
}
|
||||
|
||||
this.notifier.notify();
|
||||
} catch (e) {
|
||||
console.error("Failed to withdraw coin from precoin, retrying in",
|
||||
@ -1266,19 +1297,12 @@ export class Wallet {
|
||||
|
||||
|
||||
private async withdrawExecute(pc: PreCoinRecord): Promise<CoinRecord> {
|
||||
const reserve = await this.q().get<ReserveRecord>(Stores.reserves,
|
||||
pc.reservePub);
|
||||
|
||||
if (!reserve) {
|
||||
throw Error("db inconsistent");
|
||||
}
|
||||
|
||||
const wd: any = {};
|
||||
wd.denom_pub = pc.denomPub;
|
||||
wd.reserve_pub = pc.reservePub;
|
||||
wd.reserve_sig = pc.withdrawSig;
|
||||
wd.coin_ev = pc.coinEv;
|
||||
const reqUrl = (new URI("reserve/withdraw")).absoluteTo(reserve.exchange_base_url);
|
||||
const reqUrl = (new URI("reserve/withdraw")).absoluteTo(pc.exchangeBaseUrl);
|
||||
const resp = await this.http.postJson(reqUrl.href(), wd);
|
||||
|
||||
if (resp.status !== 200) {
|
||||
@ -2809,4 +2833,113 @@ export class Wallet {
|
||||
}
|
||||
return feeAcc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get planchets for a tip. Creates new planchets if they don't exist already
|
||||
* for this tip. The tip is uniquely identified by the merchant's domain and the tip id.
|
||||
*/
|
||||
async getTipPlanchets(merchantDomain: string, tipId: string, amount: AmountJson, deadline: number, exchangeUrl: string): Promise<TipPlanchetDetail[]> {
|
||||
let tipRecord = await this.q().get(Stores.tips, [tipId, merchantDomain]);
|
||||
if (!tipRecord) {
|
||||
await this.updateExchangeFromUrl(exchangeUrl);
|
||||
const denomsForWithdraw = await this.getVerifiedWithdrawDenomList(exchangeUrl, amount);
|
||||
const planchets = await Promise.all(denomsForWithdraw.map(d => this.cryptoApi.createTipPlanchet(d)));
|
||||
const coinPubs: string[] = planchets.map(x => x.coinPub);
|
||||
tipRecord = {
|
||||
accepted: false,
|
||||
amount,
|
||||
coinPubs,
|
||||
deadline,
|
||||
exchangeUrl,
|
||||
merchantDomain,
|
||||
planchets,
|
||||
tipId,
|
||||
};
|
||||
await this.q().put(Stores.tips, tipRecord).finish();
|
||||
}
|
||||
// Planchets in the form that the merchant expects
|
||||
const planchetDetail: TipPlanchetDetail[]= tipRecord.planchets.map((p) => ({
|
||||
denom_pub_hash: p.denomPubHash,
|
||||
coin_ev: p.coinEv,
|
||||
}));
|
||||
return planchetDetail;
|
||||
}
|
||||
|
||||
/**
|
||||
* Accept a merchant's response to a tip pickup and start withdrawing the coins.
|
||||
* These coins will not appear in the wallet yet.
|
||||
*/
|
||||
async processTipResponse(merchantDomain: string, tipId: string, response: TipResponse): Promise<void> {
|
||||
let tipRecord = await this.q().get(Stores.tips, [tipId, merchantDomain]);
|
||||
if (!tipRecord) {
|
||||
throw Error("tip not found");
|
||||
}
|
||||
console.log("processing tip response", response);
|
||||
if (response.reserve_sigs.length !== tipRecord.planchets.length) {
|
||||
throw Error("number of tip responses does not match requested planchets");
|
||||
}
|
||||
|
||||
for (let i = 0; i < tipRecord.planchets.length; i++) {
|
||||
let planchet = tipRecord.planchets[i];
|
||||
let preCoin = {
|
||||
coinPub: planchet.coinPub,
|
||||
coinPriv: planchet.coinPriv,
|
||||
coinEv: planchet.coinEv,
|
||||
coinValue: planchet.coinValue,
|
||||
reservePub: response.reserve_pub,
|
||||
denomPub: planchet.denomPub,
|
||||
blindingKey: planchet.blindingKey,
|
||||
withdrawSig: response.reserve_sigs[i].reserve_sig,
|
||||
exchangeBaseUrl: tipRecord.exchangeUrl,
|
||||
isFromTip: true,
|
||||
};
|
||||
await this.q().put(Stores.precoins, preCoin);
|
||||
this.processPreCoin(preCoin);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start using the coins from a tip.
|
||||
*/
|
||||
async acceptTip(merchantDomain: string, tipId: string): Promise<void> {
|
||||
const tipRecord = await this.q().get(Stores.tips, [tipId, merchantDomain]);
|
||||
if (!tipRecord) {
|
||||
throw Error("tip not found");
|
||||
}
|
||||
tipRecord.accepted = true;
|
||||
|
||||
// Create one transactional query, within this transaction
|
||||
// both the tip will be marked as accepted and coins
|
||||
// already withdrawn will be untainted.
|
||||
const q = this.q();
|
||||
|
||||
q.put(Stores.tips, tipRecord);
|
||||
|
||||
const updateCoin = (c: CoinRecord) => {
|
||||
if (c.status === CoinStatus.TainedByTip) {
|
||||
c.status = CoinStatus.Fresh;
|
||||
}
|
||||
return c;
|
||||
};
|
||||
|
||||
for (const coinPub of tipRecord.coinPubs) {
|
||||
q.mutate(Stores.coins, coinPub, updateCoin);
|
||||
}
|
||||
|
||||
await q.finish();
|
||||
this.notifier.notify();
|
||||
}
|
||||
|
||||
async getTipStatus(merchantDomain: string, tipId: string): Promise<TipStatus> {
|
||||
const tipRecord = await this.q().get(Stores.tips, [tipId, merchantDomain]);
|
||||
if (!tipRecord) {
|
||||
throw Error("tip not found");
|
||||
}
|
||||
const rci = await this.getReserveCreationInfo(tipRecord.exchangeUrl, tipRecord.amount);
|
||||
const tipStatus: TipStatus = {
|
||||
rci,
|
||||
tip: tipRecord,
|
||||
};
|
||||
return tipStatus;
|
||||
}
|
||||
}
|
||||
|
@ -192,6 +192,22 @@ export interface MessageMap {
|
||||
request: { refundPermissions: types.RefundPermission[] };
|
||||
response: void;
|
||||
};
|
||||
"get-tip-planchets": {
|
||||
request: types.GetTipPlanchetsRequest;
|
||||
response: void;
|
||||
};
|
||||
"process-tip-response": {
|
||||
request: types.ProcessTipResponseRequest;
|
||||
response: void;
|
||||
};
|
||||
"accept-tip": {
|
||||
request: types.AcceptTipRequest;
|
||||
response: void;
|
||||
};
|
||||
"get-tip-status": {
|
||||
request: types.TipStatusRequest;
|
||||
response: void;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -28,7 +28,9 @@ import URI = require("urijs");
|
||||
|
||||
import wxApi = require("./wxApi");
|
||||
|
||||
import { QueryPaymentResult } from "../types";
|
||||
import { getTalerStampSec } from "../helpers";
|
||||
import { TipToken, QueryPaymentResult } from "../types";
|
||||
|
||||
|
||||
import axios from "axios";
|
||||
|
||||
@ -260,6 +262,87 @@ function talerPay(msg: any): Promise<any> {
|
||||
// Use a promise directly instead of of an async
|
||||
// function since some paths never resolve the promise.
|
||||
return new Promise(async(resolve, reject) => {
|
||||
if (msg.tip) {
|
||||
const tipToken = TipToken.checked(JSON.parse(msg.tip));
|
||||
|
||||
console.log("got tip token", tipToken);
|
||||
|
||||
const deadlineSec = getTalerStampSec(tipToken.expiration);
|
||||
if (!deadlineSec) {
|
||||
wxApi.logAndDisplayError({
|
||||
message: "invalid expiration",
|
||||
name: "tipping-failed",
|
||||
sameTab: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const merchantDomain = new URI(document.location.href).origin();
|
||||
let walletResp;
|
||||
try {
|
||||
walletResp = await wxApi.getTipPlanchets(merchantDomain, tipToken.tip_id, tipToken.amount, deadlineSec, tipToken.exchange_url);
|
||||
} catch (e) {
|
||||
wxApi.logAndDisplayError({
|
||||
message: e.message,
|
||||
name: "tipping-failed",
|
||||
response: e.response,
|
||||
sameTab: true,
|
||||
});
|
||||
throw e;
|
||||
}
|
||||
|
||||
let planchets = walletResp;
|
||||
|
||||
if (!planchets) {
|
||||
wxApi.logAndDisplayError({
|
||||
message: "processing tip failed",
|
||||
detail: walletResp,
|
||||
name: "tipping-failed",
|
||||
sameTab: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let merchantResp;
|
||||
|
||||
try {
|
||||
const config = {
|
||||
validateStatus: (s: number) => s === 200,
|
||||
};
|
||||
const req = { planchets, tip_id: tipToken.tip_id };
|
||||
merchantResp = await axios.post(tipToken.pickup_url, req, config);
|
||||
} catch (e) {
|
||||
wxApi.logAndDisplayError({
|
||||
message: e.message,
|
||||
name: "tipping-failed",
|
||||
response: e.response,
|
||||
sameTab: true,
|
||||
});
|
||||
throw e;
|
||||
}
|
||||
|
||||
try {
|
||||
wxApi.processTipResponse(merchantDomain, tipToken.tip_id, merchantResp.data);
|
||||
} catch (e) {
|
||||
wxApi.logAndDisplayError({
|
||||
message: e.message,
|
||||
name: "tipping-failed",
|
||||
response: e.response,
|
||||
sameTab: true,
|
||||
});
|
||||
throw e;
|
||||
}
|
||||
|
||||
// Go to tip dialog page, where the user can confirm the tip or
|
||||
// decline if they are not happy with the exchange.
|
||||
const uri = new URI(chrome.extension.getURL("/src/webex/pages/tip.html"));
|
||||
const params = { tip_id: tipToken.tip_id, merchant_domain: merchantDomain };
|
||||
const redirectUrl = uri.query(params).href();
|
||||
window.location.href = redirectUrl;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg.refund_url) {
|
||||
console.log("processing refund");
|
||||
let resp;
|
||||
|
@ -29,7 +29,6 @@ import {
|
||||
Amounts,
|
||||
CreateReserveResponse,
|
||||
CurrencyRecord,
|
||||
DenominationRecord,
|
||||
ReserveCreationInfo,
|
||||
} from "../../types";
|
||||
|
||||
@ -41,9 +40,8 @@ import {
|
||||
getReserveCreationInfo,
|
||||
} from "../wxApi";
|
||||
|
||||
import {Collapsible, renderAmount} from "../renderHtml";
|
||||
import { renderAmount, WithdrawDetailView } from "../renderHtml";
|
||||
|
||||
import * as moment from "moment";
|
||||
import * as React from "react";
|
||||
import * as ReactDOM from "react-dom";
|
||||
import URI = require("urijs");
|
||||
@ -80,126 +78,6 @@ class EventTrigger {
|
||||
}
|
||||
|
||||
|
||||
function renderAuditorDetails(rci: ReserveCreationInfo|null) {
|
||||
console.log("rci", rci);
|
||||
if (!rci) {
|
||||
return (
|
||||
<p>
|
||||
Details will be displayed when a valid exchange provider URL is entered.
|
||||
</p>
|
||||
);
|
||||
}
|
||||
if (rci.exchangeInfo.auditors.length === 0) {
|
||||
return (
|
||||
<p>
|
||||
The exchange is not audited by any auditors.
|
||||
</p>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
{rci.exchangeInfo.auditors.map((a) => (
|
||||
<div>
|
||||
<h3>Auditor {a.auditor_url}</h3>
|
||||
<p>Public key: {a.auditor_pub}</p>
|
||||
<p>Trusted: {rci.trustedAuditorPubs.indexOf(a.auditor_pub) >= 0 ? "yes" : "no"}</p>
|
||||
<p>Audits {a.denomination_keys.length} of {rci.numOfferedDenoms} denominations</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function renderReserveCreationDetails(rci: ReserveCreationInfo|null) {
|
||||
if (!rci) {
|
||||
return (
|
||||
<p>
|
||||
Details will be displayed when a valid exchange provider URL is entered.
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
const denoms = rci.selectedDenoms;
|
||||
|
||||
const countByPub: {[s: string]: number} = {};
|
||||
const uniq: DenominationRecord[] = [];
|
||||
|
||||
denoms.forEach((x: DenominationRecord) => {
|
||||
let c = countByPub[x.denomPub] || 0;
|
||||
if (c === 0) {
|
||||
uniq.push(x);
|
||||
}
|
||||
c += 1;
|
||||
countByPub[x.denomPub] = c;
|
||||
});
|
||||
|
||||
function row(denom: DenominationRecord) {
|
||||
return (
|
||||
<tr>
|
||||
<td>{countByPub[denom.denomPub] + "x"}</td>
|
||||
<td>{renderAmount(denom.value)}</td>
|
||||
<td>{renderAmount(denom.feeWithdraw)}</td>
|
||||
<td>{renderAmount(denom.feeRefresh)}</td>
|
||||
<td>{renderAmount(denom.feeDeposit)}</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
function wireFee(s: string) {
|
||||
return [
|
||||
<thead>
|
||||
<tr>
|
||||
<th colSpan={3}>Wire Method {s}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Applies Until</th>
|
||||
<th>Wire Fee</th>
|
||||
<th>Closing Fee</th>
|
||||
</tr>
|
||||
</thead>,
|
||||
<tbody>
|
||||
{rci!.wireFees.feesForType[s].map((f) => (
|
||||
<tr>
|
||||
<td>{moment.unix(f.endStamp).format("llll")}</td>
|
||||
<td>{renderAmount(f.wireFee)}</td>
|
||||
<td>{renderAmount(f.closingFee)}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>,
|
||||
];
|
||||
}
|
||||
|
||||
const withdrawFee = renderAmount(rci.withdrawFee);
|
||||
const overhead = renderAmount(rci.overhead);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h3>Overview</h3>
|
||||
<p>{i18n.str`Withdrawal fees:`} {withdrawFee}</p>
|
||||
<p>{i18n.str`Rounding loss:`} {overhead}</p>
|
||||
<p>{i18n.str`Earliest expiration (for deposit): ${moment.unix(rci.earliestDepositExpiration).fromNow()}`}</p>
|
||||
<h3>Coin Fees</h3>
|
||||
<table className="pure-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{i18n.str`# Coins`}</th>
|
||||
<th>{i18n.str`Value`}</th>
|
||||
<th>{i18n.str`Withdraw Fee`}</th>
|
||||
<th>{i18n.str`Refresh Fee`}</th>
|
||||
<th>{i18n.str`Deposit Fee`}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{uniq.map(row)}
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>Wire Fees</h3>
|
||||
<table className="pure-table">
|
||||
{Object.keys(rci.wireFees.feesForType).map(wireFee)}
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
interface ExchangeSelectionProps {
|
||||
@ -428,12 +306,7 @@ class ExchangeSelection extends ImplicitStateComponent<ExchangeSelectionProps> {
|
||||
</button>
|
||||
</p>
|
||||
{this.renderUpdateStatus()}
|
||||
<Collapsible initiallyCollapsed={true} title="Fee and Spending Details">
|
||||
{renderReserveCreationDetails(this.reserveCreationInfo())}
|
||||
</Collapsible>
|
||||
<Collapsible initiallyCollapsed={true} title="Auditor Details">
|
||||
{renderAuditorDetails(this.reserveCreationInfo())}
|
||||
</Collapsible>
|
||||
<WithdrawDetailView rci={this.reserveCreationInfo()} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
24
src/webex/pages/tip.html
Normal file
24
src/webex/pages/tip.html
Normal file
@ -0,0 +1,24 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Taler Wallet: Received Tip</title>
|
||||
|
||||
<link rel="icon" href="/img/icon.png">
|
||||
<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/tip-bundle.js"></script>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<section id="main">
|
||||
<h1>GNU Taler Wallet</h1>
|
||||
<div id="container"></div>
|
||||
</section>
|
||||
</body>
|
||||
|
||||
</html>
|
155
src/webex/pages/tip.tsx
Normal file
155
src/webex/pages/tip.tsx
Normal file
@ -0,0 +1,155 @@
|
||||
/*
|
||||
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/>
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Page shown to the user to confirm creation
|
||||
* of a reserve, usually requested by the bank.
|
||||
*
|
||||
* @author Florian Dold
|
||||
*/
|
||||
|
||||
import * as React from "react";
|
||||
import * as ReactDOM from "react-dom";
|
||||
import URI = require("urijs");
|
||||
|
||||
import * as i18n from "../../i18n";
|
||||
|
||||
import {
|
||||
acceptTip,
|
||||
getTipStatus,
|
||||
} from "../wxApi";
|
||||
|
||||
import { renderAmount, WithdrawDetailView } from "../renderHtml";
|
||||
|
||||
import { Amounts, TipStatus } from "../../types";
|
||||
|
||||
interface TipDisplayProps {
|
||||
merchantDomain: string;
|
||||
tipId: string;
|
||||
}
|
||||
|
||||
interface TipDisplayState {
|
||||
tipStatus?: TipStatus;
|
||||
working: boolean;
|
||||
}
|
||||
|
||||
class TipDisplay extends React.Component<TipDisplayProps, TipDisplayState> {
|
||||
constructor(props: TipDisplayProps) {
|
||||
super(props);
|
||||
this.state = { working: false };
|
||||
}
|
||||
|
||||
async update() {
|
||||
let tipStatus = await getTipStatus(this.props.merchantDomain, this.props.tipId);
|
||||
this.setState({ tipStatus });
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.update();
|
||||
const port = chrome.runtime.connect();
|
||||
port.onMessage.addListener((msg: any) => {
|
||||
if (msg.notify) {
|
||||
console.log("got notified");
|
||||
this.update();
|
||||
}
|
||||
});
|
||||
this.update();
|
||||
}
|
||||
|
||||
renderExchangeInfo(ts: TipStatus) {
|
||||
const rci = ts.rci;
|
||||
if (!rci) {
|
||||
return <p>Waiting for info about exchange ...</p>
|
||||
}
|
||||
const totalCost = Amounts.add(rci.overhead, rci.withdrawFee).amount;
|
||||
return (
|
||||
<div>
|
||||
<p>
|
||||
The tip is handled by the exchange <strong>{rci.exchangeInfo.baseUrl}</strong>.{" "}
|
||||
The exchange provider will charge
|
||||
{" "}
|
||||
<strong>{renderAmount(totalCost)}</strong>
|
||||
{" "}.
|
||||
</p>
|
||||
<WithdrawDetailView rci={rci} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
accept() {
|
||||
this.setState({ working: true});
|
||||
acceptTip(this.props.merchantDomain, this.props.tipId);
|
||||
}
|
||||
|
||||
renderButtons() {
|
||||
return (
|
||||
<form className="pure-form">
|
||||
<button
|
||||
className="pure-button pure-button-primary"
|
||||
type="button"
|
||||
onClick={() => this.accept()}>
|
||||
{ this.state.working ? <span><object className="svg-icon svg-baseline" data="/img/spinner-bars.svg" /> </span> : null }
|
||||
Accept tip
|
||||
</button>
|
||||
{" "}
|
||||
<button className="pure-button" type="button" onClick={() => { window.close(); }}>Discard tip</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
render(): JSX.Element {
|
||||
const ts = this.state.tipStatus;
|
||||
if (!ts) {
|
||||
return <p>Processing ...</p>;
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<h2>Tip Received!</h2>
|
||||
<p>You received a tip of <strong>{renderAmount(ts.tip.amount)}</strong> from <strong>{this.props.merchantDomain}</strong>.</p>
|
||||
{ts.tip.accepted
|
||||
? <p>You've accepted this tip!</p>
|
||||
: this.renderButtons()
|
||||
}
|
||||
{this.renderExchangeInfo(ts)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
const url = new URI(document.location.href);
|
||||
const query: any = URI.parseQuery(url.query());
|
||||
|
||||
let merchantDomain = query.merchant_domain;
|
||||
let tipId = query.tip_id;
|
||||
let props: TipDisplayProps = { tipId, merchantDomain };
|
||||
|
||||
ReactDOM.render(<TipDisplay {...props} />,
|
||||
document.getElementById("container")!);
|
||||
|
||||
} catch (e) {
|
||||
// TODO: provide more context information, maybe factor it out into a
|
||||
// TODO:generic error reporting function or component.
|
||||
document.body.innerText = i18n.str`Fatal error: "${e.message}".`;
|
||||
console.error(`got error "${e.message}"`, e);
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
main();
|
||||
});
|
@ -27,8 +27,14 @@
|
||||
import {
|
||||
AmountJson,
|
||||
Amounts,
|
||||
DenominationRecord,
|
||||
ReserveCreationInfo,
|
||||
} from "../types";
|
||||
|
||||
import * as moment from "moment";
|
||||
|
||||
import * as i18n from "../i18n";
|
||||
|
||||
import * as React from "react";
|
||||
|
||||
|
||||
@ -101,3 +107,142 @@ export class Collapsible extends React.Component<CollapsibleProps, CollapsibleSt
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function AuditorDetailsView(props: {rci: ReserveCreationInfo|null}): JSX.Element {
|
||||
const rci = props.rci;
|
||||
console.log("rci", rci);
|
||||
if (!rci) {
|
||||
return (
|
||||
<p>
|
||||
Details will be displayed when a valid exchange provider URL is entered.
|
||||
</p>
|
||||
);
|
||||
}
|
||||
if (rci.exchangeInfo.auditors.length === 0) {
|
||||
return (
|
||||
<p>
|
||||
The exchange is not audited by any auditors.
|
||||
</p>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
{rci.exchangeInfo.auditors.map((a) => (
|
||||
<div>
|
||||
<h3>Auditor {a.auditor_url}</h3>
|
||||
<p>Public key: {a.auditor_pub}</p>
|
||||
<p>Trusted: {rci.trustedAuditorPubs.indexOf(a.auditor_pub) >= 0 ? "yes" : "no"}</p>
|
||||
<p>Audits {a.denomination_keys.length} of {rci.numOfferedDenoms} denominations</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function FeeDetailsView(props: {rci: ReserveCreationInfo|null}): JSX.Element {
|
||||
const rci = props.rci;
|
||||
if (!rci) {
|
||||
return (
|
||||
<p>
|
||||
Details will be displayed when a valid exchange provider URL is entered.
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
const denoms = rci.selectedDenoms;
|
||||
|
||||
const countByPub: {[s: string]: number} = {};
|
||||
const uniq: DenominationRecord[] = [];
|
||||
|
||||
denoms.forEach((x: DenominationRecord) => {
|
||||
let c = countByPub[x.denomPub] || 0;
|
||||
if (c === 0) {
|
||||
uniq.push(x);
|
||||
}
|
||||
c += 1;
|
||||
countByPub[x.denomPub] = c;
|
||||
});
|
||||
|
||||
function row(denom: DenominationRecord) {
|
||||
return (
|
||||
<tr>
|
||||
<td>{countByPub[denom.denomPub] + "x"}</td>
|
||||
<td>{renderAmount(denom.value)}</td>
|
||||
<td>{renderAmount(denom.feeWithdraw)}</td>
|
||||
<td>{renderAmount(denom.feeRefresh)}</td>
|
||||
<td>{renderAmount(denom.feeDeposit)}</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
function wireFee(s: string) {
|
||||
return [
|
||||
<thead>
|
||||
<tr>
|
||||
<th colSpan={3}>Wire Method {s}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Applies Until</th>
|
||||
<th>Wire Fee</th>
|
||||
<th>Closing Fee</th>
|
||||
</tr>
|
||||
</thead>,
|
||||
<tbody>
|
||||
{rci!.wireFees.feesForType[s].map((f) => (
|
||||
<tr>
|
||||
<td>{moment.unix(f.endStamp).format("llll")}</td>
|
||||
<td>{renderAmount(f.wireFee)}</td>
|
||||
<td>{renderAmount(f.closingFee)}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>,
|
||||
];
|
||||
}
|
||||
|
||||
const withdrawFee = renderAmount(rci.withdrawFee);
|
||||
const overhead = renderAmount(rci.overhead);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h3>Overview</h3>
|
||||
<p>{i18n.str`Withdrawal fees:`} {withdrawFee}</p>
|
||||
<p>{i18n.str`Rounding loss:`} {overhead}</p>
|
||||
<p>{i18n.str`Earliest expiration (for deposit): ${moment.unix(rci.earliestDepositExpiration).fromNow()}`}</p>
|
||||
<h3>Coin Fees</h3>
|
||||
<table className="pure-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{i18n.str`# Coins`}</th>
|
||||
<th>{i18n.str`Value`}</th>
|
||||
<th>{i18n.str`Withdraw Fee`}</th>
|
||||
<th>{i18n.str`Refresh Fee`}</th>
|
||||
<th>{i18n.str`Deposit Fee`}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{uniq.map(row)}
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>Wire Fees</h3>
|
||||
<table className="pure-table">
|
||||
{Object.keys(rci.wireFees.feesForType).map(wireFee)}
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export function WithdrawDetailView(props: {rci: ReserveCreationInfo | null}): JSX.Element {
|
||||
const rci = props.rci;
|
||||
return (
|
||||
<div>
|
||||
<Collapsible initiallyCollapsed={true} title="Fee and Spending Details">
|
||||
<FeeDetailsView rci={rci} />
|
||||
</Collapsible>
|
||||
<Collapsible initiallyCollapsed={true} title="Auditor Details">
|
||||
<AuditorDetailsView rci={rci} />
|
||||
</Collapsible>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -251,3 +251,18 @@ a.opener {
|
||||
.opener-collapsed::before {
|
||||
content: "\25b6 "
|
||||
}
|
||||
|
||||
.svg-icon {
|
||||
display: inline-flex;
|
||||
align-self: center;
|
||||
position: relative;
|
||||
height: 1em;
|
||||
width: 1em;
|
||||
}
|
||||
.svg-icon svg {
|
||||
height:1em;
|
||||
width:1em;
|
||||
}
|
||||
object.svg-icon.svg-baseline {
|
||||
transform: translate(0, 0.125em);
|
||||
}
|
||||
|
@ -37,6 +37,9 @@ import {
|
||||
ReserveCreationInfo,
|
||||
ReserveRecord,
|
||||
SenderWireInfos,
|
||||
TipResponse,
|
||||
TipPlanchetDetail,
|
||||
TipStatus,
|
||||
WalletBalance,
|
||||
} from "../types";
|
||||
|
||||
@ -358,3 +361,23 @@ export function getPurchase(contractTermsHash: string): Promise<PurchaseRecord>
|
||||
export function getFullRefundFees(args: { refundPermissions: RefundPermission[] }): Promise<AmountJson> {
|
||||
return callBackend("get-full-refund-fees", { refundPermissions: args.refundPermissions });
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get or generate planchets to give the merchant that wants to tip us.
|
||||
*/
|
||||
export function getTipPlanchets(merchantDomain: string, tipId: string, amount: AmountJson, deadline: number, exchangeUrl: string): Promise<TipPlanchetDetail[]> {
|
||||
return callBackend("get-tip-planchets", { merchantDomain, tipId, amount, deadline, exchangeUrl });
|
||||
}
|
||||
|
||||
export function getTipStatus(merchantDomain: string, tipId: string): Promise<TipStatus> {
|
||||
return callBackend("get-tip-status", { merchantDomain, tipId });
|
||||
}
|
||||
|
||||
export function acceptTip(merchantDomain: string, tipId: string): Promise<TipStatus> {
|
||||
return callBackend("accept-tip", { merchantDomain, tipId });
|
||||
}
|
||||
|
||||
export function processTipResponse(merchantDomain: string, tipId: string, tipResponse: TipResponse): Promise<void> {
|
||||
return callBackend("process-tip-response", { merchantDomain, tipId, tipResponse });
|
||||
}
|
||||
|
@ -31,12 +31,16 @@ import {
|
||||
Store,
|
||||
} from "../query";
|
||||
import {
|
||||
AcceptTipRequest,
|
||||
AmountJson,
|
||||
ConfirmReserveRequest,
|
||||
CreateReserveRequest,
|
||||
GetTipPlanchetsRequest,
|
||||
Notifier,
|
||||
ProcessTipResponseRequest,
|
||||
ProposalRecord,
|
||||
ReturnCoinsRequest,
|
||||
TipStatusRequest,
|
||||
} from "../types";
|
||||
import {
|
||||
Stores,
|
||||
@ -44,6 +48,7 @@ import {
|
||||
Wallet,
|
||||
} from "../wallet";
|
||||
|
||||
|
||||
import { ChromeBadge } from "./chromeBadge";
|
||||
import { MessageType } from "./messages";
|
||||
import * as wxApi from "./wxApi";
|
||||
@ -316,6 +321,22 @@ function handleMessage(sender: MessageSender,
|
||||
}
|
||||
case "get-full-refund-fees":
|
||||
return needsWallet().getFullRefundFees(detail.refundPermissions);
|
||||
case "get-tip-status": {
|
||||
const req = TipStatusRequest.checked(detail);
|
||||
return needsWallet().getTipStatus(req.merchantDomain, req.tipId);
|
||||
}
|
||||
case "accept-tip": {
|
||||
const req = AcceptTipRequest.checked(detail);
|
||||
return needsWallet().acceptTip(req.merchantDomain, req.tipId);
|
||||
}
|
||||
case "process-tip-response": {
|
||||
const req = ProcessTipResponseRequest.checked(detail);
|
||||
return needsWallet().processTipResponse(req.merchantDomain, req.tipId, req.tipResponse);
|
||||
}
|
||||
case "get-tip-planchets": {
|
||||
const req = GetTipPlanchetsRequest.checked(detail);
|
||||
return needsWallet().getTipPlanchets(req.merchantDomain, req.tipId, req.amount, req.deadline, req.exchangeUrl);
|
||||
}
|
||||
default:
|
||||
// Exhaustiveness check.
|
||||
// See https://www.typescriptlang.org/docs/handbook/advanced-types.html
|
||||
@ -409,6 +430,7 @@ function handleHttpPayment(headerList: chrome.webRequest.HttpHeader[], url: stri
|
||||
contract_url: headers["x-taler-contract-url"],
|
||||
offer_url: headers["x-taler-offer-url"],
|
||||
refund_url: headers["x-taler-refund-url"],
|
||||
tip: headers["x-taler-tip"],
|
||||
};
|
||||
|
||||
const talerHeaderFound = Object.keys(fields).filter((x: any) => (fields as any)[x]).length !== 0;
|
||||
@ -424,6 +446,7 @@ function handleHttpPayment(headerList: chrome.webRequest.HttpHeader[], url: stri
|
||||
contract_url: fields.contract_url,
|
||||
offer_url: fields.offer_url,
|
||||
refund_url: fields.refund_url,
|
||||
tip: fields.tip,
|
||||
};
|
||||
|
||||
console.log("got pay detail", payDetail);
|
||||
@ -728,7 +751,7 @@ function openTalerDb(): Promise<IDBDatabase> {
|
||||
for (const indexName in (si as any)) {
|
||||
if ((si as any)[indexName] instanceof Index) {
|
||||
const ii: Index<any, any> = (si as any)[indexName];
|
||||
s.createIndex(ii.indexName, ii.keyPath);
|
||||
s.createIndex(ii.indexName, ii.keyPath, ii.options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -65,6 +65,7 @@
|
||||
"src/webex/pages/reset-required.tsx",
|
||||
"src/webex/pages/return-coins.tsx",
|
||||
"src/webex/pages/show-db.ts",
|
||||
"src/webex/pages/tip.tsx",
|
||||
"src/webex/pages/tree.tsx",
|
||||
"src/webex/renderHtml.tsx",
|
||||
"src/webex/wxApi.ts",
|
||||
|
@ -78,6 +78,7 @@ module.exports = function (env) {
|
||||
"return-coins": "./src/webex/pages/return-coins.tsx",
|
||||
"refund": "./src/webex/pages/refund.tsx",
|
||||
"show-db": "./src/webex/pages/show-db.ts",
|
||||
"tip": "./src/webex/pages/tip.tsx",
|
||||
"tree": "./src/webex/pages/tree.tsx",
|
||||
},
|
||||
plugins: [
|
||||
|
Loading…
Reference in New Issue
Block a user