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,
|
PreCoinRecord,
|
||||||
RefreshSessionRecord,
|
RefreshSessionRecord,
|
||||||
ReserveRecord,
|
ReserveRecord,
|
||||||
|
TipPlanchet,
|
||||||
WireFee,
|
WireFee,
|
||||||
} from "../types";
|
} from "../types";
|
||||||
|
|
||||||
@ -253,6 +254,10 @@ export class CryptoApi {
|
|||||||
return this.doRpc<PreCoinRecord>("createPreCoin", 1, denom, reserve);
|
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> {
|
hashString(str: string): Promise<string> {
|
||||||
return this.doRpc<string>("hashString", 1, str);
|
return this.doRpc<string>("hashString", 1, str);
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,7 @@ import {
|
|||||||
RefreshPreCoinRecord,
|
RefreshPreCoinRecord,
|
||||||
RefreshSessionRecord,
|
RefreshSessionRecord,
|
||||||
ReserveRecord,
|
ReserveRecord,
|
||||||
|
TipPlanchet,
|
||||||
WireFee,
|
WireFee,
|
||||||
} from "../types";
|
} from "../types";
|
||||||
|
|
||||||
@ -103,6 +104,7 @@ namespace RpcFunctions {
|
|||||||
coinValue: denom.value,
|
coinValue: denom.value,
|
||||||
denomPub: denomPub.encode().toCrock(),
|
denomPub: denomPub.encode().toCrock(),
|
||||||
exchangeBaseUrl: reserve.exchange_base_url,
|
exchangeBaseUrl: reserve.exchange_base_url,
|
||||||
|
isFromTip: false,
|
||||||
reservePub: reservePub.toCrock(),
|
reservePub: reservePub.toCrock(),
|
||||||
withdrawSig: sig.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.
|
* 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"
|
msgid "Confirm payment"
|
||||||
msgstr "Bezahlung bestätigen"
|
msgstr "Bezahlung bestätigen"
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:178
|
#: src/webex/pages/confirm-create-reserve.tsx:121
|
||||||
#, 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
|
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "Select"
|
msgid "Select"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:259
|
#: src/webex/pages/confirm-create-reserve.tsx:137
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "Error: URL may not be relative"
|
msgid "Error: URL may not be relative"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:327
|
#: src/webex/pages/confirm-create-reserve.tsx:205
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "The exchange is trusted by the wallet.\n"
|
msgid "The exchange is trusted by the wallet.\n"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:333
|
#: src/webex/pages/confirm-create-reserve.tsx:211
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "The exchange is audited by a trusted auditor.\n"
|
msgid "The exchange is audited by a trusted auditor.\n"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:339
|
#: src/webex/pages/confirm-create-reserve.tsx:217
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Warning: The exchange is neither directly trusted nor audited by a trusted "
|
"Warning: The exchange is neither directly trusted nor audited by a trusted "
|
||||||
@ -134,7 +94,7 @@ msgid ""
|
|||||||
"If you withdraw from this exchange, it will be trusted in the future.\n"
|
"If you withdraw from this exchange, it will be trusted in the future.\n"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:348
|
#: src/webex/pages/confirm-create-reserve.tsx:226
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Using exchange provider%1$s.\n"
|
"Using exchange provider%1$s.\n"
|
||||||
@ -142,58 +102,59 @@ msgid ""
|
|||||||
" %2$s in fees.\n"
|
" %2$s in fees.\n"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:362
|
#: src/webex/pages/confirm-create-reserve.tsx:240
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Waiting for a response from\n"
|
"Waiting for a response from\n"
|
||||||
" %1$s"
|
" %1$s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:379
|
#: src/webex/pages/confirm-create-reserve.tsx:257
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Information about fees will be available when an exchange provider is "
|
"Information about fees will be available when an exchange provider is "
|
||||||
"selected."
|
"selected."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:422
|
#: src/webex/pages/confirm-create-reserve.tsx:300
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "Accept fees and withdraw"
|
msgid "Accept fees and withdraw"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:427
|
#: src/webex/pages/confirm-create-reserve.tsx:305
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "Change Exchange Provider"
|
msgid "Change Exchange Provider"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:484
|
#: src/webex/pages/confirm-create-reserve.tsx:357
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "You are about to withdraw %1$s from your bank account into your wallet."
|
msgid "You are about to withdraw %1$s from your bank account into your wallet."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:569
|
#: src/webex/pages/confirm-create-reserve.tsx:442
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Oops, something went wrong. The wallet responded with error status (%1$s)."
|
"Oops, something went wrong. The wallet responded with error status (%1$s)."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:578
|
#: src/webex/pages/confirm-create-reserve.tsx:451
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "Checking URL, please wait ..."
|
msgid "Checking URL, please wait ..."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:592
|
#: src/webex/pages/confirm-create-reserve.tsx:465
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "Can't parse amount: %1$s"
|
msgid "Can't parse amount: %1$s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:599
|
#: src/webex/pages/confirm-create-reserve.tsx:472
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "Can't parse wire_types: %1$s"
|
msgid "Can't parse wire_types: %1$s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#. #-#-#-#-# - (PACKAGE VERSION) #-#-#-#-#
|
||||||
#. TODO:generic error reporting function or component.
|
#. 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
|
#, c-format
|
||||||
msgid "Fatal error: \"%1$s\"."
|
msgid "Fatal error: \"%1$s\"."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -324,6 +285,46 @@ msgstr "Bezahlung bestätigen"
|
|||||||
msgid "Cancel"
|
msgid "Cancel"
|
||||||
msgstr "Saldo"
|
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
|
#: src/wire.ts:38
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "Invalid Wire"
|
msgid "Invalid Wire"
|
||||||
|
@ -66,67 +66,27 @@ msgstr ""
|
|||||||
msgid "Confirm payment"
|
msgid "Confirm payment"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:178
|
#: src/webex/pages/confirm-create-reserve.tsx:121
|
||||||
#, 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
|
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "Select"
|
msgid "Select"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:259
|
#: src/webex/pages/confirm-create-reserve.tsx:137
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "Error: URL may not be relative"
|
msgid "Error: URL may not be relative"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:327
|
#: src/webex/pages/confirm-create-reserve.tsx:205
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "The exchange is trusted by the wallet.\n"
|
msgid "The exchange is trusted by the wallet.\n"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:333
|
#: src/webex/pages/confirm-create-reserve.tsx:211
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "The exchange is audited by a trusted auditor.\n"
|
msgid "The exchange is audited by a trusted auditor.\n"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:339
|
#: src/webex/pages/confirm-create-reserve.tsx:217
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Warning: The exchange is neither directly trusted nor audited by a trusted "
|
"Warning: The exchange is neither directly trusted nor audited by a trusted "
|
||||||
@ -134,7 +94,7 @@ msgid ""
|
|||||||
"If you withdraw from this exchange, it will be trusted in the future.\n"
|
"If you withdraw from this exchange, it will be trusted in the future.\n"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:348
|
#: src/webex/pages/confirm-create-reserve.tsx:226
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Using exchange provider%1$s.\n"
|
"Using exchange provider%1$s.\n"
|
||||||
@ -142,58 +102,59 @@ msgid ""
|
|||||||
" %2$s in fees.\n"
|
" %2$s in fees.\n"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:362
|
#: src/webex/pages/confirm-create-reserve.tsx:240
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Waiting for a response from\n"
|
"Waiting for a response from\n"
|
||||||
" %1$s"
|
" %1$s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:379
|
#: src/webex/pages/confirm-create-reserve.tsx:257
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Information about fees will be available when an exchange provider is "
|
"Information about fees will be available when an exchange provider is "
|
||||||
"selected."
|
"selected."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:422
|
#: src/webex/pages/confirm-create-reserve.tsx:300
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "Accept fees and withdraw"
|
msgid "Accept fees and withdraw"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:427
|
#: src/webex/pages/confirm-create-reserve.tsx:305
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "Change Exchange Provider"
|
msgid "Change Exchange Provider"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:484
|
#: src/webex/pages/confirm-create-reserve.tsx:357
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "You are about to withdraw %1$s from your bank account into your wallet."
|
msgid "You are about to withdraw %1$s from your bank account into your wallet."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:569
|
#: src/webex/pages/confirm-create-reserve.tsx:442
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Oops, something went wrong. The wallet responded with error status (%1$s)."
|
"Oops, something went wrong. The wallet responded with error status (%1$s)."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:578
|
#: src/webex/pages/confirm-create-reserve.tsx:451
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "Checking URL, please wait ..."
|
msgid "Checking URL, please wait ..."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:592
|
#: src/webex/pages/confirm-create-reserve.tsx:465
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "Can't parse amount: %1$s"
|
msgid "Can't parse amount: %1$s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:599
|
#: src/webex/pages/confirm-create-reserve.tsx:472
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "Can't parse wire_types: %1$s"
|
msgid "Can't parse wire_types: %1$s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#. #-#-#-#-# - (PACKAGE VERSION) #-#-#-#-#
|
||||||
#. TODO:generic error reporting function or component.
|
#. 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
|
#, c-format
|
||||||
msgid "Fatal error: \"%1$s\"."
|
msgid "Fatal error: \"%1$s\"."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -321,6 +282,46 @@ msgstr ""
|
|||||||
msgid "Cancel"
|
msgid "Cancel"
|
||||||
msgstr ""
|
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
|
#: src/wire.ts:38
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "Invalid Wire"
|
msgid "Invalid Wire"
|
||||||
|
113
src/i18n/fr.po
113
src/i18n/fr.po
@ -66,67 +66,27 @@ msgstr ""
|
|||||||
msgid "Confirm payment"
|
msgid "Confirm payment"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:178
|
#: src/webex/pages/confirm-create-reserve.tsx:121
|
||||||
#, 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
|
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "Select"
|
msgid "Select"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:259
|
#: src/webex/pages/confirm-create-reserve.tsx:137
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "Error: URL may not be relative"
|
msgid "Error: URL may not be relative"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:327
|
#: src/webex/pages/confirm-create-reserve.tsx:205
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "The exchange is trusted by the wallet.\n"
|
msgid "The exchange is trusted by the wallet.\n"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:333
|
#: src/webex/pages/confirm-create-reserve.tsx:211
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "The exchange is audited by a trusted auditor.\n"
|
msgid "The exchange is audited by a trusted auditor.\n"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:339
|
#: src/webex/pages/confirm-create-reserve.tsx:217
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Warning: The exchange is neither directly trusted nor audited by a trusted "
|
"Warning: The exchange is neither directly trusted nor audited by a trusted "
|
||||||
@ -134,7 +94,7 @@ msgid ""
|
|||||||
"If you withdraw from this exchange, it will be trusted in the future.\n"
|
"If you withdraw from this exchange, it will be trusted in the future.\n"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:348
|
#: src/webex/pages/confirm-create-reserve.tsx:226
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Using exchange provider%1$s.\n"
|
"Using exchange provider%1$s.\n"
|
||||||
@ -142,58 +102,59 @@ msgid ""
|
|||||||
" %2$s in fees.\n"
|
" %2$s in fees.\n"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:362
|
#: src/webex/pages/confirm-create-reserve.tsx:240
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Waiting for a response from\n"
|
"Waiting for a response from\n"
|
||||||
" %1$s"
|
" %1$s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:379
|
#: src/webex/pages/confirm-create-reserve.tsx:257
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Information about fees will be available when an exchange provider is "
|
"Information about fees will be available when an exchange provider is "
|
||||||
"selected."
|
"selected."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:422
|
#: src/webex/pages/confirm-create-reserve.tsx:300
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "Accept fees and withdraw"
|
msgid "Accept fees and withdraw"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:427
|
#: src/webex/pages/confirm-create-reserve.tsx:305
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "Change Exchange Provider"
|
msgid "Change Exchange Provider"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:484
|
#: src/webex/pages/confirm-create-reserve.tsx:357
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "You are about to withdraw %1$s from your bank account into your wallet."
|
msgid "You are about to withdraw %1$s from your bank account into your wallet."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:569
|
#: src/webex/pages/confirm-create-reserve.tsx:442
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Oops, something went wrong. The wallet responded with error status (%1$s)."
|
"Oops, something went wrong. The wallet responded with error status (%1$s)."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:578
|
#: src/webex/pages/confirm-create-reserve.tsx:451
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "Checking URL, please wait ..."
|
msgid "Checking URL, please wait ..."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:592
|
#: src/webex/pages/confirm-create-reserve.tsx:465
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "Can't parse amount: %1$s"
|
msgid "Can't parse amount: %1$s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:599
|
#: src/webex/pages/confirm-create-reserve.tsx:472
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "Can't parse wire_types: %1$s"
|
msgid "Can't parse wire_types: %1$s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#. #-#-#-#-# - (PACKAGE VERSION) #-#-#-#-#
|
||||||
#. TODO:generic error reporting function or component.
|
#. 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
|
#, c-format
|
||||||
msgid "Fatal error: \"%1$s\"."
|
msgid "Fatal error: \"%1$s\"."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -321,6 +282,46 @@ msgstr ""
|
|||||||
msgid "Cancel"
|
msgid "Cancel"
|
||||||
msgstr ""
|
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
|
#: src/wire.ts:38
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "Invalid Wire"
|
msgid "Invalid Wire"
|
||||||
|
113
src/i18n/it.po
113
src/i18n/it.po
@ -66,67 +66,27 @@ msgstr ""
|
|||||||
msgid "Confirm payment"
|
msgid "Confirm payment"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:178
|
#: src/webex/pages/confirm-create-reserve.tsx:121
|
||||||
#, 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
|
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "Select"
|
msgid "Select"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:259
|
#: src/webex/pages/confirm-create-reserve.tsx:137
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "Error: URL may not be relative"
|
msgid "Error: URL may not be relative"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:327
|
#: src/webex/pages/confirm-create-reserve.tsx:205
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "The exchange is trusted by the wallet.\n"
|
msgid "The exchange is trusted by the wallet.\n"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:333
|
#: src/webex/pages/confirm-create-reserve.tsx:211
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "The exchange is audited by a trusted auditor.\n"
|
msgid "The exchange is audited by a trusted auditor.\n"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:339
|
#: src/webex/pages/confirm-create-reserve.tsx:217
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Warning: The exchange is neither directly trusted nor audited by a trusted "
|
"Warning: The exchange is neither directly trusted nor audited by a trusted "
|
||||||
@ -134,7 +94,7 @@ msgid ""
|
|||||||
"If you withdraw from this exchange, it will be trusted in the future.\n"
|
"If you withdraw from this exchange, it will be trusted in the future.\n"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:348
|
#: src/webex/pages/confirm-create-reserve.tsx:226
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Using exchange provider%1$s.\n"
|
"Using exchange provider%1$s.\n"
|
||||||
@ -142,58 +102,59 @@ msgid ""
|
|||||||
" %2$s in fees.\n"
|
" %2$s in fees.\n"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:362
|
#: src/webex/pages/confirm-create-reserve.tsx:240
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Waiting for a response from\n"
|
"Waiting for a response from\n"
|
||||||
" %1$s"
|
" %1$s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:379
|
#: src/webex/pages/confirm-create-reserve.tsx:257
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Information about fees will be available when an exchange provider is "
|
"Information about fees will be available when an exchange provider is "
|
||||||
"selected."
|
"selected."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:422
|
#: src/webex/pages/confirm-create-reserve.tsx:300
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "Accept fees and withdraw"
|
msgid "Accept fees and withdraw"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:427
|
#: src/webex/pages/confirm-create-reserve.tsx:305
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "Change Exchange Provider"
|
msgid "Change Exchange Provider"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:484
|
#: src/webex/pages/confirm-create-reserve.tsx:357
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "You are about to withdraw %1$s from your bank account into your wallet."
|
msgid "You are about to withdraw %1$s from your bank account into your wallet."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:569
|
#: src/webex/pages/confirm-create-reserve.tsx:442
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Oops, something went wrong. The wallet responded with error status (%1$s)."
|
"Oops, something went wrong. The wallet responded with error status (%1$s)."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:578
|
#: src/webex/pages/confirm-create-reserve.tsx:451
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "Checking URL, please wait ..."
|
msgid "Checking URL, please wait ..."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:592
|
#: src/webex/pages/confirm-create-reserve.tsx:465
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "Can't parse amount: %1$s"
|
msgid "Can't parse amount: %1$s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:599
|
#: src/webex/pages/confirm-create-reserve.tsx:472
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "Can't parse wire_types: %1$s"
|
msgid "Can't parse wire_types: %1$s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#. #-#-#-#-# - (PACKAGE VERSION) #-#-#-#-#
|
||||||
#. TODO:generic error reporting function or component.
|
#. 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
|
#, c-format
|
||||||
msgid "Fatal error: \"%1$s\"."
|
msgid "Fatal error: \"%1$s\"."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -321,6 +282,46 @@ msgstr ""
|
|||||||
msgid "Cancel"
|
msgid "Cancel"
|
||||||
msgstr ""
|
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
|
#: src/wire.ts:38
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "Invalid Wire"
|
msgid "Invalid Wire"
|
||||||
|
@ -45,30 +45,6 @@ strings['de'] = {
|
|||||||
"Confirm payment": [
|
"Confirm payment": [
|
||||||
"Bezahlung bestätigen"
|
"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": [
|
"Select": [
|
||||||
""
|
""
|
||||||
],
|
],
|
||||||
@ -186,6 +162,30 @@ strings['de'] = {
|
|||||||
"Cancel": [
|
"Cancel": [
|
||||||
"Saldo"
|
"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": [
|
"Invalid Wire": [
|
||||||
""
|
""
|
||||||
],
|
],
|
||||||
@ -231,30 +231,6 @@ strings['en-US'] = {
|
|||||||
"Confirm payment": [
|
"Confirm payment": [
|
||||||
""
|
""
|
||||||
],
|
],
|
||||||
"Withdrawal fees:": [
|
|
||||||
""
|
|
||||||
],
|
|
||||||
"Rounding loss:": [
|
|
||||||
""
|
|
||||||
],
|
|
||||||
"Earliest expiration (for deposit): %1$s": [
|
|
||||||
""
|
|
||||||
],
|
|
||||||
"# Coins": [
|
|
||||||
""
|
|
||||||
],
|
|
||||||
"Value": [
|
|
||||||
""
|
|
||||||
],
|
|
||||||
"Withdraw Fee": [
|
|
||||||
""
|
|
||||||
],
|
|
||||||
"Refresh Fee": [
|
|
||||||
""
|
|
||||||
],
|
|
||||||
"Deposit Fee": [
|
|
||||||
""
|
|
||||||
],
|
|
||||||
"Select": [
|
"Select": [
|
||||||
""
|
""
|
||||||
],
|
],
|
||||||
@ -372,6 +348,30 @@ strings['en-US'] = {
|
|||||||
"Cancel": [
|
"Cancel": [
|
||||||
""
|
""
|
||||||
],
|
],
|
||||||
|
"Withdrawal fees:": [
|
||||||
|
""
|
||||||
|
],
|
||||||
|
"Rounding loss:": [
|
||||||
|
""
|
||||||
|
],
|
||||||
|
"Earliest expiration (for deposit): %1$s": [
|
||||||
|
""
|
||||||
|
],
|
||||||
|
"# Coins": [
|
||||||
|
""
|
||||||
|
],
|
||||||
|
"Value": [
|
||||||
|
""
|
||||||
|
],
|
||||||
|
"Withdraw Fee": [
|
||||||
|
""
|
||||||
|
],
|
||||||
|
"Refresh Fee": [
|
||||||
|
""
|
||||||
|
],
|
||||||
|
"Deposit Fee": [
|
||||||
|
""
|
||||||
|
],
|
||||||
"Invalid Wire": [
|
"Invalid Wire": [
|
||||||
""
|
""
|
||||||
],
|
],
|
||||||
@ -417,30 +417,6 @@ strings['fr'] = {
|
|||||||
"Confirm payment": [
|
"Confirm payment": [
|
||||||
""
|
""
|
||||||
],
|
],
|
||||||
"Withdrawal fees:": [
|
|
||||||
""
|
|
||||||
],
|
|
||||||
"Rounding loss:": [
|
|
||||||
""
|
|
||||||
],
|
|
||||||
"Earliest expiration (for deposit): %1$s": [
|
|
||||||
""
|
|
||||||
],
|
|
||||||
"# Coins": [
|
|
||||||
""
|
|
||||||
],
|
|
||||||
"Value": [
|
|
||||||
""
|
|
||||||
],
|
|
||||||
"Withdraw Fee": [
|
|
||||||
""
|
|
||||||
],
|
|
||||||
"Refresh Fee": [
|
|
||||||
""
|
|
||||||
],
|
|
||||||
"Deposit Fee": [
|
|
||||||
""
|
|
||||||
],
|
|
||||||
"Select": [
|
"Select": [
|
||||||
""
|
""
|
||||||
],
|
],
|
||||||
@ -558,6 +534,30 @@ strings['fr'] = {
|
|||||||
"Cancel": [
|
"Cancel": [
|
||||||
""
|
""
|
||||||
],
|
],
|
||||||
|
"Withdrawal fees:": [
|
||||||
|
""
|
||||||
|
],
|
||||||
|
"Rounding loss:": [
|
||||||
|
""
|
||||||
|
],
|
||||||
|
"Earliest expiration (for deposit): %1$s": [
|
||||||
|
""
|
||||||
|
],
|
||||||
|
"# Coins": [
|
||||||
|
""
|
||||||
|
],
|
||||||
|
"Value": [
|
||||||
|
""
|
||||||
|
],
|
||||||
|
"Withdraw Fee": [
|
||||||
|
""
|
||||||
|
],
|
||||||
|
"Refresh Fee": [
|
||||||
|
""
|
||||||
|
],
|
||||||
|
"Deposit Fee": [
|
||||||
|
""
|
||||||
|
],
|
||||||
"Invalid Wire": [
|
"Invalid Wire": [
|
||||||
""
|
""
|
||||||
],
|
],
|
||||||
@ -603,30 +603,6 @@ strings['it'] = {
|
|||||||
"Confirm payment": [
|
"Confirm payment": [
|
||||||
""
|
""
|
||||||
],
|
],
|
||||||
"Withdrawal fees:": [
|
|
||||||
""
|
|
||||||
],
|
|
||||||
"Rounding loss:": [
|
|
||||||
""
|
|
||||||
],
|
|
||||||
"Earliest expiration (for deposit): %1$s": [
|
|
||||||
""
|
|
||||||
],
|
|
||||||
"# Coins": [
|
|
||||||
""
|
|
||||||
],
|
|
||||||
"Value": [
|
|
||||||
""
|
|
||||||
],
|
|
||||||
"Withdraw Fee": [
|
|
||||||
""
|
|
||||||
],
|
|
||||||
"Refresh Fee": [
|
|
||||||
""
|
|
||||||
],
|
|
||||||
"Deposit Fee": [
|
|
||||||
""
|
|
||||||
],
|
|
||||||
"Select": [
|
"Select": [
|
||||||
""
|
""
|
||||||
],
|
],
|
||||||
@ -744,6 +720,30 @@ strings['it'] = {
|
|||||||
"Cancel": [
|
"Cancel": [
|
||||||
""
|
""
|
||||||
],
|
],
|
||||||
|
"Withdrawal fees:": [
|
||||||
|
""
|
||||||
|
],
|
||||||
|
"Rounding loss:": [
|
||||||
|
""
|
||||||
|
],
|
||||||
|
"Earliest expiration (for deposit): %1$s": [
|
||||||
|
""
|
||||||
|
],
|
||||||
|
"# Coins": [
|
||||||
|
""
|
||||||
|
],
|
||||||
|
"Value": [
|
||||||
|
""
|
||||||
|
],
|
||||||
|
"Withdraw Fee": [
|
||||||
|
""
|
||||||
|
],
|
||||||
|
"Refresh Fee": [
|
||||||
|
""
|
||||||
|
],
|
||||||
|
"Deposit Fee": [
|
||||||
|
""
|
||||||
|
],
|
||||||
"Invalid Wire": [
|
"Invalid Wire": [
|
||||||
""
|
""
|
||||||
],
|
],
|
||||||
|
@ -66,67 +66,27 @@ msgstr ""
|
|||||||
msgid "Confirm payment"
|
msgid "Confirm payment"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:178
|
#: src/webex/pages/confirm-create-reserve.tsx:121
|
||||||
#, 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
|
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "Select"
|
msgid "Select"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:259
|
#: src/webex/pages/confirm-create-reserve.tsx:137
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "Error: URL may not be relative"
|
msgid "Error: URL may not be relative"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:327
|
#: src/webex/pages/confirm-create-reserve.tsx:205
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "The exchange is trusted by the wallet.\n"
|
msgid "The exchange is trusted by the wallet.\n"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:333
|
#: src/webex/pages/confirm-create-reserve.tsx:211
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "The exchange is audited by a trusted auditor.\n"
|
msgid "The exchange is audited by a trusted auditor.\n"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:339
|
#: src/webex/pages/confirm-create-reserve.tsx:217
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Warning: The exchange is neither directly trusted nor audited by a trusted "
|
"Warning: The exchange is neither directly trusted nor audited by a trusted "
|
||||||
@ -134,7 +94,7 @@ msgid ""
|
|||||||
"If you withdraw from this exchange, it will be trusted in the future.\n"
|
"If you withdraw from this exchange, it will be trusted in the future.\n"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:348
|
#: src/webex/pages/confirm-create-reserve.tsx:226
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Using exchange provider%1$s.\n"
|
"Using exchange provider%1$s.\n"
|
||||||
@ -142,58 +102,59 @@ msgid ""
|
|||||||
" %2$s in fees.\n"
|
" %2$s in fees.\n"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:362
|
#: src/webex/pages/confirm-create-reserve.tsx:240
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Waiting for a response from\n"
|
"Waiting for a response from\n"
|
||||||
" %1$s"
|
" %1$s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:379
|
#: src/webex/pages/confirm-create-reserve.tsx:257
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Information about fees will be available when an exchange provider is "
|
"Information about fees will be available when an exchange provider is "
|
||||||
"selected."
|
"selected."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:422
|
#: src/webex/pages/confirm-create-reserve.tsx:300
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "Accept fees and withdraw"
|
msgid "Accept fees and withdraw"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:427
|
#: src/webex/pages/confirm-create-reserve.tsx:305
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "Change Exchange Provider"
|
msgid "Change Exchange Provider"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:484
|
#: src/webex/pages/confirm-create-reserve.tsx:357
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "You are about to withdraw %1$s from your bank account into your wallet."
|
msgid "You are about to withdraw %1$s from your bank account into your wallet."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:569
|
#: src/webex/pages/confirm-create-reserve.tsx:442
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Oops, something went wrong. The wallet responded with error status (%1$s)."
|
"Oops, something went wrong. The wallet responded with error status (%1$s)."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:578
|
#: src/webex/pages/confirm-create-reserve.tsx:451
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "Checking URL, please wait ..."
|
msgid "Checking URL, please wait ..."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:592
|
#: src/webex/pages/confirm-create-reserve.tsx:465
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "Can't parse amount: %1$s"
|
msgid "Can't parse amount: %1$s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/webex/pages/confirm-create-reserve.tsx:599
|
#: src/webex/pages/confirm-create-reserve.tsx:472
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "Can't parse wire_types: %1$s"
|
msgid "Can't parse wire_types: %1$s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#. #-#-#-#-# - (PACKAGE VERSION) #-#-#-#-#
|
||||||
#. TODO:generic error reporting function or component.
|
#. 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
|
#, c-format
|
||||||
msgid "Fatal error: \"%1$s\"."
|
msgid "Fatal error: \"%1$s\"."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -321,6 +282,46 @@ msgstr ""
|
|||||||
msgid "Cancel"
|
msgid "Cancel"
|
||||||
msgstr ""
|
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
|
#: src/wire.ts:38
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "Invalid Wire"
|
msgid "Invalid Wire"
|
||||||
|
70
src/query.ts
70
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.
|
* Definition of an index.
|
||||||
*/
|
*/
|
||||||
@ -59,7 +73,16 @@ export class Index<S extends IDBValidKey, T> {
|
|||||||
*/
|
*/
|
||||||
storeName: string;
|
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;
|
this.storeName = s.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -671,26 +694,33 @@ 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 {
|
mutate<T>(store: Store<T>, key: any, f: (v: T|undefined) => T|undefined): QueryRoot {
|
||||||
this.checkFinished();
|
this.checkFinished();
|
||||||
const doPut = (tx: IDBTransaction) => {
|
const doPut = (tx: IDBTransaction) => {
|
||||||
const reqGet = tx.objectStore(store.name).get(key);
|
const req = tx.objectStore(store.name).openCursor(IDBKeyRange.only(key));
|
||||||
reqGet.onsuccess = () => {
|
req.onsuccess = () => {
|
||||||
const r = reqGet.result;
|
const cursor = req.result;
|
||||||
let m: T|undefined;
|
if (cursor) {
|
||||||
try {
|
const value = cursor.value;
|
||||||
m = f(r);
|
let modifiedValue: T|undefined;
|
||||||
} catch (e) {
|
try {
|
||||||
if (e === AbortTransaction) {
|
modifiedValue = f(value);
|
||||||
tx.abort();
|
} catch (e) {
|
||||||
return;
|
if (e === AbortTransaction) {
|
||||||
|
tx.abort();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
throw e;
|
if (modifiedValue !== undefined && modifiedValue !== null) {
|
||||||
}
|
cursor.update(modifiedValue);
|
||||||
if (m !== undefined && m !== null) {
|
}
|
||||||
tx.objectStore(store.name).put(m);
|
cursor.continue();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -702,8 +732,6 @@ export class QueryRoot {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Add all object from an iterable to the given object store.
|
* 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 {
|
putAll<T>(store: Store<T>, iterable: T[]): QueryRoot {
|
||||||
this.checkFinished();
|
this.checkFinished();
|
||||||
@ -822,13 +850,13 @@ export class QueryRoot {
|
|||||||
/**
|
/**
|
||||||
* Delete an object by from the given object store.
|
* 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();
|
this.checkFinished();
|
||||||
const doDelete = (tx: IDBTransaction) => {
|
const doDelete = (tx: IDBTransaction) => {
|
||||||
tx.objectStore(storeName).delete(key);
|
tx.objectStore(store.name).delete(key);
|
||||||
};
|
};
|
||||||
this.scheduleFinish();
|
this.scheduleFinish();
|
||||||
this.addWork(doDelete, storeName, true);
|
this.addWork(doDelete, store.name, true);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
224
src/types.ts
224
src/types.ts
@ -598,6 +598,12 @@ export interface PreCoinRecord {
|
|||||||
coinEv: string;
|
coinEv: string;
|
||||||
exchangeBaseUrl: string;
|
exchangeBaseUrl: string;
|
||||||
coinValue: AmountJson;
|
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.
|
* Coin was dirty but can't be refreshed.
|
||||||
*/
|
*/
|
||||||
Useless,
|
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;
|
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;
|
||||||
|
}
|
||||||
|
183
src/wallet.ts
183
src/wallet.ts
@ -81,6 +81,10 @@ import {
|
|||||||
ReserveRecord,
|
ReserveRecord,
|
||||||
ReturnCoinsRequest,
|
ReturnCoinsRequest,
|
||||||
SenderWireInfos,
|
SenderWireInfos,
|
||||||
|
TipPlanchetDetail,
|
||||||
|
TipRecord,
|
||||||
|
TipResponse,
|
||||||
|
TipStatus,
|
||||||
WalletBalance,
|
WalletBalance,
|
||||||
WalletBalanceEntry,
|
WalletBalanceEntry,
|
||||||
WireFee,
|
WireFee,
|
||||||
@ -324,7 +328,7 @@ export const WALLET_PROTOCOL_VERSION = "0:0:0";
|
|||||||
* In the future we might consider adding migration functions for
|
* In the future we might consider adding migration functions for
|
||||||
* each version increment.
|
* each version increment.
|
||||||
*/
|
*/
|
||||||
export const WALLET_DB_VERSION = 20;
|
export const WALLET_DB_VERSION = 21;
|
||||||
|
|
||||||
const builtinCurrencies: CurrencyRecord[] = [
|
const builtinCurrencies: CurrencyRecord[] = [
|
||||||
{
|
{
|
||||||
@ -506,7 +510,7 @@ export namespace Stores {
|
|||||||
super("exchanges", {keyPath: "baseUrl"});
|
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> {
|
class NonceStore extends Store<NonceRecord> {
|
||||||
@ -521,7 +525,7 @@ export namespace Stores {
|
|||||||
}
|
}
|
||||||
|
|
||||||
exchangeBaseUrlIndex = new Index<string, CoinRecord>(this, "exchangeBaseUrl", "exchangeBaseUrl");
|
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> {
|
class ProposalsStore extends Store<ProposalRecord> {
|
||||||
@ -531,7 +535,7 @@ export namespace Stores {
|
|||||||
keyPath: "id",
|
keyPath: "id",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
timestampIndex = new Index<string, ProposalRecord>(this, "timestamp", "timestamp");
|
timestampIndex = new Index<string, ProposalRecord>(this, "timestampIndex", "timestamp");
|
||||||
}
|
}
|
||||||
|
|
||||||
class PurchasesStore extends Store<PurchaseRecord> {
|
class PurchasesStore extends Store<PurchaseRecord> {
|
||||||
@ -539,9 +543,9 @@ export namespace Stores {
|
|||||||
super("purchases", {keyPath: "contractTermsHash"});
|
super("purchases", {keyPath: "contractTermsHash"});
|
||||||
}
|
}
|
||||||
|
|
||||||
fulfillmentUrlIndex = new Index<string, PurchaseRecord>(this, "fulfillment_url", "contractTerms.fulfillment_url");
|
fulfillmentUrlIndex = new Index<string, PurchaseRecord>(this, "fulfillmentUrlIndex", "contractTerms.fulfillment_url");
|
||||||
orderIdIndex = new Index<string, PurchaseRecord>(this, "order_id", "contractTerms.order_id");
|
orderIdIndex = new Index<string, PurchaseRecord>(this, "orderIdIndex", "contractTerms.order_id");
|
||||||
timestampIndex = new Index<string, PurchaseRecord>(this, "timestamp", "timestamp");
|
timestampIndex = new Index<string, PurchaseRecord>(this, "timestampIndex", "timestamp");
|
||||||
}
|
}
|
||||||
|
|
||||||
class DenominationsStore extends Store<DenominationRecord> {
|
class DenominationsStore extends Store<DenominationRecord> {
|
||||||
@ -551,9 +555,9 @@ export namespace Stores {
|
|||||||
{keyPath: ["exchangeBaseUrl", "denomPub"] as any as IDBKeyPath});
|
{keyPath: ["exchangeBaseUrl", "denomPub"] as any as IDBKeyPath});
|
||||||
}
|
}
|
||||||
|
|
||||||
denomPubHashIndex = new Index<string, DenominationRecord>(this, "denomPubHash", "denomPubHash");
|
denomPubHashIndex = new Index<string, DenominationRecord>(this, "denomPubHashIndex", "denomPubHash");
|
||||||
exchangeBaseUrlIndex = new Index<string, DenominationRecord>(this, "exchangeBaseUrl", "exchangeBaseUrl");
|
exchangeBaseUrlIndex = new Index<string, DenominationRecord>(this, "exchangeBaseUrlIndex", "exchangeBaseUrl");
|
||||||
denomPubIndex = new Index<string, DenominationRecord>(this, "denomPub", "denomPub");
|
denomPubIndex = new Index<string, DenominationRecord>(this, "denomPubIndex", "denomPub");
|
||||||
}
|
}
|
||||||
|
|
||||||
class CurrenciesStore extends Store<CurrencyRecord> {
|
class CurrenciesStore extends Store<CurrencyRecord> {
|
||||||
@ -578,9 +582,16 @@ export namespace Stores {
|
|||||||
constructor() {
|
constructor() {
|
||||||
super("reserves", {keyPath: "reserve_pub"});
|
super("reserves", {keyPath: "reserve_pub"});
|
||||||
}
|
}
|
||||||
timestampCreatedIndex = new Index<string, ReserveRecord>(this, "timestampCreated", "created");
|
timestampCreatedIndex = new Index<string, ReserveRecord>(this, "timestampCreatedIndex", "created");
|
||||||
timestampConfirmedIndex = new Index<string, ReserveRecord>(this, "timestampConfirmed", "timestamp_confirmed");
|
timestampConfirmedIndex = new Index<string, ReserveRecord>(this, "timestampConfirmedIndex", "timestamp_confirmed");
|
||||||
timestampDepletedIndex = new Index<string, ReserveRecord>(this, "timestampDepleted", "timestamp_depleted");
|
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();
|
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 refresh = new Store<RefreshSessionRecord>("refresh", {keyPath: "id", autoIncrement: true});
|
||||||
export const reserves = new ReservesStore();
|
export const reserves = new ReservesStore();
|
||||||
export const purchases = new PurchasesStore();
|
export const purchases = new PurchasesStore();
|
||||||
|
export const tips = new TipsStore();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* tslint:enable:completed-docs */
|
/* tslint:enable:completed-docs */
|
||||||
@ -1126,7 +1138,7 @@ export class Wallet {
|
|||||||
() => this.processPreCoin(preCoin, Math.min(retryDelayMs * 2, 5 * 60 * 1000)));
|
() => this.processPreCoin(preCoin, Math.min(retryDelayMs * 2, 5 * 60 * 1000)));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.log("executing processPreCoin");
|
console.log("executing processPreCoin", preCoin);
|
||||||
this.processPreCoinConcurrent++;
|
this.processPreCoinConcurrent++;
|
||||||
try {
|
try {
|
||||||
const exchange = await this.q().get(Stores.exchanges,
|
const exchange = await this.q().get(Stores.exchanges,
|
||||||
@ -1143,6 +1155,7 @@ export class Wallet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const coin = await this.withdrawExecute(preCoin);
|
const coin = await this.withdrawExecute(preCoin);
|
||||||
|
console.log("processPreCoin: got coin", coin);
|
||||||
|
|
||||||
const mutateReserve = (r: ReserveRecord) => {
|
const mutateReserve = (r: ReserveRecord) => {
|
||||||
|
|
||||||
@ -1160,10 +1173,28 @@ export class Wallet {
|
|||||||
|
|
||||||
await this.q()
|
await this.q()
|
||||||
.mutate(Stores.reserves, preCoin.reservePub, mutateReserve)
|
.mutate(Stores.reserves, preCoin.reservePub, mutateReserve)
|
||||||
.delete("precoins", coin.coinPub)
|
.delete(Stores.precoins, coin.coinPub)
|
||||||
.add(Stores.coins, coin)
|
.add(Stores.coins, coin)
|
||||||
.finish();
|
.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();
|
this.notifier.notify();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Failed to withdraw coin from precoin, retrying in",
|
console.error("Failed to withdraw coin from precoin, retrying in",
|
||||||
@ -1266,19 +1297,12 @@ export class Wallet {
|
|||||||
|
|
||||||
|
|
||||||
private async withdrawExecute(pc: PreCoinRecord): Promise<CoinRecord> {
|
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 = {};
|
const wd: any = {};
|
||||||
wd.denom_pub = pc.denomPub;
|
wd.denom_pub = pc.denomPub;
|
||||||
wd.reserve_pub = pc.reservePub;
|
wd.reserve_pub = pc.reservePub;
|
||||||
wd.reserve_sig = pc.withdrawSig;
|
wd.reserve_sig = pc.withdrawSig;
|
||||||
wd.coin_ev = pc.coinEv;
|
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);
|
const resp = await this.http.postJson(reqUrl.href(), wd);
|
||||||
|
|
||||||
if (resp.status !== 200) {
|
if (resp.status !== 200) {
|
||||||
@ -1289,8 +1313,8 @@ export class Wallet {
|
|||||||
}
|
}
|
||||||
const r = JSON.parse(resp.responseText);
|
const r = JSON.parse(resp.responseText);
|
||||||
const denomSig = await this.cryptoApi.rsaUnblind(r.ev_sig,
|
const denomSig = await this.cryptoApi.rsaUnblind(r.ev_sig,
|
||||||
pc.blindingKey,
|
pc.blindingKey,
|
||||||
pc.denomPub);
|
pc.denomPub);
|
||||||
const coin: CoinRecord = {
|
const coin: CoinRecord = {
|
||||||
blindingKey: pc.blindingKey,
|
blindingKey: pc.blindingKey,
|
||||||
coinPriv: pc.coinPriv,
|
coinPriv: pc.coinPriv,
|
||||||
@ -2809,4 +2833,113 @@ export class Wallet {
|
|||||||
}
|
}
|
||||||
return feeAcc;
|
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[] };
|
request: { refundPermissions: types.RefundPermission[] };
|
||||||
response: void;
|
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 wxApi = require("./wxApi");
|
||||||
|
|
||||||
import { QueryPaymentResult } from "../types";
|
import { getTalerStampSec } from "../helpers";
|
||||||
|
import { TipToken, QueryPaymentResult } from "../types";
|
||||||
|
|
||||||
|
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
|
||||||
@ -260,6 +262,87 @@ function talerPay(msg: any): Promise<any> {
|
|||||||
// Use a promise directly instead of of an async
|
// Use a promise directly instead of of an async
|
||||||
// function since some paths never resolve the promise.
|
// function since some paths never resolve the promise.
|
||||||
return new Promise(async(resolve, reject) => {
|
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) {
|
if (msg.refund_url) {
|
||||||
console.log("processing refund");
|
console.log("processing refund");
|
||||||
let resp;
|
let resp;
|
||||||
|
@ -22,18 +22,17 @@
|
|||||||
* @author Florian Dold
|
* @author Florian Dold
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {canonicalizeBaseUrl} from "../../helpers";
|
import { canonicalizeBaseUrl } from "../../helpers";
|
||||||
import * as i18n from "../../i18n";
|
import * as i18n from "../../i18n";
|
||||||
import {
|
import {
|
||||||
AmountJson,
|
AmountJson,
|
||||||
Amounts,
|
Amounts,
|
||||||
CreateReserveResponse,
|
CreateReserveResponse,
|
||||||
CurrencyRecord,
|
CurrencyRecord,
|
||||||
DenominationRecord,
|
|
||||||
ReserveCreationInfo,
|
ReserveCreationInfo,
|
||||||
} from "../../types";
|
} from "../../types";
|
||||||
|
|
||||||
import {ImplicitStateComponent, StateHolder} from "../components";
|
import { ImplicitStateComponent, StateHolder } from "../components";
|
||||||
import {
|
import {
|
||||||
createReserve,
|
createReserve,
|
||||||
getCurrency,
|
getCurrency,
|
||||||
@ -41,9 +40,8 @@ import {
|
|||||||
getReserveCreationInfo,
|
getReserveCreationInfo,
|
||||||
} from "../wxApi";
|
} from "../wxApi";
|
||||||
|
|
||||||
import {Collapsible, renderAmount} from "../renderHtml";
|
import { renderAmount, WithdrawDetailView } from "../renderHtml";
|
||||||
|
|
||||||
import * as moment from "moment";
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as ReactDOM from "react-dom";
|
import * as ReactDOM from "react-dom";
|
||||||
import URI = require("urijs");
|
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 {
|
interface ExchangeSelectionProps {
|
||||||
@ -428,12 +306,7 @@ class ExchangeSelection extends ImplicitStateComponent<ExchangeSelectionProps> {
|
|||||||
</button>
|
</button>
|
||||||
</p>
|
</p>
|
||||||
{this.renderUpdateStatus()}
|
{this.renderUpdateStatus()}
|
||||||
<Collapsible initiallyCollapsed={true} title="Fee and Spending Details">
|
<WithdrawDetailView rci={this.reserveCreationInfo()} />
|
||||||
{renderReserveCreationDetails(this.reserveCreationInfo())}
|
|
||||||
</Collapsible>
|
|
||||||
<Collapsible initiallyCollapsed={true} title="Auditor Details">
|
|
||||||
{renderAuditorDetails(this.reserveCreationInfo())}
|
|
||||||
</Collapsible>
|
|
||||||
</div>
|
</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 {
|
import {
|
||||||
AmountJson,
|
AmountJson,
|
||||||
Amounts,
|
Amounts,
|
||||||
|
DenominationRecord,
|
||||||
|
ReserveCreationInfo,
|
||||||
} from "../types";
|
} from "../types";
|
||||||
|
|
||||||
|
import * as moment from "moment";
|
||||||
|
|
||||||
|
import * as i18n from "../i18n";
|
||||||
|
|
||||||
import * as React from "react";
|
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 {
|
.opener-collapsed::before {
|
||||||
content: "\25b6 "
|
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,
|
ReserveCreationInfo,
|
||||||
ReserveRecord,
|
ReserveRecord,
|
||||||
SenderWireInfos,
|
SenderWireInfos,
|
||||||
|
TipResponse,
|
||||||
|
TipPlanchetDetail,
|
||||||
|
TipStatus,
|
||||||
WalletBalance,
|
WalletBalance,
|
||||||
} from "../types";
|
} from "../types";
|
||||||
|
|
||||||
@ -358,3 +361,23 @@ export function getPurchase(contractTermsHash: string): Promise<PurchaseRecord>
|
|||||||
export function getFullRefundFees(args: { refundPermissions: RefundPermission[] }): Promise<AmountJson> {
|
export function getFullRefundFees(args: { refundPermissions: RefundPermission[] }): Promise<AmountJson> {
|
||||||
return callBackend("get-full-refund-fees", { refundPermissions: args.refundPermissions });
|
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,
|
Store,
|
||||||
} from "../query";
|
} from "../query";
|
||||||
import {
|
import {
|
||||||
|
AcceptTipRequest,
|
||||||
AmountJson,
|
AmountJson,
|
||||||
ConfirmReserveRequest,
|
ConfirmReserveRequest,
|
||||||
CreateReserveRequest,
|
CreateReserveRequest,
|
||||||
|
GetTipPlanchetsRequest,
|
||||||
Notifier,
|
Notifier,
|
||||||
|
ProcessTipResponseRequest,
|
||||||
ProposalRecord,
|
ProposalRecord,
|
||||||
ReturnCoinsRequest,
|
ReturnCoinsRequest,
|
||||||
|
TipStatusRequest,
|
||||||
} from "../types";
|
} from "../types";
|
||||||
import {
|
import {
|
||||||
Stores,
|
Stores,
|
||||||
@ -44,6 +48,7 @@ import {
|
|||||||
Wallet,
|
Wallet,
|
||||||
} from "../wallet";
|
} from "../wallet";
|
||||||
|
|
||||||
|
|
||||||
import { ChromeBadge } from "./chromeBadge";
|
import { ChromeBadge } from "./chromeBadge";
|
||||||
import { MessageType } from "./messages";
|
import { MessageType } from "./messages";
|
||||||
import * as wxApi from "./wxApi";
|
import * as wxApi from "./wxApi";
|
||||||
@ -316,6 +321,22 @@ function handleMessage(sender: MessageSender,
|
|||||||
}
|
}
|
||||||
case "get-full-refund-fees":
|
case "get-full-refund-fees":
|
||||||
return needsWallet().getFullRefundFees(detail.refundPermissions);
|
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:
|
default:
|
||||||
// Exhaustiveness check.
|
// Exhaustiveness check.
|
||||||
// See https://www.typescriptlang.org/docs/handbook/advanced-types.html
|
// 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"],
|
contract_url: headers["x-taler-contract-url"],
|
||||||
offer_url: headers["x-taler-offer-url"],
|
offer_url: headers["x-taler-offer-url"],
|
||||||
refund_url: headers["x-taler-refund-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;
|
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,
|
contract_url: fields.contract_url,
|
||||||
offer_url: fields.offer_url,
|
offer_url: fields.offer_url,
|
||||||
refund_url: fields.refund_url,
|
refund_url: fields.refund_url,
|
||||||
|
tip: fields.tip,
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log("got pay detail", payDetail);
|
console.log("got pay detail", payDetail);
|
||||||
@ -728,7 +751,7 @@ function openTalerDb(): Promise<IDBDatabase> {
|
|||||||
for (const indexName in (si as any)) {
|
for (const indexName in (si as any)) {
|
||||||
if ((si as any)[indexName] instanceof Index) {
|
if ((si as any)[indexName] instanceof Index) {
|
||||||
const ii: Index<any, any> = (si as any)[indexName];
|
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/reset-required.tsx",
|
||||||
"src/webex/pages/return-coins.tsx",
|
"src/webex/pages/return-coins.tsx",
|
||||||
"src/webex/pages/show-db.ts",
|
"src/webex/pages/show-db.ts",
|
||||||
|
"src/webex/pages/tip.tsx",
|
||||||
"src/webex/pages/tree.tsx",
|
"src/webex/pages/tree.tsx",
|
||||||
"src/webex/renderHtml.tsx",
|
"src/webex/renderHtml.tsx",
|
||||||
"src/webex/wxApi.ts",
|
"src/webex/wxApi.ts",
|
||||||
|
@ -78,6 +78,7 @@ module.exports = function (env) {
|
|||||||
"return-coins": "./src/webex/pages/return-coins.tsx",
|
"return-coins": "./src/webex/pages/return-coins.tsx",
|
||||||
"refund": "./src/webex/pages/refund.tsx",
|
"refund": "./src/webex/pages/refund.tsx",
|
||||||
"show-db": "./src/webex/pages/show-db.ts",
|
"show-db": "./src/webex/pages/show-db.ts",
|
||||||
|
"tip": "./src/webex/pages/tip.tsx",
|
||||||
"tree": "./src/webex/pages/tree.tsx",
|
"tree": "./src/webex/pages/tree.tsx",
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
|
Loading…
Reference in New Issue
Block a user