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',
|
eddsa_sign: getEmsc('GNUNET_CRYPTO_eddsa_sign',
|
||||||
'number',
|
'number',
|
||||||
['number', 'number', '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',
|
hash_create_random: getEmsc('GNUNET_CRYPTO_hash_create_random',
|
||||||
'void',
|
'void',
|
||||||
['number', 'number']),
|
['number', 'number']),
|
||||||
@ -144,9 +147,10 @@ var emscAlloc = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
enum SignaturePurpose {
|
export enum SignaturePurpose {
|
||||||
RESERVE_WITHDRAW = 1200,
|
RESERVE_WITHDRAW = 1200,
|
||||||
WALLET_COIN_DEPOSIT = 1201,
|
WALLET_COIN_DEPOSIT = 1201,
|
||||||
|
MASTER_DENOMINATION_KEY_VALIDITY = 1025,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum RandomQuality {
|
enum RandomQuality {
|
||||||
@ -336,11 +340,11 @@ export class Amount extends ArenaObject {
|
|||||||
return emsc.get_fraction(this.nativePtr);
|
return emsc.get_fraction(this.nativePtr);
|
||||||
}
|
}
|
||||||
|
|
||||||
get currency() {
|
get currency(): String {
|
||||||
return emsc.get_currency(this.nativePtr);
|
return emsc.get_currency(this.nativePtr);
|
||||||
}
|
}
|
||||||
|
|
||||||
toJson() {
|
toJson(): AmountJson {
|
||||||
return {
|
return {
|
||||||
value: emsc.get_value(this.nativePtr),
|
value: emsc.get_value(this.nativePtr),
|
||||||
fraction: emsc.get_fraction(this.nativePtr),
|
fraction: emsc.get_fraction(this.nativePtr),
|
||||||
@ -351,7 +355,7 @@ export class Amount extends ArenaObject {
|
|||||||
/**
|
/**
|
||||||
* Add an amount to this amount.
|
* Add an amount to this amount.
|
||||||
*/
|
*/
|
||||||
add(a) {
|
add(a: Amount) {
|
||||||
let res = emsc.amount_add(this.nativePtr, a.nativePtr, this.nativePtr);
|
let res = emsc.amount_add(this.nativePtr, a.nativePtr, this.nativePtr);
|
||||||
if (res < 1) {
|
if (res < 1) {
|
||||||
// Overflow
|
// Overflow
|
||||||
@ -363,7 +367,7 @@ export class Amount extends ArenaObject {
|
|||||||
/**
|
/**
|
||||||
* Perform saturating subtraction on amounts.
|
* Perform saturating subtraction on amounts.
|
||||||
*/
|
*/
|
||||||
sub(a) {
|
sub(a: Amount) {
|
||||||
// this = this - a
|
// this = this - a
|
||||||
let res = emsc.amount_subtract(this.nativePtr, this.nativePtr, a.nativePtr);
|
let res = emsc.amount_subtract(this.nativePtr, this.nativePtr, a.nativePtr);
|
||||||
if (res == 0) {
|
if (res == 0) {
|
||||||
@ -376,7 +380,7 @@ export class Amount extends ArenaObject {
|
|||||||
throw Error("Incompatible currencies");
|
throw Error("Incompatible currencies");
|
||||||
}
|
}
|
||||||
|
|
||||||
cmp(a) {
|
cmp(a: Amount) {
|
||||||
return emsc.amount_cmp(this.nativePtr, a.nativePtr);
|
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 {
|
interface Encodeable {
|
||||||
encode(arena?: Arena): ByteArray;
|
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,
|
export function rsaUnblind(sig: RsaSignature,
|
||||||
bk: RsaBlindingKey,
|
bk: RsaBlindingKey,
|
||||||
pk: RsaPublicKey,
|
pk: RsaPublicKey,
|
||||||
|
@ -54,3 +54,55 @@ export class CreateReserveResponse {
|
|||||||
|
|
||||||
static checked: (obj: any) => CreateReserveResponse;
|
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 * as native from "./emscriptif";
|
||||||
import {AmountJson, CreateReserveResponse} from "./types";
|
import {AmountJson, CreateReserveResponse, IMintInfo, Denomination} from "./types";
|
||||||
import {HttpResponse, RequestException} from "./http";
|
import {HttpResponse, RequestException} from "./http";
|
||||||
import {Query} from "./query";
|
import {Query} from "./query";
|
||||||
import {Checkable} from "./checkable";
|
import {Checkable} from "./checkable";
|
||||||
import {canonicalizeBaseUrl} from "./helpers";
|
import {canonicalizeBaseUrl} from "./helpers";
|
||||||
|
import {ReserveCreationInfo} from "./types";
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
|
||||||
export interface Mint {
|
|
||||||
baseUrl: string;
|
|
||||||
keys: Keys
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CoinWithDenom {
|
export interface CoinWithDenom {
|
||||||
coin: Coin;
|
coin: Coin;
|
||||||
denom: Denomination;
|
denom: Denomination;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Keys {
|
|
||||||
|
@Checkable.Class
|
||||||
|
export class KeysJson {
|
||||||
|
@Checkable.List(Checkable.Value(Denomination))
|
||||||
denoms: 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 {
|
export interface PreCoin {
|
||||||
coinPub: string;
|
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
|
@Checkable.Class
|
||||||
export class CreateReserveRequest {
|
export class CreateReserveRequest {
|
||||||
/**
|
/**
|
||||||
@ -107,14 +228,14 @@ export class ConfirmReserveRequest {
|
|||||||
|
|
||||||
|
|
||||||
@Checkable.Class
|
@Checkable.Class
|
||||||
export class MintInfo {
|
export class MintHandle {
|
||||||
@Checkable.String
|
@Checkable.String
|
||||||
master_pub: string;
|
master_pub: string;
|
||||||
|
|
||||||
@Checkable.String
|
@Checkable.String
|
||||||
url: string;
|
url: string;
|
||||||
|
|
||||||
static checked: (obj: any) => MintInfo;
|
static checked: (obj: any) => MintHandle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -144,8 +265,8 @@ export class Contract {
|
|||||||
@Checkable.String
|
@Checkable.String
|
||||||
merchant_pub: string;
|
merchant_pub: string;
|
||||||
|
|
||||||
@Checkable.List(Checkable.Value(MintInfo))
|
@Checkable.List(Checkable.Value(MintHandle))
|
||||||
mints: MintInfo[];
|
mints: MintHandle[];
|
||||||
|
|
||||||
@Checkable.List(Checkable.AnyObject)
|
@Checkable.List(Checkable.AnyObject)
|
||||||
products: any[];
|
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
|
* Create a pre-coin of the given denomination to be withdrawn from then given
|
||||||
* reserve.
|
* reserve.
|
||||||
@ -441,7 +566,7 @@ export class Wallet {
|
|||||||
*/
|
*/
|
||||||
private getPossibleMintCoins(paymentAmount: AmountJson,
|
private getPossibleMintCoins(paymentAmount: AmountJson,
|
||||||
depositFeeLimit: AmountJson,
|
depositFeeLimit: AmountJson,
|
||||||
allowedMints: MintInfo[]): Promise<MintCoins> {
|
allowedMints: MintHandle[]): Promise<MintCoins> {
|
||||||
// Mapping from mint base URL to list of coins together with their
|
// Mapping from mint base URL to list of coins together with their
|
||||||
// denomination
|
// denomination
|
||||||
let m: MintCoins = {};
|
let m: MintCoins = {};
|
||||||
@ -639,7 +764,8 @@ export class Wallet {
|
|||||||
return Query(this.db).put("history", depleted).finish();
|
return Query(this.db).put("history", depleted).finish();
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.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.
|
* Withdraw coins from a reserve until it is empty.
|
||||||
*/
|
*/
|
||||||
private depleteReserve(reserve, mint: Mint): Promise<void> {
|
private depleteReserve(reserve, mint: MintInfo): Promise<void> {
|
||||||
let denomsAvailable: Denomination[] = copy(mint.keys.denoms);
|
let denomsAvailable: Denomination[] = copy(mint.denoms);
|
||||||
let denomsForWithdraw = getWithdrawDenomList(reserve.current_amount,
|
let denomsForWithdraw = getWithdrawDenomList(reserve.current_amount,
|
||||||
denomsAvailable);
|
denomsAvailable);
|
||||||
|
|
||||||
@ -811,7 +937,7 @@ export class Wallet {
|
|||||||
* Update the information about a reserve that is stored in the wallet
|
* Update the information about a reserve that is stored in the wallet
|
||||||
* by quering the reserve's mint.
|
* 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)
|
return Query(this.db)
|
||||||
.get("reserves", reservePub)
|
.get("reserves", reservePub)
|
||||||
.then((reserve) => {
|
.then((reserve) => {
|
||||||
@ -846,27 +972,59 @@ 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.
|
* Update or add mint DB entry by fetching the /keys information.
|
||||||
* Optionally link the reserve entry to the new or existing
|
* Optionally link the reserve entry to the new or existing
|
||||||
* mint entry in then DB.
|
* mint entry in then DB.
|
||||||
*/
|
*/
|
||||||
private updateMintFromUrl(baseUrl): Promise<Mint> {
|
updateMintFromUrl(baseUrl): Promise<MintInfo> {
|
||||||
|
baseUrl = canonicalizeBaseUrl(baseUrl);
|
||||||
let reqUrl = URI("keys").absoluteTo(baseUrl);
|
let reqUrl = URI("keys").absoluteTo(baseUrl);
|
||||||
return this.http.get(reqUrl).then((resp) => {
|
return this.http.get(reqUrl).then((resp) => {
|
||||||
if (resp.status != 200) {
|
if (resp.status != 200) {
|
||||||
throw Error("/keys request failed");
|
throw Error("/keys request failed");
|
||||||
}
|
}
|
||||||
let mintKeysJson = JSON.parse(resp.responseText);
|
let mintKeysJson = KeysJson.checked(JSON.parse(resp.responseText));
|
||||||
if (!mintKeysJson) {
|
|
||||||
throw new RequestException({url: reqUrl, hint: "keys invalid"});
|
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");
|
||||||
}
|
}
|
||||||
let mint: Mint = {
|
|
||||||
baseUrl: baseUrl,
|
mint.mergeKeys(mintKeysJson);
|
||||||
keys: mintKeysJson
|
|
||||||
};
|
|
||||||
return Query(this.db).put("mints", mint).finish().then(() => mint);
|
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 {deleteDb, exportDb, openTalerDb} from "./db";
|
||||||
import {BrowserHttpLib} from "./http";
|
import {BrowserHttpLib} from "./http";
|
||||||
import {Checkable} from "./checkable";
|
import {Checkable} from "./checkable";
|
||||||
|
import {AmountJson} from "./types";
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
@ -77,7 +78,11 @@ function makeHandlers(db: IDBDatabase,
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof Checkable.SchemaError) {
|
if (e instanceof Checkable.SchemaError) {
|
||||||
console.error("schema error:", e.message);
|
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 {
|
} else {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
@ -88,6 +93,19 @@ function makeHandlers(db: IDBDatabase,
|
|||||||
["execute-payment"]: function(detail) {
|
["execute-payment"]: function(detail) {
|
||||||
return wallet.executePayment(detail.H_contract);
|
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) {
|
["get-history"]: function(detail) {
|
||||||
// TODO: limit history length
|
// TODO: limit history length
|
||||||
return wallet.getHistory();
|
return wallet.getHistory();
|
||||||
@ -119,7 +137,7 @@ function dispatch(handlers, req, sendResponse) {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
console.log("exception during wallet handler'");
|
console.log("exception during wallet handler");
|
||||||
console.error(e.stack);
|
console.error(e.stack);
|
||||||
sendResponse({
|
sendResponse({
|
||||||
error: "exception",
|
error: "exception",
|
||||||
@ -155,7 +173,18 @@ export function wxMain() {
|
|||||||
let wallet = new Wallet(db, http, badge);
|
let wallet = new Wallet(db, http, badge);
|
||||||
let handlers = makeHandlers(db, wallet);
|
let handlers = makeHandlers(db, wallet);
|
||||||
chrome.runtime.onMessage.addListener((req, sender, sendResponse) => {
|
chrome.runtime.onMessage.addListener((req, sender, sendResponse) => {
|
||||||
|
try {
|
||||||
return dispatch(handlers, req, sendResponse)
|
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) => {
|
.catch((e) => {
|
||||||
|
@ -42,6 +42,7 @@
|
|||||||
"background": {
|
"background": {
|
||||||
"scripts": [
|
"scripts": [
|
||||||
"lib/vendor/URI.js",
|
"lib/vendor/URI.js",
|
||||||
|
"lib/vendor/lodash.core.min.js",
|
||||||
"lib/emscripten/libwrapper.js",
|
"lib/emscripten/libwrapper.js",
|
||||||
"lib/vendor/system-csp-production.src.js",
|
"lib/vendor/system-csp-production.src.js",
|
||||||
"background/main.js"
|
"background/main.js"
|
||||||
|
@ -45,9 +45,49 @@ System.register(["../lib/wallet/helpers", "../lib/wallet/types", "mithril"], fun
|
|||||||
else {
|
else {
|
||||||
mx("p", "Checking URL, please wait ...");
|
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);
|
return mithril_1.default("div", controls);
|
||||||
var _a;
|
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) {
|
function probeMint(mintBaseUrl) {
|
||||||
throw Error("not implemented");
|
throw Error("not implemented");
|
||||||
}
|
}
|
||||||
@ -64,6 +104,21 @@ System.register(["../lib/wallet/helpers", "../lib/wallet/types", "mithril"], fun
|
|||||||
}
|
}
|
||||||
return Promise.resolve(mint);
|
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() {
|
function main() {
|
||||||
var url = URI(document.location.href);
|
var url = URI(document.location.href);
|
||||||
var query = URI.parseQuery(url.query());
|
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.url = mithril_1.default.prop();
|
||||||
this.statusString = null;
|
this.statusString = null;
|
||||||
this.isValidMint = false;
|
this.isValidMint = false;
|
||||||
|
this.reserveCreationInfo = null;
|
||||||
|
this.detailCollapsed = mithril_1.default.prop(true);
|
||||||
|
console.log("creating main controller");
|
||||||
this.amount = amount;
|
this.amount = amount;
|
||||||
this.callbackUrl = callbackUrl;
|
this.callbackUrl = callbackUrl;
|
||||||
this.timer = new DelayTimer(800, function () { return _this.update(); });
|
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 () {
|
var doUpdate = function () {
|
||||||
if (!_this.url()) {
|
if (!_this.url()) {
|
||||||
_this.statusString = (_a = ["Please enter a URL"], _a.raw = ["Please enter a URL"], i18n(_a));
|
_this.statusString = (_a = ["Please enter a URL"], _a.raw = ["Please enter a URL"], i18n(_a));
|
||||||
mithril_1.default.endComputation();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_this.statusString = null;
|
_this.statusString = null;
|
||||||
var parsedUrl = URI(_this.url());
|
var parsedUrl = URI(_this.url());
|
||||||
if (parsedUrl.is("relative")) {
|
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));
|
_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;
|
return;
|
||||||
}
|
}
|
||||||
var keysUrl = URI("/keys").absoluteTo(helpers_1.canonicalizeBaseUrl(_this.url()));
|
mithril_1.default.redraw(true);
|
||||||
console.log("requesting keys from '" + keysUrl + "'");
|
console.log("doing get mint info");
|
||||||
_this.request = new XMLHttpRequest();
|
getReserveCreationInfo(_this.url(), _this.amount)
|
||||||
_this.request.onreadystatechange = function () {
|
.then(function (r) {
|
||||||
if (_this.request.readyState == XMLHttpRequest.DONE) {
|
console.log("get mint info resolved");
|
||||||
switch (_this.request.status) {
|
|
||||||
case 200:
|
|
||||||
_this.isValidMint = true;
|
_this.isValidMint = true;
|
||||||
|
_this.reserveCreationInfo = r;
|
||||||
|
console.dir(r);
|
||||||
_this.statusString = "The mint base URL is valid!";
|
_this.statusString = "The mint base URL is valid!";
|
||||||
break;
|
mithril_1.default.endComputation();
|
||||||
case 0:
|
})
|
||||||
_this.statusString = "unknown request error";
|
.catch(function (e) {
|
||||||
break;
|
console.log("get mint info rejected");
|
||||||
default:
|
if (e.hasOwnProperty("httpStatus")) {
|
||||||
_this.statusString = "request failed with status " + _this.request.status;
|
_this.statusString = "request failed with status " + _this.request.status;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
_this.statusString = "unknown request error";
|
||||||
}
|
}
|
||||||
mithril_1.default.endComputation();
|
mithril_1.default.endComputation();
|
||||||
};
|
});
|
||||||
_this.request.open("get", keysUrl.href());
|
|
||||||
_this.request.send();
|
|
||||||
var _a, _b;
|
var _a, _b;
|
||||||
};
|
};
|
||||||
mithril_1.default.startComputation();
|
|
||||||
doUpdate();
|
doUpdate();
|
||||||
console.log("got update");
|
console.log("got update");
|
||||||
};
|
};
|
||||||
Controller.prototype.reset = function () {
|
Controller.prototype.reset = function () {
|
||||||
this.isValidMint = false;
|
this.isValidMint = false;
|
||||||
this.statusString = null;
|
this.statusString = null;
|
||||||
|
this.reserveCreationInfo = null;
|
||||||
if (this.request) {
|
if (this.request) {
|
||||||
this.request.abort();
|
this.request.abort();
|
||||||
this.request = null;
|
this.request = null;
|
||||||
|
@ -19,6 +19,10 @@
|
|||||||
import {amountToPretty, canonicalizeBaseUrl} from "../lib/wallet/helpers";
|
import {amountToPretty, canonicalizeBaseUrl} from "../lib/wallet/helpers";
|
||||||
import {AmountJson, CreateReserveResponse} from "../lib/wallet/types";
|
import {AmountJson, CreateReserveResponse} from "../lib/wallet/types";
|
||||||
import m from "mithril";
|
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";
|
"use strict";
|
||||||
|
|
||||||
@ -56,12 +60,15 @@ class Controller {
|
|||||||
url = m.prop<string>();
|
url = m.prop<string>();
|
||||||
statusString = null;
|
statusString = null;
|
||||||
isValidMint = false;
|
isValidMint = false;
|
||||||
|
reserveCreationInfo: ReserveCreationInfo = null;
|
||||||
private timer: DelayTimer;
|
private timer: DelayTimer;
|
||||||
private request: XMLHttpRequest;
|
private request: XMLHttpRequest;
|
||||||
amount: AmountJson;
|
amount: AmountJson;
|
||||||
callbackUrl: string;
|
callbackUrl: string;
|
||||||
|
detailCollapsed = m.prop<boolean>(true);
|
||||||
|
|
||||||
constructor(initialMintUrl: string, amount: AmountJson, callbackUrl: string) {
|
constructor(initialMintUrl: string, amount: AmountJson, callbackUrl: string) {
|
||||||
|
console.log("creating main controller");
|
||||||
this.amount = amount;
|
this.amount = amount;
|
||||||
this.callbackUrl = callbackUrl;
|
this.callbackUrl = callbackUrl;
|
||||||
this.timer = new DelayTimer(800, () => this.update());
|
this.timer = new DelayTimer(800, () => this.update());
|
||||||
@ -74,44 +81,39 @@ class Controller {
|
|||||||
const doUpdate = () => {
|
const doUpdate = () => {
|
||||||
if (!this.url()) {
|
if (!this.url()) {
|
||||||
this.statusString = i18n`Please enter a URL`;
|
this.statusString = i18n`Please enter a URL`;
|
||||||
m.endComputation();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.statusString = null;
|
this.statusString = null;
|
||||||
let parsedUrl = URI(this.url());
|
let parsedUrl = URI(this.url());
|
||||||
if (parsedUrl.is("relative")) {
|
if (parsedUrl.is("relative")) {
|
||||||
this.statusString = i18n`The URL you've entered is not valid (must be absolute)`;
|
this.statusString = i18n`The URL you've entered is not valid (must be absolute)`;
|
||||||
m.endComputation();
|
|
||||||
return;
|
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();
|
getReserveCreationInfo(this.url(), this.amount)
|
||||||
this.request.onreadystatechange = () => {
|
.then((r: ReserveCreationInfo) => {
|
||||||
if (this.request.readyState == XMLHttpRequest.DONE) {
|
console.log("get mint info resolved");
|
||||||
switch (this.request.status) {
|
|
||||||
case 200:
|
|
||||||
this.isValidMint = true;
|
this.isValidMint = true;
|
||||||
|
this.reserveCreationInfo = r;
|
||||||
|
console.dir(r);
|
||||||
this.statusString = "The mint base URL is valid!";
|
this.statusString = "The mint base URL is valid!";
|
||||||
break;
|
m.endComputation();
|
||||||
case 0:
|
})
|
||||||
this.statusString = `unknown request error`;
|
.catch((e) => {
|
||||||
break;
|
console.log("get mint info rejected");
|
||||||
default:
|
if (e.hasOwnProperty("httpStatus")) {
|
||||||
this.statusString = `request failed with status ${this.request.status}`;
|
this.statusString = `request failed with status ${this.request.status}`;
|
||||||
break;
|
} else {
|
||||||
}
|
this.statusString = `unknown request error`;
|
||||||
}
|
}
|
||||||
m.endComputation();
|
m.endComputation();
|
||||||
};
|
});
|
||||||
this.request.open("get", keysUrl.href());
|
|
||||||
this.request.send();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
m.startComputation();
|
|
||||||
doUpdate();
|
doUpdate();
|
||||||
|
|
||||||
|
|
||||||
@ -121,6 +123,7 @@ class Controller {
|
|||||||
reset() {
|
reset() {
|
||||||
this.isValidMint = false;
|
this.isValidMint = false;
|
||||||
this.statusString = null;
|
this.statusString = null;
|
||||||
|
this.reserveCreationInfo = null;
|
||||||
if (this.request) {
|
if (this.request) {
|
||||||
this.request.abort();
|
this.request.abort();
|
||||||
this.request = null;
|
this.request = null;
|
||||||
@ -168,7 +171,7 @@ class Controller {
|
|||||||
|
|
||||||
function view(ctrl: Controller) {
|
function view(ctrl: Controller) {
|
||||||
let controls = [];
|
let controls = [];
|
||||||
let mx = (x: string, ...args) => controls.push(m(x, ...args));
|
let mx = (x, ...args) => controls.push(m(x, ...args));
|
||||||
|
|
||||||
mx("p",
|
mx("p",
|
||||||
i18n`The bank wants to create a reserve over ${amountToPretty(
|
i18n`The bank wants to create a reserve over ${amountToPretty(
|
||||||
@ -196,10 +199,53 @@ function view(ctrl: Controller) {
|
|||||||
mx("p", "Checking URL, please wait ...");
|
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);
|
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 {
|
interface MintProbeResult {
|
||||||
keyInfo?: any;
|
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() {
|
export function main() {
|
||||||
const url = URI(document.location.href);
|
const url = URI(document.location.href);
|
||||||
const query: any = URI.parseQuery(url.query());
|
const query: any = URI.parseQuery(url.query());
|
||||||
|
@ -121,3 +121,18 @@ button.confirm-pay {
|
|||||||
from {opacity: 0}
|
from {opacity: 0}
|
||||||
to {opacity: 1}
|
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