This commit is contained in:
Florian Dold 2016-02-18 22:50:17 +01:00
parent 0f607edbb2
commit 079e764ae6
9 changed files with 534 additions and 102 deletions

File diff suppressed because one or more lines are too long

View File

@ -89,6 +89,9 @@ var emsc = {
eddsa_sign: getEmsc('GNUNET_CRYPTO_eddsa_sign',
'number',
['number', 'number', 'number']),
eddsa_verify: getEmsc('GNUNET_CRYPTO_eddsa_verify',
'number',
['number', 'number', 'number', 'number']),
hash_create_random: getEmsc('GNUNET_CRYPTO_hash_create_random',
'void',
['number', 'number']),
@ -144,9 +147,10 @@ var emscAlloc = {
};
enum SignaturePurpose {
export enum SignaturePurpose {
RESERVE_WITHDRAW = 1200,
WALLET_COIN_DEPOSIT = 1201,
MASTER_DENOMINATION_KEY_VALIDITY = 1025,
}
enum RandomQuality {
@ -336,11 +340,11 @@ export class Amount extends ArenaObject {
return emsc.get_fraction(this.nativePtr);
}
get currency() {
get currency(): String {
return emsc.get_currency(this.nativePtr);
}
toJson() {
toJson(): AmountJson {
return {
value: emsc.get_value(this.nativePtr),
fraction: emsc.get_fraction(this.nativePtr),
@ -351,7 +355,7 @@ export class Amount extends ArenaObject {
/**
* Add an amount to this amount.
*/
add(a) {
add(a: Amount) {
let res = emsc.amount_add(this.nativePtr, a.nativePtr, this.nativePtr);
if (res < 1) {
// Overflow
@ -363,7 +367,7 @@ export class Amount extends ArenaObject {
/**
* Perform saturating subtraction on amounts.
*/
sub(a) {
sub(a: Amount) {
// this = this - a
let res = emsc.amount_subtract(this.nativePtr, this.nativePtr, a.nativePtr);
if (res == 0) {
@ -376,7 +380,7 @@ export class Amount extends ArenaObject {
throw Error("Incompatible currencies");
}
cmp(a) {
cmp(a: Amount) {
return emsc.amount_cmp(this.nativePtr, a.nativePtr);
}
@ -848,6 +852,44 @@ export class DepositRequestPS extends SignatureStruct {
}
}
export interface DenominationKeyValidityPS_args {
master: EddsaPublicKey;
start: AbsoluteTimeNbo;
expire_withdraw: AbsoluteTimeNbo;
expire_spend: AbsoluteTimeNbo;
expire_legal: AbsoluteTimeNbo;
value: AmountNbo;
fee_withdraw: AmountNbo;
fee_deposit: AmountNbo;
fee_refresh: AmountNbo;
denom_hash: HashCode;
}
export class DenominationKeyValidityPS extends SignatureStruct {
constructor(w: DenominationKeyValidityPS_args) {
super(w);
}
purpose() {
return SignaturePurpose.MASTER_DENOMINATION_KEY_VALIDITY;
}
fieldTypes() {
return [
["master", EddsaPublicKey],
["start", AbsoluteTimeNbo],
["expire_withdraw", AbsoluteTimeNbo],
["expire_spend", AbsoluteTimeNbo],
["expire_legal", AbsoluteTimeNbo],
["value", AmountNbo],
["fee_withdraw", AmountNbo],
["fee_deposit", AmountNbo],
["fee_refresh", AmountNbo],
["denom_hash", HashCode]
];
}
}
interface Encodeable {
encode(arena?: Arena): ByteArray;
@ -932,6 +974,22 @@ export function eddsaSign(purpose: EccSignaturePurpose,
}
export function eddsaVerify(purposeNum: number,
verify: EccSignaturePurpose,
sig: EddsaSignature,
pub: EddsaPublicKey,
a?: Arena): boolean {
let r = emsc.eddsa_verify(purposeNum,
verify.nativePtr,
sig.nativePtr,
pub.nativePtr);
if (r === GNUNET_OK) {
return true;
}
return false;
}
export function rsaUnblind(sig: RsaSignature,
bk: RsaBlindingKey,
pk: RsaPublicKey,

View File

@ -53,4 +53,56 @@ export class CreateReserveResponse {
reservePub: string;
static checked: (obj: any) => CreateReserveResponse;
}
@Checkable.Class
export class Denomination {
@Checkable.Value(AmountJson)
value: AmountJson;
@Checkable.String
denom_pub: string;
@Checkable.Value(AmountJson)
fee_withdraw: AmountJson;
@Checkable.Value(AmountJson)
fee_deposit: AmountJson;
@Checkable.Value(AmountJson)
fee_refresh: AmountJson;
@Checkable.String
stamp_start: string;
@Checkable.String
stamp_expire_withdraw: string;
@Checkable.String
stamp_expire_legal: string;
@Checkable.String
stamp_expire_deposit: string;
@Checkable.String
master_sig: string;
@Checkable.Optional(Checkable.String)
pub_hash: string;
static checked: (obj: any) => Denomination;
}
export interface IMintInfo {
baseUrl: string;
masterPublicKey: string;
denoms: Denomination[];
}
export interface ReserveCreationInfo {
mintInfo: IMintInfo;
selectedDenoms: Denomination[];
withdrawFee: AmountJson;
}

View File

@ -22,36 +22,48 @@
*/
import * as native from "./emscriptif";
import {AmountJson, CreateReserveResponse} from "./types";
import {AmountJson, CreateReserveResponse, IMintInfo, Denomination} from "./types";
import {HttpResponse, RequestException} from "./http";
import {Query} from "./query";
import {Checkable} from "./checkable";
import {canonicalizeBaseUrl} from "./helpers";
import {ReserveCreationInfo} from "./types";
"use strict";
export interface Mint {
baseUrl: string;
keys: Keys
}
export interface CoinWithDenom {
coin: Coin;
denom: Denomination;
}
export interface Keys {
@Checkable.Class
export class KeysJson {
@Checkable.List(Checkable.Value(Denomination))
denoms: Denomination[];
@Checkable.String
master_public_key: string;
@Checkable.Any
auditors: any[];
@Checkable.String
list_issue_date: string;
@Checkable.Any
signkeys: any;
@Checkable.String
eddsa_pub: string;
@Checkable.String
eddsa_sig: string;
static checked: (obj: any) => KeysJson;
}
export interface Denomination {
value: AmountJson;
denom_pub: string;
fee_withdraw: AmountJson;
fee_deposit: AmountJson;
stamp_expire_withdraw: string;
}
export interface PreCoin {
coinPub: string;
@ -75,6 +87,115 @@ export interface Coin {
}
function isValidDenom(denom: Denomination,
masterPub: string): boolean {
let p = new native.DenominationKeyValidityPS({
master: native.EddsaPublicKey.fromCrock(masterPub),
denom_hash: native.RsaPublicKey.fromCrock(denom.denom_pub).encode().hash(),
expire_legal: native.AbsoluteTimeNbo.fromTalerString(denom.stamp_expire_legal),
expire_spend: native.AbsoluteTimeNbo.fromTalerString(denom.stamp_expire_deposit),
expire_withdraw: native.AbsoluteTimeNbo.fromTalerString(denom.stamp_expire_withdraw),
start: native.AbsoluteTimeNbo.fromTalerString(denom.stamp_start),
value: (new native.Amount(denom.value)).toNbo(),
fee_deposit: (new native.Amount(denom.fee_deposit)).toNbo(),
fee_refresh: (new native.Amount(denom.fee_refresh)).toNbo(),
fee_withdraw: (new native.Amount(denom.fee_withdraw)).toNbo(),
});
let nativeSig = new native.EddsaSignature();
nativeSig.loadCrock(denom.master_sig);
let nativePub = native.EddsaPublicKey.fromCrock(masterPub);
return native.eddsaVerify(native.SignaturePurpose.MASTER_DENOMINATION_KEY_VALIDITY,
p.toPurpose(),
nativeSig,
nativePub);
}
class MintInfo implements IMintInfo {
baseUrl: string;
masterPublicKey: string;
denoms: Denomination[];
constructor(obj: {baseUrl: string} & any) {
this.baseUrl = obj.baseUrl;
if (obj.denoms) {
this.denoms = Array.from(<Denomination[]>obj.denoms);
} else {
this.denoms = [];
}
if (typeof obj.masterPublicKey === "string") {
this.masterPublicKey = obj.masterPublicKey;
}
}
static fresh(baseUrl: string): MintInfo {
return new MintInfo({baseUrl});
}
/**
* Merge new key information into the mint info.
* If the new key information is invalid (missing fields,
* invalid signatures), an exception is thrown, but the
* mint info is updated with the new information up until
* the first error.
*/
mergeKeys(newKeys: KeysJson) {
if (!this.masterPublicKey) {
this.masterPublicKey = newKeys.master_public_key;
}
if (this.masterPublicKey != newKeys.master_public_key) {
throw Error("public keys do not match");
}
for (let newDenom of newKeys.denoms) {
let found = false;
for (let oldDenom of this.denoms) {
if (oldDenom.denom_pub === newDenom.denom_pub) {
let a = Object.assign({}, oldDenom);
let b = Object.assign({}, newDenom);
// pub hash is only there for convenience in the wallet
delete a["pub_hash"];
delete b["pub_hash"];
if (!_.isEqual(a, b)) {
console.log("old/new:");
console.dir(a);
console.dir(b);
throw Error("denomination modified");
}
// TODO: check if info still matches
found = true;
break;
}
}
if (found) {
continue;
}
console.log("validating denomination");
if (!isValidDenom(newDenom, this.masterPublicKey)) {
throw Error("signature on denomination invalid");
}
let d: Denomination = Object.assign({}, newDenom);
d.pub_hash = native.RsaPublicKey.fromCrock(d.denom_pub)
.encode()
.hash()
.toCrock();
this.denoms.push(d);
}
}
}
@Checkable.Class
export class CreateReserveRequest {
/**
@ -107,14 +228,14 @@ export class ConfirmReserveRequest {
@Checkable.Class
export class MintInfo {
export class MintHandle {
@Checkable.String
master_pub: string;
@Checkable.String
url: string;
static checked: (obj: any) => MintInfo;
static checked: (obj: any) => MintHandle;
}
@ -144,8 +265,8 @@ export class Contract {
@Checkable.String
merchant_pub: string;
@Checkable.List(Checkable.Value(MintInfo))
mints: MintInfo[];
@Checkable.List(Checkable.Value(MintHandle))
mints: MintHandle[];
@Checkable.List(Checkable.AnyObject)
products: any[];
@ -274,6 +395,10 @@ function rankDenom(denom1: any, denom2: any) {
}
function mergeMintKeys(oldKeys: KeysJson, newKeys: KeysJson) {
}
/**
* Create a pre-coin of the given denomination to be withdrawn from then given
* reserve.
@ -441,7 +566,7 @@ export class Wallet {
*/
private getPossibleMintCoins(paymentAmount: AmountJson,
depositFeeLimit: AmountJson,
allowedMints: MintInfo[]): Promise<MintCoins> {
allowedMints: MintHandle[]): Promise<MintCoins> {
// Mapping from mint base URL to list of coins together with their
// denomination
let m: MintCoins = {};
@ -639,7 +764,8 @@ export class Wallet {
return Query(this.db).put("history", depleted).finish();
})
.catch((e) => {
console.error("Failed to deplete reserve", e.stack);
console.error("Failed to deplete reserve");
console.error(e);
});
}
@ -791,8 +917,8 @@ export class Wallet {
/**
* Withdraw coins from a reserve until it is empty.
*/
private depleteReserve(reserve, mint: Mint): Promise<void> {
let denomsAvailable: Denomination[] = copy(mint.keys.denoms);
private depleteReserve(reserve, mint: MintInfo): Promise<void> {
let denomsAvailable: Denomination[] = copy(mint.denoms);
let denomsForWithdraw = getWithdrawDenomList(reserve.current_amount,
denomsAvailable);
@ -811,7 +937,7 @@ export class Wallet {
* Update the information about a reserve that is stored in the wallet
* by quering the reserve's mint.
*/
private updateReserve(reservePub: string, mint: Mint): Promise<Reserve> {
private updateReserve(reservePub: string, mint: MintInfo): Promise<Reserve> {
return Query(this.db)
.get("reserves", reservePub)
.then((reserve) => {
@ -846,26 +972,58 @@ export class Wallet {
}
getReserveCreationInfo(baseUrl: string,
amount: AmountJson): Promise<ReserveCreationInfo> {
return this.updateMintFromUrl(baseUrl)
.then((mintInfo: IMintInfo) => {
let selectedDenoms = getWithdrawDenomList(amount,
mintInfo.denoms);
let acc = native.Amount.getZero(amount.currency);
for (let d of selectedDenoms) {
acc.add(new native.Amount(d.fee_withdraw));
}
let ret: ReserveCreationInfo = {
mintInfo,
selectedDenoms,
withdrawFee: acc.toJson(),
};
return ret;
});
}
/**
* Update or add mint DB entry by fetching the /keys information.
* Optionally link the reserve entry to the new or existing
* mint entry in then DB.
*/
private updateMintFromUrl(baseUrl): Promise<Mint> {
updateMintFromUrl(baseUrl): Promise<MintInfo> {
baseUrl = canonicalizeBaseUrl(baseUrl);
let reqUrl = URI("keys").absoluteTo(baseUrl);
return this.http.get(reqUrl).then((resp) => {
if (resp.status != 200) {
throw Error("/keys request failed");
}
let mintKeysJson = JSON.parse(resp.responseText);
if (!mintKeysJson) {
throw new RequestException({url: reqUrl, hint: "keys invalid"});
}
let mint: Mint = {
baseUrl: baseUrl,
keys: mintKeysJson
};
return Query(this.db).put("mints", mint).finish().then(() => mint);
let mintKeysJson = KeysJson.checked(JSON.parse(resp.responseText));
return Query(this.db).get("mints", baseUrl).then((r) => {
let mint;
console.log("got mints result");
console.dir(r);
if (!r) {
mint = MintInfo.fresh(baseUrl);
console.log("making fresh mint");
} else {
mint = new MintInfo(r);
console.log("using old mint");
}
mint.mergeKeys(mintKeysJson);
return Query(this.db).put("mints", mint).finish().then(() => mint);
});
});
}

View File

@ -19,6 +19,7 @@ import {Wallet, Offer, Badge, ConfirmReserveRequest, CreateReserveRequest} from
import {deleteDb, exportDb, openTalerDb} from "./db";
import {BrowserHttpLib} from "./http";
import {Checkable} from "./checkable";
import {AmountJson} from "./types";
"use strict";
@ -77,7 +78,11 @@ function makeHandlers(db: IDBDatabase,
} catch (e) {
if (e instanceof Checkable.SchemaError) {
console.error("schema error:", e.message);
return Promise.resolve({error: "invalid contract", hint: e.message, detail: detail});
return Promise.resolve({
error: "invalid contract",
hint: e.message,
detail: detail
});
} else {
throw e;
}
@ -88,6 +93,19 @@ function makeHandlers(db: IDBDatabase,
["execute-payment"]: function(detail) {
return wallet.executePayment(detail.H_contract);
},
["mint-info"]: function(detail) {
if (!detail.baseUrl) {
return Promise.resolve({error: "bad url"});
}
return wallet.updateMintFromUrl(detail.baseUrl);
},
["reserve-creation-info"]: function(detail) {
if (!detail.baseUrl || typeof detail.baseUrl !== "string") {
return Promise.resolve({error: "bad url"});
}
let amount = AmountJson.checked(detail.amount);
return wallet.getReserveCreationInfo(detail.baseUrl, amount);
},
["get-history"]: function(detail) {
// TODO: limit history length
return wallet.getHistory();
@ -119,7 +137,7 @@ function dispatch(handlers, req, sendResponse) {
})
})
.catch((e) => {
console.log("exception during wallet handler'");
console.log("exception during wallet handler");
console.error(e.stack);
sendResponse({
error: "exception",
@ -155,7 +173,18 @@ export function wxMain() {
let wallet = new Wallet(db, http, badge);
let handlers = makeHandlers(db, wallet);
chrome.runtime.onMessage.addListener((req, sender, sendResponse) => {
return dispatch(handlers, req, sendResponse)
try {
return dispatch(handlers, req, sendResponse)
} catch (e) {
console.log("exception during wallet handler (dispatch)");
console.error(e.stack);
sendResponse({
error: "exception",
hint: e.message,
stack: e.stack.toString()
});
return false;
}
});
})
.catch((e) => {

View File

@ -42,6 +42,7 @@
"background": {
"scripts": [
"lib/vendor/URI.js",
"lib/vendor/lodash.core.min.js",
"lib/emscripten/libwrapper.js",
"lib/vendor/system-csp-production.src.js",
"background/main.js"

View File

@ -45,9 +45,49 @@ System.register(["../lib/wallet/helpers", "../lib/wallet/types", "mithril"], fun
else {
mx("p", "Checking URL, please wait ...");
}
if (ctrl.reserveCreationInfo) {
var withdrawFeeStr = helpers_1.amountToPretty(ctrl.reserveCreationInfo.withdrawFee);
mx("p", "Fee for withdrawal: " + withdrawFeeStr);
if (ctrl.detailCollapsed()) {
mx("button.linky", {
onclick: function () {
ctrl.detailCollapsed(false);
}
}, "show more");
}
else {
mx("button.linky", {
onclick: function () {
ctrl.detailCollapsed(true);
}
}, "show less");
mx("div", {}, renderCoinTable(ctrl.reserveCreationInfo.selectedDenoms));
}
}
return mithril_1.default("div", controls);
var _a;
}
function renderCoinTable(denoms) {
function row(denom) {
return mithril_1.default("tr", [
mithril_1.default("td", denom.pub_hash.substr(0, 5) + "..."),
mithril_1.default("td", helpers_1.amountToPretty(denom.value)),
mithril_1.default("td", helpers_1.amountToPretty(denom.fee_withdraw)),
mithril_1.default("td", helpers_1.amountToPretty(denom.fee_refresh)),
mithril_1.default("td", helpers_1.amountToPretty(denom.fee_deposit)),
]);
}
return mithril_1.default("table", [
mithril_1.default("tr", [
mithril_1.default("th", "Key Hash"),
mithril_1.default("th", "Value"),
mithril_1.default("th", "Withdraw Fee"),
mithril_1.default("th", "Refresh Fee"),
mithril_1.default("th", "Deposit Fee"),
]),
denoms.map(row)
]);
}
function probeMint(mintBaseUrl) {
throw Error("not implemented");
}
@ -64,6 +104,21 @@ System.register(["../lib/wallet/helpers", "../lib/wallet/types", "mithril"], fun
}
return Promise.resolve(mint);
}
function getReserveCreationInfo(baseUrl, amount) {
var m = { type: "reserve-creation-info", detail: { baseUrl: baseUrl, amount: amount } };
return new Promise(function (resolve, reject) {
chrome.runtime.sendMessage(m, function (resp) {
if (resp.error) {
console.error("error response", resp);
var e = Error("call to reserve-creation-info failed");
e.errorResponse = resp;
reject(e);
return;
}
resolve(resp);
});
});
}
function main() {
var url = URI(document.location.href);
var query = URI.parseQuery(url.query());
@ -128,6 +183,9 @@ System.register(["../lib/wallet/helpers", "../lib/wallet/types", "mithril"], fun
this.url = mithril_1.default.prop();
this.statusString = null;
this.isValidMint = false;
this.reserveCreationInfo = null;
this.detailCollapsed = mithril_1.default.prop(true);
console.log("creating main controller");
this.amount = amount;
this.callbackUrl = callbackUrl;
this.timer = new DelayTimer(800, function () { return _this.update(); });
@ -140,47 +198,44 @@ System.register(["../lib/wallet/helpers", "../lib/wallet/types", "mithril"], fun
var doUpdate = function () {
if (!_this.url()) {
_this.statusString = (_a = ["Please enter a URL"], _a.raw = ["Please enter a URL"], i18n(_a));
mithril_1.default.endComputation();
return;
}
_this.statusString = null;
var parsedUrl = URI(_this.url());
if (parsedUrl.is("relative")) {
_this.statusString = (_b = ["The URL you've entered is not valid (must be absolute)"], _b.raw = ["The URL you've entered is not valid (must be absolute)"], i18n(_b));
mithril_1.default.endComputation();
return;
}
var keysUrl = URI("/keys").absoluteTo(helpers_1.canonicalizeBaseUrl(_this.url()));
console.log("requesting keys from '" + keysUrl + "'");
_this.request = new XMLHttpRequest();
_this.request.onreadystatechange = function () {
if (_this.request.readyState == XMLHttpRequest.DONE) {
switch (_this.request.status) {
case 200:
_this.isValidMint = true;
_this.statusString = "The mint base URL is valid!";
break;
case 0:
_this.statusString = "unknown request error";
break;
default:
_this.statusString = "request failed with status " + _this.request.status;
break;
}
mithril_1.default.redraw(true);
console.log("doing get mint info");
getReserveCreationInfo(_this.url(), _this.amount)
.then(function (r) {
console.log("get mint info resolved");
_this.isValidMint = true;
_this.reserveCreationInfo = r;
console.dir(r);
_this.statusString = "The mint base URL is valid!";
mithril_1.default.endComputation();
})
.catch(function (e) {
console.log("get mint info rejected");
if (e.hasOwnProperty("httpStatus")) {
_this.statusString = "request failed with status " + _this.request.status;
}
else {
_this.statusString = "unknown request error";
}
mithril_1.default.endComputation();
};
_this.request.open("get", keysUrl.href());
_this.request.send();
});
var _a, _b;
};
mithril_1.default.startComputation();
doUpdate();
console.log("got update");
};
Controller.prototype.reset = function () {
this.isValidMint = false;
this.statusString = null;
this.reserveCreationInfo = null;
if (this.request) {
this.request.abort();
this.request = null;

View File

@ -19,6 +19,10 @@
import {amountToPretty, canonicalizeBaseUrl} from "../lib/wallet/helpers";
import {AmountJson, CreateReserveResponse} from "../lib/wallet/types";
import m from "mithril";
import {IMintInfo} from "../lib/wallet/types";
import {ReserveCreationInfo} from "../lib/wallet/types";
import MithrilComponent = _mithril.MithrilComponent;
import {Denomination} from "../lib/wallet/types";
"use strict";
@ -56,12 +60,15 @@ class Controller {
url = m.prop<string>();
statusString = null;
isValidMint = false;
reserveCreationInfo: ReserveCreationInfo = null;
private timer: DelayTimer;
private request: XMLHttpRequest;
amount: AmountJson;
callbackUrl: string;
detailCollapsed = m.prop<boolean>(true);
constructor(initialMintUrl: string, amount: AmountJson, callbackUrl: string) {
console.log("creating main controller");
this.amount = amount;
this.callbackUrl = callbackUrl;
this.timer = new DelayTimer(800, () => this.update());
@ -74,44 +81,39 @@ class Controller {
const doUpdate = () => {
if (!this.url()) {
this.statusString = i18n`Please enter a URL`;
m.endComputation();
return;
}
this.statusString = null;
let parsedUrl = URI(this.url());
if (parsedUrl.is("relative")) {
this.statusString = i18n`The URL you've entered is not valid (must be absolute)`;
m.endComputation();
return;
}
const keysUrl = URI("/keys").absoluteTo(canonicalizeBaseUrl(this.url()));
m.redraw(true);
console.log(`requesting keys from '${keysUrl}'`);
console.log("doing get mint info");
this.request = new XMLHttpRequest();
this.request.onreadystatechange = () => {
if (this.request.readyState == XMLHttpRequest.DONE) {
switch (this.request.status) {
case 200:
this.isValidMint = true;
this.statusString = "The mint base URL is valid!";
break;
case 0:
this.statusString = `unknown request error`;
break;
default:
this.statusString = `request failed with status ${this.request.status}`;
break;
getReserveCreationInfo(this.url(), this.amount)
.then((r: ReserveCreationInfo) => {
console.log("get mint info resolved");
this.isValidMint = true;
this.reserveCreationInfo = r;
console.dir(r);
this.statusString = "The mint base URL is valid!";
m.endComputation();
})
.catch((e) => {
console.log("get mint info rejected");
if (e.hasOwnProperty("httpStatus")) {
this.statusString = `request failed with status ${this.request.status}`;
} else {
this.statusString = `unknown request error`;
}
}
m.endComputation();
};
this.request.open("get", keysUrl.href());
this.request.send();
m.endComputation();
});
};
m.startComputation();
doUpdate();
@ -121,6 +123,7 @@ class Controller {
reset() {
this.isValidMint = false;
this.statusString = null;
this.reserveCreationInfo = null;
if (this.request) {
this.request.abort();
this.request = null;
@ -168,7 +171,7 @@ class Controller {
function view(ctrl: Controller) {
let controls = [];
let mx = (x: string, ...args) => controls.push(m(x, ...args));
let mx = (x, ...args) => controls.push(m(x, ...args));
mx("p",
i18n`The bank wants to create a reserve over ${amountToPretty(
@ -196,10 +199,53 @@ function view(ctrl: Controller) {
mx("p", "Checking URL, please wait ...");
}
if (ctrl.reserveCreationInfo) {
let withdrawFeeStr = amountToPretty(ctrl.reserveCreationInfo.withdrawFee);
mx("p", `Fee for withdrawal: ${withdrawFeeStr}`);
if (ctrl.detailCollapsed()) {
mx("button.linky", {
onclick: () => {
ctrl.detailCollapsed(false);
}
}, "show more");
} else {
mx("button.linky", {
onclick: () => {
ctrl.detailCollapsed(true);
}
}, "show less");
mx("div", {}, renderCoinTable(ctrl.reserveCreationInfo.selectedDenoms))
}
}
return m("div", controls);
}
function renderCoinTable(denoms: Denomination[]) {
function row(denom: Denomination) {
return m("tr", [
m("td", denom.pub_hash.substr(0, 5) + "..."),
m("td", amountToPretty(denom.value)),
m("td", amountToPretty(denom.fee_withdraw)),
m("td", amountToPretty(denom.fee_refresh)),
m("td", amountToPretty(denom.fee_deposit)),
]);
}
return m("table", [
m("tr", [
m("th", "Key Hash"),
m("th", "Value"),
m("th", "Withdraw Fee"),
m("th", "Refresh Fee"),
m("th", "Deposit Fee"),
]),
denoms.map(row)
]);
}
interface MintProbeResult {
keyInfo?: any;
}
@ -227,6 +273,24 @@ function getSuggestedMint(currency: string): Promise<string> {
}
function getReserveCreationInfo(baseUrl: string,
amount: AmountJson): Promise<ReserveCreationInfo> {
let m = {type: "reserve-creation-info", detail: {baseUrl, amount}};
return new Promise((resolve, reject) => {
chrome.runtime.sendMessage(m, (resp) => {
if (resp.error) {
console.error("error response", resp);
let e = Error("call to reserve-creation-info failed");
(e as any).errorResponse = resp;
reject(e);
return;
}
resolve(resp);
});
});
}
export function main() {
const url = URI(document.location.href);
const query: any = URI.parseQuery(url.query());

View File

@ -120,4 +120,19 @@ button.confirm-pay {
@keyframes fade {
from {opacity: 0}
to {opacity: 1}
}
}
button.linky {
background:none!important;
border:none;
padding:0!important;
font-family:arial,sans-serif;
color:#069;
text-decoration:underline;
cursor:pointer;
}
table, th, td {
border: 1px solid black;
}