aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/crypto/cryptoApi.ts147
-rw-r--r--src/crypto/cryptoWorker.ts299
-rw-r--r--src/crypto/emscInterface.ts32
-rw-r--r--src/wallet.ts1436
4 files changed, 1218 insertions, 696 deletions
diff --git a/src/crypto/cryptoApi.ts b/src/crypto/cryptoApi.ts
index 03c2a675b..43a3bc228 100644
--- a/src/crypto/cryptoApi.ts
+++ b/src/crypto/cryptoApi.ts
@@ -14,7 +14,6 @@
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-
/**
* API to access the Taler crypto worker thread.
* @author Florian Dold
@@ -35,22 +34,14 @@ import {
WireFee,
} from "../dbTypes";
-import {
- ContractTerms,
- PaybackRequest,
-} from "../talerTypes";
+import { ContractTerms, PaybackRequest } from "../talerTypes";
-import {
- BenchmarkResult,
- CoinWithDenom,
- PayCoinInfo,
-} from "../walletTypes";
+import { BenchmarkResult, CoinWithDenom, PayCoinInfo } from "../walletTypes";
import * as timer from "../timer";
import { startWorker } from "./startWorker";
-
/**
* State of a crypto worker.
*/
@@ -58,17 +49,17 @@ interface WorkerState {
/**
* The actual worker thread.
*/
- w: Worker|null;
+ w: Worker | null;
/**
* Work we're currently executing or null if not busy.
*/
- currentWorkItem: WorkItem|null;
+ currentWorkItem: WorkItem | null;
/**
* Timer to terminate the worker if it's not busy enough.
*/
- terminationTimerHandle: timer.TimerHandle|null;
+ terminationTimerHandle: timer.TimerHandle | null;
}
interface WorkItem {
@@ -88,7 +79,6 @@ interface WorkItem {
startTime: number;
}
-
/**
* Number of different priorities. Each priority p
* must be 0 <= p < NUM_PRIO.
@@ -151,8 +141,10 @@ export class CryptoApi {
handleWorkerError(ws: WorkerState, e: ErrorEvent) {
if (ws.currentWorkItem) {
- console.error(`error in worker during ${ws.currentWorkItem!.operation}`,
- e);
+ console.error(
+ `error in worker during ${ws.currentWorkItem!.operation}`,
+ e,
+ );
} else {
console.error("error in worker", e);
}
@@ -201,7 +193,10 @@ export class CryptoApi {
console.error(`RPC with id ${id} has no registry entry`);
return;
}
- console.log(`rpc ${currentWorkItem.operation} took ${timer.performanceNow() - currentWorkItem.startTime}ms`);
+ console.log(
+ `rpc ${currentWorkItem.operation} took ${timer.performanceNow() -
+ currentWorkItem.startTime}ms`,
+ );
currentWorkItem.resolve(msg.data.result);
}
@@ -230,12 +225,21 @@ export class CryptoApi {
}
}
- private doRpc<T>(operation: string, priority: number,
- ...args: any[]): Promise<T> {
-
+ private doRpc<T>(
+ operation: string,
+ priority: number,
+ ...args: any[]
+ ): Promise<T> {
const p: Promise<T> = new Promise<T>((resolve, reject) => {
const rpcId = this.nextRpcId++;
- const workItem: WorkItem = {operation, args, resolve, reject, rpcId, startTime: 0};
+ const workItem: WorkItem = {
+ operation,
+ args,
+ resolve,
+ reject,
+ rpcId,
+ startTime: 0,
+ };
if (this.numBusy === this.workers.length) {
const q = this.workQueues[priority];
@@ -263,8 +267,10 @@ export class CryptoApi {
});
}
-
- createPreCoin(denom: DenominationRecord, reserve: ReserveRecord): Promise<PreCoinRecord> {
+ createPreCoin(
+ denom: DenominationRecord,
+ reserve: ReserveRecord,
+ ): Promise<PreCoinRecord> {
return this.doRpc<PreCoinRecord>("createPreCoin", 1, denom, reserve);
}
@@ -280,27 +286,48 @@ export class CryptoApi {
return this.doRpc<string>("hashDenomPub", 1, denomPub);
}
- isValidDenom(denom: DenominationRecord,
- masterPub: string): Promise<boolean> {
+ isValidDenom(denom: DenominationRecord, masterPub: string): Promise<boolean> {
return this.doRpc<boolean>("isValidDenom", 2, denom, masterPub);
}
- isValidWireFee(type: string, wf: WireFee, masterPub: string): Promise<boolean> {
+ isValidWireFee(
+ type: string,
+ wf: WireFee,
+ masterPub: string,
+ ): Promise<boolean> {
return this.doRpc<boolean>("isValidWireFee", 2, type, wf, masterPub);
}
- isValidPaymentSignature(sig: string, contractHash: string, merchantPub: string): Promise<boolean> {
- return this.doRpc<boolean>("isValidPaymentSignature", 1, sig, contractHash, merchantPub);
+ isValidPaymentSignature(
+ sig: string,
+ contractHash: string,
+ merchantPub: string,
+ ): Promise<boolean> {
+ return this.doRpc<boolean>(
+ "isValidPaymentSignature",
+ 1,
+ sig,
+ contractHash,
+ merchantPub,
+ );
}
- signDeposit(contractTerms: ContractTerms,
- cds: CoinWithDenom[],
- totalAmount: AmountJson): Promise<PayCoinInfo> {
- return this.doRpc<PayCoinInfo>("signDeposit", 3, contractTerms, cds, totalAmount);
+ signDeposit(
+ contractTerms: ContractTerms,
+ cds: CoinWithDenom[],
+ totalAmount: AmountJson,
+ ): Promise<PayCoinInfo> {
+ return this.doRpc<PayCoinInfo>(
+ "signDeposit",
+ 3,
+ contractTerms,
+ cds,
+ totalAmount,
+ );
}
- createEddsaKeypair(): Promise<{priv: string, pub: string}> {
- return this.doRpc<{priv: string, pub: string}>("createEddsaKeypair", 1);
+ createEddsaKeypair(): Promise<{ priv: string; pub: string }> {
+ return this.doRpc<{ priv: string; pub: string }>("createEddsaKeypair", 1);
}
rsaUnblind(sig: string, bk: string, pk: string): Promise<string> {
@@ -311,23 +338,43 @@ export class CryptoApi {
return this.doRpc<PaybackRequest>("createPaybackRequest", 1, coin);
}
- createRefreshSession(exchangeBaseUrl: string,
- kappa: number,
- meltCoin: CoinRecord,
- newCoinDenoms: DenominationRecord[],
- meltFee: AmountJson): Promise<RefreshSessionRecord> {
- return this.doRpc<RefreshSessionRecord>("createRefreshSession",
- 4,
- exchangeBaseUrl,
- kappa,
- meltCoin,
- newCoinDenoms,
- meltFee);
+ createRefreshSession(
+ exchangeBaseUrl: string,
+ kappa: number,
+ meltCoin: CoinRecord,
+ newCoinDenoms: DenominationRecord[],
+ meltFee: AmountJson,
+ ): Promise<RefreshSessionRecord> {
+ return this.doRpc<RefreshSessionRecord>(
+ "createRefreshSession",
+ 4,
+ exchangeBaseUrl,
+ kappa,
+ meltCoin,
+ newCoinDenoms,
+ meltFee,
+ );
+ }
+
+ signCoinLink(
+ oldCoinPriv: string,
+ newDenomHash: string,
+ oldCoinPub: string,
+ transferPub: string,
+ coinEv: string,
+ ): Promise<string> {
+ return this.doRpc<string>(
+ "signCoinLink",
+ 4,
+ oldCoinPriv,
+ newDenomHash,
+ oldCoinPub,
+ transferPub,
+ coinEv,
+ );
}
benchmark(repetitions: number): Promise<BenchmarkResult> {
- return this.doRpc<BenchmarkResult>("benchmark",
- 1,
- repetitions);
+ return this.doRpc<BenchmarkResult>("benchmark", 1, repetitions);
}
}
diff --git a/src/crypto/cryptoWorker.ts b/src/crypto/cryptoWorker.ts
index 5013e3acf..9c5263a6f 100644
--- a/src/crypto/cryptoWorker.ts
+++ b/src/crypto/cryptoWorker.ts
@@ -18,7 +18,6 @@
* Web worker for crypto operations.
*/
-
/**
* Imports.
*/
@@ -39,17 +38,9 @@ import {
WireFee,
} from "../dbTypes";
-import {
- CoinPaySig,
- ContractTerms,
- PaybackRequest,
-} from "../talerTypes";
+import { CoinPaySig, ContractTerms, PaybackRequest } from "../talerTypes";
-import {
- BenchmarkResult,
- CoinWithDenom,
- PayCoinInfo,
-} from "../walletTypes";
+import { BenchmarkResult, CoinWithDenom, PayCoinInfo } from "../walletTypes";
import { canonicalJson } from "../helpers";
@@ -64,15 +55,15 @@ import {
} from "./emscInterface";
import * as native from "./emscInterface";
-
namespace RpcFunctions {
-
/**
* Create a pre-coin of the given denomination to be withdrawn from then given
* reserve.
*/
- export function createPreCoin(denom: DenominationRecord,
- reserve: ReserveRecord): PreCoinRecord {
+ export function createPreCoin(
+ denom: DenominationRecord,
+ reserve: ReserveRecord,
+ ): PreCoinRecord {
const reservePriv = new native.EddsaPrivateKey();
reservePriv.loadCrock(reserve.reserve_priv);
const reservePub = new native.EddsaPublicKey();
@@ -125,7 +116,6 @@ namespace RpcFunctions {
return preCoin;
}
-
/**
* Create a planchet used for tipping, including the private keys.
*/
@@ -152,12 +142,14 @@ namespace RpcFunctions {
coinPub: coinPub.toCrock(),
coinValue: denom.value,
denomPub: denomPub.encode().toCrock(),
- denomPubHash: denomPub.encode().hash().toCrock(),
+ denomPubHash: denomPub
+ .encode()
+ .hash()
+ .toCrock(),
};
return tipPlanchet;
}
-
/**
* Create and sign a message to request payback for a coin.
*/
@@ -165,7 +157,9 @@ namespace RpcFunctions {
const p = new native.PaybackRequestPS({
coin_blind: native.RsaBlindingKeySecret.fromCrock(coin.blindingKey),
coin_pub: native.EddsaPublicKey.fromCrock(coin.coinPub),
- h_denom_pub: native.RsaPublicKey.fromCrock(coin.denomPub).encode().hash(),
+ h_denom_pub: native.RsaPublicKey.fromCrock(coin.denomPub)
+ .encode()
+ .hash(),
});
const coinPriv = native.EddsaPrivateKey.fromCrock(coin.coinPriv);
const coinSig = native.eddsaSign(p.toPurpose(), coinPriv);
@@ -179,63 +173,83 @@ namespace RpcFunctions {
return paybackRequest;
}
-
/**
* Check if a payment signature is valid.
*/
- export function isValidPaymentSignature(sig: string, contractHash: string, merchantPub: string): boolean {
+ export function isValidPaymentSignature(
+ sig: string,
+ contractHash: string,
+ merchantPub: string,
+ ): boolean {
const p = new native.PaymentSignaturePS({
contract_hash: native.HashCode.fromCrock(contractHash),
});
const nativeSig = new native.EddsaSignature();
nativeSig.loadCrock(sig);
const nativePub = native.EddsaPublicKey.fromCrock(merchantPub);
- return native.eddsaVerify(native.SignaturePurpose.MERCHANT_PAYMENT_OK,
- p.toPurpose(),
- nativeSig,
- nativePub);
+ return native.eddsaVerify(
+ native.SignaturePurpose.MERCHANT_PAYMENT_OK,
+ p.toPurpose(),
+ nativeSig,
+ nativePub,
+ );
}
/**
* Check if a wire fee is correctly signed.
*/
- export function isValidWireFee(type: string, wf: WireFee, masterPub: string): boolean {
+ export function isValidWireFee(
+ type: string,
+ wf: WireFee,
+ masterPub: string,
+ ): boolean {
const p = new native.MasterWireFeePS({
- closing_fee: (new native.Amount(wf.closingFee)).toNbo(),
+ closing_fee: new native.Amount(wf.closingFee).toNbo(),
end_date: native.AbsoluteTimeNbo.fromStampSeconds(wf.endStamp),
h_wire_method: native.ByteArray.fromStringWithNull(type).hash(),
start_date: native.AbsoluteTimeNbo.fromStampSeconds(wf.startStamp),
- wire_fee: (new native.Amount(wf.wireFee)).toNbo(),
+ wire_fee: new native.Amount(wf.wireFee).toNbo(),
});
const nativeSig = new native.EddsaSignature();
nativeSig.loadCrock(wf.sig);
const nativePub = native.EddsaPublicKey.fromCrock(masterPub);
- return native.eddsaVerify(native.SignaturePurpose.MASTER_WIRE_FEES,
- p.toPurpose(),
- nativeSig,
- nativePub);
+ return native.eddsaVerify(
+ native.SignaturePurpose.MASTER_WIRE_FEES,
+ p.toPurpose(),
+ nativeSig,
+ nativePub,
+ );
}
-
/**
* Check if the signature of a denomination is valid.
*/
- export function isValidDenom(denom: DenominationRecord,
- masterPub: string): boolean {
+ export function isValidDenom(
+ denom: DenominationRecord,
+ masterPub: string,
+ ): boolean {
const p = new native.DenominationKeyValidityPS({
- denom_hash: native.RsaPublicKey.fromCrock(denom.denomPub) .encode() .hash(),
- expire_legal: native.AbsoluteTimeNbo.fromTalerString(denom.stampExpireLegal),
- expire_spend: native.AbsoluteTimeNbo.fromTalerString(denom.stampExpireDeposit),
- expire_withdraw: native.AbsoluteTimeNbo.fromTalerString(denom.stampExpireWithdraw),
- fee_deposit: (new native.Amount(denom.feeDeposit)).toNbo(),
- fee_refresh: (new native.Amount(denom.feeRefresh)).toNbo(),
- fee_refund: (new native.Amount(denom.feeRefund)).toNbo(),
- fee_withdraw: (new native.Amount(denom.feeWithdraw)).toNbo(),
+ denom_hash: native.RsaPublicKey.fromCrock(denom.denomPub)
+ .encode()
+ .hash(),
+ expire_legal: native.AbsoluteTimeNbo.fromTalerString(
+ denom.stampExpireLegal,
+ ),
+ expire_spend: native.AbsoluteTimeNbo.fromTalerString(
+ denom.stampExpireDeposit,
+ ),
+ expire_withdraw: native.AbsoluteTimeNbo.fromTalerString(
+ denom.stampExpireWithdraw,
+ ),
+ fee_deposit: new native.Amount(denom.feeDeposit).toNbo(),
+ fee_refresh: new native.Amount(denom.feeRefresh).toNbo(),
+ fee_refund: new native.Amount(denom.feeRefund).toNbo(),
+ fee_withdraw: new native.Amount(denom.feeWithdraw).toNbo(),
master: native.EddsaPublicKey.fromCrock(masterPub),
start: native.AbsoluteTimeNbo.fromTalerString(denom.stampStart),
- value: (new native.Amount(denom.value)).toNbo(),
+ value: new native.Amount(denom.value).toNbo(),
});
const nativeSig = new native.EddsaSignature();
@@ -243,42 +257,44 @@ namespace RpcFunctions {
const nativePub = native.EddsaPublicKey.fromCrock(masterPub);
- return native.eddsaVerify(native.SignaturePurpose.MASTER_DENOMINATION_KEY_VALIDITY,
- p.toPurpose(),
- nativeSig,
- nativePub);
-
+ return native.eddsaVerify(
+ native.SignaturePurpose.MASTER_DENOMINATION_KEY_VALIDITY,
+ p.toPurpose(),
+ nativeSig,
+ nativePub,
+ );
}
-
/**
* Create a new EdDSA key pair.
*/
- export function createEddsaKeypair(): {priv: string, pub: string} {
+ export function createEddsaKeypair(): { priv: string; pub: string } {
const priv = native.EddsaPrivateKey.create();
const pub = priv.getPublicKey();
- return {priv: priv.toCrock(), pub: pub.toCrock()};
+ return { priv: priv.toCrock(), pub: pub.toCrock() };
}
-
/**
* Unblind a blindly signed value.
*/
export function rsaUnblind(sig: string, bk: string, pk: string): string {
- const denomSig = native.rsaUnblind(native.RsaSignature.fromCrock(sig),
- native.RsaBlindingKeySecret.fromCrock(bk),
- native.RsaPublicKey.fromCrock(pk));
+ const denomSig = native.rsaUnblind(
+ native.RsaSignature.fromCrock(sig),
+ native.RsaBlindingKeySecret.fromCrock(bk),
+ native.RsaPublicKey.fromCrock(pk),
+ );
return denomSig.encode().toCrock();
}
-
/**
* Generate updated coins (to store in the database)
* and deposit permissions for each given coin.
*/
- export function signDeposit(contractTerms: ContractTerms,
- cds: CoinWithDenom[],
- totalAmount: AmountJson): PayCoinInfo {
+ export function signDeposit(
+ contractTerms: ContractTerms,
+ cds: CoinWithDenom[],
+ totalAmount: AmountJson,
+ ): PayCoinInfo {
const ret: PayCoinInfo = {
originalCoins: [],
sigs: [],
@@ -287,17 +303,21 @@ namespace RpcFunctions {
const contractTermsHash = hashString(canonicalJson(contractTerms));
- const feeList: AmountJson[] = cds.map((x) => x.denom.feeDeposit);
- let fees = Amounts.add(Amounts.getZero(feeList[0].currency), ...feeList).amount;
+ const feeList: AmountJson[] = cds.map(x => x.denom.feeDeposit);
+ let fees = Amounts.add(Amounts.getZero(feeList[0].currency), ...feeList)
+ .amount;
// okay if saturates
- fees = Amounts.sub(fees, Amounts.parseOrThrow(contractTerms.max_fee)).amount;
+ fees = Amounts.sub(fees, Amounts.parseOrThrow(contractTerms.max_fee))
+ .amount;
const total = Amounts.add(fees, totalAmount).amount;
- const amountSpent = native.Amount.getZero(cds[0].coin.currentAmount.currency);
+ const amountSpent = native.Amount.getZero(
+ cds[0].coin.currentAmount.currency,
+ );
const amountRemaining = new native.Amount(total);
for (const cd of cds) {
let coinSpend: Amount;
- const originalCoin = { ...(cd.coin) };
+ const originalCoin = { ...cd.coin };
if (amountRemaining.value === 0 && amountRemaining.fraction === 0) {
break;
@@ -332,13 +352,20 @@ namespace RpcFunctions {
h_contract: native.HashCode.fromCrock(contractTermsHash),
h_wire: native.HashCode.fromCrock(contractTerms.H_wire),
merchant: native.EddsaPublicKey.fromCrock(contractTerms.merchant_pub),
- refund_deadline: native.AbsoluteTimeNbo.fromTalerString(contractTerms.refund_deadline),
- timestamp: native.AbsoluteTimeNbo.fromTalerString(contractTerms.timestamp),
+ refund_deadline: native.AbsoluteTimeNbo.fromTalerString(
+ contractTerms.refund_deadline,
+ ),
+ timestamp: native.AbsoluteTimeNbo.fromTalerString(
+ contractTerms.timestamp,
+ ),
});
- const coinSig = native.eddsaSign(d.toPurpose(),
- native.EddsaPrivateKey.fromCrock(cd.coin.coinPriv))
- .toCrock();
+ const coinSig = native
+ .eddsaSign(
+ d.toPurpose(),
+ native.EddsaPrivateKey.fromCrock(cd.coin.coinPriv),
+ )
+ .toCrock();
const s: CoinPaySig = {
coin_pub: cd.coin.coinPub,
@@ -355,22 +382,21 @@ namespace RpcFunctions {
return ret;
}
-
/**
* Create a new refresh session.
*/
- export function createRefreshSession(exchangeBaseUrl: string,
- kappa: number,
- meltCoin: CoinRecord,
- newCoinDenoms: DenominationRecord[],
- meltFee: AmountJson): RefreshSessionRecord {
-
+ export function createRefreshSession(
+ exchangeBaseUrl: string,
+ kappa: number,
+ meltCoin: CoinRecord,
+ newCoinDenoms: DenominationRecord[],
+ meltFee: AmountJson,
+ ): RefreshSessionRecord {
let valueWithFee = Amounts.getZero(newCoinDenoms[0].value.currency);
for (const ncd of newCoinDenoms) {
- valueWithFee = Amounts.add(valueWithFee,
- ncd.value,
- ncd.feeWithdraw).amount;
+ valueWithFee = Amounts.add(valueWithFee, ncd.value, ncd.feeWithdraw)
+ .amount;
}
// melt fee
@@ -397,12 +423,11 @@ namespace RpcFunctions {
}
sessionHc.read(native.EddsaPublicKey.fromCrock(meltCoin.coinPub));
- sessionHc.read((new native.Amount(valueWithFee)).toNbo());
+ sessionHc.read(new native.Amount(valueWithFee).toNbo());
for (let i = 0; i < kappa; i++) {
const preCoins: RefreshPreCoinRecord[] = [];
for (let j = 0; j < newCoinDenoms.length; j++) {
-
const transferPriv = native.EcdhePrivateKey.fromCrock(transferPrivs[i]);
const oldCoinPub = native.EddsaPublicKey.fromCrock(meltCoin.coinPub);
const transferSecret = native.ecdhEddsa(transferPriv, oldCoinPub);
@@ -413,10 +438,10 @@ namespace RpcFunctions {
const coinPub = coinPriv.getPublicKey();
const blindingFactor = fresh.blindingKey;
const pubHash: native.HashCode = coinPub.hash();
- const denomPub = native.RsaPublicKey.fromCrock(newCoinDenoms[j].denomPub);
- const ev = native.rsaBlind(pubHash,
- blindingFactor,
- denomPub);
+ const denomPub = native.RsaPublicKey.fromCrock(
+ newCoinDenoms[j].denomPub,
+ );
+ const ev = native.rsaBlind(pubHash, blindingFactor, denomPub);
if (!ev) {
throw Error("couldn't blind (malicious exchange key?)");
}
@@ -437,16 +462,18 @@ namespace RpcFunctions {
sessionHc.finish(sessionHash);
const confirmData = new RefreshMeltCoinAffirmationPS({
- amount_with_fee: (new Amount(valueWithFee)).toNbo(),
+ amount_with_fee: new Amount(valueWithFee).toNbo(),
coin_pub: EddsaPublicKey.fromCrock(meltCoin.coinPub),
- melt_fee: (new Amount(meltFee)).toNbo(),
+ melt_fee: new Amount(meltFee).toNbo(),
session_hash: sessionHash,
});
-
- const confirmSig: string = native.eddsaSign(confirmData.toPurpose(),
- native.EddsaPrivateKey.fromCrock(
- meltCoin.coinPriv)).toCrock();
+ const confirmSig: string = native
+ .eddsaSign(
+ confirmData.toPurpose(),
+ native.EddsaPrivateKey.fromCrock(meltCoin.coinPriv),
+ )
+ .toCrock();
let valueOutput = Amounts.getZero(newCoinDenoms[0].value.currency);
for (const denom of newCoinDenoms) {
@@ -459,8 +486,8 @@ namespace RpcFunctions {
finished: false,
hash: sessionHash.toCrock(),
meltCoinPub: meltCoin.coinPub,
- newDenomHashes: newCoinDenoms.map((d) => d.denomPubHash),
- newDenoms: newCoinDenoms.map((d) => d.denomPub),
+ newDenomHashes: newCoinDenoms.map(d => d.denomPubHash),
+ newDenoms: newCoinDenoms.map(d => d.denomPub),
norevealIndex: undefined,
preCoinsForGammas,
transferPrivs,
@@ -484,7 +511,33 @@ namespace RpcFunctions {
* Hash a denomination public key.
*/
export function hashDenomPub(denomPub: string): string {
- return native.RsaPublicKey.fromCrock(denomPub).encode().hash().toCrock();
+ return native.RsaPublicKey.fromCrock(denomPub)
+ .encode()
+ .hash()
+ .toCrock();
+ }
+
+ export function signCoinLink(
+ oldCoinPriv: string,
+ newDenomHash: string,
+ oldCoinPub: string,
+ transferPub: string,
+ coinEv: string,
+ ): string {
+ const coinEvHash = native.ByteArray.fromCrock(coinEv).hash();
+
+ const coinLink = new native.CoinLinkSignaturePS({
+ coin_envelope_hash: coinEvHash,
+ h_denom_pub: native.HashCode.fromCrock(newDenomHash),
+ old_coin_pub: native.EddsaPublicKey.fromCrock(oldCoinPub),
+ transfer_pub: native.EcdhePublicKey.fromCrock(transferPub),
+ });
+
+ const coinPriv = native.EddsaPrivateKey.fromCrock(oldCoinPriv);
+
+ const sig = native.eddsaSign(coinLink.toPurpose(), coinPriv);
+
+ return sig.toCrock();
}
export function benchmark(repetitions: number): BenchmarkResult {
@@ -500,7 +553,7 @@ namespace RpcFunctions {
for (let i = 0; i < repetitions; i++) {
ba.randomize(native.RandomQuality.WEAK);
const start = timer.performanceNow();
- ba.hash();
+ ba.hash();
time_hash_big += timer.performanceNow() - start;
}
@@ -508,7 +561,7 @@ namespace RpcFunctions {
for (let i = 0; i < repetitions; i++) {
const start = timer.performanceNow();
const priv: native.EddsaPrivateKey = native.EddsaPrivateKey.create();
- time_eddsa_create += timer.performanceNow() - start;
+ time_eddsa_create += timer.performanceNow() - start;
priv.destroy();
}
@@ -541,14 +594,15 @@ namespace RpcFunctions {
priv.destroy();
}
-
let time_eddsa_verify = 0;
for (let i = 0; i < repetitions; i++) {
const start = timer.performanceNow();
- native.eddsaVerify(native.SignaturePurpose.MERCHANT_PAYMENT_OK,
- p,
- eddsaSig,
- eddsaPub);
+ native.eddsaVerify(
+ native.SignaturePurpose.MERCHANT_PAYMENT_OK,
+ p,
+ eddsaSig,
+ eddsaPub,
+ );
time_eddsa_verify += timer.performanceNow() - start;
}
@@ -564,11 +618,18 @@ namespace RpcFunctions {
time_rsa_2048_blind += timer.performanceNow() - start;
}
- const blindedMessage2048 = native.rsaBlind(h, blindingSecret2048, rsaPub2048);
+ const blindedMessage2048 = native.rsaBlind(
+ h,
+ blindingSecret2048,
+ rsaPub2048,
+ );
if (!blindedMessage2048) {
throw Error("should not happen");
}
- const rsaBlindSig2048 = native.rsaSignBlinded(rsaPriv2048, blindedMessage2048);
+ const rsaBlindSig2048 = native.rsaSignBlinded(
+ rsaPriv2048,
+ blindedMessage2048,
+ );
let time_rsa_2048_unblind = 0;
for (let i = 0; i < repetitions; i++) {
@@ -577,7 +638,11 @@ namespace RpcFunctions {
time_rsa_2048_unblind += timer.performanceNow() - start;
}
- const unblindedSig2048 = native.rsaUnblind(rsaBlindSig2048, blindingSecret2048, rsaPub2048);
+ const unblindedSig2048 = native.rsaUnblind(
+ rsaBlindSig2048,
+ blindingSecret2048,
+ rsaPub2048,
+ );
let time_rsa_2048_verify = 0;
for (let i = 0; i < repetitions; i++) {
@@ -586,7 +651,6 @@ namespace RpcFunctions {
time_rsa_2048_verify += timer.performanceNow() - start;
}
-
/* rsa 4096 */
let time_rsa_4096_blind = 0;
@@ -599,11 +663,18 @@ namespace RpcFunctions {
time_rsa_4096_blind += timer.performanceNow() - start;
}
- const blindedMessage4096 = native.rsaBlind(h, blindingSecret4096, rsaPub4096);
+ const blindedMessage4096 = native.rsaBlind(
+ h,
+ blindingSecret4096,
+ rsaPub4096,
+ );
if (!blindedMessage4096) {
throw Error("should not happen");
}
- const rsaBlindSig4096 = native.rsaSignBlinded(rsaPriv4096, blindedMessage4096);
+ const rsaBlindSig4096 = native.rsaSignBlinded(
+ rsaPriv4096,
+ blindedMessage4096,
+ );
let time_rsa_4096_unblind = 0;
for (let i = 0; i < repetitions; i++) {
@@ -612,7 +683,11 @@ namespace RpcFunctions {
time_rsa_4096_unblind += timer.performanceNow() - start;
}
- const unblindedSig4096 = native.rsaUnblind(rsaBlindSig4096, blindingSecret4096, rsaPub4096);
+ const unblindedSig4096 = native.rsaUnblind(
+ rsaBlindSig4096,
+ blindingSecret4096,
+ rsaPub4096,
+ );
let time_rsa_4096_verify = 0;
for (let i = 0; i < repetitions; i++) {
@@ -621,7 +696,6 @@ namespace RpcFunctions {
time_rsa_4096_verify += timer.performanceNow() - start;
}
-
return {
repetitions,
time: {
@@ -637,12 +711,11 @@ namespace RpcFunctions {
rsa_4096_blind: time_rsa_4096_blind,
rsa_4096_unblind: time_rsa_4096_unblind,
rsa_4096_verify: time_rsa_4096_verify,
- }
+ },
};
}
}
-
const worker: Worker = (self as any) as Worker;
worker.onmessage = (msg: MessageEvent) => {
@@ -665,7 +738,7 @@ worker.onmessage = (msg: MessageEvent) => {
console.log("onmessage with", msg.data.operation);
console.log("foo");
- emscLoader.getLib().then((p) => {
+ emscLoader.getLib().then(p => {
const lib = p.lib;
if (!native.isInitialized()) {
console.log("initializing emscripten for then first time with lib");
diff --git a/src/crypto/emscInterface.ts b/src/crypto/emscInterface.ts
index dcd16e633..2ddc15a37 100644
--- a/src/crypto/emscInterface.ts
+++ b/src/crypto/emscInterface.ts
@@ -223,6 +223,7 @@ export enum SignaturePurpose {
MERCHANT_PAYMENT_OK = 1104,
MASTER_WIRE_FEES = 1028,
WALLET_COIN_PAYBACK = 1203,
+ WALLET_COIN_LINK = 1204,
}
@@ -970,7 +971,7 @@ abstract class SignatureStruct {
throw Error(`Key ${name} not found`);
}
if (!(value instanceof typemap[name])) {
- throw Error("Wrong type for ${name}");
+ throw Error(`Wrong type for ${name}`);
}
this.members[name] = value;
}
@@ -1293,6 +1294,35 @@ export class DepositRequestPS extends SignatureStruct {
}
}
+
+interface CoinLinkSignaturePS_args {
+ h_denom_pub: HashCode;
+ old_coin_pub: EddsaPublicKey;
+ transfer_pub: EcdhePublicKey;
+ coin_envelope_hash: HashCode;
+}
+
+
+export class CoinLinkSignaturePS extends SignatureStruct {
+ constructor(w: CoinLinkSignaturePS_args) {
+ super(w);
+ }
+
+ purpose() {
+ return SignaturePurpose.WALLET_COIN_LINK;
+ }
+
+ fieldTypes() {
+ return [
+ ["h_denom_pub", HashCode],
+ ["old_coin_pub", EddsaPublicKey],
+ ["transfer_pub", EcdhePublicKey],
+ ["coin_envelope_hash", HashCode],
+ ];
+ }
+}
+
+
/**
* Arguments for constuctor of [[DenominationKeyValidityPS]].
*/
diff --git a/src/wallet.ts b/src/wallet.ts
index 4c7e8c181..0dfb77554 100644
--- a/src/wallet.ts
+++ b/src/wallet.ts
@@ -30,10 +30,7 @@ import {
getTalerStampSec,
strcmp,
} from "./helpers";
-import {
- HttpRequestLibrary,
- RequestException,
-} from "./http";
+import { HttpRequestLibrary, RequestException } from "./http";
import * as LibtoolVersion from "./libtoolVersion";
import {
AbortTransaction,
@@ -109,7 +106,6 @@ import {
WalletBalanceEntry,
} from "./walletTypes";
-
interface SpeculativePayData {
payCoinInfo: PayCoinInfo;
exchangeUrl: string;
@@ -117,7 +113,6 @@ interface SpeculativePayData {
proposal: ProposalDownloadRecord;
}
-
/**
* Wallet protocol version spoken with the exchange
* and merchant.
@@ -132,7 +127,7 @@ const builtinCurrencies: CurrencyRecord[] = [
{
auditorPub: "BW9DC48PHQY4NH011SHHX36DZZ3Q22Y6X7FZ1VD1CMZ2PTFZ6PN0",
baseUrl: "https://auditor.demo.taler.net/",
- expirationStamp: (new Date(2027, 1)).getTime(),
+ expirationStamp: new Date(2027, 1).getTime(),
},
],
exchanges: [],
@@ -141,9 +136,8 @@ const builtinCurrencies: CurrencyRecord[] = [
},
];
-
function isWithdrawableDenom(d: DenominationRecord) {
- const nowSec = (new Date()).getTime() / 1000;
+ const nowSec = new Date().getTime() / 1000;
const stampWithdrawSec = getTalerStampSec(d.stampExpireWithdraw);
if (stampWithdrawSec === null) {
return false;
@@ -153,19 +147,17 @@ function isWithdrawableDenom(d: DenominationRecord) {
return false;
}
// Withdraw if still possible to withdraw within a minute
- if ((stampWithdrawSec + 60 > nowSec) && (nowSec >= stampStartSec)) {
+ if (stampWithdrawSec + 60 > nowSec && nowSec >= stampStartSec) {
return true;
}
return false;
}
-
interface SelectPayCoinsResult {
cds: CoinWithDenom[];
totalFees: AmountJson;
}
-
/**
* Get the amount that we lose when refreshing a coin of the given denomination
* with a certain amount left.
@@ -177,38 +169,54 @@ interface SelectPayCoinsResult {
* Considers refresh fees, withdrawal fees after refresh and amounts too small
* to refresh.
*/
-export function getTotalRefreshCost(denoms: DenominationRecord[],
- refreshedDenom: DenominationRecord,
- amountLeft: AmountJson): AmountJson {
- const withdrawAmount = Amounts.sub(amountLeft, refreshedDenom.feeRefresh).amount;
+export function getTotalRefreshCost(
+ denoms: DenominationRecord[],
+ refreshedDenom: DenominationRecord,
+ amountLeft: AmountJson,
+): AmountJson {
+ const withdrawAmount = Amounts.sub(amountLeft, refreshedDenom.feeRefresh)
+ .amount;
const withdrawDenoms = getWithdrawDenomList(withdrawAmount, denoms);
- const resultingAmount = Amounts.add(Amounts.getZero(withdrawAmount.currency),
- ...withdrawDenoms.map((d) => d.value)).amount;
+ const resultingAmount = Amounts.add(
+ Amounts.getZero(withdrawAmount.currency),
+ ...withdrawDenoms.map(d => d.value),
+ ).amount;
const totalCost = Amounts.sub(amountLeft, resultingAmount).amount;
- console.log("total refresh cost for", amountToPretty(amountLeft), "is", amountToPretty(totalCost));
+ console.log(
+ "total refresh cost for",
+ amountToPretty(amountLeft),
+ "is",
+ amountToPretty(totalCost),
+ );
return totalCost;
}
-
/**
* Select coins for a payment under the merchant's constraints.
*
* @param denoms all available denoms, used to compute refresh fees
*/
-export function selectPayCoins(denoms: DenominationRecord[], cds: CoinWithDenom[], paymentAmount: AmountJson,
- depositFeeLimit: AmountJson): SelectPayCoinsResult|undefined {
+export function selectPayCoins(
+ denoms: DenominationRecord[],
+ cds: CoinWithDenom[],
+ paymentAmount: AmountJson,
+ depositFeeLimit: AmountJson,
+): SelectPayCoinsResult | undefined {
if (cds.length === 0) {
return undefined;
}
// Sort by ascending deposit fee and denomPub if deposit fee is the same
// (to guarantee deterministic results)
- cds.sort((o1, o2) => Amounts.cmp(o1.denom.feeDeposit, o2.denom.feeDeposit) ||
- strcmp(o1.denom.denomPub, o2.denom.denomPub));
+ cds.sort(
+ (o1, o2) =>
+ Amounts.cmp(o1.denom.feeDeposit, o2.denom.feeDeposit) ||
+ strcmp(o1.denom.denomPub, o2.denom.denomPub),
+ );
const currency = cds[0].denom.value.currency;
const cdsResult: CoinWithDenom[] = [];
let accDepositFee: AmountJson = Amounts.getZero(currency);
let accAmount: AmountJson = Amounts.getZero(currency);
- for (const {coin, denom} of cds) {
+ for (const { coin, denom } of cds) {
if (coin.suspended) {
continue;
}
@@ -218,20 +226,32 @@ export function selectPayCoins(denoms: DenominationRecord[], cds: CoinWithDenom[
if (Amounts.cmp(denom.feeDeposit, coin.currentAmount) >= 0) {
continue;
}
- cdsResult.push({coin, denom});
+ cdsResult.push({ coin, denom });
accDepositFee = Amounts.add(denom.feeDeposit, accDepositFee).amount;
- let leftAmount = Amounts.sub(coin.currentAmount, Amounts.sub(paymentAmount, accAmount).amount).amount;
+ let leftAmount = Amounts.sub(
+ coin.currentAmount,
+ Amounts.sub(paymentAmount, accAmount).amount,
+ ).amount;
accAmount = Amounts.add(coin.currentAmount, accAmount).amount;
const coversAmount = Amounts.cmp(accAmount, paymentAmount) >= 0;
- const coversAmountWithFee = Amounts.cmp(accAmount,
- Amounts.add(paymentAmount,
- denom.feeDeposit).amount) >= 0;
+ const coversAmountWithFee =
+ Amounts.cmp(
+ accAmount,
+ Amounts.add(paymentAmount, denom.feeDeposit).amount,
+ ) >= 0;
const isBelowFee = Amounts.cmp(accDepositFee, depositFeeLimit) <= 0;
- console.log("coin selection", { coversAmount, isBelowFee, accDepositFee, accAmount, paymentAmount });
+ console.log("coin selection", {
+ coversAmount,
+ isBelowFee,
+ accDepositFee,
+ accAmount,
+ paymentAmount,
+ });
if ((coversAmount && isBelowFee) || coversAmountWithFee) {
- const depositFeeToCover = Amounts.sub(accDepositFee, depositFeeLimit).amount;
+ const depositFeeToCover = Amounts.sub(accDepositFee, depositFeeLimit)
+ .amount;
leftAmount = Amounts.sub(leftAmount, depositFeeToCover).amount;
console.log("deposit fee to cover", amountToPretty(depositFeeToCover));
@@ -241,21 +261,25 @@ export function selectPayCoins(denoms: DenominationRecord[], cds: CoinWithDenom[
// because the merchant doesn't cover them
totalFees = Amounts.sub(depositFeeLimit, accDepositFee).amount;
}
- totalFees = Amounts.add(totalFees, getTotalRefreshCost(denoms, denom, leftAmount)).amount;
+ totalFees = Amounts.add(
+ totalFees,
+ getTotalRefreshCost(denoms, denom, leftAmount),
+ ).amount;
return { cds: cdsResult, totalFees };
}
}
return undefined;
}
-
/**
* Get a list of denominations (with repetitions possible)
* whose total value is as close as possible to the available
* amount, but never larger.
*/
-function getWithdrawDenomList(amountAvailable: AmountJson,
- denoms: DenominationRecord[]): DenominationRecord[] {
+function getWithdrawDenomList(
+ amountAvailable: AmountJson,
+ denoms: DenominationRecord[],
+): DenominationRecord[] {
let remaining = Amounts.copy(amountAvailable);
const ds: DenominationRecord[] = [];
@@ -284,7 +308,6 @@ function getWithdrawDenomList(amountAvailable: AmountJson,
return ds;
}
-
interface CoinsForPaymentArgs {
allowedAuditors: Auditor[];
allowedExchanges: ExchangeHandle[];
@@ -296,7 +319,6 @@ interface CoinsForPaymentArgs {
wireMethod: string;
}
-
/**
* The platform-independent wallet implementation.
*/
@@ -310,7 +332,7 @@ export class Wallet {
private notifier: Notifier;
private cryptoApi: CryptoApi;
private processPreCoinConcurrent = 0;
- private processPreCoinThrottle: {[url: string]: number} = {};
+ private processPreCoinThrottle: { [url: string]: number } = {};
private timerGroup: TimerGroup;
private speculativePayData: SpeculativePayData | undefined;
private cachedNextUrl: { [fulfillmentUrl: string]: NextUrlResult } = {};
@@ -325,10 +347,12 @@ export class Wallet {
return new QueryRoot(this.db);
}
- constructor(db: IDBDatabase,
- http: HttpRequestLibrary,
- badge: Badge,
- notifier: Notifier) {
+ constructor(
+ db: IDBDatabase,
+ http: HttpRequestLibrary,
+ badge: Badge,
+ notifier: Notifier,
+ ) {
this.db = db;
this.http = http;
this.badge = badge;
@@ -337,8 +361,8 @@ export class Wallet {
this.timerGroup = new TimerGroup();
const init = async () => {
- await this.fillDefaults().catch((e) => console.log(e));
- await this.collectGarbage().catch((e) => console.log(e));
+ await this.fillDefaults().catch(e => console.log(e));
+ await this.collectGarbage().catch(e => console.log(e));
this.updateExchanges();
this.resumePendingFromDb();
this.timerGroup.every(1000 * 60 * 15, () => this.updateExchanges());
@@ -353,20 +377,17 @@ export class Wallet {
};
const onFalse = (r: QueryRoot) => {
console.log("applying defaults");
- r.put(Stores.config, {key: "currencyDefaultsApplied", value: true})
+ r.put(Stores.config, { key: "currencyDefaultsApplied", value: true })
.putAll(Stores.currencies, builtinCurrencies)
.finish();
};
- await (
- this.q()
- .iter(Stores.config)
- .filter((x) => x.key === "currencyDefaultsApplied")
- .first()
- .cond((x) => x && x.value, onTrue, onFalse)
- );
+ await this.q()
+ .iter(Stores.config)
+ .filter(x => x.key === "currencyDefaultsApplied")
+ .first()
+ .cond(x => x && x.value, onTrue, onFalse);
}
-
private startOperation(operationId: string) {
this.runningOperations.add(operationId);
this.badge.startBusy();
@@ -383,15 +404,14 @@ export class Wallet {
console.log("updating exchanges");
const exchangesUrls = await this.q()
- .iter(Stores.exchanges)
- .map((e) => e.baseUrl)
- .toArray();
+ .iter(Stores.exchanges)
+ .map(e => e.baseUrl)
+ .toArray();
for (const url of exchangesUrls) {
- this.updateExchangeFromUrl(url)
- .catch((e) => {
- console.error("updating exchange failed", e);
- });
+ this.updateExchangeFromUrl(url).catch(e => {
+ console.error("updating exchange failed", e);
+ });
}
}
@@ -403,68 +423,69 @@ export class Wallet {
console.log("resuming pending operations from db");
this.q()
- .iter(Stores.reserves)
- .forEach((reserve) => {
- console.log("resuming reserve", reserve.reserve_pub);
- this.processReserve(reserve);
- });
+ .iter(Stores.reserves)
+ .forEach(reserve => {
+ console.log("resuming reserve", reserve.reserve_pub);
+ this.processReserve(reserve);
+ });
this.q()
- .iter(Stores.precoins)
- .forEach((preCoin) => {
- console.log("resuming precoin");
- this.processPreCoin(preCoin);
- });
+ .iter(Stores.precoins)
+ .forEach(preCoin => {
+ console.log("resuming precoin");
+ this.processPreCoin(preCoin);
+ });
this.q()
- .iter(Stores.refresh)
- .forEach((r: RefreshSessionRecord) => {
- this.continueRefreshSession(r);
- });
+ .iter(Stores.refresh)
+ .forEach((r: RefreshSessionRecord) => {
+ this.continueRefreshSession(r);
+ });
this.q()
- .iter(Stores.coinsReturns)
- .forEach((r: CoinsReturnRecord) => {
- this.depositReturnedCoins(r);
- });
+ .iter(Stores.coinsReturns)
+ .forEach((r: CoinsReturnRecord) => {
+ this.depositReturnedCoins(r);
+ });
// FIXME: optimize via index
this.q()
- .iter(Stores.coins)
- .forEach((c: CoinRecord) => {
- if (c.status === CoinStatus.Dirty) {
- console.log("resuming pending refresh for coin", c);
- this.refresh(c.coinPub);
- }
- });
+ .iter(Stores.coins)
+ .forEach((c: CoinRecord) => {
+ if (c.status === CoinStatus.Dirty) {
+ console.log("resuming pending refresh for coin", c);
+ this.refresh(c.coinPub);
+ }
+ });
}
-
- private async getCoinsForReturn(exchangeBaseUrl: string, amount: AmountJson): Promise<CoinWithDenom[] | undefined> {
+ private async getCoinsForReturn(
+ exchangeBaseUrl: string,
+ amount: AmountJson,
+ ): Promise<CoinWithDenom[] | undefined> {
const exchange = await this.q().get(Stores.exchanges, exchangeBaseUrl);
if (!exchange) {
throw Error(`Exchange ${exchangeBaseUrl} not known to the wallet`);
}
- const coins: CoinRecord[] = await (
- this.q()
- .iterIndex(Stores.coins.exchangeBaseUrlIndex, exchange.baseUrl)
- .toArray()
- );
+ const coins: CoinRecord[] = await this.q()
+ .iterIndex(Stores.coins.exchangeBaseUrlIndex, exchange.baseUrl)
+ .toArray();
if (!coins || !coins.length) {
return [];
}
- const denoms = await this.q().iterIndex(Stores.denominations.exchangeBaseUrlIndex, exchange.baseUrl).toArray();
+ const denoms = await this.q()
+ .iterIndex(Stores.denominations.exchangeBaseUrlIndex, exchange.baseUrl)
+ .toArray();
// Denomination of the first coin, we assume that all other
// coins have the same currency
- const firstDenom = await this.q().get(Stores.denominations,
- [
- exchange.baseUrl,
- coins[0].denomPub,
- ]);
+ const firstDenom = await this.q().get(Stores.denominations, [
+ exchange.baseUrl,
+ coins[0].denomPub,
+ ]);
if (!firstDenom) {
throw Error("db inconsistent");
}
@@ -472,13 +493,19 @@ export class Wallet {
const cds: CoinWithDenom[] = [];
for (const coin of coins) {
- const denom = await this.q().get(Stores.denominations,
- [exchange.baseUrl, coin.denomPub]);
+ const denom = await this.q().get(Stores.denominations, [
+ exchange.baseUrl,
+ coin.denomPub,
+ ]);
if (!denom) {
throw Error("db inconsistent");
}
if (denom.value.currency !== currency) {
- console.warn(`same pubkey for different currencies at exchange ${exchange.baseUrl}`);
+ console.warn(
+ `same pubkey for different currencies at exchange ${
+ exchange.baseUrl
+ }`,
+ );
continue;
}
if (coin.suspended) {
@@ -487,10 +514,10 @@ export class Wallet {
if (coin.status !== CoinStatus.Fresh) {
continue;
}
- cds.push({coin, denom});
+ cds.push({ coin, denom });
}
- console.log("coin return: selecting from possible coins", { cds, amount } );
+ console.log("coin return: selecting from possible coins", { cds, amount });
const res = selectPayCoins(denoms, cds, amount, amount);
if (res) {
@@ -499,12 +526,13 @@ export class Wallet {
return undefined;
}
-
/**
* Get exchanges and associated coins that are still spendable,
* but only if the sum the coins' remaining value exceeds the payment amount.
*/
- private async getCoinsForPayment(args: CoinsForPaymentArgs): Promise<CoinSelectionResult|undefined> {
+ private async getCoinsForPayment(
+ args: CoinsForPaymentArgs,
+ ): Promise<CoinSelectionResult | undefined> {
const {
allowedAuditors,
allowedExchanges,
@@ -518,7 +546,9 @@ export class Wallet {
let remainingAmount = paymentAmount;
- const exchanges = await this.q().iter(Stores.exchanges).toArray();
+ const exchanges = await this.q()
+ .iter(Stores.exchanges)
+ .toArray();
for (const exchange of exchanges) {
let isOkay: boolean = false;
@@ -551,34 +581,40 @@ export class Wallet {
}
const coins: CoinRecord[] = await this.q()
- .iterIndex(Stores.coins.exchangeBaseUrlIndex,
- exchange.baseUrl)
- .toArray();
- const denoms = await this.q().iterIndex(Stores.denominations.exchangeBaseUrlIndex, exchange.baseUrl).toArray();
+ .iterIndex(Stores.coins.exchangeBaseUrlIndex, exchange.baseUrl)
+ .toArray();
+ const denoms = await this.q()
+ .iterIndex(Stores.denominations.exchangeBaseUrlIndex, exchange.baseUrl)
+ .toArray();
if (!coins || coins.length === 0) {
continue;
}
// Denomination of the first coin, we assume that all other
// coins have the same currency
- const firstDenom = await this.q().get(Stores.denominations,
- [
- exchange.baseUrl,
- coins[0].denomPub,
- ]);
+ const firstDenom = await this.q().get(Stores.denominations, [
+ exchange.baseUrl,
+ coins[0].denomPub,
+ ]);
if (!firstDenom) {
throw Error("db inconsistent");
}
const currency = firstDenom.value.currency;
const cds: CoinWithDenom[] = [];
for (const coin of coins) {
- const denom = await this.q().get(Stores.denominations,
- [exchange.baseUrl, coin.denomPub]);
+ const denom = await this.q().get(Stores.denominations, [
+ exchange.baseUrl,
+ coin.denomPub,
+ ]);
if (!denom) {
throw Error("db inconsistent");
}
if (denom.value.currency !== currency) {
- console.warn(`same pubkey for different currencies at exchange ${exchange.baseUrl}`);
+ console.warn(
+ `same pubkey for different currencies at exchange ${
+ exchange.baseUrl
+ }`,
+ );
continue;
}
if (coin.suspended) {
@@ -587,10 +623,13 @@ export class Wallet {
if (coin.status !== CoinStatus.Fresh) {
continue;
}
- cds.push({coin, denom});
+ cds.push({ coin, denom });
}
- const fees = await this.q().get(Stores.exchangeWireFees, exchange.baseUrl);
+ const fees = await this.q().get(
+ Stores.exchangeWireFees,
+ exchange.baseUrl,
+ );
if (!fees) {
console.error("no fees found for exchange", exchange);
continue;
@@ -603,8 +642,8 @@ export class Wallet {
console.log("payment coins: wireFeeTime", wireFeeTime);
let totalFees = Amounts.getZero(currency);
- let wireFee: AmountJson|undefined;
- for (const fee of (fees.feesForType[wireMethod] || [])) {
+ let wireFee: AmountJson | undefined;
+ for (const fee of fees.feesForType[wireMethod] || []) {
if (fee.startStamp <= wireFeeTime && fee.endStamp >= wireFeeTime) {
wireFee = fee.wireFee;
break;
@@ -617,7 +656,8 @@ export class Wallet {
const amortizedWireFee = Amounts.divide(wireFee, wireFeeAmortization);
if (Amounts.cmp(wireFeeLimit, amortizedWireFee) < 0) {
totalFees = Amounts.add(amortizedWireFee, totalFees).amount;
- remainingAmount = Amounts.add(amortizedWireFee, remainingAmount).amount;
+ remainingAmount = Amounts.add(amortizedWireFee, remainingAmount)
+ .amount;
}
}
@@ -635,14 +675,15 @@ export class Wallet {
return undefined;
}
-
/**
* Record all information that is necessary to
* pay for a proposal in the wallet's database.
*/
- private async recordConfirmPay(proposal: ProposalDownloadRecord,
- payCoinInfo: PayCoinInfo,
- chosenExchange: string): Promise<PurchaseRecord> {
+ private async recordConfirmPay(
+ proposal: ProposalDownloadRecord,
+ payCoinInfo: PayCoinInfo,
+ chosenExchange: string,
+ ): Promise<PurchaseRecord> {
const payReq: PayReq = {
coins: payCoinInfo.sigs,
merchant_pub: proposal.contractTerms.merchant_pub,
@@ -661,26 +702,28 @@ export class Wallet {
payReq,
refundsDone: {},
refundsPending: {},
- timestamp: (new Date()).getTime(),
+ timestamp: new Date().getTime(),
timestamp_refund: 0,
};
await this.q()
- .put(Stores.purchases, t)
- .putAll(Stores.coins, payCoinInfo.updatedCoins)
- .finish();
+ .put(Stores.purchases, t)
+ .putAll(Stores.coins, payCoinInfo.updatedCoins)
+ .finish();
this.badge.showNotification();
this.notifier.notify();
return t;
}
-
/**
* Download a proposal and store it in the database.
* Returns an id for it to retrieve it later.
*/
async downloadProposal(url: string): Promise<number> {
- const oldProposal = await this.q().getIndexed(Stores.proposals.urlIndex, url);
+ const oldProposal = await this.q().getIndexed(
+ Stores.proposals.urlIndex,
+ url,
+ );
if (oldProposal) {
return oldProposal.id!;
}
@@ -691,7 +734,7 @@ export class Wallet {
console.log("downloading contract from '" + urlWithNonce + "'");
let resp;
try {
- resp = await axios.get(urlWithNonce, { validateStatus: (s) => s === 200 });
+ resp = await axios.get(urlWithNonce, { validateStatus: s => s === 200 });
} catch (e) {
console.log("contract download failed", e);
throw e;
@@ -707,7 +750,7 @@ export class Wallet {
contractTermsHash,
merchantSig: proposal.sig,
noncePriv: priv,
- timestamp: (new Date()).getTime(),
+ timestamp: new Date().getTime(),
url,
};
@@ -719,16 +762,21 @@ export class Wallet {
return id;
}
-
async refundFailedPay(proposalId: number) {
console.log(`refunding failed payment with proposal id ${proposalId}`);
- const proposal: ProposalDownloadRecord|undefined = await this.q().get(Stores.proposals, proposalId);
+ const proposal: ProposalDownloadRecord | undefined = await this.q().get(
+ Stores.proposals,
+ proposalId,
+ );
if (!proposal) {
throw Error(`proposal with id ${proposalId} not found`);
}
- const purchase = await this.q().get(Stores.purchases, proposal.contractTermsHash);
+ const purchase = await this.q().get(
+ Stores.purchases,
+ proposal.contractTermsHash,
+ );
if (!purchase) {
throw Error("purchase not found for proposal");
}
@@ -738,8 +786,10 @@ export class Wallet {
}
}
-
- async submitPay(contractTermsHash: string, sessionId: string | undefined): Promise<ConfirmPayResult> {
+ async submitPay(
+ contractTermsHash: string,
+ sessionId: string | undefined,
+ ): Promise<ConfirmPayResult> {
const purchase = await this.q().get(Stores.purchases, contractTermsHash);
if (!purchase) {
throw Error("Purchase not found: " + contractTermsHash);
@@ -753,7 +803,7 @@ export class Wallet {
try {
const config = {
headers: { "Content-Type": "application/json;charset=UTF-8" },
- timeout: 5000, /* 5 seconds */
+ timeout: 5000 /* 5 seconds */,
validateStatus: (s: number) => s === 200,
};
resp = await axios.post(purchase.contractTerms.pay_url, payReq, config);
@@ -766,8 +816,10 @@ export class Wallet {
console.log("got success from pay_url");
const merchantPub = purchase.contractTerms.merchant_pub;
- const valid: boolean = await (
- this.cryptoApi.isValidPaymentSignature(merchantResp.sig, contractTermsHash, merchantPub)
+ const valid: boolean = await this.cryptoApi.isValidPaymentSignature(
+ merchantResp.sig,
+ contractTermsHash,
+ merchantPub,
);
if (!valid) {
console.error("merchant payment signature invalid");
@@ -795,31 +847,44 @@ export class Wallet {
}
await this.q()
- .putAll(Stores.coins, modifiedCoins)
- .put(Stores.purchases, purchase)
- .finish();
+ .putAll(Stores.coins, modifiedCoins)
+ .put(Stores.purchases, purchase)
+ .finish();
for (const c of purchase.payReq.coins) {
this.refresh(c.coin_pub);
}
const nextUrl = fu.href();
- this.cachedNextUrl[purchase.contractTerms.fulfillment_url] = { nextUrl, lastSessionId: sessionId };
+ this.cachedNextUrl[purchase.contractTerms.fulfillment_url] = {
+ nextUrl,
+ lastSessionId: sessionId,
+ };
return { nextUrl };
}
-
/**
* Add a contract to the wallet and sign coins, and send them.
*/
- async confirmPay(proposalId: number, sessionId: string | undefined): Promise<ConfirmPayResult> {
- console.log(`executing confirmPay with proposalId ${proposalId} and sessionId ${sessionId}`);
- const proposal: ProposalDownloadRecord|undefined = await this.q().get(Stores.proposals, proposalId);
+ async confirmPay(
+ proposalId: number,
+ sessionId: string | undefined,
+ ): Promise<ConfirmPayResult> {
+ console.log(
+ `executing confirmPay with proposalId ${proposalId} and sessionId ${sessionId}`,
+ );
+ const proposal: ProposalDownloadRecord | undefined = await this.q().get(
+ Stores.proposals,
+ proposalId,
+ );
if (!proposal) {
throw Error(`proposal with id ${proposalId} not found`);
}
- let purchase = await this.q().get(Stores.purchases, proposal.contractTermsHash);
+ let purchase = await this.q().get(
+ Stores.purchases,
+ proposal.contractTermsHash,
+ );
if (purchase) {
return this.submitPay(purchase.contractTermsHash, sessionId);
@@ -857,20 +922,33 @@ export class Wallet {
const sd = await this.getSpeculativePayData(proposalId);
if (!sd) {
const { exchangeUrl, cds, totalAmount } = res;
- const payCoinInfo = await this.cryptoApi.signDeposit(proposal.contractTerms, cds, totalAmount);
- purchase = await this.recordConfirmPay(proposal, payCoinInfo, exchangeUrl);
+ const payCoinInfo = await this.cryptoApi.signDeposit(
+ proposal.contractTerms,
+ cds,
+ totalAmount,
+ );
+ purchase = await this.recordConfirmPay(
+ proposal,
+ payCoinInfo,
+ exchangeUrl,
+ );
} else {
- purchase = await this.recordConfirmPay(sd.proposal, sd.payCoinInfo, sd.exchangeUrl);
+ purchase = await this.recordConfirmPay(
+ sd.proposal,
+ sd.payCoinInfo,
+ sd.exchangeUrl,
+ );
}
return this.submitPay(purchase.contractTermsHash, sessionId);
}
-
/**
* Get the speculative pay data, but only if coins have not changed in between.
*/
- async getSpeculativePayData(proposalId: number): Promise<SpeculativePayData | undefined> {
+ async getSpeculativePayData(
+ proposalId: number,
+ ): Promise<SpeculativePayData | undefined> {
const sp = this.speculativePayData;
if (!sp) {
return;
@@ -888,14 +966,15 @@ export class Wallet {
if (!currentCoin) {
return;
}
- if (Amounts.cmp(specCoin.currentAmount, currentCoin.currentAmount) !== 0) {
+ if (
+ Amounts.cmp(specCoin.currentAmount, currentCoin.currentAmount) !== 0
+ ) {
return;
}
}
return sp;
}
-
/**
* Check if payment for an offer is possible, or if the offer has already
* been payed for.
@@ -911,7 +990,10 @@ export class Wallet {
}
// First check if we already payed for it.
- const purchase = await this.q().get(Stores.purchases, proposal.contractTermsHash);
+ const purchase = await this.q().get(
+ Stores.purchases,
+ proposal.contractTermsHash,
+ );
if (purchase) {
return { status: "paid" };
}
@@ -943,9 +1025,17 @@ export class Wallet {
}
// Only create speculative signature if we don't already have one for this proposal
- if ((!this.speculativePayData) || (this.speculativePayData && this.speculativePayData.proposalId !== proposalId)) {
+ if (
+ !this.speculativePayData ||
+ (this.speculativePayData &&
+ this.speculativePayData.proposalId !== proposalId)
+ ) {
const { exchangeUrl, cds, totalAmount } = res;
- const payCoinInfo = await this.cryptoApi.signDeposit(proposal.contractTerms, cds, totalAmount);
+ const payCoinInfo = await this.cryptoApi.signDeposit(
+ proposal.contractTerms,
+ cds,
+ totalAmount,
+ );
this.speculativePayData = {
exchangeUrl,
payCoinInfo,
@@ -957,15 +1047,19 @@ export class Wallet {
return { status: "payment-possible", coinSelection: res };
}
-
/**
* Retrieve information required to pay for a contract, where the
* contract is identified via the fulfillment url.
*/
- async queryPaymentByFulfillmentUrl(url: string): Promise<PurchaseRecord | undefined> {
+ async queryPaymentByFulfillmentUrl(
+ url: string,
+ ): Promise<PurchaseRecord | undefined> {
console.log("query for payment", url);
- const t = await this.q().getIndexed(Stores.purchases.fulfillmentUrlIndex, url);
+ const t = await this.q().getIndexed(
+ Stores.purchases.fulfillmentUrlIndex,
+ url,
+ );
if (!t) {
console.log("query for payment failed");
@@ -975,13 +1069,14 @@ export class Wallet {
return t;
}
-
/**
* First fetch information requred to withdraw from the reserve,
* then deplete the reserve, withdrawing coins until it is empty.
*/
- private async processReserve(reserveRecord: ReserveRecord,
- retryDelayMs: number = 250): Promise<void> {
+ private async processReserve(
+ reserveRecord: ReserveRecord,
+ retryDelayMs: number = 250,
+ ): Promise<void> {
const opId = "reserve-" + reserveRecord.reserve_pub;
this.startOperation(opId);
@@ -990,38 +1085,54 @@ export class Wallet {
await this.depleteReserve(reserve);
} catch (e) {
// random, exponential backoff truncated at 3 minutes
- const nextDelay = Math.min(2 * retryDelayMs + retryDelayMs * Math.random(), 3000 * 60);
- console.warn(`Failed to deplete reserve, trying again in ${retryDelayMs} ms`);
- this.timerGroup.after(retryDelayMs, () => this.processReserve(reserveRecord, nextDelay));
+ const nextDelay = Math.min(
+ 2 * retryDelayMs + retryDelayMs * Math.random(),
+ 3000 * 60,
+ );
+ console.warn(
+ `Failed to deplete reserve, trying again in ${retryDelayMs} ms`,
+ );
+ this.timerGroup.after(retryDelayMs, () =>
+ this.processReserve(reserveRecord, nextDelay),
+ );
} finally {
this.stopOperation(opId);
}
}
-
/**
* Given a planchet, withdraw a coin from the exchange.
*/
- private async processPreCoin(preCoin: PreCoinRecord,
- retryDelayMs = 200): Promise<void> {
+ private async processPreCoin(
+ preCoin: PreCoinRecord,
+ retryDelayMs = 200,
+ ): Promise<void> {
// Throttle concurrent executions of this function, so we don't withdraw too many coins at once.
- if (this.processPreCoinConcurrent >= 4 || this.processPreCoinThrottle[preCoin.exchangeBaseUrl]) {
+ if (
+ this.processPreCoinConcurrent >= 4 ||
+ this.processPreCoinThrottle[preCoin.exchangeBaseUrl]
+ ) {
console.log("delaying processPreCoin");
- this.timerGroup.after(retryDelayMs,
- () => this.processPreCoin(preCoin, Math.min(retryDelayMs * 2, 5 * 60 * 1000)));
+ this.timerGroup.after(retryDelayMs, () =>
+ this.processPreCoin(preCoin, Math.min(retryDelayMs * 2, 5 * 60 * 1000)),
+ );
return;
}
console.log("executing processPreCoin", preCoin);
this.processPreCoinConcurrent++;
try {
- const exchange = await this.q().get(Stores.exchanges,
- preCoin.exchangeBaseUrl);
+ const exchange = await this.q().get(
+ Stores.exchanges,
+ preCoin.exchangeBaseUrl,
+ );
if (!exchange) {
console.error("db inconsistent: exchange for precoin not found");
return;
}
- const denom = await this.q().get(Stores.denominations,
- [preCoin.exchangeBaseUrl, preCoin.denomPub]);
+ const denom = await this.q().get(Stores.denominations, [
+ preCoin.exchangeBaseUrl,
+ preCoin.denomPub,
+ ]);
if (!denom) {
console.error("db inconsistent: denom for precoin not found");
return;
@@ -1031,11 +1142,17 @@ export class Wallet {
console.log("processPreCoin: got coin", coin);
const mutateReserve = (r: ReserveRecord) => {
+ console.log(
+ `before committing coin: current ${amountToPretty(
+ r.current_amount!,
+ )}, precoin: ${amountToPretty(r.precoin_amount)})}`,
+ );
- console.log(`before committing coin: current ${amountToPretty(r.current_amount!)}, precoin: ${amountToPretty(
- r.precoin_amount)})}`);
-
- const x = Amounts.sub(r.precoin_amount, preCoin.coinValue, denom.feeWithdraw);
+ const x = Amounts.sub(
+ r.precoin_amount,
+ preCoin.coinValue,
+ denom.feeWithdraw,
+ );
if (x.saturated) {
console.error("database inconsistent");
throw AbortTransaction;
@@ -1045,15 +1162,20 @@ export class Wallet {
};
await this.q()
- .mutate(Stores.reserves, preCoin.reservePub, mutateReserve)
- .delete(Stores.precoins, coin.coinPub)
- .add(Stores.coins, coin)
- .finish();
+ .mutate(Stores.reserves, preCoin.reservePub, mutateReserve)
+ .delete(Stores.precoins, coin.coinPub)
+ .add(Stores.coins, coin)
+ .finish();
if (coin.status === CoinStatus.TainedByTip) {
- const tip = await this.q().getIndexed(Stores.tips.coinPubIndex, coin.coinPub);
+ const tip = await this.q().getIndexed(
+ Stores.tips.coinPubIndex,
+ coin.coinPub,
+ );
if (!tip) {
- throw Error(`inconsistent DB: tip for coin pub ${coin.coinPub} not found.`);
+ throw Error(
+ `inconsistent DB: tip for coin pub ${coin.coinPub} not found.`,
+ );
}
if (tip.accepted) {
@@ -1075,44 +1197,55 @@ export class Wallet {
this.notifier.notify();
} catch (e) {
- console.error("Failed to withdraw coin from precoin, retrying in",
- retryDelayMs,
- "ms", e);
+ console.error(
+ "Failed to withdraw coin from precoin, retrying in",
+ retryDelayMs,
+ "ms",
+ e,
+ );
// exponential backoff truncated at one minute
const nextRetryDelayMs = Math.min(retryDelayMs * 2, 5 * 60 * 1000);
- this.timerGroup.after(retryDelayMs, () => this.processPreCoin(preCoin, nextRetryDelayMs));
-
- const currentThrottle = this.processPreCoinThrottle[preCoin.exchangeBaseUrl] || 0;
- this.processPreCoinThrottle[preCoin.exchangeBaseUrl] = currentThrottle + 1;
- this.timerGroup.after(retryDelayMs, () => {this.processPreCoinThrottle[preCoin.exchangeBaseUrl]--; });
+ this.timerGroup.after(retryDelayMs, () =>
+ this.processPreCoin(preCoin, nextRetryDelayMs),
+ );
+
+ const currentThrottle =
+ this.processPreCoinThrottle[preCoin.exchangeBaseUrl] || 0;
+ this.processPreCoinThrottle[preCoin.exchangeBaseUrl] =
+ currentThrottle + 1;
+ this.timerGroup.after(retryDelayMs, () => {
+ this.processPreCoinThrottle[preCoin.exchangeBaseUrl]--;
+ });
} finally {
this.processPreCoinConcurrent--;
}
}
-
/**
* Update the timestamp of when an exchange was used.
*/
async updateExchangeUsedTime(exchangeBaseUrl: string): Promise<void> {
- const now = (new Date()).getTime();
+ const now = new Date().getTime();
const update = (r: ExchangeRecord) => {
r.lastUsedTime = now;
return r;
};
- await this.q().mutate(Stores.exchanges, exchangeBaseUrl, update).finish();
+ await this.q()
+ .mutate(Stores.exchanges, exchangeBaseUrl, update)
+ .finish();
}
-
/**
* Create a reserve, but do not flag it as confirmed yet.
*
* Adds the corresponding exchange as a trusted exchange if it is neither
* audited nor trusted already.
*/
- async createReserve(req: CreateReserveRequest): Promise<CreateReserveResponse> {
+ async createReserve(
+ req: CreateReserveRequest,
+ ): Promise<CreateReserveResponse> {
const keypair = await this.cryptoApi.createEddsaKeypair();
- const now = (new Date()).getTime();
+ const now = new Date().getTime();
const canonExchange = canonicalizeBaseUrl(req.exchange);
const reserveRecord: ReserveRecord = {
@@ -1134,13 +1267,18 @@ export class Wallet {
const rec = {
paytoUri: senderWire,
};
- await this.q().put(Stores.senderWires, rec).finish();
+ await this.q()
+ .put(Stores.senderWires, rec)
+ .finish();
}
await this.updateExchangeUsedTime(req.exchange);
const exchangeInfo = await this.updateExchangeFromUrl(req.exchange);
- const {isAudited, isTrusted} = await this.getExchangeTrust(exchangeInfo);
- let currencyRecord = await this.q().get(Stores.currencies, exchangeInfo.currency);
+ const { isAudited, isTrusted } = await this.getExchangeTrust(exchangeInfo);
+ let currencyRecord = await this.q().get(
+ Stores.currencies,
+ exchangeInfo.currency,
+ );
if (!currencyRecord) {
currencyRecord = {
auditors: [],
@@ -1151,13 +1289,16 @@ export class Wallet {
}
if (!isAudited && !isTrusted) {
- currencyRecord.exchanges.push({baseUrl: req.exchange, exchangePub: exchangeInfo.masterPublicKey});
+ currencyRecord.exchanges.push({
+ baseUrl: req.exchange,
+ exchangePub: exchangeInfo.masterPublicKey,
+ });
}
await this.q()
- .put(Stores.currencies, currencyRecord)
- .put(Stores.reserves, reserveRecord)
- .finish();
+ .put(Stores.currencies, currencyRecord)
+ .put(Stores.reserves, reserveRecord)
+ .finish();
const r: CreateReserveResponse = {
exchange: canonExchange,
@@ -1166,7 +1307,6 @@ export class Wallet {
return r;
}
-
/**
* Mark an existing reserve as confirmed. The wallet will start trying
* to withdraw from that reserve. This may not immediately succeed,
@@ -1177,10 +1317,10 @@ export class Wallet {
* an unconfirmed reserve should be hidden.
*/
async confirmReserve(req: ConfirmReserveRequest): Promise<void> {
- const now = (new Date()).getTime();
- const reserve: ReserveRecord|undefined = await (
- this.q().get<ReserveRecord>(Stores.reserves,
- req.reservePub));
+ const now = new Date().getTime();
+ const reserve: ReserveRecord | undefined = await this.q().get<
+ ReserveRecord
+ >(Stores.reserves, req.reservePub);
if (!reserve) {
console.error("Unable to confirm reserve, not found in DB");
return;
@@ -1188,21 +1328,20 @@ export class Wallet {
console.log("reserve confirmed");
reserve.timestamp_confirmed = now;
await this.q()
- .put(Stores.reserves, reserve)
- .finish();
+ .put(Stores.reserves, reserve)
+ .finish();
this.notifier.notify();
this.processReserve(reserve);
}
-
private async withdrawExecute(pc: PreCoinRecord): Promise<CoinRecord> {
const wd: any = {};
wd.denom_pub_hash = pc.denomPubHash;
wd.reserve_pub = pc.reservePub;
wd.reserve_sig = pc.withdrawSig;
wd.coin_ev = pc.coinEv;
- const reqUrl = (new URI("reserve/withdraw")).absoluteTo(pc.exchangeBaseUrl);
+ const reqUrl = new URI("reserve/withdraw").absoluteTo(pc.exchangeBaseUrl);
const resp = await this.http.postJson(reqUrl.href(), wd);
if (resp.status !== 200) {
@@ -1212,9 +1351,11 @@ export class Wallet {
});
}
const r = JSON.parse(resp.responseText);
- const denomSig = await this.cryptoApi.rsaUnblind(r.ev_sig,
- pc.blindingKey,
- pc.denomPub);
+ const denomSig = await this.cryptoApi.rsaUnblind(
+ r.ev_sig,
+ pc.blindingKey,
+ pc.denomPub,
+ );
const coin: CoinRecord = {
blindingKey: pc.blindingKey,
coinPriv: pc.coinPriv,
@@ -1230,7 +1371,6 @@ export class Wallet {
return coin;
}
-
/**
* Withdraw coins from a reserve until it is empty.
*
@@ -1246,23 +1386,32 @@ export class Wallet {
if (!withdrawAmount) {
throw Error("can't withdraw when amount is unknown");
}
- const denomsForWithdraw = await this.getVerifiedWithdrawDenomList(reserve.exchange_base_url, withdrawAmount);
- const smallestAmount = await this.getVerifiedSmallestWithdrawAmount(reserve.exchange_base_url);
+ const denomsForWithdraw = await this.getVerifiedWithdrawDenomList(
+ reserve.exchange_base_url,
+ withdrawAmount,
+ );
+ const smallestAmount = await this.getVerifiedSmallestWithdrawAmount(
+ reserve.exchange_base_url,
+ );
console.log(`withdrawing ${denomsForWithdraw.length} coins`);
- const ps = denomsForWithdraw.map(async(denom) => {
+ const ps = denomsForWithdraw.map(async denom => {
function mutateReserve(r: ReserveRecord): ReserveRecord {
const currentAmount = r.current_amount;
if (!currentAmount) {
throw Error("can't withdraw when amount is unknown");
}
- r.precoin_amount = Amounts.add(r.precoin_amount,
- denom.value,
- denom.feeWithdraw).amount;
- const result = Amounts.sub(currentAmount,
- denom.value,
- denom.feeWithdraw);
+ r.precoin_amount = Amounts.add(
+ r.precoin_amount,
+ denom.value,
+ denom.feeWithdraw,
+ ).amount;
+ const result = Amounts.sub(
+ currentAmount,
+ denom.value,
+ denom.feeWithdraw,
+ );
if (result.saturated) {
console.error("can't create precoin, saturated");
throw AbortTransaction;
@@ -1271,39 +1420,44 @@ export class Wallet {
// Reserve is depleted if the amount left is too small to withdraw
if (Amounts.cmp(r.current_amount, smallestAmount) < 0) {
- r.timestamp_depleted = (new Date()).getTime();
+ r.timestamp_depleted = new Date().getTime();
}
- console.log(`after creating precoin: current ${amountToPretty(r.current_amount)}, precoin: ${amountToPretty(
- r.precoin_amount)})}`);
+ console.log(
+ `after creating precoin: current ${amountToPretty(
+ r.current_amount,
+ )}, precoin: ${amountToPretty(r.precoin_amount)})}`,
+ );
return r;
}
- const preCoin = await this.cryptoApi
- .createPreCoin(denom, reserve);
+ const preCoin = await this.cryptoApi.createPreCoin(denom, reserve);
await this.q()
- .put(Stores.precoins, preCoin)
- .mutate(Stores.reserves, reserve.reserve_pub, mutateReserve);
+ .put(Stores.precoins, preCoin)
+ .mutate(Stores.reserves, reserve.reserve_pub, mutateReserve);
await this.processPreCoin(preCoin);
});
await Promise.all(ps);
}
-
/**
* Update the information about a reserve that is stored in the wallet
* by quering the reserve's exchange.
*/
private async updateReserve(reservePub: string): Promise<ReserveRecord> {
- const reserve = await this.q()
- .get<ReserveRecord>(Stores.reserves, reservePub);
+ const reserve = await this.q().get<ReserveRecord>(
+ Stores.reserves,
+ reservePub,
+ );
if (!reserve) {
throw Error("reserve not in db");
}
- const reqUrl = new URI("reserve/status").absoluteTo(reserve.exchange_base_url);
- reqUrl.query({reserve_pub: reservePub});
+ const reqUrl = new URI("reserve/status").absoluteTo(
+ reserve.exchange_base_url,
+ );
+ reqUrl.query({ reserve_pub: reservePub });
const resp = await this.http.get(reqUrl.href());
if (resp.status !== 200) {
throw Error();
@@ -1314,13 +1468,12 @@ export class Wallet {
}
reserve.current_amount = Amounts.parseOrThrow(reserveInfo.balance);
await this.q()
- .put(Stores.reserves, reserve)
- .finish();
+ .put(Stores.reserves, reserve)
+ .finish();
this.notifier.notify();
return reserve;
}
-
/**
* Get the wire information for the exchange with the given base URL.
*/
@@ -1341,35 +1494,39 @@ export class Wallet {
return ExchangeWireJson.checked(wiJson);
}
-
async getPossibleDenoms(exchangeBaseUrl: string) {
- return (
- this.q().iterIndex(Stores.denominations.exchangeBaseUrlIndex,
- exchangeBaseUrl)
- .filter((d) => d.status === DenominationStatus.Unverified || d.status === DenominationStatus.VerifiedGood)
- .toArray()
- );
+ return this.q()
+ .iterIndex(Stores.denominations.exchangeBaseUrlIndex, exchangeBaseUrl)
+ .filter(
+ d =>
+ d.status === DenominationStatus.Unverified ||
+ d.status === DenominationStatus.VerifiedGood,
+ )
+ .toArray();
}
-
/**
* Compute the smallest withdrawable amount possible, based on verified denominations.
*
* Writes to the DB in order to record the result from verifying
* denominations.
*/
- async getVerifiedSmallestWithdrawAmount(exchangeBaseUrl: string): Promise<AmountJson> {
+ async getVerifiedSmallestWithdrawAmount(
+ exchangeBaseUrl: string,
+ ): Promise<AmountJson> {
const exchange = await this.q().get(Stores.exchanges, exchangeBaseUrl);
if (!exchange) {
throw Error(`exchange ${exchangeBaseUrl} not found`);
}
- const possibleDenoms = await (
- this.q().iterIndex(Stores.denominations.exchangeBaseUrlIndex,
- exchange.baseUrl)
- .filter((d) => d.status === DenominationStatus.Unverified || d.status === DenominationStatus.VerifiedGood)
- .toArray()
- );
+ const possibleDenoms = await this.q()
+ .iterIndex(Stores.denominations.exchangeBaseUrlIndex, exchange.baseUrl)
+ .filter(
+ d =>
+ d.status === DenominationStatus.Unverified ||
+ d.status === DenominationStatus.VerifiedGood,
+ )
+ .toArray();
possibleDenoms.sort((d1, d2) => {
const a1 = Amounts.add(d1.feeWithdraw, d1.value).amount;
const a2 = Amounts.add(d2.feeWithdraw, d2.value).amount;
@@ -1381,14 +1538,18 @@ export class Wallet {
return Amounts.add(denom.feeWithdraw, denom.value).amount;
}
console.log(`verifying denom ${denom.denomPub.substr(0, 15)}`);
- const valid = await this.cryptoApi.isValidDenom(denom,
- exchange.masterPublicKey);
+ const valid = await this.cryptoApi.isValidDenom(
+ denom,
+ exchange.masterPublicKey,
+ );
if (!valid) {
denom.status = DenominationStatus.VerifiedBad;
} else {
denom.status = DenominationStatus.VerifiedGood;
}
- await this.q().put(Stores.denominations, denom).finish();
+ await this.q()
+ .put(Stores.denominations, denom)
+ .finish();
if (valid) {
return Amounts.add(denom.feeWithdraw, denom.value).amount;
}
@@ -1396,7 +1557,6 @@ export class Wallet {
return Amounts.getZero(exchange.currency);
}
-
/**
* Get a list of denominations to withdraw from the given exchange for the
* given amount, making sure that all denominations' signatures are verified.
@@ -1404,19 +1564,23 @@ export class Wallet {
* Writes to the DB in order to record the result from verifying
* denominations.
*/
- async getVerifiedWithdrawDenomList(exchangeBaseUrl: string,
- amount: AmountJson): Promise<DenominationRecord[]> {
+ async getVerifiedWithdrawDenomList(
+ exchangeBaseUrl: string,
+ amount: AmountJson,
+ ): Promise<DenominationRecord[]> {
const exchange = await this.q().get(Stores.exchanges, exchangeBaseUrl);
if (!exchange) {
throw Error(`exchange ${exchangeBaseUrl} not found`);
}
- const possibleDenoms = await (
- this.q().iterIndex(Stores.denominations.exchangeBaseUrlIndex,
- exchange.baseUrl)
- .filter((d) => d.status === DenominationStatus.Unverified || d.status === DenominationStatus.VerifiedGood)
- .toArray()
- );
+ const possibleDenoms = await this.q()
+ .iterIndex(Stores.denominations.exchangeBaseUrlIndex, exchange.baseUrl)
+ .filter(
+ d =>
+ d.status === DenominationStatus.Unverified ||
+ d.status === DenominationStatus.VerifiedGood,
+ )
+ .toArray();
let allValid = false;
@@ -1429,8 +1593,10 @@ export class Wallet {
for (const denom of selectedDenoms || []) {
if (denom.status === DenominationStatus.Unverified) {
console.log(`verifying denom ${denom.denomPub.substr(0, 15)}`);
- const valid = await this.cryptoApi.isValidDenom(denom,
- exchange.masterPublicKey);
+ const valid = await this.cryptoApi.isValidDenom(
+ denom,
+ exchange.masterPublicKey,
+ );
if (!valid) {
denom.status = DenominationStatus.VerifiedBad;
allValid = false;
@@ -1438,7 +1604,9 @@ export class Wallet {
denom.status = DenominationStatus.VerifiedGood;
nextPossibleDenoms.push(denom);
}
- await this.q().put(Stores.denominations, denom).finish();
+ await this.q()
+ .put(Stores.denominations, denom)
+ .finish();
} else {
nextPossibleDenoms.push(denom);
}
@@ -1448,14 +1616,18 @@ export class Wallet {
return selectedDenoms;
}
-
/**
* Check if and how an exchange is trusted and/or audited.
*/
- async getExchangeTrust(exchangeInfo: ExchangeRecord): Promise<{isTrusted: boolean, isAudited: boolean}> {
+ async getExchangeTrust(
+ exchangeInfo: ExchangeRecord,
+ ): Promise<{ isTrusted: boolean; isAudited: boolean }> {
let isTrusted = false;
let isAudited = false;
- const currencyRecord = await this.q().get(Stores.currencies, exchangeInfo.currency);
+ const currencyRecord = await this.q().get(
+ Stores.currencies,
+ exchangeInfo.currency,
+ );
if (currencyRecord) {
for (const trustedExchange of currencyRecord.exchanges) {
if (trustedExchange.exchangePub === exchangeInfo.masterPublicKey) {
@@ -1472,22 +1644,27 @@ export class Wallet {
}
}
}
- return {isTrusted, isAudited};
+ return { isTrusted, isAudited };
}
- async getReserveCreationInfo(baseUrl: string,
- amount: AmountJson): Promise<ReserveCreationInfo> {
+ async getReserveCreationInfo(
+ baseUrl: string,
+ amount: AmountJson,
+ ): Promise<ReserveCreationInfo> {
const exchangeInfo = await this.updateExchangeFromUrl(baseUrl);
- const selectedDenoms = await this.getVerifiedWithdrawDenomList(baseUrl,
- amount);
+ const selectedDenoms = await this.getVerifiedWithdrawDenomList(
+ baseUrl,
+ amount,
+ );
let acc = Amounts.getZero(amount.currency);
for (const d of selectedDenoms) {
acc = Amounts.add(acc, d.feeWithdraw).amount;
}
const actualCoinCost = selectedDenoms
- .map((d: DenominationRecord) => Amounts.add(d.value,
- d.feeWithdraw).amount)
+ .map(
+ (d: DenominationRecord) => Amounts.add(d.value, d.feeWithdraw).amount,
+ )
.reduce((a, b) => Amounts.add(a, b).amount);
const wireInfo = await this.getWireInfo(baseUrl);
@@ -1503,7 +1680,7 @@ export class Wallet {
exchangeWireAccounts.push(account.url);
}
- const {isTrusted, isAudited} = await this.getExchangeTrust(exchangeInfo);
+ const { isTrusted, isAudited } = await this.getExchangeTrust(exchangeInfo);
let earliestDepositExpiration = Infinity;
for (const denom of selectedDenoms) {
@@ -1513,23 +1690,35 @@ export class Wallet {
}
}
- const possibleDenoms = await (
- this.q().iterIndex(Stores.denominations.exchangeBaseUrlIndex, baseUrl)
- .filter((d) => d.isOffered)
- .toArray()
- ) || [];
+ const possibleDenoms =
+ (await this.q()
+ .iterIndex(Stores.denominations.exchangeBaseUrlIndex, baseUrl)
+ .filter(d => d.isOffered)
+ .toArray()) || [];
const trustedAuditorPubs = [];
- const currencyRecord = await this.q().get<CurrencyRecord>(Stores.currencies, amount.currency);
+ const currencyRecord = await this.q().get<CurrencyRecord>(
+ Stores.currencies,
+ amount.currency,
+ );
if (currencyRecord) {
- trustedAuditorPubs.push(...currencyRecord.auditors.map((a) => a.auditorPub));
+ trustedAuditorPubs.push(
+ ...currencyRecord.auditors.map(a => a.auditorPub),
+ );
}
let versionMatch;
if (exchangeInfo.protocolVersion) {
- versionMatch = LibtoolVersion.compare(WALLET_PROTOCOL_VERSION, exchangeInfo.protocolVersion);
-
- if (versionMatch && !versionMatch.compatible && versionMatch.currentCmp === -1) {
+ versionMatch = LibtoolVersion.compare(
+ WALLET_PROTOCOL_VERSION,
+ exchangeInfo.protocolVersion,
+ );
+
+ if (
+ versionMatch &&
+ !versionMatch.compatible &&
+ versionMatch.currentCmp === -1
+ ) {
console.log("wallet version might be outdated, checking for updates");
chrome.runtime.requestUpdateCheck((status, details) => {
console.log("update check status:", status);
@@ -1556,7 +1745,6 @@ export class Wallet {
return ret;
}
-
/**
* Update or add exchange DB entry by fetching the /keys information.
* Optionally link the reserve entry to the new or existing
@@ -1569,25 +1757,32 @@ export class Wallet {
if (keysResp.status !== 200) {
throw Error("/keys request failed");
}
- const exchangeKeysJson = KeysJson.checked(JSON.parse(keysResp.responseText));
+ const exchangeKeysJson = KeysJson.checked(
+ JSON.parse(keysResp.responseText),
+ );
const exchangeWire = await this.getWireInfo(baseUrl);
return this.updateExchangeFromJson(baseUrl, exchangeKeysJson, exchangeWire);
}
-
private async suspendCoins(exchangeInfo: ExchangeRecord): Promise<void> {
- const resultSuspendedCoins = await (
- this.q()
- .iterIndex(Stores.coins.exchangeBaseUrlIndex, exchangeInfo.baseUrl)
- .indexJoinLeft(Stores.denominations.exchangeBaseUrlIndex,
- (e) => e.exchangeBaseUrl)
- .fold((cd: JoinLeftResult<CoinRecord, DenominationRecord>,
- suspendedCoins: CoinRecord[]) => {
- if ((!cd.right) || (!cd.right.isOffered)) {
- return Array.prototype.concat(suspendedCoins, [cd.left]);
- }
- return Array.prototype.concat(suspendedCoins);
- }, []));
+ const resultSuspendedCoins = await this.q()
+ .iterIndex(Stores.coins.exchangeBaseUrlIndex, exchangeInfo.baseUrl)
+ .indexJoinLeft(
+ Stores.denominations.exchangeBaseUrlIndex,
+ e => e.exchangeBaseUrl,
+ )
+ .fold(
+ (
+ cd: JoinLeftResult<CoinRecord, DenominationRecord>,
+ suspendedCoins: CoinRecord[],
+ ) => {
+ if (!cd.right || !cd.right.isOffered) {
+ return Array.prototype.concat(suspendedCoins, [cd.left]);
+ }
+ return Array.prototype.concat(suspendedCoins);
+ },
+ [],
+ );
const q = this.q();
resultSuspendedCoins.map((c: CoinRecord) => {
@@ -1600,11 +1795,11 @@ export class Wallet {
await q.finish();
}
-
- private async updateExchangeFromJson(baseUrl: string,
- exchangeKeysJson: KeysJson,
- wireMethodDetails: ExchangeWireJson): Promise<ExchangeRecord> {
-
+ private async updateExchangeFromJson(
+ baseUrl: string,
+ exchangeKeysJson: KeysJson,
+ wireMethodDetails: ExchangeWireJson,
+ ): Promise<ExchangeRecord> {
// FIXME: all this should probably be commited atomically
const updateTimeSec = getTalerStampSec(exchangeKeysJson.list_issue_date);
if (updateTimeSec === null) {
@@ -1625,7 +1820,8 @@ export class Wallet {
exchangeInfo = {
auditors: exchangeKeysJson.auditors,
baseUrl,
- currency: Amounts.parseOrThrow(exchangeKeysJson.denoms[0].value).currency,
+ currency: Amounts.parseOrThrow(exchangeKeysJson.denoms[0].value)
+ .currency,
lastUpdateTime: updateTimeSec,
lastUsedTime: 0,
masterPublicKey: exchangeKeysJson.master_public_key,
@@ -1641,14 +1837,16 @@ export class Wallet {
console.log("updating old exchange");
}
- const updatedExchangeInfo = await this.updateExchangeInfo(exchangeInfo,
- exchangeKeysJson);
+ const updatedExchangeInfo = await this.updateExchangeInfo(
+ exchangeInfo,
+ exchangeKeysJson,
+ );
await this.suspendCoins(updatedExchangeInfo);
updatedExchangeInfo.protocolVersion = exchangeKeysJson.version;
await this.q()
- .put(Stores.exchanges, updatedExchangeInfo)
- .finish();
+ .put(Stores.exchanges, updatedExchangeInfo)
+ .finish();
let oldWireFees = await this.q().get(Stores.exchangeWireFees, baseUrl);
if (!oldWireFees) {
@@ -1689,7 +1887,11 @@ export class Wallet {
startStamp: start,
wireFee: Amounts.parseOrThrow(fee.wire_fee),
};
- const valid: boolean = await this.cryptoApi.isValidWireFee(paytoTargetType, wf, exchangeInfo.masterPublicKey);
+ const valid: boolean = await this.cryptoApi.isValidWireFee(
+ paytoTargetType,
+ wf,
+ exchangeInfo.masterPublicKey,
+ );
if (!valid) {
console.error("fee signature invalid", fee);
throw Error("fee signature invalid");
@@ -1702,12 +1904,17 @@ export class Wallet {
if (exchangeKeysJson.payback) {
for (const payback of exchangeKeysJson.payback) {
- const denom = await this.q().getIndexed(Stores.denominations.denomPubHashIndex, payback.h_denom_pub);
+ const denom = await this.q().getIndexed(
+ Stores.denominations.denomPubHashIndex,
+ payback.h_denom_pub,
+ );
if (!denom) {
continue;
}
console.log(`cashing back denom`, denom);
- const coins = await this.q().iterIndex(Stores.coins.denomPubIndex, denom.denomPub).toArray();
+ const coins = await this.q()
+ .iterIndex(Stores.coins.denomPubIndex, denom.denomPub)
+ .toArray();
for (const coin of coins) {
this.payback(coin.coinPub);
}
@@ -1717,19 +1924,27 @@ export class Wallet {
return updatedExchangeInfo;
}
-
- private async updateExchangeInfo(exchangeInfo: ExchangeRecord,
- newKeys: KeysJson): Promise<ExchangeRecord> {
+ private async updateExchangeInfo(
+ exchangeInfo: ExchangeRecord,
+ newKeys: KeysJson,
+ ): Promise<ExchangeRecord> {
if (exchangeInfo.masterPublicKey !== newKeys.master_public_key) {
throw Error("public keys do not match");
}
- const existingDenoms: {[denomPub: string]: DenominationRecord} = await (
- this.q().iterIndex(Stores.denominations.exchangeBaseUrlIndex,
- exchangeInfo.baseUrl)
- .fold((x: DenominationRecord,
- acc: typeof existingDenoms) => (acc[x.denomPub] = x, acc), {})
- );
+ const existingDenoms: {
+ [denomPub: string]: DenominationRecord;
+ } = await this.q()
+ .iterIndex(
+ Stores.denominations.exchangeBaseUrlIndex,
+ exchangeInfo.baseUrl,
+ )
+ .fold(
+ (x: DenominationRecord, acc: typeof existingDenoms) => (
+ (acc[x.denomPub] = x), acc
+ ),
+ {},
+ );
const newDenoms: typeof existingDenoms = {};
const newAndUnseenDenoms: typeof existingDenoms = {};
@@ -1750,15 +1965,18 @@ export class Wallet {
}
await this.q()
- .putAll(Stores.denominations,
- Object.keys(newAndUnseenDenoms).map((d) => newAndUnseenDenoms[d]))
- .putAll(Stores.denominations,
- Object.keys(existingDenoms).map((d) => existingDenoms[d]))
- .finish();
+ .putAll(
+ Stores.denominations,
+ Object.keys(newAndUnseenDenoms).map(d => newAndUnseenDenoms[d]),
+ )
+ .putAll(
+ Stores.denominations,
+ Object.keys(existingDenoms).map(d => existingDenoms[d]),
+ )
+ .finish();
return exchangeInfo;
}
-
/**
* Get detailed balance information, sliced by exchange and by currency.
*/
@@ -1767,15 +1985,24 @@ export class Wallet {
* Add amount to a balance field, both for
* the slicing by exchange and currency.
*/
- function addTo(balance: WalletBalance,
- field: keyof WalletBalanceEntry,
- amount: AmountJson,
- exchange: string): void {
+ function addTo(
+ balance: WalletBalance,
+ field: keyof WalletBalanceEntry,
+ amount: AmountJson,
+ exchange: string,
+ ): void {
const z = Amounts.getZero(amount.currency);
- const balanceIdentity = {available: z, paybackAmount: z, pendingIncoming: z, pendingPayment: z};
+ const balanceIdentity = {
+ available: z,
+ paybackAmount: z,
+ pendingIncoming: z,
+ pendingPayment: z,
+ };
let entryCurr = balance.byCurrency[amount.currency];
if (!entryCurr) {
- balance.byCurrency[amount.currency] = entryCurr = { ...balanceIdentity };
+ balance.byCurrency[amount.currency] = entryCurr = {
+ ...balanceIdentity,
+ };
}
let entryEx = balance.byExchange[exchange];
if (!entryEx) {
@@ -1819,14 +2046,19 @@ export class Wallet {
if (!r.hasPayback) {
return balance;
}
- if (Amounts.cmp(smallestWithdraw[r.exchange_base_url], r.current_amount!) < 0) {
+ if (
+ Amounts.cmp(smallestWithdraw[r.exchange_base_url], r.current_amount!) <
+ 0
+ ) {
addTo(balance, "paybackAmount", r.current_amount!, r.exchange_base_url);
}
return balance;
}
- function collectPendingRefresh(r: RefreshSessionRecord,
- balance: WalletBalance) {
+ function collectPendingRefresh(
+ r: RefreshSessionRecord,
+ balance: WalletBalance,
+ ) {
// Don't count finished refreshes, since the refresh already resulted
// in coins being added to the wallet.
if (r.finished) {
@@ -1842,13 +2074,20 @@ export class Wallet {
return balance;
}
for (const c of t.payReq.coins) {
- addTo(balance, "pendingPayment", Amounts.parseOrThrow(c.contribution), c.exchange_url);
+ addTo(
+ balance,
+ "pendingPayment",
+ Amounts.parseOrThrow(c.contribution),
+ c.exchange_url,
+ );
}
return balance;
}
- function collectSmallestWithdraw(e: JoinResult<ExchangeRecord, DenominationRecord>,
- sw: any) {
+ function collectSmallestWithdraw(
+ e: JoinResult<ExchangeRecord, DenominationRecord>,
+ sw: any,
+ ) {
let min = sw[e.left.baseUrl];
const v = Amounts.add(e.right.value, e.right.feeWithdraw).amount;
if (!min) {
@@ -1866,31 +2105,26 @@ export class Wallet {
};
// Mapping from exchange pub to smallest
// possible amount we can withdraw
- let smallestWithdraw: {[baseUrl: string]: AmountJson} = {};
+ let smallestWithdraw: { [baseUrl: string]: AmountJson } = {};
- smallestWithdraw = await (this.q()
- .iter(Stores.exchanges)
- .indexJoin(Stores.denominations.exchangeBaseUrlIndex,
- (x) => x.baseUrl)
- .fold(collectSmallestWithdraw, {}));
+ smallestWithdraw = await this.q()
+ .iter(Stores.exchanges)
+ .indexJoin(Stores.denominations.exchangeBaseUrlIndex, x => x.baseUrl)
+ .fold(collectSmallestWithdraw, {});
const tx = this.q();
- tx.iter(Stores.coins)
- .fold(collectBalances, balanceStore);
- tx.iter(Stores.refresh)
- .fold(collectPendingRefresh, balanceStore);
- tx.iter(Stores.reserves)
- .fold(collectPendingWithdraw, balanceStore);
- tx.iter(Stores.reserves)
- .fold(collectPaybacks, balanceStore);
- tx.iter(Stores.purchases)
- .fold(collectPayments, balanceStore);
+ tx.iter(Stores.coins).fold(collectBalances, balanceStore);
+ tx.iter(Stores.refresh).fold(collectPendingRefresh, balanceStore);
+ tx.iter(Stores.reserves).fold(collectPendingWithdraw, balanceStore);
+ tx.iter(Stores.reserves).fold(collectPaybacks, balanceStore);
+ tx.iter(Stores.purchases).fold(collectPayments, balanceStore);
await tx.finish();
return balanceStore;
}
-
- async createRefreshSession(oldCoinPub: string): Promise<RefreshSessionRecord|undefined> {
+ async createRefreshSession(
+ oldCoinPub: string,
+ ): Promise<RefreshSessionRecord | undefined> {
const coin = await this.q().get<CoinRecord>(Stores.coins, oldCoinPub);
if (!coin) {
@@ -1907,48 +2141,52 @@ export class Wallet {
throw Error("db inconsistent");
}
- const oldDenom = await this.q().get(Stores.denominations,
- [exchange.baseUrl, coin.denomPub]);
+ const oldDenom = await this.q().get(Stores.denominations, [
+ exchange.baseUrl,
+ coin.denomPub,
+ ]);
if (!oldDenom) {
throw Error("db inconsistent");
}
- const availableDenoms: DenominationRecord[] = await (
- this.q()
- .iterIndex(Stores.denominations.exchangeBaseUrlIndex,
- exchange.baseUrl)
- .toArray()
- );
+ const availableDenoms: DenominationRecord[] = await this.q()
+ .iterIndex(Stores.denominations.exchangeBaseUrlIndex, exchange.baseUrl)
+ .toArray();
- const availableAmount = Amounts.sub(coin.currentAmount,
- oldDenom.feeRefresh).amount;
+ const availableAmount = Amounts.sub(coin.currentAmount, oldDenom.feeRefresh)
+ .amount;
- const newCoinDenoms = getWithdrawDenomList(availableAmount,
- availableDenoms);
+ const newCoinDenoms = getWithdrawDenomList(
+ availableAmount,
+ availableDenoms,
+ );
console.log("refreshing coin", coin);
console.log("refreshing into", newCoinDenoms);
if (newCoinDenoms.length === 0) {
- console.log(`not refreshing, available amount ${amountToPretty(availableAmount)} too small`);
+ console.log(
+ `not refreshing, available amount ${amountToPretty(
+ availableAmount,
+ )} too small`,
+ );
coin.status = CoinStatus.Useless;
await this.q().put(Stores.coins, coin);
this.notifier.notify();
return undefined;
}
-
- const refreshSession: RefreshSessionRecord = await (
- this.cryptoApi.createRefreshSession(exchange.baseUrl,
- 3,
- coin,
- newCoinDenoms,
- oldDenom.feeRefresh));
+ const refreshSession: RefreshSessionRecord = await this.cryptoApi.createRefreshSession(
+ exchange.baseUrl,
+ 3,
+ coin,
+ newCoinDenoms,
+ oldDenom.feeRefresh,
+ );
function mutateCoin(c: CoinRecord): CoinRecord {
- const r = Amounts.sub(c.currentAmount,
- refreshSession.valueWithFee);
+ const r = Amounts.sub(c.currentAmount, refreshSession.valueWithFee);
if (r.saturated) {
// Something else must have written the coin value
throw AbortTransaction;
@@ -1961,8 +2199,9 @@ export class Wallet {
// Store refresh session and subtract refreshed amount from
// coin in the same transaction.
const query = this.q();
- query.put(Stores.refresh, refreshSession, "refreshKey")
- .mutate(Stores.coins, coin.coinPub, mutateCoin);
+ query
+ .put(Stores.refresh, refreshSession, "refreshKey")
+ .mutate(Stores.coins, coin.coinPub, mutateCoin);
await query.finish();
this.notifier.notify();
@@ -1976,10 +2215,10 @@ export class Wallet {
return refreshSession;
}
-
async refresh(oldCoinPub: string): Promise<void> {
-
- const oldRefreshSessions = await this.q().iter(Stores.refresh).toArray();
+ const oldRefreshSessions = await this.q()
+ .iter(Stores.refresh)
+ .toArray();
for (const session of oldRefreshSessions) {
console.log("got old session for", oldCoinPub, session);
this.continueRefreshSession(session);
@@ -1989,7 +2228,10 @@ export class Wallet {
console.warn("can't refresh, coin not in database");
return;
}
- if (coin.status === CoinStatus.Useless || coin.status === CoinStatus.Fresh) {
+ if (
+ coin.status === CoinStatus.Useless ||
+ coin.status === CoinStatus.Fresh
+ ) {
return;
}
const refreshSession = await this.createRefreshSession(oldCoinPub);
@@ -2007,7 +2249,10 @@ export class Wallet {
}
if (typeof refreshSession.norevealIndex !== "number") {
await this.refreshMelt(refreshSession);
- const r = await this.q().get<RefreshSessionRecord>(Stores.refresh, refreshSession.id);
+ const r = await this.q().get<RefreshSessionRecord>(
+ Stores.refresh,
+ refreshSession.id,
+ );
if (!r) {
throw Error("refresh session does not exist anymore");
}
@@ -2017,21 +2262,24 @@ export class Wallet {
await this.refreshReveal(refreshSession);
}
-
async refreshMelt(refreshSession: RefreshSessionRecord): Promise<void> {
if (refreshSession.norevealIndex !== undefined) {
console.error("won't melt again");
return;
}
- const coin = await this.q().get<CoinRecord>(Stores.coins,
- refreshSession.meltCoinPub);
+ const coin = await this.q().get<CoinRecord>(
+ Stores.coins,
+ refreshSession.meltCoinPub,
+ );
if (!coin) {
console.error("can't melt coin, it does not exist");
return;
}
- const reqUrl = new URI("refresh/melt").absoluteTo(refreshSession.exchangeBaseUrl);
+ const reqUrl = new URI("refresh/melt").absoluteTo(
+ refreshSession.exchangeBaseUrl,
+ );
const meltReq = {
coin_pub: coin.coinPub,
confirm_sig: refreshSession.confirmSig,
@@ -2064,11 +2312,12 @@ export class Wallet {
refreshSession.norevealIndex = norevealIndex;
- await this.q().put(Stores.refresh, refreshSession).finish();
+ await this.q()
+ .put(Stores.refresh, refreshSession)
+ .finish();
this.notifier.notify();
}
-
async refreshReveal(refreshSession: RefreshSessionRecord): Promise<void> {
const norevealIndex = refreshSession.norevealIndex;
if (norevealIndex === undefined) {
@@ -2082,17 +2331,40 @@ export class Wallet {
throw Error("refresh index error");
}
+ const meltCoinRecord = await this.q().get(
+ Stores.coins,
+ refreshSession.meltCoinPub,
+ );
+ if (!meltCoinRecord) {
+ throw Error("inconsistent database");
+ }
+
const evs = preCoins.map((x: RefreshPreCoinRecord) => x.coinEv);
+ const linkSigs: string[] = [];
+ for (let i = 0; i < refreshSession.newDenoms.length; i++) {
+ const linkSig = await this.cryptoApi.signCoinLink(
+ meltCoinRecord.coinPriv,
+ refreshSession.newDenomHashes[i],
+ refreshSession.meltCoinPub,
+ refreshSession.transferPubs[norevealIndex],
+ preCoins[i].coinEv,
+ );
+ linkSigs.push(linkSig);
+ }
+
const req = {
coin_evs: evs,
new_denoms_h: refreshSession.newDenomHashes,
rc: refreshSession.hash,
transfer_privs: privs,
transfer_pub: refreshSession.transferPubs[norevealIndex],
+ link_sigs: linkSigs,
};
- const reqUrl = new URI("refresh/reveal") .absoluteTo(refreshSession.exchangeBaseUrl);
+ const reqUrl = new URI("refresh/reveal").absoluteTo(
+ refreshSession.exchangeBaseUrl,
+ );
console.log("reveal request:", req);
const resp = await this.http.postJson(reqUrl.href(), req);
@@ -2110,8 +2382,10 @@ export class Wallet {
console.log("/refresh/reveal did not contain ev_sigs");
}
- const exchange = await this.q().get<ExchangeRecord>(Stores.exchanges,
- refreshSession.exchangeBaseUrl);
+ const exchange = await this.q().get<ExchangeRecord>(
+ Stores.exchanges,
+ refreshSession.exchangeBaseUrl,
+ );
if (!exchange) {
console.error(`exchange ${refreshSession.exchangeBaseUrl} not found`);
return;
@@ -2120,21 +2394,21 @@ export class Wallet {
const coins: CoinRecord[] = [];
for (let i = 0; i < respJson.ev_sigs.length; i++) {
- const denom = await (
- this.q()
- .get(Stores.denominations,
- [
- refreshSession.exchangeBaseUrl,
- refreshSession.newDenoms[i],
- ]));
+ const denom = await this.q().get(Stores.denominations, [
+ refreshSession.exchangeBaseUrl,
+ refreshSession.newDenoms[i],
+ ]);
if (!denom) {
console.error("denom not found");
continue;
}
- const pc = refreshSession.preCoinsForGammas[refreshSession.norevealIndex!][i];
- const denomSig = await this.cryptoApi.rsaUnblind(respJson.ev_sigs[i].ev_sig,
- pc.blindingKey,
- denom.denomPub);
+ const pc =
+ refreshSession.preCoinsForGammas[refreshSession.norevealIndex!][i];
+ const denomSig = await this.cryptoApi.rsaUnblind(
+ respJson.ev_sigs[i].ev_sig,
+ pc.blindingKey,
+ denom.denomPub,
+ );
const coin: CoinRecord = {
blindingKey: pc.blindingKey,
coinPriv: pc.privateKey,
@@ -2154,22 +2428,23 @@ export class Wallet {
refreshSession.finished = true;
await this.q()
- .putAll(Stores.coins, coins)
- .put(Stores.refresh, refreshSession)
- .finish();
+ .putAll(Stores.coins, coins)
+ .put(Stores.refresh, refreshSession)
+ .finish();
this.notifier.notify();
}
-
/**
* Retrive the full event history for this wallet.
*/
- async getHistory(): Promise<{history: HistoryRecord[]}> {
+ async getHistory(): Promise<{ history: HistoryRecord[] }> {
const history: HistoryRecord[] = [];
// FIXME: do pagination instead of generating the full history
- const proposals = await this.q().iter<ProposalDownloadRecord>(Stores.proposals).toArray();
+ const proposals = await this.q()
+ .iter<ProposalDownloadRecord>(Stores.proposals)
+ .toArray();
for (const p of proposals) {
history.push({
detail: {
@@ -2181,7 +2456,9 @@ export class Wallet {
});
}
- const purchases = await this.q().iter<PurchaseRecord>(Stores.purchases).toArray();
+ const purchases = await this.q()
+ .iter<PurchaseRecord>(Stores.purchases)
+ .toArray();
for (const p of purchases) {
history.push({
detail: {
@@ -2195,16 +2472,17 @@ export class Wallet {
});
if (p.timestamp_refund) {
const contractAmount = Amounts.parseOrThrow(p.contractTerms.amount);
- const amountsPending = (
- Object.keys(p.refundsPending)
- .map((x) => Amounts.parseOrThrow(p.refundsPending[x].refund_amount))
+ const amountsPending = Object.keys(p.refundsPending).map(x =>
+ Amounts.parseOrThrow(p.refundsPending[x].refund_amount),
);
- const amountsDone = (
- Object.keys(p.refundsDone)
- .map((x) => Amounts.parseOrThrow(p.refundsDone[x].refund_amount))
+ const amountsDone = Object.keys(p.refundsDone).map(x =>
+ Amounts.parseOrThrow(p.refundsDone[x].refund_amount),
);
const amounts: AmountJson[] = amountsPending.concat(amountsDone);
- const amount = Amounts.add(Amounts.getZero(contractAmount.currency), ...amounts).amount;
+ const amount = Amounts.add(
+ Amounts.getZero(contractAmount.currency),
+ ...amounts,
+ ).amount;
history.push({
detail: {
@@ -2219,7 +2497,9 @@ export class Wallet {
}
}
- const reserves: ReserveRecord[] = await this.q().iter<ReserveRecord>(Stores.reserves).toArray();
+ const reserves: ReserveRecord[] = await this.q()
+ .iter<ReserveRecord>(Stores.reserves)
+ .toArray();
for (const r of reserves) {
history.push({
detail: {
@@ -2243,7 +2523,9 @@ export class Wallet {
}
}
- const tips: TipRecord[] = await this.q().iter<TipRecord>(Stores.tips).toArray();
+ const tips: TipRecord[] = await this.q()
+ .iter<TipRecord>(Stores.tips)
+ .toArray();
for (const tip of tips) {
history.push({
detail: {
@@ -2259,70 +2541,74 @@ export class Wallet {
history.sort((h1, h2) => Math.sign(h1.timestamp - h2.timestamp));
- return {history};
+ return { history };
}
async getDenoms(exchangeUrl: string): Promise<DenominationRecord[]> {
- const denoms = await this.q().iterIndex(Stores.denominations.exchangeBaseUrlIndex, exchangeUrl).toArray();
+ const denoms = await this.q()
+ .iterIndex(Stores.denominations.exchangeBaseUrlIndex, exchangeUrl)
+ .toArray();
return denoms;
}
- async getProposal(proposalId: number): Promise<ProposalDownloadRecord|undefined> {
+ async getProposal(
+ proposalId: number,
+ ): Promise<ProposalDownloadRecord | undefined> {
const proposal = await this.q().get(Stores.proposals, proposalId);
return proposal;
}
async getExchanges(): Promise<ExchangeRecord[]> {
return this.q()
- .iter<ExchangeRecord>(Stores.exchanges)
- .toArray();
+ .iter<ExchangeRecord>(Stores.exchanges)
+ .toArray();
}
async getCurrencies(): Promise<CurrencyRecord[]> {
return this.q()
- .iter<CurrencyRecord>(Stores.currencies)
- .toArray();
+ .iter<CurrencyRecord>(Stores.currencies)
+ .toArray();
}
async updateCurrency(currencyRecord: CurrencyRecord): Promise<void> {
console.log("updating currency to", currencyRecord);
await this.q()
- .put(Stores.currencies, currencyRecord)
- .finish();
+ .put(Stores.currencies, currencyRecord)
+ .finish();
this.notifier.notify();
}
async getReserves(exchangeBaseUrl: string): Promise<ReserveRecord[]> {
return this.q()
- .iter<ReserveRecord>(Stores.reserves)
- .filter((r: ReserveRecord) => r.exchange_base_url === exchangeBaseUrl)
- .toArray();
+ .iter<ReserveRecord>(Stores.reserves)
+ .filter((r: ReserveRecord) => r.exchange_base_url === exchangeBaseUrl)
+ .toArray();
}
async getCoins(exchangeBaseUrl: string): Promise<CoinRecord[]> {
return this.q()
- .iter<CoinRecord>(Stores.coins)
- .filter((c: CoinRecord) => c.exchangeBaseUrl === exchangeBaseUrl)
- .toArray();
+ .iter<CoinRecord>(Stores.coins)
+ .filter((c: CoinRecord) => c.exchangeBaseUrl === exchangeBaseUrl)
+ .toArray();
}
async getPreCoins(exchangeBaseUrl: string): Promise<PreCoinRecord[]> {
return this.q()
- .iter<PreCoinRecord>(Stores.precoins)
- .filter((c: PreCoinRecord) => c.exchangeBaseUrl === exchangeBaseUrl)
- .toArray();
+ .iter<PreCoinRecord>(Stores.precoins)
+ .filter((c: PreCoinRecord) => c.exchangeBaseUrl === exchangeBaseUrl)
+ .toArray();
}
async hashContract(contract: ContractTerms): Promise<string> {
return this.cryptoApi.hashString(canonicalJson(contract));
}
-
- async getCurrencyRecord(currency: string): Promise<CurrencyRecord|undefined> {
+ async getCurrencyRecord(
+ currency: string,
+ ): Promise<CurrencyRecord | undefined> {
return this.q().get(Stores.currencies, currency);
}
-
async payback(coinPub: string): Promise<void> {
let coin = await this.q().get(Stores.coins, coinPub);
if (!coin) {
@@ -2338,7 +2624,9 @@ export class Wallet {
}
switch (coin.status) {
case CoinStatus.Refreshed:
- throw Error(`Can't do payback for coin ${coinPub} since it's refreshed`);
+ throw Error(
+ `Can't do payback for coin ${coinPub} since it's refreshed`,
+ );
case CoinStatus.PaybackDone:
console.log(`Coin ${coinPub} already payed back`);
return;
@@ -2348,7 +2636,9 @@ export class Wallet {
// technically we might update reserve status before we get the response
// from the reserve for the payback request.
reserve.hasPayback = true;
- await this.q().put(Stores.coins, coin).put(Stores.reserves, reserve);
+ await this.q()
+ .put(Stores.coins, coin)
+ .put(Stores.reserves, reserve);
this.notifier.notify();
const paybackRequest = await this.cryptoApi.createPaybackRequest(coin);
@@ -2357,7 +2647,9 @@ export class Wallet {
if (resp.status !== 200) {
throw Error();
}
- const paybackConfirmation = PaybackConfirmation.checked(JSON.parse(resp.responseText));
+ const paybackConfirmation = PaybackConfirmation.checked(
+ JSON.parse(resp.responseText),
+ );
if (paybackConfirmation.reserve_pub !== coin.reservePub) {
throw Error(`Coin's reserve doesn't match reserve on payback`);
}
@@ -2371,8 +2663,10 @@ export class Wallet {
await this.updateReserve(reservePub!);
}
-
- async denominationRecordFromKeys(exchangeBaseUrl: string, denomIn: Denomination): Promise<DenominationRecord> {
+ async denominationRecordFromKeys(
+ exchangeBaseUrl: string,
+ denomIn: Denomination,
+ ): Promise<DenominationRecord> {
const denomPubHash = await this.cryptoApi.hashDenomPub(denomIn.denom_pub);
const d: DenominationRecord = {
denomPub: denomIn.denom_pub,
@@ -2405,7 +2699,10 @@ export class Wallet {
}
async getPaybackReserves(): Promise<ReserveRecord[]> {
- return await this.q().iter(Stores.reserves).filter((r) => r.hasPayback).toArray();
+ return await this.q()
+ .iter(Stores.reserves)
+ .filter(r => r.hasPayback)
+ .toArray();
}
/**
@@ -2417,18 +2714,26 @@ export class Wallet {
async getSenderWireInfos(): Promise<SenderWireInfos> {
const m: { [url: string]: Set<string> } = {};
- await this.q().iter(Stores.exchangeWireFees).map((x) => {
- const s = m[x.exchangeBaseUrl] = m[x.exchangeBaseUrl] || new Set();
- Object.keys(x.feesForType).map((k) => s.add(k));
- }).run();
+ await this.q()
+ .iter(Stores.exchangeWireFees)
+ .map(x => {
+ const s = (m[x.exchangeBaseUrl] = m[x.exchangeBaseUrl] || new Set());
+ Object.keys(x.feesForType).map(k => s.add(k));
+ })
+ .run();
console.log(m);
const exchangeWireTypes: { [url: string]: string[] } = {};
- Object.keys(m).map((e) => { exchangeWireTypes[e] = Array.from(m[e]); });
+ Object.keys(m).map(e => {
+ exchangeWireTypes[e] = Array.from(m[e]);
+ });
const senderWiresSet = new Set();
- await this.q().iter(Stores.senderWires).map((x) => {
+ await this.q()
+ .iter(Stores.senderWires)
+ .map(x => {
senderWiresSet.add(x.paytoUri);
- }).run();
+ })
+ .run();
const senderWires = Array.from(senderWiresSet);
return {
@@ -2448,7 +2753,7 @@ export class Wallet {
console.error(`wire type must be a non-empty string, not ${wireType}`);
return;
}
- const stampSecNow = Math.floor((new Date()).getTime() / 1000);
+ const stampSecNow = Math.floor(new Date().getTime() / 1000);
const exchange = await this.q().get(Stores.exchanges, req.exchange);
if (!exchange) {
console.error(`Exchange ${req.exchange} not known to the wallet`);
@@ -2464,13 +2769,17 @@ export class Wallet {
const { priv, pub } = await this.cryptoApi.createEddsaKeypair();
- const wireHash = await this.cryptoApi.hashString(canonicalJson(req.senderWire));
+ const wireHash = await this.cryptoApi.hashString(
+ canonicalJson(req.senderWire),
+ );
const contractTerms: ContractTerms = {
H_wire: wireHash,
amount: Amounts.toString(req.amount),
auditors: [],
- exchanges: [ { master_pub: exchange.masterPublicKey, url: exchange.baseUrl } ],
+ exchanges: [
+ { master_pub: exchange.masterPublicKey, url: exchange.baseUrl },
+ ],
extra: {},
fulfillment_url: "",
locations: [],
@@ -2486,15 +2795,19 @@ export class Wallet {
wire_method: wireType,
};
- const contractTermsHash = await this.cryptoApi.hashString(canonicalJson(contractTerms));
+ const contractTermsHash = await this.cryptoApi.hashString(
+ canonicalJson(contractTerms),
+ );
- const payCoinInfo = await (
- this.cryptoApi.signDeposit(contractTerms, cds, Amounts.parseOrThrow(contractTerms.amount))
+ const payCoinInfo = await this.cryptoApi.signDeposit(
+ contractTerms,
+ cds,
+ Amounts.parseOrThrow(contractTerms.amount),
);
console.log("pci", payCoinInfo);
- const coins = payCoinInfo.sigs.map((s) => ({ coinPaySig: s }));
+ const coins = payCoinInfo.sigs.map(s => ({ coinPaySig: s }));
const coinsReturnRecord: CoinsReturnRecord = {
coins,
@@ -2506,16 +2819,18 @@ export class Wallet {
};
await this.q()
- .put(Stores.coinsReturns, coinsReturnRecord)
- .putAll(Stores.coins, payCoinInfo.updatedCoins)
- .finish();
+ .put(Stores.coinsReturns, coinsReturnRecord)
+ .putAll(Stores.coins, payCoinInfo.updatedCoins)
+ .finish();
this.badge.showNotification();
this.notifier.notify();
this.depositReturnedCoins(coinsReturnRecord);
}
- async depositReturnedCoins(coinsReturnRecord: CoinsReturnRecord): Promise<void> {
+ async depositReturnedCoins(
+ coinsReturnRecord: CoinsReturnRecord,
+ ): Promise<void> {
for (const c of coinsReturnRecord.coins) {
if (c.depositedSig) {
continue;
@@ -2536,7 +2851,7 @@ export class Wallet {
wire_transfer_deadline: coinsReturnRecord.contractTerms.pay_deadline,
};
console.log("req", req);
- const reqUrl = (new URI("deposit")).absoluteTo(coinsReturnRecord.exchange);
+ const reqUrl = new URI("deposit").absoluteTo(coinsReturnRecord.exchange);
const resp = await this.http.postJson(reqUrl.href(), req);
if (resp.status !== 200) {
console.error("deposit failed due to status code", resp);
@@ -2556,7 +2871,10 @@ export class Wallet {
// FIXME: verify signature
// For every successful deposit, we replace the old record with an updated one
- const currentCrr = await this.q().get(Stores.coinsReturns, coinsReturnRecord.contractTermsHash);
+ const currentCrr = await this.q().get(
+ Stores.coinsReturns,
+ coinsReturnRecord.contractTermsHash,
+ );
if (!currentCrr) {
console.error("database inconsistent");
continue;
@@ -2571,7 +2889,9 @@ export class Wallet {
}
}
- async acceptRefundResponse(refundResponse: MerchantRefundResponse): Promise<string> {
+ async acceptRefundResponse(
+ refundResponse: MerchantRefundResponse,
+ ): Promise<string> {
const refundPermissions = refundResponse.refund_permissions;
if (!refundPermissions.length) {
@@ -2582,16 +2902,19 @@ export class Wallet {
/**
* Add refund to purchase if not already added.
*/
- function f(t: PurchaseRecord|undefined): PurchaseRecord|undefined {
+ function f(t: PurchaseRecord | undefined): PurchaseRecord | undefined {
if (!t) {
console.error("purchase not found, not adding refunds");
return;
}
- t.timestamp_refund = (new Date()).getTime();
+ t.timestamp_refund = new Date().getTime();
for (const perm of refundPermissions) {
- if (!t.refundsPending[perm.merchant_sig] && !t.refundsDone[perm.merchant_sig]) {
+ if (
+ !t.refundsPending[perm.merchant_sig] &&
+ !t.refundsDone[perm.merchant_sig]
+ ) {
t.refundsPending[perm.merchant_sig] = perm;
}
}
@@ -2601,7 +2924,9 @@ export class Wallet {
const hc = refundResponse.h_contract_terms;
// Add the refund permissions to the purchase within a DB transaction
- await this.q().mutate(Stores.purchases, hc, f).finish();
+ await this.q()
+ .mutate(Stores.purchases, hc, f)
+ .finish();
this.notifier.notify();
// Start submitting it but don't wait for it here.
@@ -2610,7 +2935,6 @@ export class Wallet {
return hc;
}
-
/**
* Accept a refund, return the contract hash for the contract
* that was involved in the refund.
@@ -2632,11 +2956,13 @@ export class Wallet {
return this.acceptRefundResponse(refundResponse);
}
-
private async submitRefunds(contractTermsHash: string): Promise<void> {
const purchase = await this.q().get(Stores.purchases, contractTermsHash);
if (!purchase) {
- console.error("not submitting refunds, contract terms not found:", contractTermsHash);
+ console.error(
+ "not submitting refunds, contract terms not found:",
+ contractTermsHash,
+ );
return;
}
const pendingKeys = Object.keys(purchase.refundsPending);
@@ -2657,7 +2983,7 @@ export class Wallet {
console.log("sending refund permission", perm);
// FIXME: not correct once we support multiple exchanges per payment
const exchangeUrl = purchase.payReq.coins[0].exchange_url;
- const reqUrl = (new URI("refund")).absoluteTo(exchangeUrl);
+ const reqUrl = new URI("refund").absoluteTo(exchangeUrl);
const resp = await this.http.postJson(reqUrl.href(), req);
if (resp.status !== 200) {
console.error("refund failed", resp);
@@ -2665,7 +2991,9 @@ export class Wallet {
}
// Transactionally mark successful refunds as done
- const transformPurchase = (t: PurchaseRecord|undefined): PurchaseRecord|undefined => {
+ const transformPurchase = (
+ t: PurchaseRecord | undefined,
+ ): PurchaseRecord | undefined => {
if (!t) {
console.warn("purchase not found, not updating refund");
return;
@@ -2676,7 +3004,9 @@ export class Wallet {
}
return t;
};
- const transformCoin = (c: CoinRecord|undefined): CoinRecord|undefined => {
+ const transformCoin = (
+ c: CoinRecord | undefined,
+ ): CoinRecord | undefined => {
if (!c) {
console.warn("coin not found, can't apply refund");
return;
@@ -2691,9 +3021,9 @@ export class Wallet {
};
await this.q()
- .mutate(Stores.purchases, contractTermsHash, transformPurchase)
- .mutate(Stores.coins, perm.coin_pub, transformCoin)
- .finish();
+ .mutate(Stores.purchases, contractTermsHash, transformPurchase)
+ .mutate(Stores.coins, perm.coin_pub, transformCoin)
+ .finish();
this.refresh(perm.coin_pub);
}
@@ -2701,27 +3031,44 @@ export class Wallet {
this.notifier.notify();
}
- async getPurchase(contractTermsHash: string): Promise<PurchaseRecord|undefined> {
+ async getPurchase(
+ contractTermsHash: string,
+ ): Promise<PurchaseRecord | undefined> {
return this.q().get(Stores.purchases, contractTermsHash);
}
- async getFullRefundFees(refundPermissions: MerchantRefundPermission[]): Promise<AmountJson> {
+ async getFullRefundFees(
+ refundPermissions: MerchantRefundPermission[],
+ ): Promise<AmountJson> {
if (refundPermissions.length === 0) {
throw Error("no refunds given");
}
- const coin0 = await this.q().get(Stores.coins, refundPermissions[0].coin_pub);
+ const coin0 = await this.q().get(
+ Stores.coins,
+ refundPermissions[0].coin_pub,
+ );
if (!coin0) {
throw Error("coin not found");
}
- let feeAcc = Amounts.getZero(Amounts.parseOrThrow(refundPermissions[0].refund_amount).currency);
+ let feeAcc = Amounts.getZero(
+ Amounts.parseOrThrow(refundPermissions[0].refund_amount).currency,
+ );
- const denoms = await this.q().iterIndex(Stores.denominations.exchangeBaseUrlIndex, coin0.exchangeBaseUrl).toArray();
+ const denoms = await this.q()
+ .iterIndex(
+ Stores.denominations.exchangeBaseUrlIndex,
+ coin0.exchangeBaseUrl,
+ )
+ .toArray();
for (const rp of refundPermissions) {
const coin = await this.q().get(Stores.coins, rp.coin_pub);
if (!coin) {
throw Error("coin not found");
}
- const denom = await this.q().get(Stores.denominations, [coin0.exchangeBaseUrl, coin.denomPub]);
+ const denom = await this.q().get(Stores.denominations, [
+ coin0.exchangeBaseUrl,
+ coin.denomPub,
+ ]);
if (!denom) {
throw Error(`denom not found (${coin.denomPub})`);
}
@@ -2731,13 +3078,16 @@ export class Wallet {
// refreshed normally (and what about incremental refunds?)
const refundAmount = Amounts.parseOrThrow(rp.refund_amount);
const refundFee = Amounts.parseOrThrow(rp.refund_fee);
- const refreshCost = getTotalRefreshCost(denoms, denom, Amounts.sub(refundAmount, refundFee).amount);
+ const refreshCost = getTotalRefreshCost(
+ denoms,
+ denom,
+ Amounts.sub(refundAmount, refundFee).amount,
+ );
feeAcc = Amounts.add(feeAcc, refreshCost, refundFee).amount;
}
return feeAcc;
}
-
async processTip(tipToken: TipToken): Promise<TipRecord> {
const merchantDomain = new URI(tipToken.pickup_url).origin();
const key = tipToken.tip_id + merchantDomain;
@@ -2754,7 +3104,6 @@ export class Wallet {
}
}
-
private async processTipImpl(tipToken: TipToken): Promise<TipRecord> {
console.log("got tip token", tipToken);
@@ -2765,17 +3114,25 @@ export class Wallet {
throw Error("tipping failed (invalid expiration)");
}
- let tipRecord = await this.q().get(Stores.tips, [tipToken.tip_id, merchantDomain]);
+ let tipRecord = await this.q().get(Stores.tips, [
+ tipToken.tip_id,
+ merchantDomain,
+ ]);
if (tipRecord && tipRecord.pickedUp) {
return tipRecord;
}
const tipAmount = Amounts.parseOrThrow(tipToken.amount);
await this.updateExchangeFromUrl(tipToken.exchange_url);
- const denomsForWithdraw = await this.getVerifiedWithdrawDenomList(tipToken.exchange_url, tipAmount);
- const planchets = await Promise.all(denomsForWithdraw.map(d => this.cryptoApi.createTipPlanchet(d)));
+ const denomsForWithdraw = await this.getVerifiedWithdrawDenomList(
+ tipToken.exchange_url,
+ tipAmount,
+ );
+ const planchets = await Promise.all(
+ denomsForWithdraw.map(d => this.cryptoApi.createTipPlanchet(d)),
+ );
const coinPubs: string[] = planchets.map(x => x.coinPub);
- const now = (new Date()).getTime();
+ const now = new Date().getTime();
tipRecord = {
accepted: false,
amount: Amounts.parseOrThrow(tipToken.amount),
@@ -2792,11 +3149,14 @@ export class Wallet {
let merchantResp;
- tipRecord = await this.q().putOrGetExisting(Stores.tips, tipRecord, [tipRecord.tipId, merchantDomain]);
+ tipRecord = await this.q().putOrGetExisting(Stores.tips, tipRecord, [
+ tipRecord.tipId,
+ merchantDomain,
+ ]);
this.notifier.notify();
// Planchets in the form that the merchant expects
- const planchetsDetail: TipPlanchetDetail[] = tipRecord.planchets.map((p) => ({
+ const planchetsDetail: TipPlanchetDetail[] = tipRecord.planchets.map(p => ({
coin_ev: p.coinEv,
denom_pub_hash: p.denomPubHash,
}));
@@ -2839,13 +3199,14 @@ export class Wallet {
tipRecord.pickedUp = true;
- await this.q().put(Stores.tips, tipRecord).finish();
+ await this.q()
+ .put(Stores.tips, tipRecord)
+ .finish();
this.notifier.notify();
return tipRecord;
}
-
/**
* Start using the coins from a tip.
*/
@@ -2881,7 +3242,6 @@ export class Wallet {
this.notifier.notify();
}
-
async getTipStatus(tipToken: TipToken): Promise<TipStatus> {
const tipId = tipToken.tip_id;
const merchantDomain = new URI(tipToken.pickup_url).origin();
@@ -2901,7 +3261,6 @@ export class Wallet {
return tipStatus;
}
-
async abortFailedPayment(contractTermsHash: string): Promise<void> {
const purchase = await this.q().get(Stores.purchases, contractTermsHash);
if (!purchase) {
@@ -2929,7 +3288,7 @@ export class Wallet {
try {
const config = {
headers: { "Content-Type": "application/json;charset=UTF-8" },
- timeout: 5000, /* 5 seconds */
+ timeout: 5000 /* 5 seconds */,
validateStatus: (s: number) => s === 200,
};
resp = await axios.post(purchase.contractTerms.pay_url, abortReq, config);
@@ -2946,10 +3305,13 @@ export class Wallet {
p.abortDone = true;
return p;
};
- await this.q().mutate(Stores.purchases, purchase.contractTermsHash, markAbortDone);
+ await this.q().mutate(
+ Stores.purchases,
+ purchase.contractTermsHash,
+ markAbortDone,
+ );
}
-
/**
* Synchronously get the paid URL for a resource from the plain fulfillment
* URL. Returns undefined if the fulfillment URL is not a resource that was
@@ -2965,7 +3327,7 @@ export class Wallet {
* based on the current system time.
*/
async collectGarbage() {
- const nowMilli = (new Date()).getTime();
+ const nowMilli = new Date().getTime();
const nowSec = Math.floor(nowMilli / 1000);
const gcReserve = (r: ReserveRecord, n: number) => {
@@ -2978,28 +3340,35 @@ export class Wallet {
}
return false;
};
- await this.q().deleteIf(Stores.reserves, gcReserve).finish();
+ await this.q()
+ .deleteIf(Stores.reserves, gcReserve)
+ .finish();
const gcProposal = (d: ProposalDownloadRecord, n: number) => {
// Delete proposal after 60 minutes or 5 minutes before pay deadline,
// whatever comes first.
- const deadlinePayMilli = getTalerStampSec(d.contractTerms.pay_deadline)! * 1000;
- const deadlineExpireMilli = nowMilli + (1000 * 60 * 60);
+ const deadlinePayMilli =
+ getTalerStampSec(d.contractTerms.pay_deadline)! * 1000;
+ const deadlineExpireMilli = nowMilli + 1000 * 60 * 60;
return d.timestamp < Math.min(deadlinePayMilli, deadlineExpireMilli);
};
- await this.q().deleteIf(Stores.proposals, gcProposal).finish();
+ await this.q()
+ .deleteIf(Stores.proposals, gcProposal)
+ .finish();
const activeExchanges: string[] = [];
const gcExchange = (d: ExchangeRecord, n: number) => {
// Delete if if unused and last update more than 20 minutes ago
- if (!d.lastUsedTime && nowMilli > d.lastUpdateTime + (1000 * 60 * 20)) {
+ if (!d.lastUsedTime && nowMilli > d.lastUpdateTime + 1000 * 60 * 20) {
return true;
}
activeExchanges.push(d.baseUrl);
return false;
};
- await this.q().deleteIf(Stores.exchanges, gcExchange).finish();
+ await this.q()
+ .deleteIf(Stores.exchanges, gcExchange)
+ .finish();
const gcDenominations = (d: DenominationRecord, n: number) => {
if (nowSec > getTalerStampSec(d.stampExpireDeposit)!) {
@@ -3010,7 +3379,9 @@ export class Wallet {
}
return false;
};
- await this.q().deleteIf(Stores.denominations, gcDenominations).finish();
+ await this.q()
+ .deleteIf(Stores.denominations, gcDenominations)
+ .finish();
const gcWireFees = (r: ExchangeWireFeesRecord, n: number) => {
if (activeExchanges.indexOf(r.exchangeBaseUrl) < 0) {
@@ -3018,8 +3389,9 @@ export class Wallet {
}
return false;
};
- await this.q().deleteIf(Stores.exchangeWireFees, gcWireFees).finish();
-
+ await this.q()
+ .deleteIf(Stores.exchangeWireFees, gcWireFees)
+ .finish();
// FIXME(#5210) also GC coins
}