parent
0f607edbb2
commit
079e764ae6
File diff suppressed because one or more lines are too long
@ -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,
|
||||
|
@ -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;
|
||||
}
|
@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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) => {
|
||||
|
@ -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"
|
||||
|
@ -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;
|
||||
|
@ -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());
|
||||
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user