WIP: simplification and error handling

This commit is contained in:
Florian Dold 2019-11-21 23:09:43 +01:00
parent e8f362ccfe
commit c623309430
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
20 changed files with 1142 additions and 943 deletions

View File

@ -63,7 +63,7 @@
"@types/chrome": "^0.0.91", "@types/chrome": "^0.0.91",
"@types/urijs": "^1.19.3", "@types/urijs": "^1.19.3",
"axios": "^0.19.0", "axios": "^0.19.0",
"idb-bridge": "^0.0.11", "idb-bridge": "^0.0.14",
"qrcode-generator": "^1.4.3", "qrcode-generator": "^1.4.3",
"source-map-support": "^0.5.12", "source-map-support": "^0.5.12",
"urijs": "^1.18.10" "urijs": "^1.18.10"

View File

@ -22,6 +22,7 @@ import {
DenominationRecord, DenominationRecord,
DenominationStatus, DenominationStatus,
ReserveRecord, ReserveRecord,
ReserveRecordStatus,
} from "../dbTypes"; } from "../dbTypes";
import { CryptoApi } from "./cryptoApi"; import { CryptoApi } from "./cryptoApi";
@ -86,18 +87,18 @@ test("precoin creation", async t => {
const crypto = new CryptoApi(new NodeCryptoWorkerFactory()); const crypto = new CryptoApi(new NodeCryptoWorkerFactory());
const { priv, pub } = await crypto.createEddsaKeypair(); const { priv, pub } = await crypto.createEddsaKeypair();
const r: ReserveRecord = { const r: ReserveRecord = {
created: 0, created: { t_ms: 0 },
current_amount: null, currentAmount: null,
exchange_base_url: "https://example.com/exchange", exchangeBaseUrl: "https://example.com/exchange",
hasPayback: false, hasPayback: false,
precoin_amount: { currency: "PUDOS", value: 0, fraction: 0 }, precoinAmount: { currency: "PUDOS", value: 0, fraction: 0 },
requested_amount: { currency: "PUDOS", value: 0, fraction: 0 }, requestedAmount: { currency: "PUDOS", value: 0, fraction: 0 },
reserve_priv: priv, reservePriv: priv,
reserve_pub: pub, reservePub: pub,
timestamp_confirmed: 0, timestampConfirmed: undefined,
timestamp_depleted: 0, timestampReserveInfoPosted: undefined,
timestamp_reserve_info_posted: 0, exchangeWire: "payto://foo",
exchangeWire: "payto://foo" reserveStatus: ReserveRecordStatus.UNCONFIRMED,
}; };
const precoin = await crypto.createPreCoin(denomValid1, r); const precoin = await crypto.createPreCoin(denomValid1, r);

View File

@ -45,6 +45,7 @@ import * as native from "./emscInterface";
import { AmountJson } from "../amounts"; import { AmountJson } from "../amounts";
import * as Amounts from "../amounts"; import * as Amounts from "../amounts";
import * as timer from "../timer"; import * as timer from "../timer";
import { getRandomBytes, encodeCrock } from "./nativeCrypto";
export class CryptoImplementation { export class CryptoImplementation {
static enableTracing: boolean = false; static enableTracing: boolean = false;
@ -60,9 +61,9 @@ export class CryptoImplementation {
reserve: ReserveRecord, reserve: ReserveRecord,
): PreCoinRecord { ): PreCoinRecord {
const reservePriv = new native.EddsaPrivateKey(this.emsc); const reservePriv = new native.EddsaPrivateKey(this.emsc);
reservePriv.loadCrock(reserve.reserve_priv); reservePriv.loadCrock(reserve.reservePriv);
const reservePub = new native.EddsaPublicKey(this.emsc); const reservePub = new native.EddsaPublicKey(this.emsc);
reservePub.loadCrock(reserve.reserve_pub); reservePub.loadCrock(reserve.reservePub);
const denomPub = native.RsaPublicKey.fromCrock(this.emsc, denom.denomPub); const denomPub = native.RsaPublicKey.fromCrock(this.emsc, denom.denomPub);
const coinPriv = native.EddsaPrivateKey.create(this.emsc); const coinPriv = native.EddsaPrivateKey.create(this.emsc);
const coinPub = coinPriv.getPublicKey(); const coinPub = coinPriv.getPublicKey();
@ -103,7 +104,7 @@ export class CryptoImplementation {
coinValue: denom.value, coinValue: denom.value,
denomPub: denomPub.toCrock(), denomPub: denomPub.toCrock(),
denomPubHash: denomPubHash.toCrock(), denomPubHash: denomPubHash.toCrock(),
exchangeBaseUrl: reserve.exchange_base_url, exchangeBaseUrl: reserve.exchangeBaseUrl,
isFromTip: false, isFromTip: false,
reservePub: reservePub.toCrock(), reservePub: reservePub.toCrock(),
withdrawSig: sig.toCrock(), withdrawSig: sig.toCrock(),
@ -199,14 +200,14 @@ export class CryptoImplementation {
isValidWireFee(type: string, wf: WireFee, masterPub: string): boolean { isValidWireFee(type: string, wf: WireFee, masterPub: string): boolean {
const p = new native.MasterWireFeePS(this.emsc, { const p = new native.MasterWireFeePS(this.emsc, {
closing_fee: new native.Amount(this.emsc, wf.closingFee).toNbo(), closing_fee: new native.Amount(this.emsc, wf.closingFee).toNbo(),
end_date: native.AbsoluteTimeNbo.fromStampSeconds(this.emsc, wf.endStamp), end_date: native.AbsoluteTimeNbo.fromStampSeconds(this.emsc, (wf.endStamp.t_ms / 1000)),
h_wire_method: native.ByteArray.fromStringWithNull( h_wire_method: native.ByteArray.fromStringWithNull(
this.emsc, this.emsc,
type, type,
).hash(), ).hash(),
start_date: native.AbsoluteTimeNbo.fromStampSeconds( start_date: native.AbsoluteTimeNbo.fromStampSeconds(
this.emsc, this.emsc,
wf.startStamp, Math.floor(wf.startStamp.t_ms / 1000),
), ),
wire_fee: new native.Amount(this.emsc, wf.wireFee).toNbo(), wire_fee: new native.Amount(this.emsc, wf.wireFee).toNbo(),
}); });
@ -354,7 +355,7 @@ export class CryptoImplementation {
const newAmount = new native.Amount(this.emsc, cd.coin.currentAmount); const newAmount = new native.Amount(this.emsc, cd.coin.currentAmount);
newAmount.sub(coinSpend); newAmount.sub(coinSpend);
cd.coin.currentAmount = newAmount.toJson(); cd.coin.currentAmount = newAmount.toJson();
cd.coin.status = CoinStatus.PurchasePending; cd.coin.status = CoinStatus.Dirty;
const d = new native.DepositRequestPS(this.emsc, { const d = new native.DepositRequestPS(this.emsc, {
amount_with_fee: coinSpend.toNbo(), amount_with_fee: coinSpend.toNbo(),
@ -505,7 +506,10 @@ export class CryptoImplementation {
valueOutput = Amounts.add(valueOutput, denom.value).amount; valueOutput = Amounts.add(valueOutput, denom.value).amount;
} }
const refreshSessionId = encodeCrock(getRandomBytes(32));
const refreshSession: RefreshSessionRecord = { const refreshSession: RefreshSessionRecord = {
refreshSessionId,
confirmSig, confirmSig,
exchangeBaseUrl, exchangeBaseUrl,
finished: false, finished: false,

View File

@ -12,7 +12,6 @@ export function openTalerDb(
onVersionChange: () => void, onVersionChange: () => void,
onUpgradeUnsupported: (oldVersion: number, newVersion: number) => void, onUpgradeUnsupported: (oldVersion: number, newVersion: number) => void,
): Promise<IDBDatabase> { ): Promise<IDBDatabase> {
console.log("in openTalerDb");
return new Promise<IDBDatabase>((resolve, reject) => { return new Promise<IDBDatabase>((resolve, reject) => {
const req = idbFactory.open(DB_NAME, WALLET_DB_VERSION); const req = idbFactory.open(DB_NAME, WALLET_DB_VERSION);
req.onerror = e => { req.onerror = e => {

View File

@ -46,6 +46,36 @@ import { Timestamp, OperationError } from "./walletTypes";
*/ */
export const WALLET_DB_VERSION = 27; export const WALLET_DB_VERSION = 27;
export enum ReserveRecordStatus {
/**
* Waiting for manual confirmation.
*/
UNCONFIRMED = "unconfirmed",
/**
* Reserve must be registered with the bank.
*/
REGISTERING_BANK = "registering-bank",
/**
* Querying reserve status with the exchange.
*/
QUERYING_STATUS = "querying-status",
/**
* Status is queried, the wallet must now select coins
* and start withdrawing.
*/
WITHDRAWING = "withdrawing",
/**
* The corresponding withdraw record has been created.
* No further processing is done, unless explicitly requested
* by the user.
*/
DORMANT = "dormant",
}
/** /**
* A reserve record as stored in the wallet's database. * A reserve record as stored in the wallet's database.
*/ */
@ -53,28 +83,22 @@ export interface ReserveRecord {
/** /**
* The reserve public key. * The reserve public key.
*/ */
reserve_pub: string; reservePub: string;
/** /**
* The reserve private key. * The reserve private key.
*/ */
reserve_priv: string; reservePriv: string;
/** /**
* The exchange base URL. * The exchange base URL.
*/ */
exchange_base_url: string; exchangeBaseUrl: string;
/** /**
* Time when the reserve was created. * Time when the reserve was created.
*/ */
created: number; created: Timestamp;
/**
* Time when the reserve was depleted.
* Set to 0 if not depleted yet.
*/
timestamp_depleted: number;
/** /**
* Time when the information about this reserve was posted to the bank. * Time when the information about this reserve was posted to the bank.
@ -83,32 +107,32 @@ export interface ReserveRecord {
* *
* Set to 0 if that hasn't happened yet. * Set to 0 if that hasn't happened yet.
*/ */
timestamp_reserve_info_posted: number; timestampReserveInfoPosted: Timestamp | undefined;
/** /**
* Time when the reserve was confirmed. * Time when the reserve was confirmed.
* *
* Set to 0 if not confirmed yet. * Set to 0 if not confirmed yet.
*/ */
timestamp_confirmed: number; timestampConfirmed: Timestamp | undefined;
/** /**
* Current amount left in the reserve * Current amount left in the reserve
*/ */
current_amount: AmountJson | null; currentAmount: AmountJson | null;
/** /**
* Amount requested when the reserve was created. * Amount requested when the reserve was created.
* When a reserve is re-used (rare!) the current_amount can * When a reserve is re-used (rare!) the current_amount can
* be higher than the requested_amount * be higher than the requested_amount
*/ */
requested_amount: AmountJson; requestedAmount: AmountJson;
/** /**
* What's the current amount that sits * What's the current amount that sits
* in precoins? * in precoins?
*/ */
precoin_amount: AmountJson; precoinAmount: AmountJson;
/** /**
* We got some payback to this reserve. We'll cease to automatically * We got some payback to this reserve. We'll cease to automatically
@ -129,6 +153,10 @@ export interface ReserveRecord {
exchangeWire: string; exchangeWire: string;
bankWithdrawStatusUrl?: string; bankWithdrawStatusUrl?: string;
reserveStatus: ReserveRecordStatus;
lastError?: OperationError;
} }
/** /**
@ -341,9 +369,9 @@ export interface ExchangeDetails {
} }
export enum ExchangeUpdateStatus { export enum ExchangeUpdateStatus {
NONE = "none",
FETCH_KEYS = "fetch_keys", FETCH_KEYS = "fetch_keys",
FETCH_WIRE = "fetch_wire", FETCH_WIRE = "fetch_wire",
FINISHED = "finished",
} }
export interface ExchangeBankAccount { export interface ExchangeBankAccount {
@ -374,13 +402,18 @@ export interface ExchangeRecord {
*/ */
wireInfo: ExchangeWireInfo | undefined; wireInfo: ExchangeWireInfo | undefined;
/**
* When was the exchange added to the wallet?
*/
timestampAdded: Timestamp;
/** /**
* Time when the update to the exchange has been started or * Time when the update to the exchange has been started or
* undefined if no update is in progress. * undefined if no update is in progress.
*/ */
updateStarted: Timestamp | undefined; updateStarted: Timestamp | undefined;
updateStatus: ExchangeUpdateStatus; updateStatus: ExchangeUpdateStatus;
updateReason?: "initial" | "forced";
lastError?: OperationError; lastError?: OperationError;
} }
@ -436,31 +469,15 @@ export enum CoinStatus {
/** /**
* Withdrawn and never shown to anybody. * Withdrawn and never shown to anybody.
*/ */
Fresh, Fresh = "fresh",
/**
* Currently planned to be sent to a merchant for a purchase.
*/
PurchasePending,
/** /**
* Used for a completed transaction and now dirty. * Used for a completed transaction and now dirty.
*/ */
Dirty, Dirty = "dirty",
/** /**
* A coin that was refreshed. * A coin that has been spent and refreshed.
*/ */
Refreshed, Dormant = "dormant",
/**
* Coin marked to be paid back, but payback not finished.
*/
PaybackPending,
/**
* Coin fully paid back.
*/
PaybackDone,
/**
* Coin was dirty but can't be refreshed.
*/
Useless,
} }
/** /**
@ -569,7 +586,7 @@ export class ProposalDownloadRecord {
* was created. * was created.
*/ */
@Checkable.Number() @Checkable.Number()
timestamp: number; timestamp: Timestamp;
/** /**
* Private key for the nonce. * Private key for the nonce.
@ -658,7 +675,7 @@ export interface TipRecord {
*/ */
nextUrl?: string; nextUrl?: string;
timestamp: number; timestamp: Timestamp;
pickupUrl: string; pickupUrl: string;
} }
@ -735,9 +752,9 @@ export interface RefreshSessionRecord {
finished: boolean; finished: boolean;
/** /**
* Record ID when retrieved from the DB. * A 32-byte base32-crockford encoded random identifier.
*/ */
id?: number; refreshSessionId: string;
} }
/** /**
@ -771,12 +788,12 @@ export interface WireFee {
/** /**
* Start date of the fee. * Start date of the fee.
*/ */
startStamp: number; startStamp: Timestamp;
/** /**
* End date of the fee. * End date of the fee.
*/ */
endStamp: number; endStamp: Timestamp;
/** /**
* Signature made by the exchange master key. * Signature made by the exchange master key.
@ -830,14 +847,13 @@ export interface PurchaseRecord {
* When was the purchase made? * When was the purchase made?
* Refers to the time that the user accepted. * Refers to the time that the user accepted.
*/ */
timestamp: number; timestamp: Timestamp;
/** /**
* When was the last refund made? * When was the last refund made?
* Set to 0 if no refund was made on the purchase. * Set to 0 if no refund was made on the purchase.
*/ */
timestamp_refund: number; timestamp_refund: Timestamp | undefined;
/** /**
* Last session signature that we submitted to /pay (if any). * Last session signature that we submitted to /pay (if any).
@ -917,7 +933,6 @@ export interface CoinsReturnRecord {
wire: any; wire: any;
} }
export interface WithdrawalRecord { export interface WithdrawalRecord {
/** /**
* Reserve that we're withdrawing from. * Reserve that we're withdrawing from.
@ -928,18 +943,22 @@ export interface WithdrawalRecord {
* When was the withdrawal operation started started? * When was the withdrawal operation started started?
* Timestamp in milliseconds. * Timestamp in milliseconds.
*/ */
startTimestamp: number; startTimestamp: Timestamp;
/** /**
* When was the withdrawal operation completed? * When was the withdrawal operation completed?
*/ */
finishTimestamp?: number; finishTimestamp?: Timestamp;
/** /**
* Amount that is being withdrawn with this operation. * Amount that is being withdrawn with this operation.
* This does not include fees. * This does not include fees.
*/ */
withdrawalAmount: string; withdrawalAmount: string;
numCoinsTotal: number;
numCoinsWithdrawn: number;
} }
/* tslint:disable:completed-docs */ /* tslint:disable:completed-docs */
@ -983,11 +1002,6 @@ export namespace Stores {
"urlIndex", "urlIndex",
"url", "url",
); );
timestampIndex = new Index<string, ProposalDownloadRecord>(
this,
"timestampIndex",
"timestamp",
);
} }
class PurchasesStore extends Store<PurchaseRecord> { class PurchasesStore extends Store<PurchaseRecord> {
@ -1005,11 +1019,6 @@ export namespace Stores {
"orderIdIndex", "orderIdIndex",
"contractTerms.order_id", "contractTerms.order_id",
); );
timestampIndex = new Index<string, PurchaseRecord>(
this,
"timestampIndex",
"timestamp",
);
} }
class DenominationsStore extends Store<DenominationRecord> { class DenominationsStore extends Store<DenominationRecord> {
@ -1051,23 +1060,8 @@ export namespace Stores {
class ReservesStore extends Store<ReserveRecord> { class ReservesStore extends Store<ReserveRecord> {
constructor() { constructor() {
super("reserves", { keyPath: "reserve_pub" }); super("reserves", { keyPath: "reservePub" });
} }
timestampCreatedIndex = new Index<string, ReserveRecord>(
this,
"timestampCreatedIndex",
"created",
);
timestampConfirmedIndex = new Index<string, ReserveRecord>(
this,
"timestampConfirmedIndex",
"timestamp_confirmed",
);
timestampDepletedIndex = new Index<string, ReserveRecord>(
this,
"timestampDepletedIndex",
"timestamp_depleted",
);
} }
class TipsStore extends Store<TipRecord> { class TipsStore extends Store<TipRecord> {
@ -1092,8 +1086,26 @@ export namespace Stores {
class WithdrawalsStore extends Store<WithdrawalRecord> { class WithdrawalsStore extends Store<WithdrawalRecord> {
constructor() { constructor() {
super("withdrawals", { keyPath: "id", autoIncrement: true }) super("withdrawals", { keyPath: "id", autoIncrement: true });
} }
byReservePub = new Index<string, WithdrawalRecord>(
this,
"withdrawalsReservePubIndex",
"reservePub",
);
}
class PreCoinsStore extends Store<PreCoinRecord> {
constructor() {
super("precoins", {
keyPath: "coinPub",
});
}
byReservePub = new Index<string, PreCoinRecord>(
this,
"precoinsReservePubIndex",
"reservePub",
);
} }
export const coins = new CoinsStore(); export const coins = new CoinsStore();
@ -1104,13 +1116,10 @@ export namespace Stores {
export const currencies = new CurrenciesStore(); export const currencies = new CurrenciesStore();
export const denominations = new DenominationsStore(); export const denominations = new DenominationsStore();
export const exchanges = new ExchangeStore(); export const exchanges = new ExchangeStore();
export const precoins = new Store<PreCoinRecord>("precoins", { export const precoins = new PreCoinsStore();
keyPath: "coinPub",
});
export const proposals = new ProposalsStore(); export const proposals = new ProposalsStore();
export const refresh = new Store<RefreshSessionRecord>("refresh", { export const refresh = new Store<RefreshSessionRecord>("refresh", {
keyPath: "id", keyPath: "refreshSessionId",
autoIncrement: true,
}); });
export const reserves = new ReservesStore(); export const reserves = new ReservesStore();
export const purchases = new PurchasesStore(); export const purchases = new PurchasesStore();

View File

@ -440,7 +440,7 @@ export class CommandGroup<GN extends keyof any, TG> {
if (option.isFlag == false && option.required == true) { if (option.isFlag == false && option.required == true) {
if (!foundOptions[option.name]) { if (!foundOptions[option.name]) {
if (option.args.default !== undefined) { if (option.args.default !== undefined) {
parsedArgs[this.argKey] = option.args.default; myArgs[option.name] = option.args.default;
} else { } else {
const name = option.flagspec.join(",") const name = option.flagspec.join(",")
console.error(`error: missing option '${name}'`); console.error(`error: missing option '${name}'`);

View File

@ -21,7 +21,7 @@
/** /**
* Imports. * Imports.
*/ */
import { Wallet } from "../wallet"; import { Wallet, OperationFailedAndReportedError } from "../wallet";
import { Notifier, Badge } from "../walletTypes"; import { Notifier, Badge } from "../walletTypes";
import { MemoryBackend, BridgeIDBFactory, shimIndexedDB } from "idb-bridge"; import { MemoryBackend, BridgeIDBFactory, shimIndexedDB } from "idb-bridge";
import { SynchronousCryptoWorkerFactory } from "../crypto/synchronousWorker"; import { SynchronousCryptoWorkerFactory } from "../crypto/synchronousWorker";
@ -139,18 +139,16 @@ export async function getDefaultNodeWallet(
const storagePath = args.persistentStoragePath; const storagePath = args.persistentStoragePath;
if (storagePath) { if (storagePath) {
console.log(`using storage path ${storagePath}`);
try { try {
const dbContentStr: string = fs.readFileSync(storagePath, { encoding: "utf-8" }); const dbContentStr: string = fs.readFileSync(storagePath, { encoding: "utf-8" });
const dbContent = JSON.parse(dbContentStr); const dbContent = JSON.parse(dbContentStr);
myBackend.importDump(dbContent); myBackend.importDump(dbContent);
console.log("imported wallet");
} catch (e) { } catch (e) {
console.log("could not read wallet file"); console.error("could not read wallet file");
} }
myBackend.afterCommitCallback = async () => { myBackend.afterCommitCallback = async () => {
console.log("DATABASE COMMITTED");
// Allow caller to stop persisting the wallet. // Allow caller to stop persisting the wallet.
if (args.persistentStoragePath === undefined) { if (args.persistentStoragePath === undefined) {
return; return;
@ -190,8 +188,6 @@ export async function getDefaultNodeWallet(
myUnsupportedUpgrade, myUnsupportedUpgrade,
); );
console.log("opened db");
return new Wallet( return new Wallet(
myDb, myDb,
myHttpLib, myHttpLib,
@ -214,6 +210,8 @@ export async function withdrawTestBalance(
exchangeWire: "payto://unknown", exchangeWire: "payto://unknown",
}); });
const reservePub = reserveResponse.reservePub;
const bank = new Bank(bankBaseUrl); const bank = new Bank(bankBaseUrl);
const bankUser = await bank.registerRandomUser(); const bankUser = await bank.registerRandomUser();
@ -228,11 +226,11 @@ export async function withdrawTestBalance(
await bank.createReserve( await bank.createReserve(
bankUser, bankUser,
amount, amount,
reserveResponse.reservePub, reservePub,
exchangePaytoUri, exchangePaytoUri,
); );
await myWallet.confirmReserve({ reservePub: reserveResponse.reservePub }); await myWallet.confirmReserve({ reservePub: reserveResponse.reservePub });
await myWallet.processReserve(reserveResponse.reservePub); await myWallet.runUntilReserveDepleted(reservePub);
} }

View File

@ -31,6 +31,7 @@ export async function runIntegrationTest(args: {
amountToWithdraw: string; amountToWithdraw: string;
amountToSpend: string; amountToSpend: string;
}) { }) {
console.log("running test with", args);
const myWallet = await getDefaultNodeWallet(); const myWallet = await getDefaultNodeWallet();
await withdrawTestBalance(myWallet, args.amountToWithdraw, args.bankBaseUrl, args.exchangeBaseUrl); await withdrawTestBalance(myWallet, args.amountToWithdraw, args.bankBaseUrl, args.exchangeBaseUrl);

View File

@ -18,9 +18,14 @@ import os = require("os");
import { getDefaultNodeWallet, withdrawTestBalance } from "./helpers"; import { getDefaultNodeWallet, withdrawTestBalance } from "./helpers";
import { MerchantBackendConnection } from "./merchant"; import { MerchantBackendConnection } from "./merchant";
import { runIntegrationTest } from "./integrationtest"; import { runIntegrationTest } from "./integrationtest";
import { Wallet } from "../wallet"; import { Wallet, OperationFailedAndReportedError } from "../wallet";
import qrcodeGenerator = require("qrcode-generator"); import qrcodeGenerator = require("qrcode-generator");
import * as clk from "./clk"; import * as clk from "./clk";
import { BridgeIDBFactory, MemoryBackend } from "idb-bridge";
import { Logger } from "../logging";
import * as Amounts from "../amounts";
const logger = new Logger("taler-wallet-cli.ts");
const walletDbPath = os.homedir + "/" + ".talerwalletdb.json"; const walletDbPath = os.homedir + "/" + ".talerwalletdb.json";
@ -82,6 +87,7 @@ function applyVerbose(verbose: boolean) {
if (verbose) { if (verbose) {
console.log("enabled verbose logging"); console.log("enabled verbose logging");
Wallet.enableTracing = true; Wallet.enableTracing = true;
BridgeIDBFactory.enableTracing = true;
} }
} }
@ -103,16 +109,21 @@ async function withWallet<T>(
walletCliArgs: WalletCliArgsType, walletCliArgs: WalletCliArgsType,
f: (w: Wallet) => Promise<T>, f: (w: Wallet) => Promise<T>,
): Promise<T> { ): Promise<T> {
applyVerbose(walletCliArgs.wallet.verbose);
const wallet = await getDefaultNodeWallet({ const wallet = await getDefaultNodeWallet({
persistentStoragePath: walletDbPath, persistentStoragePath: walletDbPath,
}); });
applyVerbose(walletCliArgs.wallet.verbose);
try { try {
await wallet.fillDefaults(); await wallet.fillDefaults();
const ret = await f(wallet); const ret = await f(wallet);
return ret; return ret;
} catch (e) { } catch (e) {
console.error("caught exception:", e); if (e instanceof OperationFailedAndReportedError) {
console.error("Operation failed: " + e.message);
console.log("Hint: check pending operations for details.");
} else {
console.error("caught exception:", e);
}
process.exit(1); process.exit(1);
} finally { } finally {
wallet.stop(); wallet.stop();
@ -120,6 +131,161 @@ async function withWallet<T>(
} }
walletCli walletCli
.subcommand("", "balance", { help: "Show wallet balance." })
.action(async args => {
console.log("balance command called");
await withWallet(args, async wallet => {
const balance = await wallet.getBalances();
console.log(JSON.stringify(balance, undefined, 2));
});
});
walletCli
.subcommand("", "history", { help: "Show wallet event history." })
.maybeOption("from", ["--from"], clk.STRING)
.maybeOption("to", ["--to"], clk.STRING)
.maybeOption("limit", ["--limit"], clk.STRING)
.maybeOption("contEvt", ["--continue-with"], clk.STRING)
.action(async args => {
await withWallet(args, async wallet => {
const history = await wallet.getHistory();
console.log(JSON.stringify(history, undefined, 2));
});
});
walletCli
.subcommand("", "pending", { help: "Show pending operations." })
.action(async args => {
await withWallet(args, async wallet => {
const pending = await wallet.getPendingOperations();
console.log(JSON.stringify(pending, undefined, 2));
});
});
async function asyncSleep(milliSeconds: number): Promise<void> {
return new Promise<void>((resolve, reject) => {
setTimeout(() => resolve(), milliSeconds);
});
}
walletCli
.subcommand("runPendingOpt", "run-pending", {
help: "Run pending operations.",
})
.action(async args => {
await withWallet(args, async wallet => {
await wallet.runPending();
});
});
walletCli
.subcommand("handleUri", "handle-uri", {
help: "Handle a taler:// URI.",
})
.requiredArgument("uri", clk.STRING)
.flag("autoYes", ["-y", "--yes"])
.action(async args => {
await withWallet(args, async wallet => {
const uri: string = args.handleUri.uri;
if (uri.startsWith("taler://pay/")) {
await doPay(wallet, uri, { alwaysYes: args.handleUri.autoYes });
} else if (uri.startsWith("taler://tip/")) {
const res = await wallet.getTipStatus(uri);
console.log("tip status", res);
await wallet.acceptTip(uri);
} else if (uri.startsWith("taler://refund/")) {
await wallet.applyRefund(uri);
} else if (uri.startsWith("taler://withdraw/")) {
const withdrawInfo = await wallet.getWithdrawalInfo(uri);
const selectedExchange = withdrawInfo.suggestedExchange;
if (!selectedExchange) {
console.error("no suggested exchange!");
process.exit(1);
return;
}
const { confirmTransferUrl } = await wallet.acceptWithdrawal(
uri,
selectedExchange,
);
if (confirmTransferUrl) {
console.log("please confirm the transfer at", confirmTransferUrl);
}
} else {
console.error("unrecognized URI");
}
});
});
const exchangesCli = walletCli.subcommand("exchangesCmd", "exchanges", {
help: "Manage exchanges.",
});
exchangesCli
.subcommand("exchangesListCmd", "list", {
help: "List known exchanges.",
})
.action(async args => {
console.log("Listing exchanges ...");
await withWallet(args, async wallet => {
const exchanges = await wallet.getExchanges();
console.log("exchanges", exchanges);
});
});
exchangesCli
.subcommand("exchangesUpdateCmd", "update", {
help: "Update or add an exchange by base URL.",
})
.requiredArgument("url", clk.STRING, {
help: "Base URL of the exchange.",
})
.flag("force", ["-f", "--force"])
.action(async args => {
await withWallet(args, async wallet => {
const res = await wallet.updateExchangeFromUrl(
args.exchangesUpdateCmd.url,
args.exchangesUpdateCmd.force,
);
});
});
const advancedCli = walletCli.subcommand("advancedArgs", "advanced", {
help:
"Subcommands for advanced operations (only use if you know what you're doing!).",
});
advancedCli
.subcommand("refresh", "force-refresh", {
help: "Force a refresh on a coin.",
})
.requiredArgument("coinPub", clk.STRING)
.action(async args => {
await withWallet(args, async wallet => {
await wallet.refresh(args.refresh.coinPub, true);
});
});
advancedCli
.subcommand("coins", "list-coins", {
help: "List coins.",
})
.action(async args => {
await withWallet(args, async wallet => {
const coins = await wallet.getCoins();
for (const coin of coins) {
console.log(`coin ${coin.coinPub}`);
console.log(` status ${coin.status}`);
console.log(` exchange ${coin.exchangeBaseUrl}`);
console.log(` remaining amount ${Amounts.toString(coin.currentAmount)}`);
}
});
});
const testCli = walletCli.subcommand("testingArgs", "testing", {
help: "Subcommands for testing GNU Taler deployments.",
});
testCli
.subcommand("testPayCmd", "test-pay", { help: "create contract and pay" }) .subcommand("testPayCmd", "test-pay", { help: "create contract and pay" })
.requiredOption("amount", ["-a", "--amount"], clk.STRING) .requiredOption("amount", ["-a", "--amount"], clk.STRING)
.requiredOption("summary", ["-s", "--summary"], clk.STRING, { .requiredOption("summary", ["-s", "--summary"], clk.STRING, {
@ -146,63 +312,60 @@ walletCli
return; return;
} }
console.log("taler pay URI:", talerPayUri); console.log("taler pay URI:", talerPayUri);
await withWallet(args, async (wallet) => {
const wallet = await getDefaultNodeWallet({ await doPay(wallet, talerPayUri, { alwaysYes: true });
persistentStoragePath: walletDbPath,
});
await doPay(wallet, talerPayUri, { alwaysYes: true });
});
walletCli
.subcommand("", "balance", { help: "Show wallet balance." })
.action(async args => {
console.log("balance command called");
withWallet(args, async (wallet) => {
const balance = await wallet.getBalances();
console.log(JSON.stringify(balance, undefined, 2));
}); });
}); });
walletCli
.subcommand("", "history", { help: "Show wallet event history." })
.requiredOption("from", ["--from"], clk.STRING)
.requiredOption("to", ["--to"], clk.STRING)
.requiredOption("limit", ["--limit"], clk.STRING)
.requiredOption("contEvt", ["--continue-with"], clk.STRING)
.action(async args => {
withWallet(args, async (wallet) => {
const history = await wallet.getHistory();
console.log(JSON.stringify(history, undefined, 2));
});
});
walletCli testCli
.subcommand("", "pending", { help: "Show pending operations." }) .subcommand("integrationtestCmd", "integrationtest", {
.action(async args => { help: "Run integration test with bank, exchange and merchant.",
withWallet(args, async (wallet) => {
const pending = await wallet.getPendingOperations();
console.log(JSON.stringify(pending, undefined, 2));
});
});
async function asyncSleep(milliSeconds: number): Promise<void> {
return new Promise<void>((resolve, reject) => {
setTimeout(() => resolve(), milliSeconds);
});
}
walletCli
.subcommand("runPendingOpt", "run-pending", {
help: "Run pending operations."
}) })
.action(async (args) => { .requiredOption("exchange", ["-e", "--exchange"], clk.STRING, {
withWallet(args, async (wallet) => { default: "https://exchange.test.taler.net/",
await wallet.processPending(); })
}); .requiredOption("merchant", ["-m", "--merchant"], clk.STRING, {
default: "https://backend.test.taler.net/",
})
.requiredOption("merchantApiKey", ["-k", "--merchant-api-key"], clk.STRING, {
default: "sandbox",
})
.requiredOption("bank", ["-b", "--bank"], clk.STRING, {
default: "https://bank.test.taler.net/",
})
.requiredOption("withdrawAmount", ["-a", "--amount"], clk.STRING, {
default: "TESTKUDOS:10",
})
.requiredOption("spendAmount", ["-s", "--spend-amount"], clk.STRING, {
default: "TESTKUDOS:4",
})
.action(async args => {
console.log("parsed args", args);
applyVerbose(args.wallet.verbose);
let cmdObj = args.integrationtestCmd;
try {
await runIntegrationTest({
amountToSpend: cmdObj.spendAmount,
amountToWithdraw: cmdObj.withdrawAmount,
bankBaseUrl: cmdObj.bank,
exchangeBaseUrl: cmdObj.exchange,
merchantApiKey: cmdObj.merchantApiKey,
merchantBaseUrl: cmdObj.merchant,
}).catch(err => {
console.error("Failed with exception:");
console.error(err);
});
process.exit(0);
} catch (e) {
console.error(e);
process.exit(1);
}
}); });
walletCli testCli
.subcommand("testMerchantQrcodeCmd", "test-merchant-qrcode") .subcommand("testMerchantQrcodeCmd", "test-merchant-qrcode")
.requiredOption("amount", ["-a", "--amount"], clk.STRING, { .requiredOption("amount", ["-a", "--amount"], clk.STRING, {
default: "TESTKUDOS:1", default: "TESTKUDOS:1",
@ -249,174 +412,14 @@ walletCli
} }
}); });
walletCli
.subcommand("integrationtestCmd", "integrationtest", {
help: "Run integration test with bank, exchange and merchant.",
})
.requiredOption("exchange", ["-e", "--exchange"], clk.STRING, {
default: "https://exchange.test.taler.net/",
})
.requiredOption("merchant", ["-m", "--merchant"], clk.STRING, {
default: "https://backend.test.taler.net/",
})
.requiredOption("merchantApiKey", ["-k", "--merchant-api-key"], clk.STRING, {
default: "sandbox",
})
.requiredOption("bank", ["-b", "--bank"], clk.STRING, {
default: "https://bank.test.taler.net/",
})
.requiredOption("withdrawAmount", ["-b", "--bank"], clk.STRING, {
default: "TESTKUDOS:10",
})
.requiredOption("spendAmount", ["-s", "--spend-amount"], clk.STRING, {
default: "TESTKUDOS:4",
})
.action(async args => {
applyVerbose(args.wallet.verbose);
let cmdObj = args.integrationtestCmd;
try {
await runIntegrationTest({
amountToSpend: cmdObj.spendAmount,
amountToWithdraw: cmdObj.withdrawAmount,
bankBaseUrl: cmdObj.bank,
exchangeBaseUrl: cmdObj.exchange,
merchantApiKey: cmdObj.merchantApiKey,
merchantBaseUrl: cmdObj.merchant,
}).catch(err => {
console.error("Failed with exception:");
console.error(err);
});
process.exit(0);
} catch (e) {
console.error(e);
process.exit(1);
}
});
walletCli
.subcommand("withdrawUriCmd", "withdraw-uri")
.requiredArgument("withdrawUri", clk.STRING)
.action(async args => {
applyVerbose(args.wallet.verbose);
const cmdArgs = args.withdrawUriCmd;
const withdrawUrl = cmdArgs.withdrawUri;
console.log("withdrawing", withdrawUrl);
const wallet = await getDefaultNodeWallet({
persistentStoragePath: walletDbPath,
});
const withdrawInfo = await wallet.getWithdrawalInfo(withdrawUrl);
console.log("withdraw info", withdrawInfo);
const selectedExchange = withdrawInfo.suggestedExchange;
if (!selectedExchange) {
console.error("no suggested exchange!");
process.exit(1);
return;
}
const { reservePub, confirmTransferUrl } = await wallet.acceptWithdrawal(
withdrawUrl,
selectedExchange,
);
if (confirmTransferUrl) {
console.log("please confirm the transfer at", confirmTransferUrl);
}
await wallet.processReserve(reservePub);
console.log("finished withdrawing");
wallet.stop();
});
walletCli
.subcommand("tipUriCmd", "tip-uri")
.requiredArgument("uri", clk.STRING)
.action(async args => {
applyVerbose(args.wallet.verbose);
const tipUri = args.tipUriCmd.uri;
console.log("getting tip", tipUri);
const wallet = await getDefaultNodeWallet({
persistentStoragePath: walletDbPath,
});
const res = await wallet.getTipStatus(tipUri);
console.log("tip status", res);
await wallet.acceptTip(tipUri);
wallet.stop();
});
walletCli
.subcommand("refundUriCmd", "refund-uri")
.requiredArgument("uri", clk.STRING)
.action(async args => {
applyVerbose(args.wallet.verbose);
const refundUri = args.refundUriCmd.uri;
console.log("getting refund", refundUri);
const wallet = await getDefaultNodeWallet({
persistentStoragePath: walletDbPath,
});
await wallet.applyRefund(refundUri);
wallet.stop();
});
const exchangesCli = walletCli.subcommand("exchangesCmd", "exchanges", {
help: "Manage exchanges.",
});
exchangesCli
.subcommand("exchangesListCmd", "list", {
help: "List known exchanges.",
})
.action(async args => {
console.log("Listing exchanges ...");
withWallet(args, async (wallet) => {
const exchanges = await wallet.getExchanges();
console.log("exchanges", exchanges);
});
});
exchangesCli
.subcommand("exchangesUpdateCmd", "update", {
help: "Update or add an exchange by base URL.",
})
.requiredArgument("url", clk.STRING, {
help: "Base URL of the exchange.",
})
.action(async args => {
withWallet(args, async (wallet) => {
const res = await wallet.updateExchangeFromUrl(args.exchangesUpdateCmd.url);
});
});
walletCli
.subcommand("payUriCmd", "pay-uri")
.requiredArgument("url", clk.STRING)
.flag("autoYes", ["-y", "--yes"])
.action(async args => {
applyVerbose(args.wallet.verbose);
const payUrl = args.payUriCmd.url;
console.log("paying for", payUrl);
const wallet = await getDefaultNodeWallet({
persistentStoragePath: walletDbPath,
});
await doPay(wallet, payUrl, { alwaysYes: args.payUriCmd.autoYes });
wallet.stop();
});
const testCli = walletCli.subcommand("testingArgs", "testing", {
help: "Subcommands for testing GNU Taler deployments.",
});
testCli testCli
.subcommand("withdrawArgs", "withdraw", { .subcommand("withdrawArgs", "withdraw", {
help: "Withdraw from a test bank (must support test registrations).", help: "Withdraw from a test bank (must support test registrations).",
}) })
.requiredOption("amount", ["-a", "--amount"], clk.STRING, {
default: "TESTKUDOS:10",
help: "Amount to withdraw.",
})
.requiredOption("exchange", ["-e", "--exchange"], clk.STRING, { .requiredOption("exchange", ["-e", "--exchange"], clk.STRING, {
default: "https://exchange.test.taler.net/", default: "https://exchange.test.taler.net/",
help: "Exchange base URL.", help: "Exchange base URL.",
@ -426,14 +429,15 @@ testCli
help: "Bank base URL", help: "Bank base URL",
}) })
.action(async args => { .action(async args => {
applyVerbose(args.wallet.verbose); await withWallet(args, async wallet => {
console.log("balance command called"); await withdrawTestBalance(
const wallet = await getDefaultNodeWallet({ wallet,
persistentStoragePath: walletDbPath, args.withdrawArgs.amount,
args.withdrawArgs.bank,
args.withdrawArgs.exchange,
);
logger.info("Withdraw done");
}); });
console.log("got wallet");
const balance = await wallet.getBalances();
console.log(JSON.stringify(balance, undefined, 2));
}); });
walletCli.run(); walletCli.run();

View File

@ -107,10 +107,3 @@ export class BrowserHttpLib implements HttpRequestLibrary {
return this.req("post", url, { req: form }); return this.req("post", url, { req: form });
} }
} }
/**
* Exception thrown on request errors.
*/
export class RequestException {
constructor(public detail: any) {}
}

View File

@ -1,5 +1,3 @@
import { openPromise } from "./promiseUtils";
/* /*
This file is part of TALER This file is part of TALER
(C) 2016 GNUnet e.V. (C) 2016 GNUnet e.V.
@ -22,6 +20,12 @@ import { openPromise } from "./promiseUtils";
* @author Florian Dold * @author Florian Dold
*/ */
/**
* Imports.
*/
import { openPromise } from "./promiseUtils";
/** /**
* Result of an inner join. * Result of an inner join.
*/ */
@ -63,27 +67,48 @@ export interface IndexOptions {
} }
function requestToPromise(req: IDBRequest): Promise<any> { function requestToPromise(req: IDBRequest): Promise<any> {
const stack = Error("Failed request was started here.")
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
req.onsuccess = () => { req.onsuccess = () => {
resolve(req.result); resolve(req.result);
}; };
req.onerror = () => { req.onerror = () => {
console.log("error in DB request", req.error);
reject(req.error); reject(req.error);
console.log("Request failed:", stack);
}; };
}); });
} }
export function oneShotGet<T>( function transactionToPromise(tx: IDBTransaction): Promise<void> {
const stack = Error("Failed transaction was started here.");
return new Promise((resolve, reject) => {
tx.onabort = () => {
reject(TransactionAbort);
};
tx.oncomplete = () => {
resolve();
};
tx.onerror = () => {
console.error("Transaction failed:", stack);
reject(tx.error);
};
});
}
export async function oneShotGet<T>(
db: IDBDatabase, db: IDBDatabase,
store: Store<T>, store: Store<T>,
key: any, key: any,
): Promise<T | undefined> { ): Promise<T | undefined> {
const tx = db.transaction([store.name], "readonly"); const tx = db.transaction([store.name], "readonly");
const req = tx.objectStore(store.name).get(key); const req = tx.objectStore(store.name).get(key);
return requestToPromise(req); const v = await requestToPromise(req)
await transactionToPromise(tx);
return v;
} }
export function oneShotGetIndexed<S extends IDBValidKey, T>( export async function oneShotGetIndexed<S extends IDBValidKey, T>(
db: IDBDatabase, db: IDBDatabase,
index: Index<S, T>, index: Index<S, T>,
key: any, key: any,
@ -93,10 +118,12 @@ export function oneShotGetIndexed<S extends IDBValidKey, T>(
.objectStore(index.storeName) .objectStore(index.storeName)
.index(index.indexName) .index(index.indexName)
.get(key); .get(key);
return requestToPromise(req); const v = await requestToPromise(req);
await transactionToPromise(tx);
return v;
} }
export function oneShotPut<T>( export async function oneShotPut<T>(
db: IDBDatabase, db: IDBDatabase,
store: Store<T>, store: Store<T>,
value: T, value: T,
@ -104,7 +131,9 @@ export function oneShotPut<T>(
): Promise<any> { ): Promise<any> {
const tx = db.transaction([store.name], "readwrite"); const tx = db.transaction([store.name], "readwrite");
const req = tx.objectStore(store.name).put(value, key); const req = tx.objectStore(store.name).put(value, key);
return requestToPromise(req); const v = await requestToPromise(req);
await transactionToPromise(tx);
return v;
} }
function applyMutation<T>( function applyMutation<T>(
@ -115,7 +144,7 @@ function applyMutation<T>(
req.onsuccess = () => { req.onsuccess = () => {
const cursor = req.result; const cursor = req.result;
if (cursor) { if (cursor) {
const val = cursor.value(); const val = cursor.value;
const modVal = f(val); const modVal = f(val);
if (modVal !== undefined && modVal !== null) { if (modVal !== undefined && modVal !== null) {
const req2: IDBRequest = cursor.update(modVal); const req2: IDBRequest = cursor.update(modVal);
@ -138,7 +167,7 @@ function applyMutation<T>(
}); });
} }
export function oneShotMutate<T>( export async function oneShotMutate<T>(
db: IDBDatabase, db: IDBDatabase,
store: Store<T>, store: Store<T>,
key: any, key: any,
@ -146,7 +175,8 @@ export function oneShotMutate<T>(
): Promise<void> { ): Promise<void> {
const tx = db.transaction([store.name], "readwrite"); const tx = db.transaction([store.name], "readwrite");
const req = tx.objectStore(store.name).openCursor(key); const req = tx.objectStore(store.name).openCursor(key);
return applyMutation(req, f); await applyMutation(req, f);
await transactionToPromise(tx);
} }
type CursorResult<T> = CursorEmptyResult<T> | CursorValueResult<T>; type CursorResult<T> = CursorEmptyResult<T> | CursorValueResult<T>;
@ -326,15 +356,12 @@ export function runWithWriteTransaction<T>(
stores: Store<any>[], stores: Store<any>[],
f: (t: TransactionHandle) => Promise<T>, f: (t: TransactionHandle) => Promise<T>,
): Promise<T> { ): Promise<T> {
const stack = Error("Failed transaction was started here.");
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const storeName = stores.map(x => x.name); const storeName = stores.map(x => x.name);
const tx = db.transaction(storeName, "readwrite"); const tx = db.transaction(storeName, "readwrite");
let funResult: any = undefined; let funResult: any = undefined;
let gotFunResult: boolean = false; let gotFunResult: boolean = false;
tx.onerror = () => {
console.error("error in transaction:", tx.error);
reject(tx.error);
};
tx.oncomplete = () => { tx.oncomplete = () => {
// This is a fatal error: The transaction completed *before* // This is a fatal error: The transaction completed *before*
// the transaction function returned. Likely, the transaction // the transaction function returned. Likely, the transaction
@ -350,15 +377,30 @@ export function runWithWriteTransaction<T>(
} }
resolve(funResult); resolve(funResult);
}; };
tx.onerror = () => {
console.error("error in transaction");
};
tx.onabort = () => { tx.onabort = () => {
console.error("aborted transaction"); if (tx.error) {
reject(AbortTransaction); console.error("Transaction aborted with error:", tx.error);
} else {
console.log("Trasaction aborted (no error)");
}
reject(TransactionAbort);
}; };
const th = new TransactionHandle(tx); const th = new TransactionHandle(tx);
const resP = f(th); const resP = f(th);
resP.then(result => { resP.then(result => {
gotFunResult = true; gotFunResult = true;
funResult = result; funResult = result;
}).catch((e) => {
if (e == TransactionAbort) {
console.info("aborting transaction");
} else {
tx.abort();
console.error("Transaction failed:", e);
console.error(stack);
}
}); });
}); });
} }
@ -401,4 +443,4 @@ export class Index<S extends IDBValidKey, T> {
/** /**
* Exception that should be thrown by client code to abort a transaction. * Exception that should be thrown by client code to abort a transaction.
*/ */
export const AbortTransaction = Symbol("abort_transaction"); export const TransactionAbort = Symbol("transaction_abort");

File diff suppressed because it is too large Load Diff

View File

@ -233,7 +233,7 @@ export interface ConfirmPayResult {
/** /**
* Activity history record. * Activity history record.
*/ */
export interface HistoryRecord { export interface HistoryEvent {
/** /**
* Type of the history event. * Type of the history event.
*/ */
@ -242,7 +242,7 @@ export interface HistoryRecord {
/** /**
* Time when the activity was recorded. * Time when the activity was recorded.
*/ */
timestamp: number; timestamp: Timestamp;
/** /**
* Subject of the entry. Used to group multiple history records together. * Subject of the entry. Used to group multiple history records together.
@ -254,6 +254,13 @@ export interface HistoryRecord {
* Details used when rendering the history record. * Details used when rendering the history record.
*/ */
detail: any; detail: any;
/**
* Set to 'true' if the event has been explicitly created,
* and set to 'false' if the event has been derived from the
* state of the database.
*/
explicit: boolean;
} }
/** /**
@ -516,6 +523,8 @@ export interface WalletDiagnostics {
export interface PendingWithdrawOperation { export interface PendingWithdrawOperation {
type: "withdraw"; type: "withdraw";
stage: string;
reservePub: string;
} }
export interface PendingRefreshOperation { export interface PendingRefreshOperation {
@ -535,6 +544,7 @@ export interface OperationError {
export interface PendingExchangeUpdateOperation { export interface PendingExchangeUpdateOperation {
type: "exchange-update"; type: "exchange-update";
stage: string; stage: string;
reason: string;
exchangeBaseUrl: string; exchangeBaseUrl: string;
lastError?: OperationError; lastError?: OperationError;
} }
@ -545,10 +555,28 @@ export interface PendingBugOperation {
details: any; details: any;
} }
export interface PendingReserveOperation {
type: "reserve";
lastError?: OperationError;
stage: string;
timestampCreated: Timestamp;
reserveType: string;
}
export interface PendingRefreshOperation {
type: "refresh";
lastError?: OperationError;
oldCoinPub: string;
refreshStatus: string;
refreshOutputSize: number;
}
export type PendingOperationInfo = export type PendingOperationInfo =
| PendingWithdrawOperation | PendingWithdrawOperation
| PendingReserveOperation
| PendingBugOperation | PendingBugOperation
| PendingExchangeUpdateOperation; | PendingExchangeUpdateOperation
| PendingRefreshOperation;
export interface PendingOperationsResponse { export interface PendingOperationsResponse {
pendingOperations: PendingOperationInfo[]; pendingOperations: PendingOperationInfo[];

View File

@ -79,7 +79,7 @@ export interface MessageMap {
}; };
"get-history": { "get-history": {
request: {}; request: {};
response: walletTypes.HistoryRecord[]; response: walletTypes.HistoryEvent[];
}; };
"get-coins": { "get-coins": {
request: { exchangeBaseUrl: string }; request: { exchangeBaseUrl: string };

View File

@ -57,11 +57,11 @@ function Payback() {
<div> <div>
{reserves.map(r => ( {reserves.map(r => (
<div> <div>
<h2>Reserve for ${renderAmount(r.current_amount!)}</h2> <h2>Reserve for ${renderAmount(r.currentAmount!)}</h2>
<ul> <ul>
<li>Exchange: ${r.exchange_base_url}</li> <li>Exchange: ${r.exchangeBaseUrl}</li>
</ul> </ul>
<button onClick={() => withdrawPaybackReserve(r.reserve_pub)}> <button onClick={() => withdrawPaybackReserve(r.reservePub)}>
Withdraw again Withdraw again
</button> </button>
</div> </div>

View File

@ -30,7 +30,7 @@ import { AmountJson } from "../../amounts";
import * as Amounts from "../../amounts"; import * as Amounts from "../../amounts";
import { import {
HistoryRecord, HistoryEvent,
WalletBalance, WalletBalance,
WalletBalanceEntry, WalletBalanceEntry,
} from "../../walletTypes"; } from "../../walletTypes";
@ -327,7 +327,7 @@ class WalletBalanceView extends React.Component<any, any> {
} }
} }
function formatHistoryItem(historyItem: HistoryRecord) { function formatHistoryItem(historyItem: HistoryEvent) {
const d = historyItem.detail; const d = historyItem.detail;
console.log("hist item", historyItem); console.log("hist item", historyItem);
switch (historyItem.type) { switch (historyItem.type) {
@ -459,7 +459,7 @@ class WalletHistory extends React.Component<any, any> {
render(): JSX.Element { render(): JSX.Element {
console.log("rendering history"); console.log("rendering history");
const history: HistoryRecord[] = this.myHistory; const history: HistoryEvent[] = this.myHistory;
if (this.gotError) { if (this.gotError) {
return i18n.str`Error: could not retrieve event history`; return i18n.str`Error: could not retrieve event history`;
} }
@ -474,7 +474,7 @@ class WalletHistory extends React.Component<any, any> {
const item = ( const item = (
<div className="historyItem"> <div className="historyItem">
<div className="historyDate"> <div className="historyDate">
{new Date(record.timestamp).toString()} {new Date(record.timestamp.t_ms).toString()}
</div> </div>
{formatHistoryItem(record)} {formatHistoryItem(record)}
</div> </div>

View File

@ -215,7 +215,7 @@ function FeeDetailsView(props: {
<tbody> <tbody>
{rci!.wireFees.feesForType[s].map(f => ( {rci!.wireFees.feesForType[s].map(f => (
<tr> <tr>
<td>{moment.unix(f.endStamp).format("llll")}</td> <td>{moment.unix(Math.floor(f.endStamp.t_ms / 1000)).format("llll")}</td>
<td>{renderAmount(f.wireFee)}</td> <td>{renderAmount(f.wireFee)}</td>
<td>{renderAmount(f.closingFee)}</td> <td>{renderAmount(f.closingFee)}</td>
</tr> </tr>

View File

@ -176,7 +176,7 @@ async function handleMessage(
if (typeof detail.exchangeBaseUrl !== "string") { if (typeof detail.exchangeBaseUrl !== "string") {
return Promise.reject(Error("exchangBaseUrl missing")); return Promise.reject(Error("exchangBaseUrl missing"));
} }
return needsWallet().getCoins(detail.exchangeBaseUrl); return needsWallet().getCoinsForExchange(detail.exchangeBaseUrl);
} }
case "get-precoins": { case "get-precoins": {
if (typeof detail.exchangeBaseUrl !== "string") { if (typeof detail.exchangeBaseUrl !== "string") {

View File

@ -33,6 +33,8 @@
"src/crypto/cryptoWorker.ts", "src/crypto/cryptoWorker.ts",
"src/crypto/emscInterface-test.ts", "src/crypto/emscInterface-test.ts",
"src/crypto/emscInterface.ts", "src/crypto/emscInterface.ts",
"src/crypto/nativeCrypto-test.ts",
"src/crypto/nativeCrypto.ts",
"src/crypto/nodeEmscriptenLoader.ts", "src/crypto/nodeEmscriptenLoader.ts",
"src/crypto/nodeProcessWorker.ts", "src/crypto/nodeProcessWorker.ts",
"src/crypto/nodeWorkerEntry.ts", "src/crypto/nodeWorkerEntry.ts",
@ -53,6 +55,7 @@
"src/index.ts", "src/index.ts",
"src/libtoolVersion-test.ts", "src/libtoolVersion-test.ts",
"src/libtoolVersion.ts", "src/libtoolVersion.ts",
"src/logging.ts",
"src/promiseUtils.ts", "src/promiseUtils.ts",
"src/query.ts", "src/query.ts",
"src/talerTypes.ts", "src/talerTypes.ts",

View File

@ -3412,10 +3412,10 @@ iconv-lite@0.4.24, iconv-lite@^0.4.4, iconv-lite@~0.4.13:
dependencies: dependencies:
safer-buffer ">= 2.1.2 < 3" safer-buffer ">= 2.1.2 < 3"
idb-bridge@^0.0.11: idb-bridge@^0.0.14:
version "0.0.11" version "0.0.14"
resolved "https://registry.yarnpkg.com/idb-bridge/-/idb-bridge-0.0.11.tgz#ba2fbd24b7e6f7f4de8333ed12b0912e64dda308" resolved "https://registry.yarnpkg.com/idb-bridge/-/idb-bridge-0.0.14.tgz#5fd50cd68b574df0eb6b1a960cef0cb984a21ded"
integrity sha512-fLlHce/WwT6eD3sc54gsfvM5fZqrhAPwBNH4uU/y6D0C1+0higH7OgC5/wploMhkmNYkQID3BMNZvSUBr0leSQ== integrity sha512-jc9ZYGhhIrW6nh/pWyycGWzCmsLTFQ0iMY61lN+y9YcIOCxREpAkZxdfmhwNL7H0RvsYp7iJv0GH7ujs7HPC+g==
ieee754@^1.1.4: ieee754@^1.1.4:
version "1.1.13" version "1.1.13"